@gloom-sh/terminal 0.18.5 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/patches/electrobun@1.18.1.patch +140 -7
- package/src/api-client/cot.ts +6 -2
- package/src/api-client/credit-documents.ts +6 -0
- package/src/api-client/data.ts +17 -6
- package/src/api-client/equity-screener.ts +24 -0
- package/src/api-client/fund-facts.ts +45 -4
- package/src/api-client/index.ts +11 -0
- package/src/api-client/market-discovery.ts +10 -0
- package/src/api-client/market-movers.ts +7 -0
- package/src/api-client/members.ts +1 -1
- package/src/api-client/perps.ts +43 -0
- package/src/api-client/research-activity.ts +3 -2
- package/src/api-client/types.ts +70 -2
- package/src/app/pane-runtime/plugin-bindings.ts +9 -5
- package/src/app/runtime/broker-import.ts +0 -2
- package/src/app/runtime/plugin-bindings.ts +73 -56
- package/src/architecture/pane-tabs-allowlist.json +3 -0
- package/src/architecture/ttl-caches-allowlist.json +19 -0
- package/src/brokers/sync-broker-instance.ts +2 -8
- package/src/capabilities/chart-series.ts +1 -1
- package/src/cli/commands/crypto-hints.ts +40 -24
- package/src/cli/commands/market.ts +72 -26
- package/src/cli/commands/options-chain.ts +48 -7
- package/src/cli/commands/options-leaps.ts +9 -3
- package/src/cli/commands/overview.ts +61 -12
- package/src/cli/commands/search.ts +21 -1
- package/src/cli/commands/themes.ts +11 -2
- package/src/cli/commands/ticker.ts +55 -34
- package/src/cli/company-name-hint.ts +41 -0
- package/src/cli/earnings-calendar.ts +1 -1
- package/src/cli/errors.ts +8 -0
- package/src/cli/glossary.ts +25 -2
- package/src/cli/history-rows.ts +57 -9
- package/src/cli/index.ts +2 -0
- package/src/cli/listing-arg.ts +180 -52
- package/src/cli/output-file.ts +68 -0
- package/src/cli/pane-functions/capabilities.ts +3 -0
- package/src/cli/pane-functions/catalog.ts +80 -7
- package/src/cli/pane-functions/dom.ts +13 -3
- package/src/cli/pane-functions/freshness.ts +86 -3
- package/src/cli/pane-functions/function-help.ts +13 -10
- package/src/cli/pane-functions/headless.ts +17 -7
- package/src/cli/pane-functions/index.ts +49 -14
- package/src/cli/pane-functions/market-session.ts +66 -4
- package/src/cli/pane-functions/options.ts +19 -1
- package/src/cli/pane-functions/resolver.ts +19 -8
- package/src/cli/pane-functions/screenshot-assessment.ts +6 -4
- package/src/cli/pane-functions/screenshot-types.ts +5 -0
- package/src/cli/quote-answer.ts +22 -11
- package/src/cli/ticker-resolution.ts +3 -2
- package/src/components/chart/composite/composite-chart.tsx +4 -0
- package/src/components/chart/composite/interactions.ts +6 -0
- package/src/components/chart/composite/legend.tsx +45 -7
- package/src/components/chart/composite/panel-gestures.ts +33 -0
- package/src/components/chart/composite/panel-surface.tsx +52 -3
- package/src/components/chart/composite/rasterizer.ts +24 -13
- package/src/components/chart/composite/scene.ts +39 -4
- package/src/components/chart/composite/series-cues.ts +14 -0
- package/src/components/chart/composite/text-renderer.ts +9 -2
- package/src/components/chart/composite/tool-catalog.ts +36 -0
- package/src/components/chart/composite/tools.ts +203 -44
- package/src/components/chart/composite/use-composite-chart-keyboard.ts +11 -4
- package/src/components/command-bar/assist/inventory.ts +2 -1
- package/src/components/command-bar/commands/direct/index.ts +28 -13
- package/src/components/command-bar/pane-templates/items.ts +3 -2
- package/src/components/command-bar/workflow/collection-actions.ts +3 -3
- package/src/components/onboarding/onboarding-wizard.tsx +4 -1
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +12 -7
- package/src/components/onboarding/wizard-broker-sync.ts +0 -1
- package/src/components/onboarding/wizard-positions.ts +3 -3
- package/src/components/ticker/list-table-view.tsx +4 -1
- package/src/components/ui/choice-dialog.tsx +23 -18
- package/src/components/ui/choice-groups.ts +18 -0
- package/src/components/ui/data-table/opentui/index.tsx +9 -7
- package/src/components/ui/data-table/opentui/model.ts +26 -1
- package/src/components/ui/data-table/types.ts +2 -0
- package/src/components/ui/list-view.tsx +8 -4
- package/src/components/ui/query-bar-keys.tsx +2 -0
- package/src/components/ui/query-bar.tsx +1 -0
- package/src/components/ui/select-button.tsx +5 -1
- package/src/components/ui/select-field.tsx +1 -0
- package/src/components/ui/table-layout.ts +8 -5
- package/src/core/app-runtime.ts +2 -0
- package/src/core/app-services.ts +2 -2
- package/src/data/config/chart-settings.ts +1 -1
- package/src/data/json-ticker-repository.ts +17 -8
- package/src/data/ticker-repository.ts +16 -9
- package/src/data/ticker-writes.ts +81 -0
- package/src/data/ttl-cache.ts +165 -0
- package/src/docs/snapshot.json +71 -137
- 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/market-data/depositary-pair.ts +2 -2
- package/src/market-data/fund-facts.ts +50 -0
- package/src/market-data/market/change-basis.ts +16 -3
- package/src/market-data/market/format.ts +25 -0
- package/src/market-data/market/freshness.ts +2 -1
- package/src/market-data/options-alternatives.ts +81 -4
- package/src/news/aggregator.ts +113 -9
- package/src/news/cloud-session.ts +21 -0
- package/src/news/hooks.ts +1 -0
- package/src/news/news-model.ts +1 -0
- package/src/news/types.ts +11 -0
- package/src/plugins/builtin/alerts/alert-engine.ts +20 -1
- package/src/plugins/builtin/alerts/alert-log.ts +33 -0
- package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
- package/src/plugins/builtin/alerts/cli.ts +336 -77
- package/src/plugins/builtin/alerts/command.ts +25 -1
- package/src/plugins/builtin/alerts/events-pane.tsx +19 -4
- package/src/plugins/builtin/alerts/fired-log.ts +73 -9
- package/src/plugins/builtin/alerts/format.ts +75 -5
- package/src/plugins/builtin/alerts/headless.ts +24 -6
- package/src/plugins/builtin/alerts/history-rows.ts +3 -2
- package/src/plugins/builtin/alerts/history.ts +1 -1
- package/src/plugins/builtin/alerts/index.tsx +79 -16
- package/src/plugins/builtin/alerts/metric-command.ts +77 -4
- package/src/plugins/builtin/alerts/metric-data.ts +54 -13
- package/src/plugins/builtin/alerts/metric-model.ts +163 -14
- package/src/plugins/builtin/alerts/metric-parity-fixture.json +1 -1
- package/src/plugins/builtin/alerts/pane.tsx +61 -23
- package/src/plugins/builtin/alerts/quotes.ts +6 -0
- package/src/plugins/builtin/alerts/storage.ts +12 -8
- package/src/plugins/builtin/alerts/types.ts +4 -0
- package/src/plugins/builtin/analytics/index.tsx +2 -1
- package/src/plugins/builtin/analytics/risk-client.ts +16 -5
- package/src/plugins/builtin/analytics/risk-fx.ts +70 -15
- package/src/plugins/builtin/analytics/risk-headless.ts +27 -0
- package/src/plugins/builtin/analytics/risk-model.ts +55 -9
- package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
- package/src/plugins/builtin/attention/index.tsx +2 -1
- package/src/plugins/builtin/attention/listing.ts +14 -0
- package/src/plugins/builtin/broker-manager/index.tsx +7 -3
- package/src/plugins/builtin/buildout/cells.tsx +12 -4
- package/src/plugins/builtin/buildout/format.ts +38 -0
- package/src/plugins/builtin/buildout/model/normalizers.ts +36 -1
- package/src/plugins/builtin/buildout/pane/index.tsx +4 -1
- package/src/plugins/builtin/buildout/table-model.ts +23 -2
- package/src/plugins/builtin/builtin-plugin-meta.ts +6 -2
- package/src/plugins/builtin/changelog/index.tsx +9 -6
- package/src/plugins/builtin/chart-composer/catalog-inventory.ts +3 -0
- package/src/plugins/builtin/chart-composer/cli-options.ts +4 -0
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +7 -0
- package/src/plugins/builtin/chart-composer/editor/controller.ts +2 -2
- package/src/plugins/builtin/chart-composer/headless-schema.ts +7 -1
- package/src/plugins/builtin/chart-composer/headless.ts +6 -1
- package/src/plugins/builtin/chart-composer/index.tsx +1 -1
- package/src/plugins/builtin/chart-composer/pane.tsx +37 -8
- package/src/plugins/builtin/chart-composer/settings.ts +27 -0
- package/src/plugins/builtin/chat/content/command-suggestions.ts +1 -0
- package/src/plugins/builtin/chat/pane-links/commands.ts +7 -1
- package/src/plugins/builtin/clinical-trials/model.ts +0 -9
- package/src/plugins/builtin/clinical-trials/pane.tsx +15 -22
- package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +8 -6
- package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
- package/src/plugins/builtin/company-attention/building.tsx +5 -1
- package/src/plugins/builtin/company-attention/client.ts +8 -9
- package/src/plugins/builtin/company-attention/evidence.ts +4 -1
- package/src/plugins/builtin/company-attention/moves.tsx +5 -1
- package/src/plugins/builtin/company-attention/pane.tsx +9 -2
- package/src/plugins/builtin/company-attention/signals.tsx +5 -1
- package/src/plugins/builtin/correlation/headless.ts +13 -5
- package/src/plugins/builtin/correlation/history.ts +22 -0
- package/src/plugins/builtin/cot/client.ts +22 -18
- package/src/plugins/builtin/cot/headless.ts +15 -10
- package/src/plugins/builtin/cot/model.ts +14 -1
- package/src/plugins/builtin/cot/pane.tsx +22 -14
- package/src/plugins/builtin/credit-boards/client.ts +6 -10
- package/src/plugins/builtin/credit-conditions/headless.ts +8 -2
- package/src/plugins/builtin/credit-conditions/index.tsx +5 -1
- package/src/plugins/builtin/credit-conditions/model.ts +4 -0
- package/src/plugins/builtin/credit-conditions/series.ts +3 -1
- package/src/plugins/builtin/credit-documents/detail.tsx +2 -2
- package/src/plugins/builtin/credit-documents/export.ts +173 -0
- package/src/plugins/builtin/credit-documents/headless.ts +7 -7
- package/src/plugins/builtin/credit-documents/model.ts +104 -14
- package/src/plugins/builtin/credit-documents/pane.tsx +12 -5
- package/src/plugins/builtin/custom-view/sources.ts +2 -0
- package/src/plugins/builtin/dividend-yield/client.ts +131 -23
- package/src/plugins/builtin/dividend-yield/headless.ts +44 -16
- package/src/plugins/builtin/dividend-yield/model.ts +8 -5
- package/src/plugins/builtin/dividend-yield/pane.tsx +38 -13
- package/src/plugins/builtin/dividend-yield/ranking.ts +37 -14
- package/src/plugins/builtin/dividend-yield/types.ts +20 -2
- package/src/plugins/builtin/dividend-yield/view.ts +30 -2
- package/src/plugins/builtin/docs/targets.ts +5 -1
- package/src/plugins/builtin/earnings/board-model.ts +16 -15
- package/src/plugins/builtin/earnings/board.tsx +62 -71
- package/src/plugins/builtin/earnings/headless.ts +40 -4
- package/src/plugins/builtin/earnings/scope.ts +31 -43
- package/src/plugins/builtin/earnings-calls/calendar.tsx +2 -2
- package/src/plugins/builtin/earnings-calls/format.ts +31 -2
- package/src/plugins/builtin/earnings-calls/headless.ts +7 -2
- package/src/plugins/builtin/earnings-calls/model.ts +6 -1
- package/src/plugins/builtin/earnings-calls/pane.tsx +5 -4
- package/src/plugins/builtin/econ/types.ts +2 -0
- package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +6 -4
- package/src/plugins/builtin/econ-statistics/stats.ts +36 -9
- package/src/plugins/builtin/equity-screener/categories.ts +179 -0
- package/src/plugins/builtin/equity-screener/client.ts +4 -2
- package/src/plugins/builtin/equity-screener/headless.ts +144 -13
- package/src/plugins/builtin/equity-screener/ideas.ts +49 -0
- package/src/plugins/builtin/equity-screener/model.ts +99 -10
- package/src/plugins/builtin/equity-screener/pane.tsx +68 -13
- package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
- package/src/plugins/builtin/futures/use-board-terms.ts +42 -28
- package/src/plugins/builtin/futures/use-front-returns.ts +45 -28
- package/src/plugins/builtin/fx-matrix/client.ts +6 -3
- package/src/plugins/builtin/fx-matrix/headless.ts +1 -1
- package/src/plugins/builtin/fx-matrix/index.tsx +4 -2
- package/src/plugins/builtin/fx-matrix/live-legs.ts +42 -3
- package/src/plugins/builtin/gpu/headless.ts +8 -2
- package/src/plugins/builtin/help/function-index.ts +4 -1
- package/src/plugins/builtin/help/index.tsx +1 -0
- package/src/plugins/builtin/help/shortcut-model.ts +1 -1
- package/src/plugins/builtin/holders/fund-holders.ts +51 -0
- package/src/plugins/builtin/holders/headless.ts +18 -1
- package/src/plugins/builtin/iv-history/client.ts +52 -11
- package/src/plugins/builtin/iv-history/format.ts +36 -23
- package/src/plugins/builtin/iv-history/headless.ts +33 -18
- package/src/plugins/builtin/iv-history/index.tsx +2 -2
- package/src/plugins/builtin/iv-history/model.ts +163 -27
- package/src/plugins/builtin/iv-history/pane.tsx +10 -6
- package/src/plugins/builtin/iv-history/screen-pane.tsx +22 -15
- package/src/plugins/builtin/iv-history/universe.ts +5 -1
- package/src/plugins/builtin/jobs/client.ts +25 -29
- package/src/plugins/builtin/kelly-sizer/model.ts +12 -8
- package/src/plugins/builtin/kelly-sizer/pane.tsx +9 -9
- package/src/plugins/builtin/kelly-sizer/sections.tsx +19 -10
- package/src/plugins/builtin/kelly-sizer/types.ts +10 -4
- package/src/plugins/builtin/macro-day/release-table.ts +2 -2
- package/src/plugins/builtin/market-heatmap/data.ts +8 -33
- package/src/plugins/builtin/market-movers/client.ts +25 -7
- package/src/plugins/builtin/market-movers/headless.ts +32 -12
- package/src/plugins/builtin/market-movers/index.tsx +26 -14
- package/src/plugins/builtin/market-movers/min-float.ts +74 -0
- package/src/plugins/builtin/market-movers/model.ts +4 -1
- package/src/plugins/builtin/market-movers/screener.ts +14 -0
- package/src/plugins/builtin/market-movers/session-body.tsx +13 -8
- package/src/plugins/builtin/market-movers/session-table.ts +4 -3
- package/src/plugins/builtin/market-movers/table.ts +17 -5
- package/src/plugins/builtin/members/breadth-pane.tsx +2 -1
- package/src/plugins/builtin/news/index.tsx +1 -0
- package/src/plugins/builtin/news/wire/categories.ts +19 -3
- package/src/plugins/builtin/news/wire/industry-pane.tsx +1 -0
- package/src/plugins/builtin/news/wire/news/footer.ts +31 -5
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +6 -1
- package/src/plugins/builtin/news/wire/news/table.tsx +2 -2
- package/src/plugins/builtin/openfda/client.ts +14 -2
- package/src/plugins/builtin/openfda/index.tsx +3 -2
- package/src/plugins/builtin/openfda/model.ts +9 -0
- package/src/plugins/builtin/openfda/pane.tsx +42 -6
- package/src/plugins/builtin/options/analytics.ts +29 -5
- package/src/plugins/builtin/options/enrichment.ts +17 -5
- package/src/plugins/builtin/options/table.ts +5 -2
- package/src/plugins/builtin/options/types.ts +2 -0
- package/src/plugins/builtin/options/view.tsx +17 -4
- package/src/plugins/builtin/options-positioning/client.ts +11 -0
- package/src/plugins/builtin/options-positioning/headless.ts +21 -8
- package/src/plugins/builtin/options-positioning/index.tsx +1 -1
- package/src/plugins/builtin/options-positioning/model.ts +78 -9
- package/src/plugins/builtin/options-positioning/pane.tsx +21 -6
- package/src/plugins/builtin/options-scenario/client.ts +116 -47
- package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +142 -10
- package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +206 -55
- package/src/plugins/builtin/options-scenario/csp-screen.ts +63 -23
- package/src/plugins/builtin/options-scenario/csp.ts +8 -2
- package/src/plugins/builtin/options-scenario/index.tsx +9 -7
- package/src/plugins/builtin/options-scenario/strategy-screen.ts +532 -0
- package/src/plugins/builtin/pe-band/model.ts +12 -49
- package/src/plugins/builtin/perps/cascade-tabs.tsx +161 -0
- package/src/plugins/builtin/perps/cascade.ts +236 -0
- package/src/plugins/builtin/perps/client.ts +56 -2
- package/src/plugins/builtin/perps/headless.ts +65 -9
- package/src/plugins/builtin/perps/index.tsx +7 -5
- package/src/plugins/builtin/perps/model.ts +18 -4
- package/src/plugins/builtin/perps/pane.tsx +19 -22
- package/src/plugins/builtin/perps/status-line.ts +25 -0
- package/src/plugins/builtin/plugin-module.ts +5 -1
- package/src/plugins/builtin/portfolio-list/cached-account.ts +3 -1
- package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +2 -8
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +4 -4
- package/src/plugins/builtin/portfolio-list/cli/render.ts +7 -5
- package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +4 -1
- package/src/plugins/builtin/portfolio-list/pane/streaming.ts +5 -2
- package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +3 -2
- package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +3 -1
- package/src/plugins/builtin/portfolio-list/table.tsx +11 -3
- package/src/plugins/builtin/primary-dealers/client.ts +13 -38
- package/src/plugins/builtin/realized-vol/index.tsx +2 -2
- package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
- package/src/plugins/builtin/relative-rotation/client.ts +18 -4
- package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
- package/src/plugins/builtin/relative-rotation/index.tsx +2 -1
- package/src/plugins/builtin/relative-rotation/model.ts +16 -40
- package/src/plugins/builtin/relative-rotation/pane.tsx +3 -2
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -2
- package/src/plugins/builtin/research/event-model.ts +10 -8
- package/src/plugins/builtin/research/headless.ts +1 -1
- package/src/plugins/builtin/research/relative-valuation-headless.ts +5 -1
- package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +7 -5
- package/src/plugins/builtin/reverse-dcf/headless.ts +5 -3
- package/src/plugins/builtin/reverse-dcf/model.ts +61 -6
- package/src/plugins/builtin/reverse-dcf/pane.tsx +7 -4
- package/src/plugins/builtin/scanner/flow-model.ts +50 -4
- package/src/plugins/builtin/scanner/flow-pane.tsx +367 -65
- package/src/plugins/builtin/scanner/flow-session.ts +146 -0
- package/src/plugins/builtin/scanner/headless.ts +204 -35
- package/src/plugins/builtin/scanner/index.ts +16 -5
- package/src/plugins/builtin/scanner/live-headless.ts +27 -3
- package/src/plugins/builtin/scanner/live-model.ts +11 -0
- package/src/plugins/builtin/scanner/live-pane.tsx +3 -3
- package/src/plugins/builtin/scanner/lock-note.ts +3 -2
- package/src/plugins/builtin/seasonality/headless.ts +10 -1
- package/src/plugins/builtin/sec/fund-facts-view.tsx +5 -2
- package/src/plugins/builtin/sec/fund-facts.ts +116 -34
- package/src/plugins/builtin/sec/headless.ts +2 -1
- package/src/plugins/builtin/series-inventory.ts +2 -0
- package/src/plugins/builtin/shared/cloud-resource.ts +3 -2
- package/src/plugins/builtin/shared/cloud-upgrade.ts +4 -3
- package/src/plugins/builtin/shared/earnings-calendar.ts +6 -2
- package/src/plugins/builtin/shared/earnings-report.ts +2 -2
- package/src/plugins/builtin/shared/job-posting.ts +1 -0
- package/src/plugins/builtin/shared/members-menu.tsx +3 -2
- package/src/plugins/builtin/shared/pro-lock-note.ts +12 -12
- package/src/plugins/builtin/shared/report-freshness.ts +3 -2
- package/src/plugins/builtin/shared/ticker-choice.tsx +2 -1
- package/src/plugins/builtin/shared/ticker-company.ts +77 -0
- package/src/plugins/builtin/shared/use-quote-board.ts +2 -1
- package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
- package/src/plugins/builtin/shared/volatility/pricing.ts +19 -0
- package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
- package/src/plugins/builtin/themes/model.ts +16 -7
- package/src/plugins/builtin/themes/pane.tsx +5 -5
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +43 -18
- package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +3 -2
- package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +62 -0
- package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +83 -46
- package/src/plugins/builtin/ticker-detail/headless.ts +31 -16
- package/src/plugins/builtin/ticker-detail/overview/model.ts +1 -1
- package/src/plugins/builtin/ticker-detail/pane.tsx +3 -2
- package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -1
- package/src/plugins/builtin/ticker-detail/settings.ts +1 -0
- package/src/plugins/builtin/time-sales/pane.tsx +31 -19
- package/src/plugins/builtin/treasury-daily/client.ts +41 -5
- package/src/plugins/builtin/treasury-daily/headless.ts +2 -2
- package/src/plugins/builtin/treasury-daily/index.tsx +1 -1
- package/src/plugins/builtin/treasury-daily/model.ts +50 -3
- package/src/plugins/builtin/treasury-daily/pane.tsx +81 -2
- package/src/plugins/builtin/vol-surface/client.ts +3 -13
- package/src/plugins/builtin/vol-surface/headless.ts +3 -3
- package/src/plugins/builtin/vol-surface/index.tsx +1 -1
- package/src/plugins/builtin/vol-surface/pane.tsx +7 -5
- package/src/plugins/builtin/volatility/index.tsx +2 -2
- package/src/plugins/builtin/volatility/pane.tsx +2 -2
- package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
- package/src/plugins/catalog-manifest.ts +2 -1
- package/src/plugins/marketplace/model.ts +18 -5
- package/src/portfolio/futures-positions.ts +6 -3
- package/src/portfolio/position-quantity.ts +15 -9
- package/src/remote/resources.ts +2 -1
- package/src/renderers/dom/data-table/row.tsx +26 -0
- package/src/renderers/dom/desktop/list-view.tsx +13 -10
- package/src/renderers/dom/desktop/query-bar.tsx +4 -2
- package/src/renderers/dom/desktop/select-field.tsx +4 -2
- package/src/renderers/dom/host/chart-surface.tsx +21 -0
- package/src/renderers/electrobun/bun/desktop/app-name.ts +3 -3
- package/src/renderers/electrobun/bun/desktop/backend.ts +12 -0
- package/src/renderers/electrobun/bun/desktop/host-requests.ts +7 -0
- package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +115 -14
- package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
- package/src/renderers/electrobun/shared/app-name.ts +13 -0
- package/src/renderers/electrobun/shared/protocol.ts +10 -0
- package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
- package/src/renderers/electrobun/view/main.tsx +24 -1
- package/src/renderers/electrobun/view/news-source.ts +3 -1
- package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
- package/src/renderers/electrobun/view/view-ids.ts +85 -0
- package/src/renderers/opentui/start.tsx +5 -0
- package/src/series/registry.ts +1 -0
- package/src/series/types.ts +6 -0
- package/src/series/validate.ts +1 -0
- package/src/sources/gloomberb-cloud/index.ts +1 -0
- package/src/sources/gloomberb-cloud/news.ts +47 -7
- package/src/sources/provider-router/financials.ts +15 -3
- package/src/sources/provider-router/index.ts +8 -5
- package/src/state/ticker-cleanup.ts +50 -0
- package/src/sync/controller.ts +4 -3
- package/src/sync/core-contributors.ts +90 -31
- package/src/telemetry/stall-reports-dom.ts +38 -0
- package/src/telemetry/stall-reports-node.ts +75 -0
- package/src/telemetry/stall-reports.ts +214 -0
- package/src/test-support/preload.ts +14 -0
- package/src/theme/themes.ts +10 -0
- package/src/tickers/company-name.ts +44 -3
- package/src/tickers/listing-venues.ts +15 -3
- package/src/tickers/terminal-codes.ts +19 -0
- package/src/time-series/alignment.ts +1 -1
- package/src/time-series/economic.ts +18 -4
- package/src/time-series/field-catalog.ts +1 -1
- package/src/time-series/filed-trailing-eps.ts +197 -0
- package/src/time-series/fundamentals.ts +44 -1
- package/src/time-series/ice-credit.ts +3 -0
- package/src/time-series/price-comparison.ts +30 -2
- package/src/time-series/resolve-context.ts +3 -1
- package/src/time-series/resolve.ts +56 -4
- package/src/time-series/session-bar.ts +11 -0
- package/src/time-series/spec.ts +3 -2
- package/src/time-series/studies.ts +8 -2
- package/src/time-series/total-return.ts +160 -0
- package/src/time-series/transforms.ts +2 -3
- package/src/time-series/types.ts +22 -1
- package/src/types/financials.ts +32 -1
- package/src/types/headless.ts +21 -3
- package/src/types/news-source.ts +1 -0
- package/src/types/plugin.ts +9 -0
- package/src/ui/host.tsx +3 -0
- package/src/updater/failure.ts +4 -1
- package/src/utils/cli-output.ts +42 -1
- package/src/utils/crypto-pair.ts +44 -0
- package/src/utils/forward-dividend.ts +2 -0
- package/src/utils/fundamentals.ts +24 -3
- package/src/utils/fx-market-hours.ts +26 -3
- package/src/utils/main-thread-stall.ts +35 -2
- package/src/utils/market-capitalization.ts +24 -1
- package/src/utils/price-history.ts +7 -1
- package/src/utils/shell-word.ts +8 -0
- package/src/utils/trailing-dividend.ts +53 -4
- package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
|
@@ -6,10 +6,13 @@ import { t } from "../../i18n";
|
|
|
6
6
|
import { DialogFrame, fitDialogWidth } from "./frame";
|
|
7
7
|
import { ListView, listCursorMove, type ListViewItem } from "./list-view";
|
|
8
8
|
import { clampIndex } from "../../utils/math";
|
|
9
|
+
import { withChoiceGroupHeadings } from "./choice-groups";
|
|
9
10
|
|
|
10
11
|
export interface ChoiceDialogChoice {
|
|
11
12
|
id: string;
|
|
12
13
|
label: string;
|
|
14
|
+
/** Heading for consecutive choices in the same group. */
|
|
15
|
+
group?: string;
|
|
13
16
|
description?: string;
|
|
14
17
|
detail?: string;
|
|
15
18
|
disabled?: boolean;
|
|
@@ -31,10 +34,10 @@ function getChoiceDescription(choice: ChoiceDialogChoice | undefined): string {
|
|
|
31
34
|
return choice?.description ?? "";
|
|
32
35
|
}
|
|
33
36
|
|
|
34
|
-
function getInitialChoiceIndex(choices:
|
|
37
|
+
function getInitialChoiceIndex(choices: ListViewItem[], selectedChoiceId: string | undefined): number {
|
|
35
38
|
if (!selectedChoiceId) return choices.findIndex((choice) => !choice.disabled);
|
|
36
|
-
const selectedIndex = choices.findIndex((choice) => choice.id === selectedChoiceId);
|
|
37
|
-
return selectedIndex >= 0 ? selectedIndex :
|
|
39
|
+
const selectedIndex = choices.findIndex((choice) => choice.id === selectedChoiceId && choice.kind !== "heading");
|
|
40
|
+
return selectedIndex >= 0 ? selectedIndex : choices.findIndex((choice) => choice.kind !== "heading");
|
|
38
41
|
}
|
|
39
42
|
|
|
40
43
|
function choiceDialogWidth(title: string, choices: ChoiceDialogChoice[]): number {
|
|
@@ -42,6 +45,7 @@ function choiceDialogWidth(title: string, choices: ChoiceDialogChoice[]): number
|
|
|
42
45
|
title.length,
|
|
43
46
|
...choices.map((choice) => choice.label.length + (choice.detail ? choice.detail.length + 4 : 0)),
|
|
44
47
|
...choices.map((choice) => getChoiceDescription(choice).length),
|
|
48
|
+
...choices.map((choice) => choice.group?.length ?? 0),
|
|
45
49
|
);
|
|
46
50
|
return Math.max(34, Math.min(76, contentWidth + 4));
|
|
47
51
|
}
|
|
@@ -56,27 +60,28 @@ export function ChoiceDialog({
|
|
|
56
60
|
}: ChoiceDialogProps) {
|
|
57
61
|
const colors = useThemeColors();
|
|
58
62
|
const desktop = useUiHost().kind === "desktop-web";
|
|
59
|
-
const [
|
|
60
|
-
clampIndex(getInitialChoiceIndex(choices, selectedChoiceId), choices.length, -1)
|
|
61
|
-
);
|
|
62
|
-
const selectedIndex = clampIndex(index, choices.length, -1);
|
|
63
|
-
const selectedChoice = selectedIndex >= 0 ? choices[selectedIndex] : undefined;
|
|
64
|
-
// Terminal input can deliver navigation and Enter before React renders.
|
|
65
|
-
const cursor = useRef(selectedIndex);
|
|
66
|
-
const selectIndex = (next: number) => { cursor.current = next; setIndex(next); };
|
|
67
|
-
const items = useMemo<ListViewItem[]>(() => choices.map((choice) => ({
|
|
63
|
+
const items = useMemo<ListViewItem[]>(() => withChoiceGroupHeadings(choices.map((choice) => ({
|
|
68
64
|
id: choice.id,
|
|
69
65
|
label: choice.label,
|
|
66
|
+
group: choice.group,
|
|
70
67
|
description: getChoiceDescription(choice),
|
|
71
68
|
detail: choice.detail,
|
|
72
69
|
disabled: choice.disabled,
|
|
73
70
|
color: choice.color,
|
|
74
|
-
})), [choices]);
|
|
71
|
+
}))), [choices]);
|
|
72
|
+
const [index, setIndex] = useState(() =>
|
|
73
|
+
clampIndex(getInitialChoiceIndex(items, selectedChoiceId), items.length, -1)
|
|
74
|
+
);
|
|
75
|
+
const selectedIndex = clampIndex(index, items.length, -1);
|
|
76
|
+
const selectedChoice = selectedIndex >= 0 ? items[selectedIndex] : undefined;
|
|
77
|
+
// Terminal input can deliver navigation and Enter before React renders.
|
|
78
|
+
const cursor = useRef(selectedIndex);
|
|
79
|
+
const selectIndex = (next: number) => { cursor.current = next; setIndex(next); };
|
|
75
80
|
const width = useMemo(() => choiceDialogWidth(title, choices), [choices, title]);
|
|
76
81
|
|
|
77
82
|
useEffect(() => {
|
|
78
|
-
selectIndex(clampIndex(cursor.current,
|
|
79
|
-
}, [
|
|
83
|
+
selectIndex(clampIndex(cursor.current, items.length, -1));
|
|
84
|
+
}, [items.length]);
|
|
80
85
|
|
|
81
86
|
const activateChoice = (choice: ChoiceDialogChoice | undefined) => {
|
|
82
87
|
if (!choice || choice.disabled) return;
|
|
@@ -87,9 +92,9 @@ export function ChoiceDialog({
|
|
|
87
92
|
event.stopPropagation();
|
|
88
93
|
const move = listCursorMove(event, MAX_VISIBLE_CHOICE_ROWS - 1);
|
|
89
94
|
if (move) {
|
|
90
|
-
selectIndex(move(
|
|
95
|
+
selectIndex(move(items, cursor.current));
|
|
91
96
|
} else if (event.name === "enter" || event.name === "return") {
|
|
92
|
-
activateChoice(
|
|
97
|
+
activateChoice(items[clampIndex(cursor.current, items.length, -1)]);
|
|
93
98
|
} else if (event.name === "escape") {
|
|
94
99
|
resolve("");
|
|
95
100
|
}
|
|
@@ -110,7 +115,7 @@ export function ChoiceDialog({
|
|
|
110
115
|
scrollable={items.length > MAX_VISIBLE_CHOICE_ROWS}
|
|
111
116
|
selectOnHover
|
|
112
117
|
onSelect={selectIndex}
|
|
113
|
-
onActivate={
|
|
118
|
+
onActivate={activateChoice}
|
|
114
119
|
/>
|
|
115
120
|
<Box height={1} />
|
|
116
121
|
<Text fg={colors.textDim} wrapText>{getChoiceDescription(selectedChoice)}</Text>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
interface ChoiceGroupHeading {
|
|
2
|
+
id: string;
|
|
3
|
+
label: string;
|
|
4
|
+
kind: "heading";
|
|
5
|
+
disabled: true;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/** Label each contiguous group without changing the order or identity of its choices. */
|
|
9
|
+
export function withChoiceGroupHeadings<T extends { group?: string }>(choices: readonly T[]): Array<T | ChoiceGroupHeading> {
|
|
10
|
+
let previousGroup: string | undefined;
|
|
11
|
+
return choices.flatMap((choice, index): Array<T | ChoiceGroupHeading> => {
|
|
12
|
+
const heading = choice.group && choice.group !== previousGroup
|
|
13
|
+
? { id: `\u0000group:${index}`, label: choice.group, kind: "heading" as const, disabled: true as const }
|
|
14
|
+
: null;
|
|
15
|
+
previousGroup = choice.group;
|
|
16
|
+
return heading ? [heading, choice] : [choice];
|
|
17
|
+
});
|
|
18
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
-
import { Box, ScrollBox, Text, TextAttributes, useNativeRenderer } from "../../../../ui";
|
|
2
|
+
import { Box, ScrollBox, Span, Text, TextAttributes, useNativeRenderer } from "../../../../ui";
|
|
3
3
|
import { hoverBg } from "../../../../theme/colors";
|
|
4
4
|
import { useThemeColors } from "../../../../theme/theme-context";
|
|
5
5
|
import { usePaneInstance } from "../../../../state/app/context";
|
|
@@ -12,7 +12,6 @@ import { EmptyState } from "../../status";
|
|
|
12
12
|
import { observeScrollBoxContentSize, observeScrollBoxViewportSize } from "../../../../renderers/opentui/scrollbox-layout";
|
|
13
13
|
import {
|
|
14
14
|
expandTableColumns,
|
|
15
|
-
fitTableCellText,
|
|
16
15
|
fitTableHeaderText,
|
|
17
16
|
getTableWidth,
|
|
18
17
|
hasMeaningfulTableHorizontalOverflow,
|
|
@@ -30,6 +29,7 @@ import { useFocusOwningPane, useVisibleRangeEmitter } from "../hooks";
|
|
|
30
29
|
import { useStableColumns } from "../stable-columns";
|
|
31
30
|
import {
|
|
32
31
|
cutColumnText,
|
|
32
|
+
fitColumnCellText,
|
|
33
33
|
resolveDataTableColumnClips,
|
|
34
34
|
resolveDataTableVisibleWindow,
|
|
35
35
|
resolveMoreColumnsHint,
|
|
@@ -212,10 +212,8 @@ function OpenTuiDataTableRowInner<
|
|
|
212
212
|
const shownWidth = clip ? clip.width : column.width - inset;
|
|
213
213
|
// Cut text is fitted to what shows; a number the edge cuts stays blank in its own place,
|
|
214
214
|
// and its cell keeps its background and its press.
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
: shownWidth > 0 ? fitTableCellText(cell.text, shownWidth, column.align) : "";
|
|
218
|
-
const contentCut = clip !== null && column.align === "right" && shownText.trim() === "";
|
|
215
|
+
const fitted = fitColumnCellText(cell.text, clip ? column.width : shownWidth, column.align, clip, cell.note);
|
|
216
|
+
const contentCut = clip !== null && column.align === "right" && fitted.text.trim() === "";
|
|
219
217
|
return (
|
|
220
218
|
<Box
|
|
221
219
|
key={column.id}
|
|
@@ -255,7 +253,11 @@ function OpenTuiDataTableRowInner<
|
|
|
255
253
|
? colors.selectedText
|
|
256
254
|
: cell.color ?? colors.text}
|
|
257
255
|
>
|
|
258
|
-
{
|
|
256
|
+
{fitted.note ? <>
|
|
257
|
+
{`${frozen ? " ".repeat(horizontalPadding) : ""}${" ".repeat(inset)}${fitted.text}`}
|
|
258
|
+
<Span fg={colors.textDim} attributes={TextAttributes.NONE}>{fitted.note}</Span>
|
|
259
|
+
{`${fitted.padding ?? ""}${frozen ? " ".repeat(columnGap) : ""}`}
|
|
260
|
+
</> : `${frozen ? " ".repeat(horizontalPadding) : ""}${" ".repeat(inset)}${fitted.text}${frozen ? " ".repeat(columnGap) : ""}`}
|
|
259
261
|
</Text>
|
|
260
262
|
)}
|
|
261
263
|
</Box>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { displayWidth } from "../../../../utils/format";
|
|
2
|
-
import { tableColumnLeadGap } from "../../table-layout";
|
|
2
|
+
import { fitTableCellText, tableColumnLeadGap } from "../../table-layout";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Where the viewport cuts a column: `inset` cells hidden on its left (past the
|
|
@@ -46,6 +46,31 @@ export function cutColumnText(
|
|
|
46
46
|
return `${" ".repeat(start - clip.inset)}${body}${" ".repeat(shownEnd - end)}`;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
/** A note uses only spare room: neither the label nor part of the note is cut to fit it. */
|
|
50
|
+
export function fitColumnCellText(
|
|
51
|
+
text: string,
|
|
52
|
+
width: number,
|
|
53
|
+
align: string | undefined,
|
|
54
|
+
clip: DataTableColumnClip | null,
|
|
55
|
+
note?: string,
|
|
56
|
+
): { text: string; note?: string; padding?: string } {
|
|
57
|
+
const fit = (value: string) => clip
|
|
58
|
+
? cutColumnText(value, align, width, clip, (body, shown) => fitTableCellText(body, shown, align))
|
|
59
|
+
: width > 0 ? fitTableCellText(value, width, align) : "";
|
|
60
|
+
const combined = note ? `${text} ${note}` : "";
|
|
61
|
+
if (combined && displayWidth(combined) <= (clip?.width ?? width)) {
|
|
62
|
+
const fitted = fit(combined);
|
|
63
|
+
const start = fitted.indexOf(combined);
|
|
64
|
+
// A right-aligned value may fit the visible width but sit beyond its edge.
|
|
65
|
+
if (start >= 0) return {
|
|
66
|
+
text: fitted.slice(0, start + text.length + 1),
|
|
67
|
+
note,
|
|
68
|
+
padding: fitted.slice(start + combined.length),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return { text: fit(text) };
|
|
72
|
+
}
|
|
73
|
+
|
|
49
74
|
function sameClip(a: DataTableColumnClip | null, b: DataTableColumnClip | null): boolean {
|
|
50
75
|
return a === b || (a !== null && b !== null && a.inset === b.inset && a.width === b.width);
|
|
51
76
|
}
|
|
@@ -24,6 +24,8 @@ export type DataTableColumn = Pick<
|
|
|
24
24
|
|
|
25
25
|
export interface DataTableCell {
|
|
26
26
|
text: string;
|
|
27
|
+
/** Muted context after `text`, shown whole only when both fit on one line. Does not change the exported value. */
|
|
28
|
+
note?: string;
|
|
27
29
|
/**
|
|
28
30
|
* What CSV export writes instead of `text`: the full-precision number behind
|
|
29
31
|
* it (3.45 for +3.45%, 1234567890 for 1.23B), or a date as an ISO string or
|
|
@@ -15,6 +15,7 @@ export interface ListViewItem {
|
|
|
15
15
|
description?: string;
|
|
16
16
|
detail?: string;
|
|
17
17
|
category?: string;
|
|
18
|
+
/** `heading` draws a muted group label; pair it with `disabled: true`. */
|
|
18
19
|
kind?: string;
|
|
19
20
|
right?: string;
|
|
20
21
|
checked?: boolean;
|
|
@@ -148,6 +149,7 @@ function DefaultRow({
|
|
|
148
149
|
selected: boolean;
|
|
149
150
|
}) {
|
|
150
151
|
const colors = useThemeColors();
|
|
152
|
+
if (item.kind === "heading") return <Text fg={colors.textMuted}>{t(item.label)}</Text>;
|
|
151
153
|
// The label gives way to the detail: a long label is cut with an ellipsis
|
|
152
154
|
// instead of wrapping under the row or running into the right column.
|
|
153
155
|
return (
|
|
@@ -289,6 +291,7 @@ export function ListView({
|
|
|
289
291
|
const rowHoverBg = hoverBgColor ?? hoverBg(colors);
|
|
290
292
|
const selectedItem = selectedIndex >= 0 ? items[selectedIndex] : undefined;
|
|
291
293
|
const activeScrollIndex = scrollIndex ?? selectedIndex;
|
|
294
|
+
const headingAboveSelection = items[activeScrollIndex - 1]?.kind === "heading";
|
|
292
295
|
const terminalRowHeight = rowHeight ?? 1;
|
|
293
296
|
const terminalRowGap = rowGap ?? 0;
|
|
294
297
|
const rowStride = terminalRowHeight + terminalRowGap;
|
|
@@ -299,13 +302,14 @@ export function ListView({
|
|
|
299
302
|
if (!sb) return;
|
|
300
303
|
const safeIndex = Math.min(activeScrollIndex, items.length - 1);
|
|
301
304
|
const viewportH = Math.max(sb.viewport?.height ?? 1, 1);
|
|
302
|
-
const rowTop = safeIndex * rowStride;
|
|
305
|
+
const rowTop = Math.max(0, safeIndex - (headingAboveSelection ? 1 : 0)) * rowStride;
|
|
306
|
+
const rowBottom = safeIndex * rowStride + terminalRowHeight;
|
|
303
307
|
if (rowTop < sb.scrollTop) {
|
|
304
308
|
sb.scrollTo(rowTop);
|
|
305
|
-
} else if (
|
|
306
|
-
sb.scrollTo(
|
|
309
|
+
} else if (rowBottom > sb.scrollTop + viewportH) {
|
|
310
|
+
sb.scrollTo(rowBottom - viewportH);
|
|
307
311
|
}
|
|
308
|
-
}, [activeScrollIndex, autoScrollToIndex, items.length, rowStride, scrollable, terminalRowHeight]);
|
|
312
|
+
}, [activeScrollIndex, autoScrollToIndex, headingAboveSelection, items.length, rowStride, scrollable, terminalRowHeight]);
|
|
309
313
|
useEffect(revealActiveRow, [revealActiveRow]);
|
|
310
314
|
// A list that opens on a row past the fold has nothing to scroll until its
|
|
311
315
|
// first computed layout sizes the viewport and the rows.
|
|
@@ -12,6 +12,7 @@ import type { QueryBarFilter, QueryBarSearch, QueryBarView } from "./query-bar";
|
|
|
12
12
|
interface ChoiceOption {
|
|
13
13
|
value: string;
|
|
14
14
|
label: string;
|
|
15
|
+
group?: string;
|
|
15
16
|
description?: string;
|
|
16
17
|
disabled?: boolean;
|
|
17
18
|
color?: string;
|
|
@@ -70,6 +71,7 @@ export function useQueryBarKeys({
|
|
|
70
71
|
choices={options.map((option) => ({
|
|
71
72
|
id: option.value,
|
|
72
73
|
label: option.label,
|
|
74
|
+
group: option.group,
|
|
73
75
|
description: option.description,
|
|
74
76
|
disabled: option.disabled,
|
|
75
77
|
color: option.color,
|
|
@@ -417,6 +417,7 @@ export function QueryBar({ width, search, filters = [], view, meta: given, asOf,
|
|
|
417
417
|
options: filter.options.map((option) => ({
|
|
418
418
|
value: option.value,
|
|
419
419
|
label: option.label,
|
|
420
|
+
group: option.group,
|
|
420
421
|
description: option.description,
|
|
421
422
|
disabled: option.disabled,
|
|
422
423
|
selected: option.value === filter.value,
|
|
@@ -18,6 +18,8 @@ export interface SelectControl {
|
|
|
18
18
|
export interface SelectButtonOption<T extends string = string> {
|
|
19
19
|
value: T;
|
|
20
20
|
label: string;
|
|
21
|
+
/** Heading for a contiguous group of choices; never selectable itself. */
|
|
22
|
+
group?: string;
|
|
21
23
|
/** Trigger wording when the full label is too wide for a pane header. */
|
|
22
24
|
short?: string;
|
|
23
25
|
description?: string;
|
|
@@ -130,6 +132,7 @@ export function SelectButton<T extends string = string>({
|
|
|
130
132
|
choices={options.map((option) => ({
|
|
131
133
|
id: option.value,
|
|
132
134
|
label: option.label,
|
|
135
|
+
group: option.group,
|
|
133
136
|
description: option.description,
|
|
134
137
|
disabled: option.disabled,
|
|
135
138
|
color: option.color,
|
|
@@ -158,7 +161,7 @@ export function SelectButton<T extends string = string>({
|
|
|
158
161
|
selectRef={(element) => { selectFieldRef.current = element; }}
|
|
159
162
|
onFocus={onFocus}
|
|
160
163
|
value={value}
|
|
161
|
-
options={options.map((option) => ({ value: option.value, label: option.label, disabled: option.disabled }))}
|
|
164
|
+
options={options.map((option) => ({ value: option.value, label: option.label, group: option.group, disabled: option.disabled }))}
|
|
162
165
|
disabled={disabled}
|
|
163
166
|
onChange={selectValue}
|
|
164
167
|
/>
|
|
@@ -180,6 +183,7 @@ export function SelectButton<T extends string = string>({
|
|
|
180
183
|
options={options.map((option) => ({
|
|
181
184
|
value: option.value,
|
|
182
185
|
label: option.label,
|
|
186
|
+
group: option.group,
|
|
183
187
|
disabled: option.disabled,
|
|
184
188
|
}))}
|
|
185
189
|
disabled={disabled}
|
|
@@ -106,18 +106,21 @@ export function getTableWidth(
|
|
|
106
106
|
* The columns that fit in `width` cells: whole columns in `dropOrder` (least
|
|
107
107
|
* needed first) give way until the rest fit, rather than the table's right
|
|
108
108
|
* edge cutting a value, so a narrow pane drops a name that repeats its ticker
|
|
109
|
-
* before it cuts a date.
|
|
110
|
-
*
|
|
109
|
+
* before it cuts a date. An entry that lists several ids drops them together,
|
|
110
|
+
* for columns that mean little apart (a bid's venue and an ask's). Columns not
|
|
111
|
+
* listed stay at every width. A table that scrolls vertically passes its width
|
|
112
|
+
* less the scrollbar's cell.
|
|
111
113
|
*/
|
|
112
114
|
export function dropColumnsToFit<C extends TableWidthColumn & { id: string }>(
|
|
113
115
|
columns: readonly C[],
|
|
114
116
|
width: number,
|
|
115
|
-
dropOrder: readonly string[],
|
|
117
|
+
dropOrder: readonly (string | readonly string[])[],
|
|
116
118
|
): C[] {
|
|
117
119
|
let shown = [...columns];
|
|
118
|
-
for (const
|
|
120
|
+
for (const step of dropOrder) {
|
|
119
121
|
if (getTableWidth(shown) <= width) break;
|
|
120
|
-
|
|
122
|
+
const ids = typeof step === "string" ? [step] : step;
|
|
123
|
+
shown = shown.filter((column) => !ids.includes(column.id));
|
|
121
124
|
}
|
|
122
125
|
return shown;
|
|
123
126
|
}
|
package/src/core/app-runtime.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { MarketDataCoordinator, getSharedMarketDataCoordinator, setSharedMarketDataCoordinator } from "../market-data/coordinator";
|
|
2
2
|
import { NewsService, type NewsServiceOptions } from "../news/aggregator";
|
|
3
|
+
import { cloudNewsSession } from "../news/cloud-session";
|
|
3
4
|
import { getSharedNewsService, setSharedNewsService } from "../news/hooks";
|
|
4
5
|
import { PluginRegistry } from "../plugins/registry";
|
|
5
6
|
import { releaseSharedRegistry } from "../plugins/registry/shared";
|
|
@@ -61,6 +62,7 @@ export function createAppRuntime({
|
|
|
61
62
|
const newsService = new NewsService({
|
|
62
63
|
connectionHealth: pluginRegistry.connectionHealth,
|
|
63
64
|
pollIntervalMs: () => Math.max(1, config.refreshIntervalMinutes) * 60_000,
|
|
65
|
+
session: cloudNewsSession,
|
|
64
66
|
...newsOptions,
|
|
65
67
|
});
|
|
66
68
|
const modules = { pluginRegistry, marketData, newsService };
|
package/src/core/app-services.ts
CHANGED
|
@@ -27,8 +27,8 @@ export function createAppServices({ config, plugins, externalPlugins }: AppServi
|
|
|
27
27
|
provider: {
|
|
28
28
|
fetchNews: async (query) => (await newsService.load(query)).articles,
|
|
29
29
|
fetchNewsPage: async (query) => {
|
|
30
|
-
const { articles, error, nextCursor } = await newsService.load(query);
|
|
31
|
-
return { articles, error, nextCursor };
|
|
30
|
+
const { articles, error, nextCursor, access } = await newsService.load(query);
|
|
31
|
+
return { articles, error, nextCursor, access };
|
|
32
32
|
},
|
|
33
33
|
getCachedNews: (query) => newsService.getQueryState(query).articles,
|
|
34
34
|
},
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
import { recordOrNull } from "../../utils/guards";
|
|
21
21
|
|
|
22
22
|
const CHART_RANGES = new Set<TimeRange>(TIME_RANGES);
|
|
23
|
-
const PRICE_RENDER_MODES = new Set<SeriesStyle>(["area", "line", "candles", "ohlc", "hlc"]);
|
|
23
|
+
const PRICE_RENDER_MODES = new Set<SeriesStyle>(["area", "line", "candles", "hollow", "heikin-ashi", "ohlc", "hlc"]);
|
|
24
24
|
const LEGACY_INDICATOR_IDS = new Set<LegacyChartIndicatorId>([
|
|
25
25
|
"volume",
|
|
26
26
|
"sma20",
|
|
@@ -4,6 +4,7 @@ import { hydrateTickerMetadata } from "../tickers/metadata";
|
|
|
4
4
|
import type { TickerMetadata, TickerRecord } from "../types/ticker";
|
|
5
5
|
import { BROWSER_STORAGE_KEYS, SafeJsonStorage, type StorageLike } from "./json-storage";
|
|
6
6
|
import { isRecord } from "../utils/guards";
|
|
7
|
+
import { runTickerWrite } from "./ticker-writes";
|
|
7
8
|
|
|
8
9
|
type StoredTickers = Record<string, TickerMetadata>;
|
|
9
10
|
|
|
@@ -39,20 +40,28 @@ export class JsonTickerRepository implements AppTickerRepositoryPort {
|
|
|
39
40
|
return metadata ? hydrate(metadata) : null;
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
|
|
43
|
+
private writeTicker(ticker: TickerRecord): void {
|
|
43
44
|
const symbol = normalizeSymbol(ticker.metadata.ticker);
|
|
44
45
|
this.data.set({ ...this.data.get(), [symbol]: ticker.metadata });
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
|
|
48
|
+
saveTicker(ticker: TickerRecord): Promise<void> {
|
|
49
|
+
return runTickerWrite(this, ticker.metadata.ticker, () => this.writeTicker(ticker));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
createTicker(metadata: TickerMetadata): Promise<TickerRecord> {
|
|
48
53
|
const ticker = { metadata };
|
|
49
|
-
|
|
50
|
-
|
|
54
|
+
return runTickerWrite(this, metadata.ticker, () => {
|
|
55
|
+
this.writeTicker(ticker);
|
|
56
|
+
return ticker;
|
|
57
|
+
});
|
|
51
58
|
}
|
|
52
59
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
60
|
+
deleteTicker(symbol: string): Promise<void> {
|
|
61
|
+
return runTickerWrite(this, symbol, () => {
|
|
62
|
+
const tickers = { ...this.data.get() };
|
|
63
|
+
delete tickers[normalizeSymbol(symbol)];
|
|
64
|
+
this.data.set(tickers);
|
|
65
|
+
});
|
|
57
66
|
}
|
|
58
67
|
}
|
|
@@ -2,6 +2,7 @@ import { logFailure } from "../utils/log-failure";
|
|
|
2
2
|
import type { TickerRecord, TickerMetadata } from "../types/ticker";
|
|
3
3
|
import type { TickerStore } from "./ticker-store";
|
|
4
4
|
import { hydrateTickerMetadata } from "../tickers/metadata";
|
|
5
|
+
import { runTickerWrite } from "./ticker-writes";
|
|
5
6
|
|
|
6
7
|
function parseTickerMetadata(json: string): TickerMetadata {
|
|
7
8
|
return hydrateTickerMetadata(JSON.parse(json) as Record<string, unknown>);
|
|
@@ -32,22 +33,28 @@ export class TickerRepository {
|
|
|
32
33
|
}
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
this
|
|
36
|
+
saveTicker(ticker: TickerRecord): Promise<void> {
|
|
37
|
+
return runTickerWrite(this, ticker.metadata.ticker, () => {
|
|
38
|
+
this.tickers.save(ticker.metadata.ticker, ticker.metadata);
|
|
39
|
+
});
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
/** Saves several tickers in one write. */
|
|
40
|
-
|
|
41
|
-
this
|
|
43
|
+
saveTickers(tickers: readonly TickerRecord[]): Promise<void> {
|
|
44
|
+
return runTickerWrite(this, tickers.map((ticker) => ticker.metadata.ticker), () => {
|
|
45
|
+
this.tickers.saveMany(tickers.map((ticker) => ({ symbol: ticker.metadata.ticker, metadata: ticker.metadata })));
|
|
46
|
+
});
|
|
42
47
|
}
|
|
43
48
|
|
|
44
|
-
|
|
49
|
+
createTicker(metadata: TickerMetadata): Promise<TickerRecord> {
|
|
45
50
|
const ticker: TickerRecord = { metadata };
|
|
46
|
-
|
|
47
|
-
|
|
51
|
+
return runTickerWrite(this, metadata.ticker, () => {
|
|
52
|
+
this.tickers.save(metadata.ticker, metadata);
|
|
53
|
+
return ticker;
|
|
54
|
+
});
|
|
48
55
|
}
|
|
49
56
|
|
|
50
|
-
|
|
51
|
-
this.tickers.delete(symbol);
|
|
57
|
+
deleteTicker(symbol: string): Promise<void> {
|
|
58
|
+
return runTickerWrite(this, symbol, () => this.tickers.delete(symbol));
|
|
52
59
|
}
|
|
53
60
|
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
interface TickerWrites {
|
|
2
|
+
revisions: Map<string, number>;
|
|
3
|
+
tails: Map<string, Promise<unknown>>;
|
|
4
|
+
pending: Set<Promise<unknown>>;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const repositories = new WeakMap<object, TickerWrites>();
|
|
8
|
+
|
|
9
|
+
function writesFor(repository: object): TickerWrites {
|
|
10
|
+
let writes = repositories.get(repository);
|
|
11
|
+
if (!writes) {
|
|
12
|
+
writes = { revisions: new Map(), tails: new Map(), pending: new Set() };
|
|
13
|
+
repositories.set(repository, writes);
|
|
14
|
+
}
|
|
15
|
+
return writes;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function tickerKey(symbol: string): string {
|
|
19
|
+
return symbol.trim().toUpperCase();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Reserve writes when requested, including ones waiting for an earlier acknowledgement. */
|
|
23
|
+
export function runTickerWrite<Result>(
|
|
24
|
+
repository: object,
|
|
25
|
+
symbols: string | readonly string[],
|
|
26
|
+
write: () => Result | PromiseLike<Result>,
|
|
27
|
+
): Promise<Result> {
|
|
28
|
+
const writes = writesFor(repository);
|
|
29
|
+
const keys = [...new Set((typeof symbols === "string" ? [symbols] : symbols).map(tickerKey))];
|
|
30
|
+
const previous = [...new Set(keys.flatMap((key) => {
|
|
31
|
+
const tail = writes.tails.get(key);
|
|
32
|
+
return tail ? [tail] : [];
|
|
33
|
+
}))];
|
|
34
|
+
let resolve!: (value: Result | PromiseLike<Result>) => void;
|
|
35
|
+
let reject!: (error: unknown) => void;
|
|
36
|
+
const result = new Promise<Result>((accept, fail) => {
|
|
37
|
+
resolve = accept;
|
|
38
|
+
reject = fail;
|
|
39
|
+
});
|
|
40
|
+
for (const key of keys) {
|
|
41
|
+
writes.revisions.set(key, (writes.revisions.get(key) ?? 0) + 1);
|
|
42
|
+
writes.tails.set(key, result);
|
|
43
|
+
}
|
|
44
|
+
writes.pending.add(result);
|
|
45
|
+
const finished = () => {
|
|
46
|
+
writes.pending.delete(result);
|
|
47
|
+
for (const key of keys) {
|
|
48
|
+
if (writes.tails.get(key) === result) writes.tails.delete(key);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
void result.then(finished, finished);
|
|
52
|
+
const start = () => {
|
|
53
|
+
try {
|
|
54
|
+
resolve(write());
|
|
55
|
+
} catch (error) {
|
|
56
|
+
reject(error);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
if (previous.length) void Promise.allSettled(previous).then(start);
|
|
60
|
+
else start();
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Changes immediately when a save or deletion is requested, before its acknowledgement. */
|
|
65
|
+
export function tickerWriteRevision(repository: object, symbol: string): number {
|
|
66
|
+
return repositories.get(repository)?.revisions.get(tickerKey(symbol)) ?? 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Whether a write arrived after a caller last waited for acknowledgements. */
|
|
70
|
+
export function hasPendingTickerWrites(repository: object): boolean {
|
|
71
|
+
return (repositories.get(repository)?.pending.size ?? 0) > 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Let save-await callers publish their edits before cleanup reads the current rows. */
|
|
75
|
+
export async function settleTickerWrites(repository: object): Promise<void> {
|
|
76
|
+
const writes = writesFor(repository);
|
|
77
|
+
do {
|
|
78
|
+
// Each writer handles its own failure; cleanup still needs the last published row.
|
|
79
|
+
await Promise.allSettled([...writes.pending]);
|
|
80
|
+
} while (writes.pending.size);
|
|
81
|
+
}
|