gloomberb 0.16.3 → 0.16.4
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 +4 -1
- package/src/api-client/askg.ts +43 -0
- package/src/api-client/auth.ts +15 -0
- package/src/api-client/beneficial-owners.ts +4 -0
- package/src/api-client/chat.ts +19 -0
- package/src/api-client/company-kpis.ts +14 -2
- package/src/api-client/data.ts +34 -16
- package/src/api-client/index.ts +12 -0
- package/src/api-client/normalizers.ts +27 -0
- package/src/api-client/paths.ts +88 -23
- package/src/api-client/request.ts +2 -1
- package/src/api-client/research-activity.ts +31 -0
- package/src/api-client/socket.ts +39 -8
- package/src/api-client/types.ts +57 -1
- package/src/api-client/upgrade-experiment.ts +90 -0
- package/src/app/global-shortcuts.ts +11 -1
- package/src/app/keybindings/actions.ts +10 -0
- package/src/app/pane-runtime/index.tsx +8 -0
- package/src/app/pane-runtime/pane-template-runtime.ts +5 -0
- package/src/app/pane-runtime/plugin-bindings.ts +65 -25
- package/src/app/pane-runtime/ticker-inspector-runtime.ts +12 -0
- package/src/app/pane-runtime/ticker-open-runtime.ts +18 -1
- package/src/app/runtime/desktop-deeplink.ts +5 -0
- package/src/app/star-prompt/model.ts +98 -0
- package/src/app/star-prompt/runtime.ts +41 -0
- package/src/app/star-prompt/status-line.tsx +190 -0
- package/src/app.tsx +13 -7
- package/src/architecture/core-builtin-allowlist.json +70 -0
- package/src/brokers/signed-in/adapter.ts +33 -2
- package/src/brokers/signed-in/client.ts +10 -0
- package/src/brokers/signed-in/sign-in-dialog.tsx +23 -9
- package/src/brokers/signed-in/sign-in.ts +62 -5
- package/src/capabilities/registry.ts +8 -1
- package/src/capabilities/types.ts +2 -0
- package/src/cli/commands/crypto-hints.ts +12 -4
- package/src/cli/commands/market.ts +359 -99
- package/src/cli/commands/options-chain.ts +172 -0
- package/src/cli/commands/options-leaps.ts +422 -0
- package/src/cli/commands/overview.ts +164 -26
- package/src/cli/commands/search.ts +29 -8
- package/src/cli/commands/system.ts +99 -19
- package/src/cli/commands/themes.ts +64 -0
- package/src/cli/commands/ticker.ts +62 -30
- package/src/cli/desktop-pane-shot.ts +97 -16
- package/src/cli/errors.ts +25 -2
- package/src/cli/freshness.ts +7 -4
- package/src/cli/fx-pair.ts +57 -0
- package/src/cli/help.ts +14 -1
- package/src/cli/index-roots.ts +51 -0
- package/src/cli/index.ts +9 -6
- package/src/cli/listing-arg.ts +211 -3
- package/src/cli/not-a-ticker.ts +37 -0
- package/src/cli/options.ts +50 -6
- package/src/cli/pane-functions/capabilities.ts +8 -2
- package/src/cli/pane-functions/catalog.ts +13 -2
- package/src/cli/pane-functions/data.ts +36 -5
- package/src/cli/pane-functions/dom.ts +21 -13
- package/src/cli/pane-functions/freshness.ts +205 -26
- package/src/cli/pane-functions/function-help.ts +67 -22
- package/src/cli/pane-functions/headless.ts +5 -4
- package/src/cli/pane-functions/index.ts +3 -0
- package/src/cli/pane-functions/market-session.ts +133 -0
- package/src/cli/pane-functions/options.ts +15 -4
- package/src/cli/pane-functions/report-notices.ts +63 -0
- package/src/cli/pane-functions/report.ts +4 -0
- package/src/cli/pane-functions/resolver.ts +4 -6
- package/src/cli/pane-functions/screenshot-evidence.ts +2 -0
- package/src/cli/pane-functions/screenshot.ts +105 -23
- package/src/cli/registry.ts +2 -1
- package/src/cli/result.ts +50 -13
- package/src/cli/row-window.ts +76 -0
- package/src/cli/scroll-truncation.ts +34 -0
- package/src/components/chart/composite/composite-chart.tsx +43 -4
- package/src/components/chart/composite/legend.tsx +39 -6
- package/src/components/chart/composite/rasterizer.ts +64 -4
- package/src/components/chart/composite/series-cues.ts +71 -0
- package/src/components/chart/composite/text-renderer.ts +16 -8
- package/src/components/chart/composite/types.ts +3 -0
- package/src/components/command-bar/commands/direct/index.ts +9 -1
- package/src/components/command-bar/commands/plugin/items.ts +3 -1
- package/src/components/command-bar/commands/registry.ts +7 -0
- package/src/components/command-bar/layout-items/focused-pane.ts +38 -0
- package/src/components/command-bar/pane-templates/workflow.ts +5 -0
- package/src/components/command-bar/routes/root/command-items.ts +2 -0
- package/src/components/command-bar/routes/root/plugin-turn-on.ts +92 -0
- package/src/components/command-bar/routes/root/results.ts +40 -10
- package/src/components/command-bar/routes/root/runtime.ts +8 -0
- package/src/components/command-bar/routes/root/search-providers.ts +97 -14
- package/src/components/command-bar/surface/index.tsx +18 -4
- package/src/components/command-bar/theme-picker.tsx +48 -13
- package/src/components/command-bar/workflow/ops.ts +5 -3
- package/src/components/data-table/view.tsx +28 -9
- package/src/components/layout/floating-pane.tsx +6 -2
- package/src/components/layout/market-summary.ts +23 -9
- package/src/components/layout/pane/content.tsx +46 -29
- package/src/components/layout/pane/cycle-order.ts +2 -11
- package/src/components/layout/pane/header.tsx +2 -1
- package/src/components/layout/pane/title.ts +1 -1
- package/src/components/layout/presentation.ts +22 -0
- package/src/components/layout/shell/active-drag.ts +1 -1
- package/src/components/layout/shell/drag/index.ts +4 -0
- package/src/components/layout/shell/drag/live.ts +76 -5
- package/src/components/layout/shell/drag/runtime.ts +8 -8
- package/src/components/layout/shell/index.tsx +47 -6
- package/src/components/layout/shell/layout-state.ts +5 -11
- package/src/components/layout/shell/menu.ts +28 -1
- package/src/components/layout/shell/pane/actions.ts +2 -1
- package/src/components/layout/shell/pane/layers.tsx +51 -10
- package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
- package/src/components/layout/shell/pane/new-layout.ts +116 -0
- package/src/components/layout/shell/shortcuts.ts +5 -2
- package/src/components/layout/shell/visible-layout.ts +44 -1
- package/src/components/layout/status-bar.tsx +4 -1
- package/src/components/markdown-text.tsx +9 -5
- package/src/components/metric-treemap/heat-surface.tsx +27 -8
- package/src/components/metric-treemap/heat-tiles.ts +48 -17
- package/src/components/onboarding/onboarding-frame.tsx +33 -10
- package/src/components/onboarding/onboarding-wizard.tsx +49 -40
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +5 -2
- package/src/components/onboarding/pro-step-layout.ts +86 -0
- package/src/components/onboarding/use-onboarding-upgrade.ts +6 -2
- package/src/components/ui/button.tsx +10 -0
- package/src/components/ui/data-table/index.tsx +38 -0
- package/src/components/ui/data-table/types.ts +8 -1
- package/src/components/ui/data-table/visible-range.ts +2 -0
- package/src/components/ui/icon.tsx +11 -2
- package/src/components/ui/index.ts +1 -1
- package/src/core/connection-health.ts +1 -1
- package/src/core/state/app/layout-reducer.ts +89 -3
- package/src/core/state/app/state.ts +8 -5
- package/src/core/state/app/types.ts +13 -2
- package/src/data/config/layout.ts +55 -21
- package/src/data/config/store/fold-panes.ts +149 -0
- package/src/data/config/store/migrations.ts +7 -0
- package/src/data/config/store/node.ts +27 -6
- package/src/data/config/store/normalize.ts +37 -3
- package/src/i18n/es.ts +14 -1
- package/src/i18n/ja.ts +14 -1
- package/src/i18n/ko.ts +14 -1
- package/src/i18n/zh-cn.ts +14 -1
- package/src/i18n/zh-tw.ts +14 -1
- package/src/layout/desks.ts +1 -2
- package/src/layout/pane-layout-move.ts +264 -0
- package/src/layout/pane-manager/hidden-panes.ts +160 -0
- package/src/layout/pane-manager/index.ts +1 -0
- package/src/market-data/coordinator/entries.ts +7 -1
- package/src/market-data/coordinator/quotes.ts +2 -2
- package/src/market-data/market/freshness.ts +118 -18
- package/src/market-data/market/status.ts +40 -0
- package/src/market-data/options-alternatives.ts +69 -0
- package/src/market-data/quotes/contributions.ts +19 -2
- package/src/market-data/quotes/metadata.ts +16 -0
- package/src/market-data/quotes/screener-live-quotes.ts +56 -5
- package/src/plugins/builtin/account-management/keyboard.ts +3 -0
- package/src/plugins/builtin/account-management/model.ts +1 -0
- package/src/plugins/builtin/account-management/navigation.ts +1 -1
- package/src/plugins/builtin/account-management/pane.tsx +31 -4
- package/src/plugins/builtin/alerts/quotes.ts +3 -0
- package/src/plugins/builtin/analytics/risk-headless.ts +3 -1
- package/src/plugins/builtin/analytics/risk-model.ts +37 -8
- package/src/plugins/builtin/analytics/risk-pane.tsx +8 -4
- package/src/plugins/builtin/backtest/pane.tsx +1 -1
- package/src/plugins/builtin/broker-manager/detail.tsx +4 -0
- package/src/plugins/builtin/broker-manager/index.tsx +7 -3
- package/src/plugins/builtin/broker-manager/model.ts +8 -0
- package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -2
- package/src/plugins/builtin/broker-manager/sign-in-end.ts +82 -0
- package/src/plugins/builtin/broker-manager/table.ts +5 -0
- package/src/plugins/builtin/builtin-plugin-meta.ts +121 -11
- package/src/plugins/builtin/chart-composer/pane.tsx +1 -1
- package/src/plugins/builtin/chat/channels.ts +34 -36
- package/src/plugins/builtin/chat/command-bar.ts +138 -0
- package/src/plugins/builtin/chat/composer-commands.ts +21 -1
- package/src/plugins/builtin/chat/content/composer-runtime.ts +11 -1
- package/src/plugins/builtin/chat/content/footer.tsx +5 -3
- package/src/plugins/builtin/chat/content/index.tsx +21 -1
- package/src/plugins/builtin/chat/content/mentions.ts +2 -1
- package/src/plugins/builtin/chat/content/user-map.ts +3 -1
- package/src/plugins/builtin/chat/controller/index.ts +48 -0
- package/src/plugins/builtin/chat/conversations.ts +256 -0
- package/src/plugins/builtin/chat/discord-command.ts +91 -0
- package/src/plugins/builtin/chat/edit-window.ts +12 -6
- package/src/plugins/builtin/chat/ghost-user.ts +14 -0
- package/src/plugins/builtin/chat/index.tsx +40 -2
- package/src/plugins/builtin/chat/layout.ts +2 -0
- package/src/plugins/builtin/chat/message/desktop.tsx +2 -1
- package/src/plugins/builtin/chat/message/header.tsx +24 -15
- package/src/plugins/builtin/chat/message/profile-popover.tsx +2 -1
- package/src/plugins/builtin/chat/message/render-state.ts +3 -0
- package/src/plugins/builtin/chat/pane-requests.ts +60 -0
- package/src/plugins/builtin/chat/pane-state.ts +32 -2
- package/src/plugins/builtin/chat/pane.tsx +8 -0
- package/src/plugins/builtin/cloud/askg/controller.ts +112 -4
- package/src/plugins/builtin/cloud/askg/conversation-store.ts +21 -0
- package/src/plugins/builtin/cloud/askg/manifest.ts +1 -0
- package/src/plugins/builtin/cloud/askg/model.ts +129 -70
- package/src/plugins/builtin/cloud/askg/pane.tsx +772 -377
- package/src/plugins/builtin/cloud/askg/protocol.ts +40 -2
- package/src/plugins/builtin/cloud/askg/tool-display.ts +437 -0
- package/src/plugins/builtin/cloud/askg/tool-timeline.tsx +403 -0
- package/src/plugins/builtin/cloud/company-picker.tsx +1 -9
- package/src/plugins/builtin/cloud/mcp-connect/command.ts +24 -0
- package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +360 -0
- package/src/plugins/builtin/cloud/mcp-connect/model.ts +135 -0
- package/src/plugins/builtin/cloud/mcp-connect/sections.ts +62 -0
- package/src/plugins/builtin/cloud/plugin.tsx +36 -1
- package/src/plugins/builtin/cloud/starter-symbols.ts +8 -0
- package/src/plugins/builtin/cloud/terminal-relay/acm-tab.tsx +89 -0
- package/src/plugins/builtin/cloud/terminal-relay/client.ts +97 -0
- package/src/plugins/builtin/cloud/terminal-relay/connect-section.tsx +57 -0
- package/src/plugins/builtin/cloud/terminal-relay/engine.ts +666 -0
- package/src/plugins/builtin/cloud/terminal-relay/grants.ts +137 -0
- package/src/plugins/builtin/cloud/terminal-relay/host.tsx +81 -0
- package/src/plugins/builtin/cloud/terminal-relay/indicator.tsx +82 -0
- package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +234 -0
- package/src/plugins/builtin/cloud/terminal-relay/policy.ts +153 -0
- package/src/plugins/builtin/cloud/terminal-relay/prompts.tsx +168 -0
- package/src/plugins/builtin/cloud/terminal-relay/redact.ts +73 -0
- package/src/plugins/builtin/cloud/terminal-relay/summary.ts +62 -0
- package/src/plugins/builtin/cloud/upgrade-dialog.tsx +26 -2
- package/src/plugins/builtin/cloud/upgrade-personalization.ts +57 -0
- package/src/plugins/builtin/cloud/upgrade-tickers.ts +124 -0
- package/src/plugins/builtin/comment-letters/client.ts +1 -3
- package/src/plugins/builtin/company-kpis/client.ts +23 -8
- package/src/plugins/builtin/company-kpis/headless.ts +3 -1
- package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
- package/src/plugins/builtin/company-kpis/screenshot-evidence.ts +10 -3
- package/src/plugins/builtin/composite-plugins.ts +107 -64
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +4 -4
- package/src/plugins/builtin/credit-boards/index.tsx +21 -21
- package/src/plugins/builtin/credit-boards/model.ts +0 -1
- package/src/plugins/builtin/credit-boards/pane.tsx +30 -0
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +4 -4
- package/src/plugins/builtin/debt-maturities/client.ts +37 -9
- package/src/plugins/builtin/debt-maturities/headless.ts +3 -1
- package/src/plugins/builtin/debt-maturities/pane.tsx +5 -4
- package/src/plugins/builtin/dividend-yield/index.tsx +1 -1
- package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
- package/src/plugins/builtin/earnings-calls/data.ts +28 -7
- package/src/plugins/builtin/earnings-calls/headless.ts +3 -2
- package/src/plugins/builtin/earnings-calls/pane.tsx +10 -5
- package/src/plugins/builtin/econ/calendar-model.ts +42 -17
- package/src/plugins/builtin/econ/detail-view.tsx +2 -2
- package/src/plugins/builtin/econ/index.tsx +68 -22
- package/src/plugins/builtin/econ-statistics/headless.ts +12 -2
- package/src/plugins/builtin/econ-statistics/pane.tsx +1 -1
- package/src/plugins/builtin/econ-statistics/view.ts +17 -0
- package/src/plugins/builtin/executives/data.ts +17 -6
- package/src/plugins/builtin/executives/pane.tsx +16 -7
- package/src/plugins/builtin/filing-events/pane.tsx +22 -5
- package/src/plugins/builtin/fx-matrix/freshness.ts +65 -0
- package/src/plugins/builtin/fx-matrix/index.tsx +22 -1
- package/src/plugins/builtin/fx-matrix/pairs.ts +1 -1
- package/src/plugins/builtin/fx-matrix/settings.ts +42 -2
- package/src/plugins/builtin/gpu/headless.ts +9 -3
- package/src/plugins/builtin/gpu/model.ts +14 -5
- package/src/plugins/builtin/help/index.tsx +1 -0
- package/src/plugins/builtin/holders/beneficial-client.ts +29 -7
- package/src/plugins/builtin/holders/beneficial-view.tsx +7 -3
- package/src/plugins/builtin/holders/headless.ts +6 -1
- package/src/plugins/builtin/holders/pane.tsx +3 -0
- package/src/plugins/builtin/insider/index.tsx +4 -4
- package/src/plugins/builtin/iv-history/charts.tsx +21 -7
- package/src/plugins/builtin/iv-history/client.ts +12 -2
- package/src/plugins/builtin/iv-history/format.ts +33 -2
- package/src/plugins/builtin/iv-history/headless.ts +22 -10
- package/src/plugins/builtin/iv-history/index.tsx +3 -3
- package/src/plugins/builtin/iv-history/model.ts +61 -13
- package/src/plugins/builtin/iv-history/pane.tsx +11 -3
- package/src/plugins/builtin/iv-history/screen-pane.tsx +42 -18
- package/src/plugins/builtin/jobs/pane.tsx +1 -1
- package/src/plugins/builtin/kelly-sizer/headless.ts +149 -0
- package/src/plugins/builtin/kelly-sizer/history.ts +80 -0
- package/src/plugins/builtin/kelly-sizer/index.tsx +2 -0
- package/src/plugins/builtin/kelly-sizer/pane.tsx +109 -39
- package/src/plugins/builtin/kelly-sizer/sections.tsx +14 -0
- package/src/plugins/builtin/kelly-sizer/types.ts +9 -1
- package/src/plugins/builtin/layout-manager/index.tsx +9 -1
- package/src/plugins/builtin/macro-day/client.ts +24 -0
- package/src/plugins/builtin/macro-day/headless.ts +11 -8
- package/src/plugins/builtin/macro-day/index.tsx +3 -0
- package/src/plugins/builtin/macro-day/model.ts +11 -6
- package/src/plugins/builtin/macro-day/pane.tsx +12 -8
- package/src/plugins/builtin/macro-day/release-table.ts +40 -0
- package/src/plugins/builtin/macro-day/releases.ts +62 -44
- package/src/plugins/builtin/market-heatmap/data.ts +25 -5
- package/src/plugins/builtin/market-heatmap/evidence.ts +90 -0
- package/src/plugins/builtin/market-heatmap/index.tsx +144 -63
- package/src/plugins/builtin/market-heatmap/model.ts +94 -3
- package/src/plugins/builtin/market-heatmap/portfolio.ts +12 -2
- package/src/plugins/builtin/market-movers/headless.ts +31 -4
- package/src/plugins/builtin/market-movers/index.tsx +8 -1
- package/src/plugins/builtin/market-movers/model.ts +71 -17
- package/src/plugins/builtin/market-movers/table.ts +50 -23
- package/src/plugins/builtin/news/headless.ts +9 -2
- package/src/plugins/builtin/news/wire/news/footer.ts +10 -0
- package/src/plugins/builtin/openfda/client.ts +1 -1
- package/src/plugins/builtin/options/carry.ts +95 -0
- package/src/plugins/builtin/options/index.tsx +58 -5
- package/src/plugins/builtin/options/strike-window.ts +183 -0
- package/src/plugins/builtin/options/table.ts +55 -3
- package/src/plugins/builtin/options/types.ts +8 -1
- package/src/plugins/builtin/options/view.tsx +161 -12
- package/src/plugins/builtin/options-positioning/headless.ts +4 -2
- package/src/plugins/builtin/options-scenario/charts.tsx +2 -2
- package/src/plugins/builtin/options-scenario/client.ts +73 -17
- package/src/plugins/builtin/options-scenario/headless.ts +71 -24
- package/src/plugins/builtin/options-scenario/hedge.ts +284 -0
- package/src/plugins/builtin/options-scenario/index.tsx +9 -2
- package/src/plugins/builtin/options-scenario/model.ts +33 -0
- package/src/plugins/builtin/options-scenario/pane.tsx +40 -16
- package/src/plugins/builtin/pe-band/model.ts +2 -2
- package/src/plugins/builtin/pe-band/report-dates.ts +11 -7
- package/src/plugins/builtin/perps/equity-row.tsx +8 -5
- package/src/plugins/builtin/perps/model.ts +16 -5
- package/src/plugins/builtin/plugin-marketplace/model.ts +64 -21
- package/src/plugins/builtin/plugin-marketplace/packs.ts +118 -0
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +344 -32
- package/src/plugins/builtin/plugin-marketplace/store.ts +7 -1
- package/src/plugins/builtin/plugin-module.ts +87 -4
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +71 -36
- package/src/plugins/builtin/portfolio-list/cli/render.ts +87 -18
- package/src/plugins/builtin/portfolio-list/cli/shared.ts +2 -16
- package/src/plugins/builtin/portfolio-list/headless.ts +38 -7
- package/src/plugins/builtin/realized-vol/headless.ts +3 -0
- package/src/plugins/builtin/realized-vol/pane.tsx +1 -1
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +2 -2
- package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +1 -1
- package/src/plugins/builtin/revenue-breakdown/client.ts +13 -6
- package/src/plugins/builtin/revenue-breakdown/headless.ts +4 -1
- package/src/plugins/builtin/revenue-breakdown/pane.tsx +7 -4
- package/src/plugins/builtin/reverse-dcf/headless.ts +5 -1
- package/src/plugins/builtin/reverse-dcf/pane.tsx +1 -1
- package/src/plugins/builtin/risk-factors/data.ts +23 -9
- package/src/plugins/builtin/risk-factors/headless.ts +5 -2
- package/src/plugins/builtin/risk-factors/pane.tsx +6 -5
- package/src/plugins/builtin/seasonality/pane.tsx +1 -1
- package/src/plugins/builtin/sec/index.tsx +5 -5
- package/src/plugins/builtin/sectors/client.ts +10 -2
- package/src/plugins/builtin/sectors/headless.ts +35 -6
- package/src/plugins/builtin/sectors/index.tsx +18 -1
- package/src/plugins/builtin/sectors/sector-model.ts +150 -41
- package/src/plugins/builtin/shared/command-tab.ts +51 -0
- package/src/plugins/builtin/shared/headless-market-data.ts +33 -1
- package/src/plugins/builtin/shared/members-menu.tsx +4 -4
- package/src/plugins/builtin/shared/report-freshness.ts +50 -2
- package/src/plugins/builtin/shared/ticker-request.ts +25 -1
- package/src/plugins/builtin/shared/use-quote-board.ts +25 -5
- package/src/plugins/builtin/shared/volatility/options.ts +1 -46
- package/src/plugins/builtin/short-interest/index.tsx +19 -4
- package/src/plugins/builtin/short-interest/surface.tsx +4 -4
- package/src/plugins/builtin/short-volume/index.tsx +1 -10
- package/src/plugins/builtin/supply-chain/model.ts +1 -1
- package/src/plugins/builtin/ticker-detail/headless.ts +43 -21
- package/src/plugins/builtin/ticker-detail/pane.tsx +4 -5
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +94 -6
- package/src/plugins/builtin/treasury-auctions/cache.ts +5 -2
- package/src/plugins/builtin/treasury-auctions/client.ts +22 -8
- package/src/plugins/builtin/treasury-auctions/headless.ts +40 -4
- package/src/plugins/builtin/treasury-auctions/index.tsx +11 -9
- package/src/plugins/builtin/treasury-auctions/model.ts +197 -30
- package/src/plugins/builtin/treasury-auctions/pane.tsx +35 -11
- package/src/plugins/builtin/treasury-auctions/types.ts +11 -1
- package/src/plugins/builtin/vol-surface/charts.tsx +1 -3
- package/src/plugins/builtin/vol-surface/client.ts +6 -2
- package/src/plugins/builtin/vol-surface/evidence.ts +11 -6
- package/src/plugins/builtin/vol-surface/headless.ts +124 -20
- package/src/plugins/builtin/vol-surface/model.ts +23 -11
- package/src/plugins/builtin/vol-surface/pane.tsx +38 -27
- package/src/plugins/builtin/vol-surface/tables.ts +124 -0
- package/src/plugins/builtin/world-indices/client.ts +22 -4
- package/src/plugins/builtin/world-indices/headless.ts +54 -27
- package/src/plugins/builtin/world-indices/model.ts +4 -2
- package/src/plugins/builtin/world-indices/table.tsx +13 -2
- package/src/plugins/builtin/world-venue-map/geo-series.ts +1 -1
- package/src/plugins/builtin/world-venue-map/index.ts +1 -1
- package/src/plugins/builtin/yield-curve/index.tsx +1 -1
- package/src/plugins/builtin-editorial.ts +126 -0
- package/src/plugins/catalog-backend.ts +16 -9
- package/src/plugins/catalog-browser.ts +127 -63
- package/src/plugins/catalog-manifest.ts +73 -40
- package/src/plugins/catalog-ui.ts +26 -6
- package/src/plugins/extracted-plugins.ts +7 -1
- package/src/plugins/ownership.ts +150 -7
- package/src/plugins/plugin-functions.ts +54 -0
- package/src/plugins/plugin-off.ts +36 -0
- package/src/plugins/registry/host-actions.ts +16 -0
- package/src/plugins/registry/index.ts +84 -24
- package/src/plugins/registry/plugin-state.ts +25 -1
- package/src/plugins/runtime/context.tsx +29 -0
- package/src/react/input.ts +48 -1
- package/src/remote/app-host.tsx +5 -0
- package/src/remote/controller.ts +3 -53
- package/src/remote/matching.ts +69 -0
- package/src/remote/schema.ts +1 -1
- package/src/remote/semantic-tree.tsx +14 -0
- package/src/remote/types.ts +2 -0
- package/src/renderers/dom/desktop/icons.tsx +14 -0
- package/src/renderers/dom/desktop/query-bar.tsx +59 -32
- package/src/renderers/dom/host/box.tsx +1 -1
- package/src/renderers/dom/host/drag-shield.ts +17 -6
- package/src/renderers/dom/host/live-frame.ts +10 -2
- package/src/renderers/dom/input-host.tsx +6 -1
- package/src/renderers/dom/styles.css +13 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +5 -1
- package/src/renderers/electrobun/bun/desktop/detached-windows.ts +12 -1
- package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -1
- package/src/renderers/electrobun/shared/protocol.ts +2 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -1
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +101 -5
- package/src/renderers/electrobun/view/main.tsx +4 -1
- package/src/renderers/opentui/input-host.tsx +6 -1
- package/src/renderers/opentui/start.tsx +9 -0
- package/src/shares/portable-layout.ts +16 -6
- package/src/sources/gloomberb-cloud/index.ts +19 -10
- package/src/sources/gloomberb-cloud/normalizers.ts +4 -1
- package/src/sources/listing-symbols.ts +11 -5
- package/src/sources/provider-errors.ts +88 -3
- package/src/sources/provider-router/cached-routes.ts +8 -4
- package/src/sources/provider-router/financial-routes.ts +11 -5
- package/src/sources/provider-router/financials.ts +42 -15
- package/src/sources/provider-router/history.ts +27 -11
- package/src/sources/provider-router/index.ts +1 -1
- package/src/sources/provider-router/primary.ts +23 -5
- package/src/sources/sec-edgar.ts +68 -8
- package/src/sync/core-contributors.ts +4 -3
- package/src/test-support/cli-context.ts +2 -1
- package/src/test-support/data-provider.ts +34 -0
- package/src/test-support/shell-pane-keys.tsx +1 -0
- package/src/theme/series-colors.ts +46 -0
- package/src/theme/themes.ts +149 -0
- package/src/tickers/search/index.ts +22 -2
- package/src/tickers/search/ranking.ts +45 -1
- package/src/tickers/search/types.ts +3 -1
- package/src/time-series/resolve.ts +1 -14
- package/src/types/config.ts +61 -1
- package/src/types/headless.ts +11 -0
- package/src/types/instrument.ts +8 -0
- package/src/types/plugin.ts +49 -3
- package/src/ui/host.tsx +8 -4
- package/src/ui/index.tsx +2 -0
- package/src/utils/cli-output.ts +14 -1
- package/src/utils/exchanges.ts +13 -0
- package/src/utils/format.ts +2 -1
- package/src/utils/fx-market-hours.ts +34 -0
- package/src/utils/option-expiry.ts +126 -0
- package/src/utils/price-history.ts +39 -6
- package/src/utils/rpc-timeout-error.ts +6 -2
- package/src/utils/sec-eps-basis.ts +159 -30
- package/src/utils/sec.ts +38 -34
- package/src/utils/throttled-fetch.ts +51 -22
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { RemoteUiNodeSnapshot } from "../../remote/types";
|
|
2
|
+
import type { HeadlessFreshnessStatus, HeadlessPaneFreshness } from "../../types/headless";
|
|
3
|
+
import { isFiniteNumber, isRecord } from "../../utils/guards";
|
|
4
|
+
|
|
5
|
+
export interface RenderedReportNotices {
|
|
6
|
+
/** Lines a report prints under its title, such as "21 of 145 strikes". */
|
|
7
|
+
notices: string[];
|
|
8
|
+
/** The same facts by key, for `metadata` in JSON: `strikes: { shown, total, window }`. */
|
|
9
|
+
facts: Record<string, unknown>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* What a rendered pane says about the rows it lists, from the `report-notice`
|
|
14
|
+
* nodes it publishes: `{ text, key?, value? }`. The pane knows what its view
|
|
15
|
+
* leaves out (the option chain's strike window); the report repeats it.
|
|
16
|
+
*/
|
|
17
|
+
export function renderedReportNotices(semanticUi: readonly RemoteUiNodeSnapshot[]): RenderedReportNotices {
|
|
18
|
+
const notices: string[] = [];
|
|
19
|
+
const facts: Record<string, unknown> = {};
|
|
20
|
+
for (const node of semanticUi) {
|
|
21
|
+
if (node.role !== "report-notice" || !node.metadata) continue;
|
|
22
|
+
const { text, key, value } = node.metadata as { text?: unknown; key?: unknown; value?: unknown };
|
|
23
|
+
if (typeof text === "string" && text.trim() && !notices.includes(text.trim())) notices.push(text.trim());
|
|
24
|
+
if (typeof key === "string" && key && value !== undefined) facts[key] = value;
|
|
25
|
+
}
|
|
26
|
+
return { notices, facts };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const FRESHNESS_STATUSES: readonly HeadlessFreshnessStatus[] = ["live", "delayed", "stale", "not-a-feed"];
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* How current a rendered pane says its data is, from the `report-freshness`
|
|
33
|
+
* node it publishes: `{ asOf?, status?, delayMinutes? }`. A capture keeps no
|
|
34
|
+
* footer status and few cells carry a time, so a pane that knows its feed
|
|
35
|
+
* (OMON's chain) dates the report here, as the plain command over the same
|
|
36
|
+
* data does. A pane whose figures come from several dated quotes (FXC's pair
|
|
37
|
+
* rates) publishes them instead, see `renderedReportObservations`.
|
|
38
|
+
*/
|
|
39
|
+
export function renderedReportFreshness(
|
|
40
|
+
semanticUi: readonly RemoteUiNodeSnapshot[],
|
|
41
|
+
): Pick<HeadlessPaneFreshness, "asOf" | "status" | "delayMinutes"> {
|
|
42
|
+
const metadata = semanticUi.find((node) => node.role === "report-freshness")?.metadata;
|
|
43
|
+
if (!metadata) return {};
|
|
44
|
+
const { asOf, status, delayMinutes } = metadata as { asOf?: unknown; status?: unknown; delayMinutes?: unknown };
|
|
45
|
+
return {
|
|
46
|
+
...(typeof asOf === "string" || isFiniteNumber(asOf) ? { asOf } : {}),
|
|
47
|
+
...(FRESHNESS_STATUSES.includes(status as HeadlessFreshnessStatus) ? { status: status as HeadlessFreshnessStatus } : {}),
|
|
48
|
+
...(isFiniteNumber(delayMinutes) && delayMinutes > 0 ? { delayMinutes } : {}),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The dated observations a rendered pane's figures come from, from the
|
|
54
|
+
* `observations` its `report-freshness` node publishes: one record per quote
|
|
55
|
+
* or rate, in the fields of a headless report's rows (`quoteTime`,
|
|
56
|
+
* `dataSource`, `delayMinutes`, `stale`, `sessionExchange`, `marketState`).
|
|
57
|
+
* The report reads them as it reads those rows, so it says what the newest is,
|
|
58
|
+
* how far the feed is held back, which are stale and where the market stands.
|
|
59
|
+
*/
|
|
60
|
+
export function renderedReportObservations(semanticUi: readonly RemoteUiNodeSnapshot[]): Record<string, unknown>[] {
|
|
61
|
+
const observations = semanticUi.find((node) => node.role === "report-freshness")?.metadata?.observations;
|
|
62
|
+
return Array.isArray(observations) ? observations.filter(isRecord) : [];
|
|
63
|
+
}
|
|
@@ -49,6 +49,10 @@ export async function buildFunctionReport(
|
|
|
49
49
|
const startedAt = performance.now();
|
|
50
50
|
const source = resolvePaneFunctionReportSource(resolved);
|
|
51
51
|
if (!source) {
|
|
52
|
+
// HELP is what a newcomer tries first: send them to the command guide and the function list.
|
|
53
|
+
if (resolved.pane.id === "help") {
|
|
54
|
+
throw new Error("HELP is an in-app pane, so it has no report. For the commands run `gloomberb help`; for the functions run `gloomberb catalog`.");
|
|
55
|
+
}
|
|
52
56
|
throw new Error(`${resolved.token} is an interactive pane and does not expose a data report.`);
|
|
53
57
|
}
|
|
54
58
|
|
|
@@ -180,12 +180,10 @@ export async function resolvePaneFunction(
|
|
|
180
180
|
const normalizedOptions = normalizeCapabilityOptions(capability, args.options, {
|
|
181
181
|
strict: args.requireBotSafe || (!!headless && resolutionSettings.strictHeadlessOptions === true),
|
|
182
182
|
});
|
|
183
|
-
const settings =
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}
|
|
188
|
-
: optionSettings(args.options);
|
|
183
|
+
const settings = {
|
|
184
|
+
...optionSettings(args.options),
|
|
185
|
+
...capabilityPaneSettings(capability, normalizedOptions),
|
|
186
|
+
};
|
|
189
187
|
if (args.requireBotSafe) {
|
|
190
188
|
validateTickerCardinality(capability, createOptions);
|
|
191
189
|
}
|
|
@@ -18,6 +18,7 @@ import { scenarioScreenshotEvidence } from "../../plugins/builtin/options-scenar
|
|
|
18
18
|
import { realizedVolScreenshotEvidence } from "../../plugins/builtin/realized-vol/evidence";
|
|
19
19
|
import { volSurfaceScreenshotEvidence } from "../../plugins/builtin/vol-surface/evidence";
|
|
20
20
|
import { volatilityScreenshotEvidence } from "../../plugins/builtin/volatility/evidence";
|
|
21
|
+
import { marketHeatmapScreenshotEvidence } from "../../plugins/builtin/market-heatmap/evidence";
|
|
21
22
|
import { worldMapScreenshotEvidence } from "../../plugins/builtin/world-venue-map/evidence";
|
|
22
23
|
|
|
23
24
|
/** The envelope every pane's rendered chart-data evidence carries. */
|
|
@@ -95,6 +96,7 @@ const PANE_SCREENSHOT_EVIDENCE: readonly PaneScreenshotEvidenceHook[] = [
|
|
|
95
96
|
volSurfaceScreenshotEvidence,
|
|
96
97
|
volatilityScreenshotEvidence,
|
|
97
98
|
worldMapScreenshotEvidence,
|
|
99
|
+
marketHeatmapScreenshotEvidence,
|
|
98
100
|
];
|
|
99
101
|
|
|
100
102
|
export function paneScreenshotEvidenceHook(resolved: ResolvedPaneFunction): PaneScreenshotEvidenceHook | null {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { fetchTape } from "../../plugins/builtin/time-sales/client";
|
|
2
|
+
import { isOptionsUnavailableError } from "../../market-data/options-alternatives";
|
|
2
3
|
import type { TapeCapture } from "../../plugins/builtin/time-sales/snapshot-client";
|
|
3
4
|
import { apiClient } from "../../api-client";
|
|
4
5
|
import { snapshotInstrumentKey, type SnapshotMarketData } from "../../market-data/snapshot-provider";
|
|
@@ -9,7 +10,7 @@ import { dirname, resolve } from "path";
|
|
|
9
10
|
import { mkdir } from "fs/promises";
|
|
10
11
|
import type { PaneRuntimeState } from "../../core/state/app/state";
|
|
11
12
|
import { CHART_COMPOSER_PANE_ID, type AppConfig } from "../../types/config";
|
|
12
|
-
import type { OptionsChain, PricePoint, TickerFinancials } from "../../types/financials";
|
|
13
|
+
import type { OptionsChain, PricePoint, Quote, TickerFinancials } from "../../types/financials";
|
|
13
14
|
import type { TickerRecord } from "../../types/ticker";
|
|
14
15
|
import { slugifyName } from "../../utils/slugify";
|
|
15
16
|
import {
|
|
@@ -18,7 +19,7 @@ import {
|
|
|
18
19
|
type HttpProxyRequestEnvelope,
|
|
19
20
|
type HttpProxyResponseEnvelope,
|
|
20
21
|
} from "../../utils/http-proxy-response";
|
|
21
|
-
import {
|
|
22
|
+
import { requireThemeId } from "../commands/themes";
|
|
22
23
|
import { isRecord } from "../../utils/guards";
|
|
23
24
|
|
|
24
25
|
const DEFAULT_SHOT_DEVICE_SCALE_FACTOR = 2;
|
|
@@ -86,6 +87,7 @@ import {
|
|
|
86
87
|
clipPriceHistoryToRange,
|
|
87
88
|
createFallbackTicker,
|
|
88
89
|
fetchTickerFinancials,
|
|
90
|
+
loadBrokerMarkedHolding,
|
|
89
91
|
isFinancialAnalysisFunction,
|
|
90
92
|
readsDailyReturns,
|
|
91
93
|
withShotDailyReturns,
|
|
@@ -93,6 +95,9 @@ import {
|
|
|
93
95
|
withShotPriceHistory,
|
|
94
96
|
withShotSeasonalityHistory,
|
|
95
97
|
} from "./data";
|
|
98
|
+
import { renderedReportFreshness, renderedReportNotices, renderedReportObservations } from "./report-notices";
|
|
99
|
+
import { deriveRenderedFreshness, statusLineVariants, type ReportFreshness } from "./freshness";
|
|
100
|
+
import { quotesFreshness } from "../freshness";
|
|
96
101
|
import {
|
|
97
102
|
financialRatioRenderMismatches,
|
|
98
103
|
financialRatioShotEvidence,
|
|
@@ -426,6 +431,10 @@ export interface PaneScreenshotResult {
|
|
|
426
431
|
usable: boolean;
|
|
427
432
|
unusableReason: string | null;
|
|
428
433
|
dataEvidence: PaneScreenshotDataEvidence | null;
|
|
434
|
+
/** What the pane says its view leaves out, such as OMON's "21 of 145 strikes". */
|
|
435
|
+
notices?: string[];
|
|
436
|
+
/** How current the captured data is, as the capture's status line says it; absent with `--no-status`. */
|
|
437
|
+
freshness?: ReportFreshness;
|
|
429
438
|
outputPath: string;
|
|
430
439
|
render: DesktopPaneShotRenderResult & {
|
|
431
440
|
expectedText: string[];
|
|
@@ -493,7 +502,7 @@ export async function buildDesktopShotPayload(
|
|
|
493
502
|
};
|
|
494
503
|
const config = stripDesktopShotCredentials<AppConfig>({
|
|
495
504
|
...context.config,
|
|
496
|
-
...(theme ? { theme:
|
|
505
|
+
...(theme ? { theme: requireThemeId(theme) } : {}),
|
|
497
506
|
layout,
|
|
498
507
|
layouts: [{
|
|
499
508
|
name: "CLI Shot",
|
|
@@ -525,7 +534,7 @@ export async function buildDesktopShotPayload(
|
|
|
525
534
|
shotInstance = { ...shotInstance, settings: { ...shotInstance.settings, ...prepared.settings } };
|
|
526
535
|
for (const [symbol, data] of prepared.financials ?? []) {
|
|
527
536
|
financials.push([symbol, data]);
|
|
528
|
-
tickers.push(createFallbackTicker(symbol, data, context));
|
|
537
|
+
tickers.push(await createFallbackTicker(symbol, data, context));
|
|
529
538
|
}
|
|
530
539
|
} else if (resolved.pane.id === "analytics") {
|
|
531
540
|
const loaded = await loadResolvedHeadlessPaneModel(resolved, context, rawArg);
|
|
@@ -551,13 +560,23 @@ export async function buildDesktopShotPayload(
|
|
|
551
560
|
if (!target.instrument) financials.push([label, data]);
|
|
552
561
|
const { symbol } = parsePublicTickerKey(key);
|
|
553
562
|
const ticker = await context.store.loadTicker(key) ?? await context.store.loadTicker(symbol);
|
|
554
|
-
tickers.push(ticker ?? createFallbackTicker(key, data, context));
|
|
563
|
+
tickers.push(ticker ?? await createFallbackTicker(key, data, context));
|
|
555
564
|
}
|
|
556
565
|
intradayHistories.push(...chartModel.snapshot.intradayHistories.map((history) => ({
|
|
557
566
|
...history, start: history.start?.toISOString() ?? null, end: history.end?.toISOString() ?? null,
|
|
558
567
|
})));
|
|
559
568
|
} else for (const symbol of await collectShotSymbolsWithCollections(resolved, context, rawArg)) {
|
|
560
|
-
|
|
569
|
+
let entry: Awaited<ReturnType<typeof fetchTickerFinancials>>;
|
|
570
|
+
try {
|
|
571
|
+
entry = await fetchTickerFinancials(context, symbol);
|
|
572
|
+
} catch (error) {
|
|
573
|
+
// The pane values a holding no quote covers, such as an option contract,
|
|
574
|
+
// at the broker's mark. It has no financials to capture.
|
|
575
|
+
const holding = COLLECTION_PANE_IDS.has(resolved.pane.id) ? await loadBrokerMarkedHolding(context, symbol) : null;
|
|
576
|
+
if (!holding) throw error;
|
|
577
|
+
tickers.push(holding);
|
|
578
|
+
continue;
|
|
579
|
+
}
|
|
561
580
|
const requestedRange = shotPriceHistoryRange(resolved);
|
|
562
581
|
let data = entry.financials;
|
|
563
582
|
const exchange = entry.instrument.exchange
|
|
@@ -565,7 +584,9 @@ export async function buildDesktopShotPayload(
|
|
|
565
584
|
?? data.quote?.listingExchangeName
|
|
566
585
|
?? data.quote?.exchangeName
|
|
567
586
|
?? "";
|
|
568
|
-
const fiveYearsDaily = resolved.pane.id === "realized-vol" || resolved.pane.id === "iv-history" || resolved.pane.id === "macro-day"
|
|
587
|
+
const fiveYearsDaily = resolved.pane.id === "realized-vol" || resolved.pane.id === "iv-history" || resolved.pane.id === "macro-day"
|
|
588
|
+
// KELLY's History tab reads monthly returns off five years of daily closes.
|
|
589
|
+
|| resolved.pane.id === "kelly-sizer";
|
|
569
590
|
if (fiveYearsDaily || resolved.pane.id === "iv-screen" || resolved.pane.id === "short-watch"
|
|
570
591
|
|| resolved.pane.id === "backtest" || resolved.pane.id === OPTIONS_PANE_ID) {
|
|
571
592
|
// Generic 5Y snapshots can contain weekly bars. The snapshot provider
|
|
@@ -600,15 +621,19 @@ export async function buildDesktopShotPayload(
|
|
|
600
621
|
// letting the pane fetch them.
|
|
601
622
|
data = await withShotPriceHistory(context, symbol, entry.tickerFile, data);
|
|
602
623
|
}
|
|
603
|
-
tickers.push(entry.tickerFile ?? createFallbackTicker(symbol, data, context));
|
|
624
|
+
tickers.push(entry.tickerFile ?? await createFallbackTicker(symbol, data, context));
|
|
604
625
|
financials.push([symbol, data]);
|
|
605
626
|
if (resolved.pane.id === "time-sales") {
|
|
606
627
|
tapeSnapshots.push([entry.instrument.symbol, exchange, await fetchTape(entry.instrument.symbol, exchange)]);
|
|
607
628
|
}
|
|
608
629
|
if (includeOptionsChains && context.dataProvider.getOptionsChain) {
|
|
630
|
+
// A future with no chain of its own is the pane's to explain (the listed alternative), not a failed shot.
|
|
609
631
|
const chain = await context.dataProvider.getOptionsChain(entry.instrument.symbol, exchange, undefined,
|
|
610
|
-
context.refresh ? { cacheMode: "refresh" } : undefined)
|
|
611
|
-
|
|
632
|
+
context.refresh ? { cacheMode: "refresh" } : undefined).catch((error: unknown) => {
|
|
633
|
+
if (isOptionsUnavailableError(error)) return null;
|
|
634
|
+
throw error;
|
|
635
|
+
});
|
|
636
|
+
if (chain) optionsChains.push([symbol, chain]);
|
|
612
637
|
}
|
|
613
638
|
}
|
|
614
639
|
layout.instances[0] = shotInstance;
|
|
@@ -666,17 +691,6 @@ async function collectShotSymbolsWithCollections(
|
|
|
666
691
|
return [...new Set([...symbols, ...members])];
|
|
667
692
|
}
|
|
668
693
|
|
|
669
|
-
function resolveShotTheme(requested: string): string {
|
|
670
|
-
const normalized = requested.trim().toLowerCase().replace(/[\s_]+/g, "-");
|
|
671
|
-
const ids = getThemeIds();
|
|
672
|
-
const match = ids.find((id) => id.toLowerCase() === normalized)
|
|
673
|
-
?? ids.find((id) => getTheme(id).name.toLowerCase().replace(/[\s_]+/g, "-") === normalized);
|
|
674
|
-
if (!match) {
|
|
675
|
-
throw new Error(`Unknown theme "${requested}". Available themes: ${ids.join(", ")}`);
|
|
676
|
-
}
|
|
677
|
-
return match;
|
|
678
|
-
}
|
|
679
|
-
|
|
680
694
|
function shotPriceHistoryRange(resolved: ResolvedPaneFunction): TimeRange | null {
|
|
681
695
|
// Captured history is authoritative, and the generic 5Y capture is weekly.
|
|
682
696
|
if (resolved.pane.id === "quote-monitor") return getQuoteMonitorPaneSettings(resolved.instance.settings).chartPeriod;
|
|
@@ -691,6 +705,60 @@ function shotPriceHistoryRange(resolved: ResolvedPaneFunction): TimeRange | null
|
|
|
691
705
|
}
|
|
692
706
|
}
|
|
693
707
|
|
|
708
|
+
/** The bridge, keeping every quote it serves the page: what a pane that loads its own quotes (WEI) shows. */
|
|
709
|
+
function recordBridgedQuotes(bridge: DesktopPaneShotBridge): { bridge: DesktopPaneShotBridge; quotes: Quote[] } {
|
|
710
|
+
const quotes: Quote[] = [];
|
|
711
|
+
const keep = (value: unknown) => {
|
|
712
|
+
if (isRecord(value) && typeof value.symbol === "string" && typeof value.price === "number") quotes.push(value as unknown as Quote);
|
|
713
|
+
};
|
|
714
|
+
return {
|
|
715
|
+
quotes,
|
|
716
|
+
bridge: {
|
|
717
|
+
...bridge,
|
|
718
|
+
async marketData(operation, args) {
|
|
719
|
+
const result = await bridge.marketData(operation, args);
|
|
720
|
+
if (operation === "getQuote") keep(result);
|
|
721
|
+
if (operation === "getQuotesBatch" && Array.isArray(result)) {
|
|
722
|
+
for (const entry of result) keep(isRecord(entry) ? entry.quote : null);
|
|
723
|
+
}
|
|
724
|
+
return result;
|
|
725
|
+
},
|
|
726
|
+
},
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* How current a capture's data is. A pane that says what its data is (its
|
|
732
|
+
* declared status, or a feed it publishes, such as OMON's chain or the news
|
|
733
|
+
* wire's delay) is taken at its word; otherwise the quotes it shows date it,
|
|
734
|
+
* with their delay and where their markets stand; otherwise what the
|
|
735
|
+
* rendered view carries, which may be nothing.
|
|
736
|
+
*/
|
|
737
|
+
function shotFreshness(
|
|
738
|
+
resolved: Pick<ResolvedPaneFunction, "pane">,
|
|
739
|
+
payload: Pick<DesktopPaneShotPayload, "financials" | "instrumentFinancials">,
|
|
740
|
+
render: Pick<DesktopPaneShotRenderResult, "semanticUi" | "footerText" | "rows">,
|
|
741
|
+
bridgedQuotes: readonly Quote[],
|
|
742
|
+
now = Date.now(),
|
|
743
|
+
): ReportFreshness {
|
|
744
|
+
const declared = { ...resolved.pane.reportFreshness, ...renderedReportFreshness(render.semanticUi ?? []) };
|
|
745
|
+
const quotes = [
|
|
746
|
+
...payload.financials.map(([, financials]) => financials.quote),
|
|
747
|
+
...(payload.instrumentFinancials ?? []).map(({ financials }) => financials.quote),
|
|
748
|
+
...bridgedQuotes,
|
|
749
|
+
];
|
|
750
|
+
// A pane that publishes the observations behind its figures (FXC's rates) dates itself.
|
|
751
|
+
const observed = renderedReportObservations(render.semanticUi ?? []);
|
|
752
|
+
const quoted = declared.status || observed.length > 0
|
|
753
|
+
? undefined
|
|
754
|
+
: quotesFreshness(quotes, declared.source ? { source: declared.source } : undefined, now);
|
|
755
|
+
return quoted ?? deriveRenderedFreshness(declared, {
|
|
756
|
+
footerText: render.footerText ?? "",
|
|
757
|
+
cellTimes: render.rows.flatMap((row) => row.cells.flatMap((cell) => (cell.instant ? [cell.instant] : []))),
|
|
758
|
+
observed,
|
|
759
|
+
}, now);
|
|
760
|
+
}
|
|
761
|
+
|
|
694
762
|
export async function renderDesktopShot({
|
|
695
763
|
resolved,
|
|
696
764
|
context,
|
|
@@ -701,6 +769,7 @@ export async function renderDesktopShot({
|
|
|
701
769
|
theme,
|
|
702
770
|
scale,
|
|
703
771
|
watermark,
|
|
772
|
+
statusLine = false,
|
|
704
773
|
options,
|
|
705
774
|
captureImage = true,
|
|
706
775
|
}: {
|
|
@@ -713,6 +782,8 @@ export async function renderDesktopShot({
|
|
|
713
782
|
theme?: string | null;
|
|
714
783
|
scale?: number;
|
|
715
784
|
watermark?: string | null;
|
|
785
|
+
/** Draw the dated status line in the capture's footer. Reports and `--no-status` leave it out. */
|
|
786
|
+
statusLine?: boolean;
|
|
716
787
|
options: Record<string, string | true>;
|
|
717
788
|
captureImage?: boolean;
|
|
718
789
|
}): Promise<PaneScreenshotResult> {
|
|
@@ -721,6 +792,7 @@ export async function renderDesktopShot({
|
|
|
721
792
|
const previousSessionToken = apiClient.getSessionToken();
|
|
722
793
|
let payload: DesktopPaneShotPayload;
|
|
723
794
|
let render: DesktopPaneShotRenderResult;
|
|
795
|
+
let freshness: ReportFreshness | undefined;
|
|
724
796
|
apiClient.setSessionToken(apiProxy.sessionToken);
|
|
725
797
|
try {
|
|
726
798
|
payload = await buildDesktopShotPayload(
|
|
@@ -734,16 +806,24 @@ export async function renderDesktopShot({
|
|
|
734
806
|
scale ?? 1,
|
|
735
807
|
watermark ?? null,
|
|
736
808
|
);
|
|
809
|
+
if (statusLine && captureImage) payload.statusLine = true;
|
|
810
|
+
const recorded = recordBridgedQuotes(createDesktopShotBridge(context));
|
|
811
|
+
const shotPayload = payload;
|
|
737
812
|
render = await renderDesktopPaneScreenshot(payload, outputPath, apiProxy, {
|
|
738
813
|
captureImage,
|
|
739
|
-
bridge:
|
|
814
|
+
bridge: recorded.bridge,
|
|
815
|
+
statusLine: (rendered) => {
|
|
816
|
+
freshness = shotFreshness(resolved, shotPayload, rendered, recorded.quotes);
|
|
817
|
+
return statusLineVariants(freshness);
|
|
818
|
+
},
|
|
740
819
|
});
|
|
741
820
|
} finally {
|
|
742
821
|
apiClient.setSessionToken(previousSessionToken);
|
|
743
822
|
}
|
|
744
823
|
const renderedInstance = payload.config.layout.instances.find(({ instanceId }) => instanceId === payload.paneId);
|
|
745
824
|
if (renderedInstance) resolved = { ...resolved, instance: renderedInstance };
|
|
746
|
-
|
|
825
|
+
const assessed = assessPaneScreenshot(resolved, payload, render, rawArg, outputPath);
|
|
826
|
+
return freshness ? { ...assessed, freshness } : assessed;
|
|
747
827
|
}
|
|
748
828
|
|
|
749
829
|
/** What a rendered capture shows, and whether it is fit to use. */
|
|
@@ -807,6 +887,7 @@ export function assessPaneScreenshot(
|
|
|
807
887
|
const unusableReason = usable
|
|
808
888
|
? null
|
|
809
889
|
: shotUnusableReasonFor(resolved, payload, render, unavailableSymbols, semanticMismatch);
|
|
890
|
+
const { notices } = renderedReportNotices(render.semanticUi ?? []);
|
|
810
891
|
return {
|
|
811
892
|
kind: "pane-screenshot",
|
|
812
893
|
target: resolved.token,
|
|
@@ -827,6 +908,7 @@ export function assessPaneScreenshot(
|
|
|
827
908
|
usable,
|
|
828
909
|
unusableReason,
|
|
829
910
|
dataEvidence,
|
|
911
|
+
...(notices.length > 0 ? { notices } : {}),
|
|
830
912
|
outputPath,
|
|
831
913
|
render: {
|
|
832
914
|
...stripDesktopShotCredentials(render),
|
package/src/cli/registry.ts
CHANGED
|
@@ -149,6 +149,7 @@ export function createCliCommandContext(
|
|
|
149
149
|
ownerId: string,
|
|
150
150
|
registryOrPlugins: Pick<CliCommandRegistry, "plugins" | "externalPlugins"> | GloomPlugin[],
|
|
151
151
|
cliOptions: CliGlobalOptions = DEFAULT_CLI_OPTIONS,
|
|
152
|
+
args: readonly string[] = [],
|
|
152
153
|
): CliCommandContext {
|
|
153
154
|
const registry = Array.isArray(registryOrPlugins)
|
|
154
155
|
? { plugins: registryOrPlugins, externalPlugins: [] }
|
|
@@ -169,7 +170,7 @@ export function createCliCommandContext(
|
|
|
169
170
|
renderStats,
|
|
170
171
|
renderTable,
|
|
171
172
|
},
|
|
172
|
-
printResult: (result, options) => printCliResult(result, cliOptions, options),
|
|
173
|
+
printResult: (result, options) => printCliResult(result, cliOptions, options, args),
|
|
173
174
|
log: debugLog.createLogger(ownerId === "core" ? "cli" : ownerId),
|
|
174
175
|
};
|
|
175
176
|
}
|
package/src/cli/result.ts
CHANGED
|
@@ -7,13 +7,16 @@ import {
|
|
|
7
7
|
statValueWidth,
|
|
8
8
|
visibleLength,
|
|
9
9
|
wrapText,
|
|
10
|
+
wrapToTerminal,
|
|
10
11
|
type CliStatEntry,
|
|
11
12
|
type CliTableColumn,
|
|
12
13
|
} from "../utils/cli-output";
|
|
13
14
|
import { serializeCsv } from "../utils/csv";
|
|
14
15
|
import { formatUtcTime, isEpochMilliseconds, isZonedIsoDateTime } from "../utils/utc-time";
|
|
15
16
|
import { formatFreshnessLine, type ReportFreshness } from "./pane-functions/freshness";
|
|
17
|
+
import { withSearchHints } from "./not-a-ticker";
|
|
16
18
|
import { renderReportCsv, renderReportNdjson, type CliReportTables } from "./report-tables";
|
|
19
|
+
import { windowRows, type RowWindow, type RowWindowOptions } from "./row-window";
|
|
17
20
|
|
|
18
21
|
export interface CliResult<T = unknown> {
|
|
19
22
|
data: T;
|
|
@@ -59,6 +62,17 @@ export interface CliResultRenderOptions<T = unknown, Row = Record<string, unknow
|
|
|
59
62
|
summary?: (data: T) => string;
|
|
60
63
|
/** Text-mode line printed first, naming what the result is about, such as the listing a symbol resolved to. */
|
|
61
64
|
heading?: string;
|
|
65
|
+
/** Text-mode line printed under the rows, such as where a listing stops. Not printed with no rows: say it in `empty`. */
|
|
66
|
+
footnote?: string;
|
|
67
|
+
/**
|
|
68
|
+
* The row key holding each row's date, which makes the rows a dated series:
|
|
69
|
+
* `--tail` keeps the newest rows whichever way the series runs, and a
|
|
70
|
+
* `--limit` that keeps the oldest says so, under the text table and as
|
|
71
|
+
* `metadata.rows` in JSON.
|
|
72
|
+
*/
|
|
73
|
+
dateKey?: string;
|
|
74
|
+
/** How many rows to show when neither `--limit` nor `--tail` is given; every row when absent. */
|
|
75
|
+
defaultLimit?: number;
|
|
62
76
|
/**
|
|
63
77
|
* What CSV and NDJSON write instead of the rows: a report's tables as the
|
|
64
78
|
* text view shows them, CSV with its `# section:` and closing `#` lines.
|
|
@@ -89,14 +103,28 @@ const KEY_ACRONYMS: Record<string, string> = {
|
|
|
89
103
|
usd: "USD",
|
|
90
104
|
};
|
|
91
105
|
|
|
92
|
-
function
|
|
93
|
-
|
|
94
|
-
|
|
106
|
+
function rowWindowOptions(options: CliGlobalOptions, renderOptions: Pick<CliResultRenderOptions, "defaultLimit">): RowWindowOptions {
|
|
107
|
+
return options.tail != null ? { tail: options.tail } : { limit: options.limit ?? renderOptions.defaultLimit };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function windowed<Row>(
|
|
111
|
+
rows: readonly Row[],
|
|
112
|
+
options: CliGlobalOptions,
|
|
113
|
+
renderOptions: Pick<CliResultRenderOptions, "dateKey" | "defaultLimit">,
|
|
114
|
+
): RowWindow<Row> {
|
|
115
|
+
const { dateKey } = renderOptions;
|
|
116
|
+
const dateOf = dateKey ? (row: Row) => (isPlainObject(row) ? row[dateKey] : undefined) : undefined;
|
|
117
|
+
return windowRows(rows, rowWindowOptions(options, renderOptions), dateOf);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function asRows<T, Row>(data: T, rows?: (data: T) => Row[]): Row[] {
|
|
121
|
+
return rows ? rows(data) : (Array.isArray(data) ? data : [data]) as Row[];
|
|
95
122
|
}
|
|
96
123
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
124
|
+
/** What JSON says about rows the window cut, under `metadata.rows`. */
|
|
125
|
+
function cutMetadata(window: RowWindow<unknown>) {
|
|
126
|
+
if (window.rows.length >= window.total) return null;
|
|
127
|
+
return { shown: window.rows.length, total: window.total, kept: window.kept, ...(window.note ? { note: window.note } : {}) };
|
|
100
128
|
}
|
|
101
129
|
|
|
102
130
|
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
@@ -277,6 +305,7 @@ function renderTextTable<Row extends Record<string, unknown>>(
|
|
|
277
305
|
width: column.width,
|
|
278
306
|
maxWidth: column.maxWidth,
|
|
279
307
|
optional: column.optional,
|
|
308
|
+
dropPriority: column.dropPriority,
|
|
280
309
|
shrink: column.shrink,
|
|
281
310
|
})),
|
|
282
311
|
cells.map((row) => shown.map(({ index }) => row[index]!)),
|
|
@@ -310,7 +339,7 @@ function overflowsTerminal<Row extends Record<string, unknown>>(rows: Row[]): bo
|
|
|
310
339
|
|
|
311
340
|
/** The closing source, as-of and status line of a text result, for output printed without `printResult`. */
|
|
312
341
|
export function cliFreshnessFooter(freshness: ReportFreshness): string {
|
|
313
|
-
return cliStyles.muted(formatFreshnessLine(freshness));
|
|
342
|
+
return cliStyles.muted(wrapToTerminal(formatFreshnessLine(freshness)));
|
|
314
343
|
}
|
|
315
344
|
|
|
316
345
|
export function serializeCliResult<T, Row extends Record<string, unknown> = Record<string, unknown>>(
|
|
@@ -321,11 +350,13 @@ export function serializeCliResult<T, Row extends Record<string, unknown> = Reco
|
|
|
321
350
|
const { freshness, ...rest } = result;
|
|
322
351
|
if (options.format === "json") {
|
|
323
352
|
const columns = serializeColumns(renderOptions.columns);
|
|
353
|
+
const window = Array.isArray(result.data) ? windowed(result.data as unknown[], options, renderOptions) : null;
|
|
354
|
+
const cut = window ? cutMetadata(window) : null;
|
|
324
355
|
const envelope: CliResultJsonEnvelope<T> = {
|
|
325
356
|
ok: true,
|
|
326
357
|
...rest,
|
|
327
|
-
...(freshness ? { metadata: { ...rest.metadata, freshness } } : {}),
|
|
328
|
-
data:
|
|
358
|
+
...(freshness || cut ? { metadata: { ...rest.metadata, ...(freshness ? { freshness } : {}), ...(cut ? { rows: cut } : {}) } } : {}),
|
|
359
|
+
data: window ? window.rows as T : result.data,
|
|
329
360
|
...(columns?.length ? { columns } : {}),
|
|
330
361
|
};
|
|
331
362
|
return JSON.stringify(envelope, null, 2);
|
|
@@ -342,7 +373,8 @@ function serializeCliRows<T, Row extends Record<string, unknown>>(
|
|
|
342
373
|
): string {
|
|
343
374
|
if (renderOptions.tables && options.format === "csv") return renderReportCsv(renderOptions.tables);
|
|
344
375
|
if (renderOptions.tables && options.format === "ndjson") return renderReportNdjson(renderOptions.tables);
|
|
345
|
-
const
|
|
376
|
+
const window = windowed(asRows(result.data, renderOptions.rows), options, renderOptions);
|
|
377
|
+
const rows = window.rows;
|
|
346
378
|
if (options.format === "ndjson") {
|
|
347
379
|
return rows.map((row) => JSON.stringify(row)).join("\n");
|
|
348
380
|
}
|
|
@@ -357,7 +389,9 @@ function serializeCliRows<T, Row extends Record<string, unknown>>(
|
|
|
357
389
|
if (rows.length === 0) {
|
|
358
390
|
return withHeading(summary || cliStyles.muted(renderOptions.empty ?? "No results."));
|
|
359
391
|
}
|
|
360
|
-
const
|
|
392
|
+
const table = renderTextRows(rows as Row[], result.data, renderOptions);
|
|
393
|
+
const notes = [window.note, renderOptions.footnote].filter((note): note is string => !!note);
|
|
394
|
+
const body = [table, ...notes.map((note) => cliStyles.muted(note))].join("\n");
|
|
361
395
|
return withHeading(summary ? `${summary}\n\n${body}` : body);
|
|
362
396
|
}
|
|
363
397
|
|
|
@@ -382,9 +416,12 @@ export function printCliResult<T, Row extends Record<string, unknown> = Record<s
|
|
|
382
416
|
result: CliResult<T>,
|
|
383
417
|
options: CliGlobalOptions,
|
|
384
418
|
renderOptions: CliResultRenderOptions<T, Row> = {},
|
|
419
|
+
args: readonly string[] = [],
|
|
385
420
|
): void {
|
|
386
421
|
if (options.quiet && options.format === "text") return;
|
|
387
|
-
|
|
422
|
+
// JSON keeps the router's own words; everything else is for a person, so a "Not a ticker" gets its hint.
|
|
423
|
+
const hinted = (text: string) => options.format === "json" ? text : withSearchHints(text, args);
|
|
424
|
+
const output = hinted(serializeCliResult(result, options, renderOptions));
|
|
388
425
|
// Bun's console writer can truncate a large pipe write after stdout has been
|
|
389
426
|
// initialized. The stream queues the remaining bytes until the reader drains.
|
|
390
427
|
if (output) process.stdout.write(`${output}\n`);
|
|
@@ -392,7 +429,7 @@ export function printCliResult<T, Row extends Record<string, unknown> = Record<s
|
|
|
392
429
|
// rows, so a warning goes to stderr, out of a file the rows are piped into.
|
|
393
430
|
if (options.format !== "json" && !options.quiet) {
|
|
394
431
|
for (const warning of result.warnings ?? []) {
|
|
395
|
-
console.error(`${cliStyles.warning("warning:")} ${warning}`);
|
|
432
|
+
console.error(`${cliStyles.warning("warning:")} ${hinted(warning)}`);
|
|
396
433
|
}
|
|
397
434
|
}
|
|
398
435
|
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which rows `--limit` and `--tail` keep, for every command that prints a
|
|
3
|
+
* list. `--limit n` keeps the first n rows as printed. `--tail n` keeps the
|
|
4
|
+
* newest n rows of a dated series, whichever way it runs (the first n of a
|
|
5
|
+
* newest-first series, the last n of an oldest-first one), and the last n of
|
|
6
|
+
* any other list. The kept rows stay in their printed order.
|
|
7
|
+
*/
|
|
8
|
+
export interface RowWindowOptions {
|
|
9
|
+
limit?: number;
|
|
10
|
+
tail?: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface RowWindow<Row> {
|
|
14
|
+
rows: Row[];
|
|
15
|
+
/** How many rows there were before the cut. */
|
|
16
|
+
total: number;
|
|
17
|
+
/** The end of a dated series the cut kept; null when nothing was cut or the rows are not a dated series. */
|
|
18
|
+
kept: "oldest" | "newest" | null;
|
|
19
|
+
/**
|
|
20
|
+
* Said when `--limit` cut an oldest-first series, which reads as the latest
|
|
21
|
+
* rows but is the earliest: "showing the oldest 2 of 252 rows; --tail 2 for
|
|
22
|
+
* the latest". Null otherwise.
|
|
23
|
+
*/
|
|
24
|
+
note: string | null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function timeOf(value: unknown): number | null {
|
|
28
|
+
if (value instanceof Date) return Number.isFinite(value.getTime()) ? value.getTime() : null;
|
|
29
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : null;
|
|
30
|
+
if (typeof value !== "string" || !value.trim()) return null;
|
|
31
|
+
const time = Date.parse(value);
|
|
32
|
+
return Number.isFinite(time) ? time : null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function firstTime<Row>(rows: readonly Row[], dateOf: (row: Row) => unknown, fromEnd: boolean): number | null {
|
|
36
|
+
for (let step = 0; step < rows.length; step += 1) {
|
|
37
|
+
const time = timeOf(dateOf(rows[fromEnd ? rows.length - 1 - step : step]!));
|
|
38
|
+
if (time != null) return time;
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Which way a dated series runs, from its first and last dated rows; null when it is undated or spans one instant. */
|
|
44
|
+
function seriesOrder<Row>(rows: readonly Row[], dateOf: (row: Row) => unknown): "ascending" | "descending" | null {
|
|
45
|
+
const first = firstTime(rows, dateOf, false);
|
|
46
|
+
const last = firstTime(rows, dateOf, true);
|
|
47
|
+
if (first == null || last == null || first === last) return null;
|
|
48
|
+
return first < last ? "ascending" : "descending";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** The rows `options` keep. `dateOf` reads a row's date, which makes the rows a dated series. */
|
|
52
|
+
export function windowRows<Row>(
|
|
53
|
+
rows: readonly Row[],
|
|
54
|
+
options: RowWindowOptions,
|
|
55
|
+
dateOf?: (row: Row) => unknown,
|
|
56
|
+
): RowWindow<Row> {
|
|
57
|
+
const total = rows.length;
|
|
58
|
+
const count = options.tail ?? options.limit;
|
|
59
|
+
if (count == null || count >= total) return { rows: [...rows], total, kept: null, note: null };
|
|
60
|
+
const order = dateOf ? seriesOrder(rows, dateOf) : null;
|
|
61
|
+
if (options.tail != null) {
|
|
62
|
+
return {
|
|
63
|
+
rows: order === "descending" ? rows.slice(0, count) : rows.slice(total - count),
|
|
64
|
+
total,
|
|
65
|
+
kept: order ? "newest" : null,
|
|
66
|
+
note: null,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const kept = order === "ascending" ? "oldest" : order === "descending" ? "newest" : null;
|
|
70
|
+
return {
|
|
71
|
+
rows: rows.slice(0, count),
|
|
72
|
+
total,
|
|
73
|
+
kept,
|
|
74
|
+
note: kept === "oldest" ? `showing the oldest ${count} of ${total} rows; --tail ${count} for the latest` : null,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** A scrolling box as the page measures it. */
|
|
2
|
+
export interface ScrollBoxMetrics {
|
|
3
|
+
scrollTop: number;
|
|
4
|
+
scrollHeight: number;
|
|
5
|
+
clientHeight: number;
|
|
6
|
+
scrollLeft: number;
|
|
7
|
+
scrollWidth: number;
|
|
8
|
+
clientWidth: number;
|
|
9
|
+
/** The box draws a scrollbar on that axis, which says it overflows even when the measures cannot. */
|
|
10
|
+
scrollbarY: boolean;
|
|
11
|
+
scrollbarX: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* What a capture leaves out of a scrolling box, naming the side: a chain
|
|
16
|
+
* centred on the money is cut above and below, a list at its top only below.
|
|
17
|
+
* Self-contained so the screenshot runner can evaluate it in the browser.
|
|
18
|
+
*/
|
|
19
|
+
export function scrollTruncationReasons(box: ScrollBoxMetrics): string[] {
|
|
20
|
+
const reasons: string[] = [];
|
|
21
|
+
const cut = (offset: number, content: number, viewport: number, flagged: boolean, noun: string, sides: readonly [string, string, string, string]) => {
|
|
22
|
+
if (!flagged && !(content > viewport + 1)) return;
|
|
23
|
+
// One pixel either way is subpixel layout, not a hidden row.
|
|
24
|
+
const before = offset > 1;
|
|
25
|
+
const after = offset + viewport < content - 1;
|
|
26
|
+
// A drawn scrollbar over content that measures as fitting (a list that loads more) cannot say which side.
|
|
27
|
+
reasons.push(before || after
|
|
28
|
+
? `${noun} ${before && after ? sides[2] : before ? sides[0] : sides[1]} the rendered viewport are cut`
|
|
29
|
+
: `${noun} ${sides[3]} the rendered viewport may be cut`);
|
|
30
|
+
};
|
|
31
|
+
cut(box.scrollTop, box.scrollHeight, box.clientHeight, box.scrollbarY, "rows", ["above", "below", "above and below", "above or below"]);
|
|
32
|
+
cut(box.scrollLeft, box.scrollWidth, box.clientWidth, box.scrollbarX, "columns", ["left of", "right of", "left and right of", "left or right of"]);
|
|
33
|
+
return reasons;
|
|
34
|
+
}
|