@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
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { mkdir, stat, writeFile } from "fs/promises";
|
|
2
|
+
import { homedir } from "os";
|
|
3
|
+
import { posix, win32 } from "path";
|
|
4
|
+
|
|
5
|
+
interface OutputPathContext {
|
|
6
|
+
cwd?: string;
|
|
7
|
+
home?: string;
|
|
8
|
+
platform?: NodeJS.Platform;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** `C:\Users\me\out.csv`, `C:/out.csv`, `\\server\share\out.csv`: a path only Windows reads. */
|
|
12
|
+
const WINDOWS_PATH = /^(?:[A-Za-z]:[\\/]|\\\\)/;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The absolute path `--output <path>` names, in any form a shell passes it:
|
|
16
|
+
* relative to the folder the command runs in, absolute, or under the home
|
|
17
|
+
* folder (`~/Desktop/rv.csv`, which a shell leaves as typed after
|
|
18
|
+
* `--output=`). A Windows path is read as one on Windows; anywhere else it
|
|
19
|
+
* is refused, since it would write a file named after the whole path.
|
|
20
|
+
*/
|
|
21
|
+
export function resolveOutputPath(raw: string, context: OutputPathContext = {}): string {
|
|
22
|
+
const platform = context.platform ?? process.platform;
|
|
23
|
+
const paths = platform === "win32" ? win32 : posix;
|
|
24
|
+
const typed = raw.trim();
|
|
25
|
+
if (!typed) throw new Error("--output needs a file to write, such as --output report.csv.");
|
|
26
|
+
if (platform !== "win32" && WINDOWS_PATH.test(typed)) {
|
|
27
|
+
throw new Error(`${typed} is a Windows path. Write a path on this computer, such as --output ~/Desktop/report.csv.`);
|
|
28
|
+
}
|
|
29
|
+
const home = context.home ?? homedir();
|
|
30
|
+
const homeRelative = /^~(?:$|[\\/])/.test(typed);
|
|
31
|
+
const expanded = homeRelative ? paths.join(home, typed.slice(1)) : typed;
|
|
32
|
+
return paths.resolve(context.cwd ?? process.cwd(), expanded);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function errorCode(error: unknown): string | undefined {
|
|
36
|
+
return error && typeof error === "object" && "code" in error ? String((error as { code: unknown }).code) : undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function isFolder(path: string, paths: typeof posix): Error {
|
|
40
|
+
return new Error(`Cannot write ${path}: it is a folder. Name a file in it, such as ${paths.join(path, "report.csv")}.`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Fails when `path` names a folder: checked before a report loads, so it stops at once. */
|
|
44
|
+
export async function checkOutputPath(path: string, platform: NodeJS.Platform = process.platform): Promise<void> {
|
|
45
|
+
const existing = await stat(path).catch(() => {
|
|
46
|
+
// Intentional ignore: a path that does not exist yet is the usual case, and writing reports any real failure.
|
|
47
|
+
return null;
|
|
48
|
+
});
|
|
49
|
+
if (existing?.isDirectory()) throw isFolder(path, platform === "win32" ? win32 : posix);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Writes a command's output to `path`, creating the folders it names as
|
|
54
|
+
* `shot --output` does, and failing with what to do when it cannot.
|
|
55
|
+
*/
|
|
56
|
+
export async function writeOutputFile(path: string, contents: string, platform: NodeJS.Platform = process.platform): Promise<void> {
|
|
57
|
+
const paths = platform === "win32" ? win32 : posix;
|
|
58
|
+
try {
|
|
59
|
+
await mkdir(paths.dirname(path), { recursive: true });
|
|
60
|
+
await writeFile(path, contents);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
const code = errorCode(error);
|
|
63
|
+
if (code === "EISDIR") throw isFolder(path, paths);
|
|
64
|
+
if (code === "EACCES" || code === "EPERM") throw new Error(`Cannot write ${path}: permission denied.`);
|
|
65
|
+
if (code === "ENOTDIR" || code === "EEXIST") throw new Error(`Cannot write ${path}: a file stands where one of its folders would be.`);
|
|
66
|
+
throw new Error(`Cannot write ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -2,6 +2,7 @@ import type {
|
|
|
2
2
|
HeadlessPaneArgumentDef,
|
|
3
3
|
HeadlessPaneDefinition,
|
|
4
4
|
HeadlessPaneOptionDef,
|
|
5
|
+
HeadlessPaneReference,
|
|
5
6
|
PaneDef,
|
|
6
7
|
PaneTemplateDef,
|
|
7
8
|
} from "../../types/plugin";
|
|
@@ -26,6 +27,8 @@ export interface PaneFunctionCapability {
|
|
|
26
27
|
screenshotReadiness: PaneFunctionScreenshotReadiness;
|
|
27
28
|
dataRequirements: string[];
|
|
28
29
|
limitations: string[];
|
|
30
|
+
/** Lists the catalog prints under the options (EQS's sector and industry names). */
|
|
31
|
+
reference?: HeadlessPaneReference[];
|
|
29
32
|
options: PaneFunctionOptionDef[];
|
|
30
33
|
/** A rendered view that refuses any option it does not declare (PaneDef.reportOptionsOnly), as a headless report does. */
|
|
31
34
|
strictOptions?: boolean;
|
|
@@ -79,6 +79,14 @@ function builtInPanePrefixes(paneId: string): string[] {
|
|
|
79
79
|
return command ? getCommandPrefixes(command) : [];
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
/**
|
|
83
|
+
* What a user types for a function, as the catalog lists it and every error names it: a template's
|
|
84
|
+
* shortcut or id, a pane opened by a built-in command (LAY) by that command's mnemonic, else the pane's id.
|
|
85
|
+
*/
|
|
86
|
+
export function paneFunctionCode(template: PaneTemplateDef | undefined, pane: PaneDef): string {
|
|
87
|
+
return template?.shortcut?.prefix ?? template?.id ?? builtInPanePrefixes(pane.id)[0] ?? pane.id;
|
|
88
|
+
}
|
|
89
|
+
|
|
82
90
|
function registerResolverToken(
|
|
83
91
|
lookup: Map<string, PaneTemplateDef | PaneDef>,
|
|
84
92
|
token: string | undefined,
|
|
@@ -195,7 +203,8 @@ export function nearFunctionCodes(
|
|
|
195
203
|
const near = new Map<PaneTemplateDef, string>();
|
|
196
204
|
for (const template of registry.paneTemplates.values()) {
|
|
197
205
|
const prefix = template.shortcut?.prefix;
|
|
198
|
-
|
|
206
|
+
// An unlisted function is never offered for a typo.
|
|
207
|
+
if (!prefix || template.listed === false) continue;
|
|
199
208
|
const code = [prefix, ...template.shortcut?.aliases ?? []].find((candidate) => {
|
|
200
209
|
const known = normalizeLookupToken(candidate);
|
|
201
210
|
return (!commandWord || known[0] === wanted[0]) && typoDistance(wanted, known) === 1;
|
|
@@ -215,6 +224,7 @@ export function suggestedFunctionCodes(registry: PaneFunctionCatalog, name: stri
|
|
|
215
224
|
const wanted = normalizeLookupToken(name);
|
|
216
225
|
if (!wanted) return [];
|
|
217
226
|
return [...registry.paneTemplates.values()].flatMap((template) => {
|
|
227
|
+
if (template.listed === false) return [];
|
|
218
228
|
const pane = registry.panes.get(template.paneId);
|
|
219
229
|
const words = pane ? getHeadlessPaneDefinition(template, pane)?.discovery?.suggestFor ?? [] : [];
|
|
220
230
|
const code = template.shortcut?.prefix;
|
|
@@ -277,9 +287,15 @@ async function buildTemplateCatalogEntry(
|
|
|
277
287
|
};
|
|
278
288
|
}
|
|
279
289
|
|
|
290
|
+
/**
|
|
291
|
+
* The functions `gloom catalog` and search list. An unlisted one (`listed:
|
|
292
|
+
* false`) is left out, and its pane is not listed in its place; `unlisted`
|
|
293
|
+
* keeps it, for its own help (`fn ATTN --help`).
|
|
294
|
+
*/
|
|
280
295
|
export async function buildPaneCatalogEntries(
|
|
281
296
|
registry: PaneFunctionCatalog,
|
|
282
297
|
context: MarketContext,
|
|
298
|
+
{ unlisted = false }: { unlisted?: boolean } = {},
|
|
283
299
|
): Promise<PaneCatalogEntry[]> {
|
|
284
300
|
const entries: PaneCatalogEntry[] = [];
|
|
285
301
|
const templatedPaneIds = new Set<string>();
|
|
@@ -288,6 +304,7 @@ export async function buildPaneCatalogEntries(
|
|
|
288
304
|
const pane = registry.panes.get(template.paneId);
|
|
289
305
|
if (!pane) continue;
|
|
290
306
|
templatedPaneIds.add(pane.id);
|
|
307
|
+
if (template.listed === false && !unlisted) continue;
|
|
291
308
|
entries.push(await buildTemplateCatalogEntry(template, pane, context));
|
|
292
309
|
}
|
|
293
310
|
|
|
@@ -296,7 +313,7 @@ export async function buildPaneCatalogEntries(
|
|
|
296
313
|
const [shortcut, ...aliases] = builtInPanePrefixes(pane.id);
|
|
297
314
|
const argument = paneFunctionArgument(undefined, pane);
|
|
298
315
|
entries.push({
|
|
299
|
-
token:
|
|
316
|
+
token: paneFunctionCode(undefined, pane),
|
|
300
317
|
label: pane.name,
|
|
301
318
|
description: `Open the ${pane.name} pane.`,
|
|
302
319
|
paneId: pane.id,
|
|
@@ -333,6 +350,14 @@ const ALL_WORDS_WITH_FLAGS_BAND = 1000;
|
|
|
333
350
|
const PER_MATCHED_WORD = 100;
|
|
334
351
|
/** Loose matches (no function carries every word) listed before the rest are left to a refined query. */
|
|
335
352
|
const LOOSE_MATCH_LIMIT = 5;
|
|
353
|
+
/** Loose matches a word of the query names (in the function's code, name or aliases) are all listed, up to this many. */
|
|
354
|
+
const LOOSE_NAMED_LIMIT = 8;
|
|
355
|
+
/** A query word found in what the function is called (its code, name or aliases) outweighs one found in its keywords or description. */
|
|
356
|
+
const PER_WORD_IN_NAME = 3;
|
|
357
|
+
/** A query word that begins a function's code (`vol` for VOLS, `fx` for FXC) is the start of typing it. */
|
|
358
|
+
const PER_WORD_STARTING_CODE = 3;
|
|
359
|
+
/** A query word that is one of the function's own keywords (`vol` for the volatility functions, which also say volume's) names it. */
|
|
360
|
+
const PER_WORD_IS_KEYWORD = 3;
|
|
336
361
|
|
|
337
362
|
function phraseBand(entry: PaneCatalogEntry, phrase: string): number {
|
|
338
363
|
const lower = (values: Array<string | undefined>) => values
|
|
@@ -375,6 +400,12 @@ function nameAndDescription(entry: PaneCatalogEntry): string {
|
|
|
375
400
|
return nameAndDescriptionFields(entry).join(" ");
|
|
376
401
|
}
|
|
377
402
|
|
|
403
|
+
/** What a function is called: its code, name and aliases, apart from the keywords and description that say what it covers. */
|
|
404
|
+
function identityFields(entry: PaneCatalogEntry): string[] {
|
|
405
|
+
return [entry.token, entry.label, entry.paneName, entry.shortcut, ...entry.aliases]
|
|
406
|
+
.filter((value): value is string => !!value).map((value) => value.toLowerCase().replace(/\s+/g, " "));
|
|
407
|
+
}
|
|
408
|
+
|
|
378
409
|
function flagText(entry: PaneCatalogEntry): string {
|
|
379
410
|
return [
|
|
380
411
|
entry.argKind,
|
|
@@ -394,12 +425,14 @@ interface CatalogMatchScore {
|
|
|
394
425
|
terms: number;
|
|
395
426
|
/** Those found in the function's name, keywords or description. */
|
|
396
427
|
named: number;
|
|
428
|
+
/** Those found in what the function is called: its code, name or aliases. */
|
|
429
|
+
called: number;
|
|
397
430
|
}
|
|
398
431
|
|
|
399
432
|
function scorePaneCatalogEntry(entry: PaneCatalogEntry, query: string): CatalogMatchScore {
|
|
400
433
|
const terms = queryTerms(query);
|
|
401
|
-
if (terms.length === 0) return { score: 1, terms: 0, named: 0 };
|
|
402
|
-
const none = { score: 0, terms: terms.length, named: 0 };
|
|
434
|
+
if (terms.length === 0) return { score: 1, terms: 0, named: 0, called: 0 };
|
|
435
|
+
const none = { score: 0, terms: terms.length, named: 0, called: 0 };
|
|
403
436
|
|
|
404
437
|
const exactTokens = [
|
|
405
438
|
entry.token,
|
|
@@ -414,6 +447,8 @@ function scorePaneCatalogEntry(entry: PaneCatalogEntry, query: string): CatalogM
|
|
|
414
447
|
const named = nameAndDescription(entry);
|
|
415
448
|
const flags = flagText(entry);
|
|
416
449
|
const wholeQuery = query.toLowerCase().trim().replace(/\s+/g, " ");
|
|
450
|
+
const called = identityFields(entry);
|
|
451
|
+
const codes = [entry.token, entry.shortcut, ...entry.aliases].filter((value): value is string => !!value).map((value) => normalizeLookupToken(value));
|
|
417
452
|
|
|
418
453
|
let score = containsPhrase(`${named} ${flags}`, wholeQuery) ? 12 : 0;
|
|
419
454
|
// A query that is one of the function's own keywords ("hedge" for OSA) beats one found inside a
|
|
@@ -423,9 +458,16 @@ function scorePaneCatalogEntry(entry: PaneCatalogEntry, query: string): CatalogM
|
|
|
423
458
|
if (keywords.includes(wholeQuery)) score += 4;
|
|
424
459
|
let matchedTerms = 0;
|
|
425
460
|
let matchedNamed = 0;
|
|
461
|
+
let matchedCalled = 0;
|
|
426
462
|
let matchedFlagKeys = 0;
|
|
427
463
|
for (const term of terms) {
|
|
428
464
|
const normalized = normalizeLookupToken(term);
|
|
465
|
+
if (called.some((field) => containsPhrase(field, term))) {
|
|
466
|
+
score += PER_WORD_IN_NAME;
|
|
467
|
+
matchedCalled += 1;
|
|
468
|
+
}
|
|
469
|
+
if (codes.some((code) => normalized.length >= 2 && code.startsWith(normalized))) score += PER_WORD_STARTING_CODE;
|
|
470
|
+
if (keywords.includes(term)) score += PER_WORD_IS_KEYWORD;
|
|
429
471
|
// A term starts a word: "rate" is found in "rates" but not in "separate", nor "budget" in "budgetBps".
|
|
430
472
|
if (exactTokens.includes(normalized)) {
|
|
431
473
|
score += 8;
|
|
@@ -455,12 +497,13 @@ function scorePaneCatalogEntry(entry: PaneCatalogEntry, query: string): CatalogM
|
|
|
455
497
|
score += Math.round((matchedTerms / terms.length) * 8);
|
|
456
498
|
if (entry.capability.botSafe) score += 2;
|
|
457
499
|
// One word has no phrase to prefer: it keeps the plain term scores.
|
|
458
|
-
if (terms.length === 1) return { score, terms: 1, named: matchedNamed };
|
|
500
|
+
if (terms.length === 1) return { score, terms: 1, named: matchedNamed, called: matchedCalled };
|
|
459
501
|
const allWords = matchedNamed === terms.length ? ALL_WORDS_BAND : matchedTerms === terms.length ? ALL_WORDS_WITH_FLAGS_BAND : 0;
|
|
460
502
|
return {
|
|
461
503
|
score: score + matchedTerms * PER_MATCHED_WORD + (phraseBand(entry, wholeQuery) || allWords),
|
|
462
504
|
terms: terms.length,
|
|
463
505
|
named: matchedNamed,
|
|
506
|
+
called: matchedCalled,
|
|
464
507
|
};
|
|
465
508
|
}
|
|
466
509
|
|
|
@@ -535,8 +578,14 @@ export function selectPaneCatalogMatches(ranked: PaneCatalogEntry[], query: stri
|
|
|
535
578
|
const shown = every(complete).slice(0, limit);
|
|
536
579
|
return { shown, hidden: ranked.length - shown.length, loose: false };
|
|
537
580
|
}
|
|
581
|
+
// No function carries every word: the ones a word names come first, the code or name of a function being what a
|
|
582
|
+
// reader types ("fx vol" is FXC, whose name has fx, and VOLS, whose name has vol), ahead of one that mentions a word
|
|
583
|
+
// in its keywords ("implied fx" in ADR) or its flags, and however many of the words a weaker match has.
|
|
538
584
|
const named = scored.filter(({ named }) => named > 0);
|
|
539
|
-
const
|
|
585
|
+
const pool = named.length > 0 ? named : scored;
|
|
586
|
+
const leading = pool.filter((item) => item.called > 0).slice(0, LOOSE_NAMED_LIMIT);
|
|
587
|
+
const rest = pool.filter((item) => !leading.includes(item)).slice(0, Math.max(0, LOOSE_MATCH_LIMIT - leading.length));
|
|
588
|
+
const shown = every([...leading, ...rest]).slice(0, limit);
|
|
540
589
|
return { shown, hidden: ranked.length - shown.length, loose: true };
|
|
541
590
|
}
|
|
542
591
|
|
|
@@ -576,7 +625,7 @@ function optionDescription(option: PaneFunctionOptionDef): string {
|
|
|
576
625
|
const choices = optionChoices(option);
|
|
577
626
|
const parts = [option.description.replace(/\.$/, "")];
|
|
578
627
|
// An enum that also reads other values (a bar size of any length) lists its values as examples.
|
|
579
|
-
if (choices.join("|").length > MAX_INLINE_CHOICES) parts.push(`${option.normalize ? "Such as" : "One of"} ${choices.join(", ")}`);
|
|
628
|
+
if (choices.join("|").length > MAX_INLINE_CHOICES) parts.push(`${option.list ? "Each one of" : option.normalize ? "Such as" : "One of"} ${choices.join(", ")}`);
|
|
580
629
|
if (option.defaultValue !== undefined && option.defaultValue !== "") parts.push(`Default ${String(option.defaultValue)}`);
|
|
581
630
|
return `${parts.join(". ")}.`;
|
|
582
631
|
}
|
|
@@ -587,6 +636,26 @@ export function limitationSentence(text: string): string {
|
|
|
587
636
|
return !trimmed || /[.!?]["')\]]*$/.test(trimmed) ? trimmed : `${trimmed}.`;
|
|
588
637
|
}
|
|
589
638
|
|
|
639
|
+
/**
|
|
640
|
+
* Names as lines of at most `width`, each name whole and the names apart by
|
|
641
|
+
* semicolons, since a name may hold a comma (Furnishings, Fixtures & Appliances).
|
|
642
|
+
*/
|
|
643
|
+
function packNames(names: readonly string[], width: number): string[] {
|
|
644
|
+
const lines: string[] = [];
|
|
645
|
+
let line = "";
|
|
646
|
+
names.forEach((name, index) => {
|
|
647
|
+
const word = index < names.length - 1 ? `${name};` : name;
|
|
648
|
+
if (line && line.length + 1 + word.length > width) {
|
|
649
|
+
lines.push(line);
|
|
650
|
+
line = word;
|
|
651
|
+
} else {
|
|
652
|
+
line = line ? `${line} ${word}` : word;
|
|
653
|
+
}
|
|
654
|
+
});
|
|
655
|
+
if (line) lines.push(line);
|
|
656
|
+
return lines;
|
|
657
|
+
}
|
|
658
|
+
|
|
590
659
|
function renderCatalogEntry(entry: PaneCatalogEntry): string {
|
|
591
660
|
const width = Math.min(cliTerminalWidth() ?? CATALOG_TEXT_WIDTH, CATALOG_TEXT_WIDTH);
|
|
592
661
|
const capability = entry.capability;
|
|
@@ -620,6 +689,10 @@ function renderCatalogEntry(entry: PaneCatalogEntry): string {
|
|
|
620
689
|
));
|
|
621
690
|
}
|
|
622
691
|
|
|
692
|
+
for (const { title, items } of capability.reference ?? []) {
|
|
693
|
+
lines.push("", renderSection(title), ...packNames(items, width - 2).map((line) => ` ${line}`));
|
|
694
|
+
}
|
|
695
|
+
|
|
623
696
|
lines.push("", renderSection("Examples"));
|
|
624
697
|
if (capability.reportReadiness !== "unsupported") {
|
|
625
698
|
lines.push(...wrapCommandLine(`gloom fn ${invocation}`, width));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { DesktopPaneShotRenderedRow } from "../desktop-pane-shot";
|
|
2
2
|
import { cliStyles, cliTerminalWidth, renderSection, renderTable, wrapText } from "../../utils/cli-output";
|
|
3
|
-
import { formatUtcTime } from "../../utils/utc-time";
|
|
3
|
+
import { formatUtcTime, parseReportTime } from "../../utils/utc-time";
|
|
4
4
|
import type { MarketContext } from "../types";
|
|
5
5
|
import type { PaneFunctionReport } from "./report";
|
|
6
6
|
import type { ResolvedPaneFunction } from "./resolver";
|
|
@@ -83,12 +83,22 @@ function domCellKeys(row: DesktopPaneShotRenderedRow): string[] {
|
|
|
83
83
|
});
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
const DAY_MS = 86_400_000;
|
|
87
|
+
/** A time of day in what a cell draws: `11:27`, `9am`. */
|
|
88
|
+
const DRAWN_CLOCK = /\d{1,2}:\d{2}|\b\d{1,2}\s?[ap]m\b/i;
|
|
89
|
+
|
|
86
90
|
/**
|
|
87
91
|
* A pane may shorten a time to fit ("Wed 11:27", "Oct 2"); the report prints
|
|
88
|
-
* the instant behind it with its date and zone instead.
|
|
92
|
+
* the instant behind it with its date and zone instead. An instant at exactly
|
|
93
|
+
* 00:00 UTC that the cell draws without a time of day (a filing's date, an
|
|
94
|
+
* all-day event) is a day, not a midnight: it prints as the date, as a
|
|
95
|
+
* date-only value does. The CSV and JSON keep the instant.
|
|
89
96
|
*/
|
|
90
97
|
function cellText(cell: DesktopPaneShotRenderedRow["cells"][number]): string {
|
|
91
|
-
|
|
98
|
+
if (!cell.instant) return cell.text;
|
|
99
|
+
const time = parseReportTime(cell.instant);
|
|
100
|
+
if (time && !time.dateOnly && time.time % DAY_MS === 0 && !DRAWN_CLOCK.test(cell.text)) return formatUtcTime({ ...time, dateOnly: true });
|
|
101
|
+
return formatUtcTime(cell.instant) || cell.text;
|
|
92
102
|
}
|
|
93
103
|
|
|
94
104
|
interface DomTable {
|
|
@@ -11,7 +11,15 @@ import type {
|
|
|
11
11
|
import { localTimeSuffix, parseReportTime, type ReportTime } from "../../utils/utc-time";
|
|
12
12
|
import { isRecord } from "../../utils/guards";
|
|
13
13
|
import { latestRegularSessionClose, readIndexSession } from "../../market-data/market/freshness";
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
reportMarketSession,
|
|
16
|
+
reportVenueMarkets,
|
|
17
|
+
reportVenueSessionDates,
|
|
18
|
+
type ReportMarket,
|
|
19
|
+
type SessionObservation,
|
|
20
|
+
type VenueMarket,
|
|
21
|
+
type VenueSessionDate,
|
|
22
|
+
} from "./market-session";
|
|
15
23
|
import { savedCopyShownAt } from "../../data/saved-copies";
|
|
16
24
|
import { releasedText } from "../../utils/release-date";
|
|
17
25
|
|
|
@@ -77,6 +85,13 @@ export interface ReportFreshness {
|
|
|
77
85
|
tradingDayMarket?: string;
|
|
78
86
|
/** Where the venues of a quote report stand now, from their session calendars. */
|
|
79
87
|
market?: ReportMarket;
|
|
88
|
+
/**
|
|
89
|
+
* With a declared `datesEachVenue`, when the rows trade on two or three
|
|
90
|
+
* markets that do not all stand alike: where each stands and when it opens,
|
|
91
|
+
* which the status line gives in place of `market` (`LSE opens 08:00 UTC ·
|
|
92
|
+
* US 13:30 UTC`).
|
|
93
|
+
*/
|
|
94
|
+
markets?: VenueMarket[];
|
|
80
95
|
/**
|
|
81
96
|
* With a declared `datesEachVenue`, when the rows trade on more than one
|
|
82
97
|
* market: each market's session date, which the status line gives in place
|
|
@@ -534,6 +549,9 @@ function resolveFreshness({ declared, signals, observations, now }: FreshnessInp
|
|
|
534
549
|
}
|
|
535
550
|
const { market, closeDate, closeMarket } = reportMarketSession(signals.sessions, now);
|
|
536
551
|
const venueSessions = declared.datesEachVenue && !declaredAsOf ? reportVenueSessionDates(signals.sessions, now) : [];
|
|
552
|
+
// Each market's own state and open, when they are not all one: `LSE opens 08:00 UTC · US 13:30 UTC`.
|
|
553
|
+
const venueMarkets = declared.datesEachVenue ? reportVenueMarkets(signals.sessions, now) : [];
|
|
554
|
+
const markets = venueMarkets.length > 0 && !sameMarketText(venueMarkets, now) ? venueMarkets : [];
|
|
537
555
|
// The close wording dates the newest observation, so only when that is what `asOf` is.
|
|
538
556
|
const asOfClose = closeDate && asOf && !declaredAsOf ? closeDate : null;
|
|
539
557
|
const tradingDayMarket = asOfClose ? closeMarket
|
|
@@ -584,6 +602,7 @@ function resolveFreshness({ declared, signals, observations, now }: FreshnessInp
|
|
|
584
602
|
...(asOfClose ? { asOfClose } : {}),
|
|
585
603
|
...(tradingDayMarket ? { tradingDayMarket } : {}),
|
|
586
604
|
...(market ? { market } : {}),
|
|
605
|
+
...(markets.length > 0 ? { markets } : {}),
|
|
587
606
|
...(venueSessions.length > 0 ? { venueSessions } : {}),
|
|
588
607
|
...(lagDays != null ? { lagDays } : {}),
|
|
589
608
|
...(observation ? { observation } : {}),
|
|
@@ -857,6 +876,21 @@ function venueSessionsText(sessions: readonly VenueSessionDate[]): string {
|
|
|
857
876
|
return groups.map(({ markets, date, close }) => `${listText(markets)} ${dayText(date)} ${close ? "close" : "session"}`).join(", ");
|
|
858
877
|
}
|
|
859
878
|
|
|
879
|
+
/**
|
|
880
|
+
* An as-of the feed stamped at exactly 00:00:00.000 UTC reads as the day it
|
|
881
|
+
* names, as a date-only value does: a daily close, a filing's date or a
|
|
882
|
+
* published report's has no time of day, so `Mon 26 Oct 2026 00:00 UTC` would
|
|
883
|
+
* claim one. Quotes keep their time, as does anything a feed or a venue's
|
|
884
|
+
* sessions date, and bars of a size under a day, whose midnight bar opens then.
|
|
885
|
+
*/
|
|
886
|
+
function asOfAsDay(freshness: ReportFreshness, time: ReportTime | null): ReportTime | null {
|
|
887
|
+
if (!time || time.dateOnly || time.time % DAY_MS !== 0) return time;
|
|
888
|
+
const feed = freshness.status === "live" || freshness.status === "delayed" || freshness.feed != null;
|
|
889
|
+
const intradayBars = freshness.basis === "intraday bars";
|
|
890
|
+
return feed || intradayBars || freshness.market || freshness.sources?.length || freshness.oldestAgeMinutes != null
|
|
891
|
+
? time : { ...time, dateOnly: true };
|
|
892
|
+
}
|
|
893
|
+
|
|
860
894
|
/**
|
|
861
895
|
* The dated as-of: a session close, a UTC time, a date, or when the report was
|
|
862
896
|
* retrieved; with the oldest observation unless `short`, or when the line says
|
|
@@ -868,7 +902,7 @@ function asOfText(freshness: ReportFreshness, short: boolean, local: boolean, un
|
|
|
868
902
|
if (freshness.venueSessions?.length) return venueSessionsText(freshness.venueSessions);
|
|
869
903
|
const market = freshness.tradingDayMarket;
|
|
870
904
|
const period = freshness.periodicity;
|
|
871
|
-
const time = parseReportTime(freshness.asOf);
|
|
905
|
+
const time = asOfAsDay(freshness, parseReportTime(freshness.asOf));
|
|
872
906
|
const reference = freshness.referencePeriod;
|
|
873
907
|
// `Sep 2026, released Tue 20 Oct`: the period the figure covers, then when it was released.
|
|
874
908
|
const released = reference ? releasedText(freshness.asOf, reference) : null;
|
|
@@ -949,6 +983,53 @@ function marketText(market: ReportMarket, now: number, short: boolean, local: bo
|
|
|
949
983
|
}
|
|
950
984
|
}
|
|
951
985
|
|
|
986
|
+
/** Whether every market of a mixed report reads the same (`markets closed until Mon`): one line then says it for all. */
|
|
987
|
+
function sameMarketText(markets: readonly VenueMarket[], now: number): boolean {
|
|
988
|
+
return new Set(markets.map((market) => marketText(market, now, false, false))).size === 1;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
/**
|
|
992
|
+
* Where each market of a report that mixes them stands, one after another:
|
|
993
|
+
* the open ones first, then those in an extended session, then the closed ones
|
|
994
|
+
* by when they open. `markets closed · LSE opens 08:00 UTC · US 13:30 UTC`
|
|
995
|
+
* while all are closed, else each its own (`LSE open · US pre-market, opens
|
|
996
|
+
* 13:30 UTC`). A time is the regular session's open, as `marketText` says it;
|
|
997
|
+
* `short` leaves out `markets closed`.
|
|
998
|
+
*/
|
|
999
|
+
function marketsText(markets: readonly VenueMarket[], now: number, short: boolean, local: boolean): string {
|
|
1000
|
+
const opensAt = (market: VenueMarket) => market.reopensAt ? Date.parse(market.reopensAt) : Number.POSITIVE_INFINITY;
|
|
1001
|
+
const rank = (market: VenueMarket) => market.state === "open" ? 0 : market.state === "closed" ? 2 : 1;
|
|
1002
|
+
const ordered = [...markets].sort((left, right) => rank(left) - rank(right) || opensAt(left) - opensAt(right));
|
|
1003
|
+
// `LSE opens 08:00 UTC · US 13:30 UTC`: a verb is said once for the timed opens that follow each other
|
|
1004
|
+
// with it; a week's first trade after a session's open says its own (`reopens 22:00 UTC`).
|
|
1005
|
+
let previousVerb: string | null = null;
|
|
1006
|
+
const parts = ordered.map((market) => {
|
|
1007
|
+
const reopen = reopenText(market, now, local);
|
|
1008
|
+
const verb = market.state === "closed" && reopen?.time ? market.firstTrade ? "reopens" : "opens" : null;
|
|
1009
|
+
const said = (() => {
|
|
1010
|
+
switch (market.state) {
|
|
1011
|
+
case "open":
|
|
1012
|
+
return `${market.market} open`;
|
|
1013
|
+
case "after-hours":
|
|
1014
|
+
return `${market.market} after hours`;
|
|
1015
|
+
case "pre-market":
|
|
1016
|
+
return reopen?.time ? `${market.market} pre-market, opens ${reopen.text}` : `${market.market} pre-market`;
|
|
1017
|
+
case "closed":
|
|
1018
|
+
if (!reopen) return `${market.market} closed`;
|
|
1019
|
+
if (!reopen.time) return `${market.market} closed until ${reopen.text}`;
|
|
1020
|
+
return `${market.market} ${verb === previousVerb ? "" : `${verb} `}${reopen.text}`;
|
|
1021
|
+
default: {
|
|
1022
|
+
const _exhaustive: never = market.state;
|
|
1023
|
+
return _exhaustive;
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
})();
|
|
1027
|
+
previousVerb = verb;
|
|
1028
|
+
return said;
|
|
1029
|
+
});
|
|
1030
|
+
return ordered.every((market) => market.state === "closed") && !short ? ["markets closed", ...parts].join(" · ") : parts.join(" · ");
|
|
1031
|
+
}
|
|
1032
|
+
|
|
952
1033
|
interface StatusLineDetail {
|
|
953
1034
|
/** Leave out the oldest observation. */
|
|
954
1035
|
asOf?: "short";
|
|
@@ -987,7 +1068,9 @@ function statusLineParts(freshness: ReportFreshness, detail: StatusLineDetail =
|
|
|
987
1068
|
...(freshness.oldestAgeMinutes != null ? [`oldest ${ageText(freshness.oldestAgeMinutes)}`] : []),
|
|
988
1069
|
...(freshness.savedAt ? [savedCopyText(freshness.savedAt, freshness.retrievedAt, local)]
|
|
989
1070
|
: [statusText(detail.basis === "none" ? { ...freshness, basis: undefined } : freshness, detail.status === "short")].filter(Boolean)),
|
|
990
|
-
...(
|
|
1071
|
+
...(detail.market === "none" ? []
|
|
1072
|
+
: freshness.markets?.length ? [marketsText(freshness.markets, now, detail.market === "short", local)]
|
|
1073
|
+
: freshness.market ? [marketText(freshness.market, now, detail.market === "short", local)] : []),
|
|
991
1074
|
...(freshness.note && detail.note !== "none" ? [typeof detail.note === "number" ? freshness.noteVariants?.[detail.note] ?? freshness.note : freshness.note] : []),
|
|
992
1075
|
];
|
|
993
1076
|
}
|
|
@@ -234,9 +234,10 @@ export const FUNCTION_HELP: Readonly<Record<string, FunctionHelp>> = {
|
|
|
234
234
|
bloomberg: ["RV"],
|
|
235
235
|
},
|
|
236
236
|
EQS: {
|
|
237
|
-
summary: "Screen covered stocks on valuation, growth, margins, liquidity, volatility, short interest, insider activity and 13F holders, then rank the matches by any metric. "
|
|
238
|
-
+ "ATR% is the 14-session average true range as a percent of the last close. IV rank is a rank, not an IV percentile: 0 is the lowest 30-day implied volatility of the past 52 weeks and 100 the highest."
|
|
239
|
-
|
|
237
|
+
summary: "Screen covered stocks on valuation, growth, margins, liquidity, volatility, technicals, float and ownership, short interest, insider activity and 13F holders, then rank the matches by any metric. "
|
|
238
|
+
+ "ATR% is the 14-session average true range as a percent of the last close. IV rank is a rank, not an IV percentile: 0 is the lowest 30-day implied volatility of the past 52 weeks and 100 the highest. IV percentile, IV30 change in vol points, HV20 and HV rank screen premium to sell. "
|
|
239
|
+
+ "Technical fields read the last completed session: RSI 14, price vs SMA 20, 50 and 200, trading days since a MACD or golden cross and since a new 20-day or 52-week high or low, and Bollinger width percentile (0 is a six-month squeeze). Ideas holds ready technical screens.",
|
|
240
|
+
usage: ["EQS", "gloom fn EQS --preset golden-cross"],
|
|
240
241
|
keys: [key("a", "dd criterion"), key("m", "etric"), key("t", "check ticker"), key("s", "ave screen"), key("o", "pen research")],
|
|
241
242
|
data: same("Daily snapshot, statements as filed"),
|
|
242
243
|
bloomberg: ["EQS"],
|
|
@@ -285,8 +286,10 @@ export const FUNCTION_HELP: Readonly<Record<string, FunctionHelp>> = {
|
|
|
285
286
|
},
|
|
286
287
|
CSP: {
|
|
287
288
|
summary: "Which names pay most to sell a put? For each, the put nearest a delta (0.30) on the expiry nearest a date or a number of days out (30), "
|
|
288
|
-
+ "ranked by annualised yield on cash at the bid, with % out of the money, delta, IV rank, the spread (marked wide, never left out) and an earnings flag."
|
|
289
|
-
|
|
289
|
+
+ "ranked by annualised yield on cash at the bid, with % out of the money, delta, IV rank, the spread (marked wide, never left out) and an earnings flag. "
|
|
290
|
+
+ "--strategy screens covered calls, verticals, iron condors, straddles, strangles and calendars the same way, with the trade, credit or debit, "
|
|
291
|
+
+ "max gain and loss per contract, break-evens, probability of profit, and an income trade's annualised return or a debit trade's reward to risk.",
|
|
292
|
+
usage: ["CSP JPM, GS, KO", "gloom fn CSP JPM GS KO AAPL --delta 0.20 --days 45", "gloom fn CSP JPM GS KO --strategy iron-condor"],
|
|
290
293
|
keys: [key("Enter", " open in OSA")],
|
|
291
294
|
data: OPTIONS,
|
|
292
295
|
bloomberg: [],
|
|
@@ -713,8 +716,8 @@ export const FUNCTION_HELP: Readonly<Record<string, FunctionHelp>> = {
|
|
|
713
716
|
|
|
714
717
|
// Watch markets
|
|
715
718
|
MOST: {
|
|
716
|
-
summary: "Today's top gainers, losers, most active and trending tickers with price, change, volume against average and market cap, and the pre-market and after-hours movers. On Pro, the gaps at the open.",
|
|
717
|
-
usage: ["MOST"],
|
|
719
|
+
summary: "Today's top gainers, losers, most active and trending tickers with price, change, volume against average and market cap, and the pre-market and after-hours movers. On Pro, the gaps at the open. Min float keeps the names with at least that many shares in the float.",
|
|
720
|
+
usage: ["MOST", "gloom fn MOST --list gainers --min-float 10M"],
|
|
718
721
|
keys: [TABS, OPEN],
|
|
719
722
|
data: { free: DELAYED, pro: REAL_TIME },
|
|
720
723
|
bloomberg: ["MOST"],
|
|
@@ -902,9 +905,9 @@ export const FUNCTION_HELP: Readonly<Record<string, FunctionHelp>> = {
|
|
|
902
905
|
bloomberg: [],
|
|
903
906
|
},
|
|
904
907
|
FLOW: {
|
|
905
|
-
summary: "Sweeps, blocks and large premium prints
|
|
906
|
-
usage: ["FLOW"],
|
|
907
|
-
keys: [OPEN],
|
|
908
|
+
summary: "Sweeps, blocks and large premium prints, and the session's net premium tide.",
|
|
909
|
+
usage: ["FLOW", "FLOW NVDA"],
|
|
910
|
+
keys: [TABS, OPEN],
|
|
908
911
|
data: pro(REAL_TIME),
|
|
909
912
|
access: "pro",
|
|
910
913
|
bloomberg: ["FLOW"],
|
|
@@ -42,6 +42,7 @@ import type { ResolvedPaneFunction } from "./resolver";
|
|
|
42
42
|
import { isRecord } from "../../utils/guards";
|
|
43
43
|
import { splitSymbolWords } from "../symbol-args";
|
|
44
44
|
import { formatUtcTime, localTimeSuffix } from "../../utils/utc-time";
|
|
45
|
+
import { formatCompact } from "../../utils/format";
|
|
45
46
|
import { deriveHeadlessFreshness, formatFreshnessLine, staleRowsText, withSavedCopy, type ReportFreshness } from "./freshness";
|
|
46
47
|
import { DATA_SERVICE_UNREACHABLE_MESSAGE } from "../../utils/network-failure";
|
|
47
48
|
import { accessGateStatus, errorsAccessGate, isProLock, reportGateText } from "./access-gate";
|
|
@@ -235,7 +236,12 @@ export function createHeadlessPaneContext(
|
|
|
235
236
|
command, noun: "a quote", retry: (key) => `gloom ${command} ${argumentWith(key)}`,
|
|
236
237
|
}, { refresh: !!context.refresh });
|
|
237
238
|
if (!read) return null;
|
|
238
|
-
return {
|
|
239
|
+
return {
|
|
240
|
+
symbol: read.symbol,
|
|
241
|
+
...read.exchange ? { exchange: read.exchange } : {},
|
|
242
|
+
...read.name ? { name: read.name } : {},
|
|
243
|
+
...read.coinNote ? { coinNote: read.coinNote } : {},
|
|
244
|
+
};
|
|
239
245
|
},
|
|
240
246
|
};
|
|
241
247
|
}
|
|
@@ -369,8 +375,8 @@ export function serializeHeadlessPaneResult(
|
|
|
369
375
|
}
|
|
370
376
|
case "series": {
|
|
371
377
|
const series = result as HeadlessSeriesResult;
|
|
372
|
-
// `textDecimals` only
|
|
373
|
-
return { ...common, series: series.series.map(({ textDecimals:
|
|
378
|
+
// `textDecimals` and `textCompact` only shape the text report; JSON keeps every digit and does not carry them.
|
|
379
|
+
return { ...common, series: series.series.map(({ textDecimals: _decimals, textCompact: _compact, ...kept }) => kept), ...(series.stats ? { stats: series.stats } : {}) };
|
|
374
380
|
}
|
|
375
381
|
case "snapshot": {
|
|
376
382
|
const snapshot = result as HeadlessSnapshotResult;
|
|
@@ -380,6 +386,7 @@ export function serializeHeadlessPaneResult(
|
|
|
380
386
|
asOf: snapshot.asOf,
|
|
381
387
|
columns: serializableColumns(columns),
|
|
382
388
|
items: snapshot.items,
|
|
389
|
+
...(snapshot.empty && snapshot.items.length === 0 ? { empty: snapshot.empty } : {}),
|
|
383
390
|
};
|
|
384
391
|
}
|
|
385
392
|
default: {
|
|
@@ -495,14 +502,16 @@ function seriesPointExportDate(series: HeadlessSeries, date: SeriesPointDate, da
|
|
|
495
502
|
* points, multiples and
|
|
496
503
|
* ratios (P/E, EV/EBITDA, one series over another, VIX 3M / VIX) to two
|
|
497
504
|
* decimals, which a ratio under 0.1 trades for three significant digits so it
|
|
498
|
-
* is not flattened to 0.03, and a 0 to 100 score to a tenth of a point.
|
|
499
|
-
*
|
|
500
|
-
*
|
|
505
|
+
* is not flattened to 0.03, and a 0 to 100 score to a tenth of a point. A
|
|
506
|
+
* company's reported total (`textCompact`) reads as the tables print one:
|
|
507
|
+
* 63.33B. The exports keep every digit. Any other unit prints as loaded,
|
|
508
|
+
* without float noise.
|
|
501
509
|
*/
|
|
502
510
|
function seriesValue(series: HeadlessSeries, value: number | null): string {
|
|
503
511
|
if (value == null) return "-";
|
|
504
512
|
if (!Number.isFinite(value)) return displayNumber(value);
|
|
505
513
|
if (series.textDecimals != null) return value.toFixed(series.textDecimals);
|
|
514
|
+
if (series.textCompact) return formatCompact(value);
|
|
506
515
|
const unit = series.unit?.trim().toLowerCase();
|
|
507
516
|
if (unit === "%" || unit === "pp") return value.toFixed(2);
|
|
508
517
|
if (unit === "x" || unit === "ratio") return value !== 0 && Math.abs(value) < 0.1 ? value.toPrecision(3) : value.toFixed(2);
|
|
@@ -701,6 +710,7 @@ function emptyReportReason(
|
|
|
701
710
|
const bundleReason = definition.shape === "bundle" ? (result as HeadlessBundleResult).empty : undefined;
|
|
702
711
|
if (bundleReason) return bundleReason;
|
|
703
712
|
const ownReason = definition.shape === "rows" ? !!(result as HeadlessRowsResult).empty
|
|
713
|
+
: definition.shape === "snapshot" ? !!(result as HeadlessSnapshotResult).empty
|
|
704
714
|
: definition.shape === "bundle" && (result as HeadlessBundleResult).sections.some((section) => "empty" in section && !!section.empty && !isLockedSection(section));
|
|
705
715
|
const { failed } = splitErrors(result);
|
|
706
716
|
if (ownReason || failed.length) return null;
|
|
@@ -909,7 +919,7 @@ export function renderHeadlessPaneText(
|
|
|
909
919
|
case "snapshot": {
|
|
910
920
|
const snapshot = result as HeadlessSnapshotResult;
|
|
911
921
|
// The closing source line carries the snapshot's as-of, so it is not printed twice.
|
|
912
|
-
lines.push(renderRows(snapshot.items, undefined, definition.columns));
|
|
922
|
+
lines.push(renderRows(snapshot.items, undefined, definition.columns, snapshot.empty));
|
|
913
923
|
break;
|
|
914
924
|
}
|
|
915
925
|
default: {
|