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
|
@@ -9,6 +9,7 @@ import { cleanTickerInput } from "./options";
|
|
|
9
9
|
import { parsePublicTickerKey, publicTickerKey } from "../../utils/exchanges";
|
|
10
10
|
import type { ResolvedPaneFunction } from "./resolver";
|
|
11
11
|
import { toMarketDataContext } from "../../market-data/selectors";
|
|
12
|
+
import { quoteMetadataFromQuote } from "../../market-data/quotes/metadata";
|
|
12
13
|
import { loadSeasonalityHistory, SEASONALITY_HISTORY_RESOLUTION } from "../../plugins/builtin/seasonality/client";
|
|
13
14
|
import type { InstrumentRef } from "../../market-data/request-types";
|
|
14
15
|
import { CORRELATION_HISTORY_RESOLUTION, loadCorrelationHistory } from "../../plugins/builtin/correlation/history";
|
|
@@ -22,6 +23,26 @@ const SHOT_PRICE_HISTORY_RANGE = "5Y" as const;
|
|
|
22
23
|
const FINANCIAL_ANALYSIS_PANE_ID = "financial-analysis";
|
|
23
24
|
const FINANCIAL_ANALYSIS_TEMPLATE_ID = "financial-analysis-pane";
|
|
24
25
|
|
|
26
|
+
async function loadStoredShotTicker(context: MarketContext, symbol: string): Promise<TickerRecord | null> {
|
|
27
|
+
const normalized = cleanTickerInput(symbol);
|
|
28
|
+
const { symbol: bare } = parsePublicTickerKey(normalized);
|
|
29
|
+
return await context.store.loadTicker(normalized)
|
|
30
|
+
?? (bare !== normalized ? await context.store.loadTicker(bare) : null);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* A holding the pane can value without a quote: some position carries the
|
|
35
|
+
* broker's own mark or market value. An option contract no quote covers stays
|
|
36
|
+
* at that mark in the pane, so a failed quote lookup for it is not an error.
|
|
37
|
+
*/
|
|
38
|
+
export async function loadBrokerMarkedHolding(context: MarketContext, symbol: string): Promise<TickerRecord | null> {
|
|
39
|
+
const ticker = await loadStoredShotTicker(context, symbol);
|
|
40
|
+
const marked = ticker?.metadata.positions.some(({ shares, markPrice, marketValue }) => (
|
|
41
|
+
shares !== 0 && (Number.isFinite(markPrice) || Number.isFinite(marketValue))
|
|
42
|
+
));
|
|
43
|
+
return marked ? ticker : null;
|
|
44
|
+
}
|
|
45
|
+
|
|
25
46
|
export async function fetchTickerFinancials(
|
|
26
47
|
context: MarketContext,
|
|
27
48
|
symbol: string,
|
|
@@ -32,8 +53,7 @@ export async function fetchTickerFinancials(
|
|
|
32
53
|
}> {
|
|
33
54
|
const normalized = cleanTickerInput(symbol);
|
|
34
55
|
const instrument = parsePublicTickerKey(normalized);
|
|
35
|
-
const tickerFile = await context
|
|
36
|
-
?? (instrument.symbol !== normalized ? await context.store.loadTicker(instrument.symbol) : null);
|
|
56
|
+
const tickerFile = await loadStoredShotTicker(context, symbol);
|
|
37
57
|
const exchange = instrument.exchange ?? tickerFile?.metadata.exchange ?? "";
|
|
38
58
|
const financials = await context.dataProvider.getTickerFinancials(instrument.symbol, exchange);
|
|
39
59
|
return { tickerFile, financials, instrument: { symbol: instrument.symbol, ...(exchange ? { exchange } : {}) } };
|
|
@@ -142,14 +162,25 @@ export function isFinancialAnalysisFunction(resolved: ResolvedPaneFunction): boo
|
|
|
142
162
|
&& resolved.instance.settings?.lockedTabId === "financials";
|
|
143
163
|
}
|
|
144
164
|
|
|
145
|
-
|
|
165
|
+
/**
|
|
166
|
+
* The ticker a shot shows for a symbol that is not saved. Without a usable
|
|
167
|
+
* quote (a thin US listing after the close), the listing comes from the quote
|
|
168
|
+
* metadata kept when the quote was dropped, or else from asking the source
|
|
169
|
+
* for it, so the pane still knows a US stock from a listing abroad. What no
|
|
170
|
+
* source knows stays empty rather than guessed from the base currency.
|
|
171
|
+
*/
|
|
172
|
+
export async function createFallbackTicker(symbol: string, financials: TickerFinancials | null, context: MarketContext): Promise<TickerRecord> {
|
|
146
173
|
const instrument = parsePublicTickerKey(symbol);
|
|
147
174
|
const quote = financials?.quote;
|
|
175
|
+
const known = quote ? quoteMetadataFromQuote(quote) : financials?.quoteMetadata;
|
|
176
|
+
const listing = instrument.exchange || known?.listingExchangeName
|
|
177
|
+
? known
|
|
178
|
+
: await context.dataProvider.getQuoteMetadata?.(instrument.symbol, "").catch(() => null) ?? known;
|
|
148
179
|
return {
|
|
149
180
|
metadata: {
|
|
150
181
|
ticker: instrument.symbol,
|
|
151
|
-
exchange: instrument.exchange ??
|
|
152
|
-
currency:
|
|
182
|
+
exchange: instrument.exchange ?? listing?.listingExchangeName ?? "",
|
|
183
|
+
currency: listing?.currency ?? "",
|
|
153
184
|
name: quote?.name ?? instrument.symbol,
|
|
154
185
|
portfolios: [],
|
|
155
186
|
watchlists: [],
|
|
@@ -8,6 +8,7 @@ import { collectShotSymbols } from "./data";
|
|
|
8
8
|
import { renderDesktopShot, type PaneScreenshotResult } from "./screenshot";
|
|
9
9
|
import { deriveRenderedFreshness, formatFreshnessLine, type ReportFreshness } from "./freshness";
|
|
10
10
|
import { exportTextTable, reportFooterLines, type CliReportTables } from "../report-tables";
|
|
11
|
+
import { renderedReportFreshness, renderedReportNotices, renderedReportObservations } from "./report-notices";
|
|
11
12
|
|
|
12
13
|
const DOM_REPORT_WIDTH = 1280;
|
|
13
14
|
const DOM_REPORT_HEIGHT = 720;
|
|
@@ -18,12 +19,6 @@ function rowsContainEllipsis(rows: DesktopPaneShotRenderedRow[]): boolean {
|
|
|
18
19
|
return rows.some((row) => row.cells.some((cell) => /\u2026|\.\.\./.test(cell.text)));
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
function isDomReportTruncated(
|
|
22
|
-
render: Pick<PaneScreenshotResult["render"], "rows" | "truncated">,
|
|
23
|
-
): boolean {
|
|
24
|
-
return render.truncated || rowsContainEllipsis(render.rows);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
22
|
/** Column identity per cell; a repeated label within one row stays distinct. */
|
|
28
23
|
function domCellKeys(row: DesktopPaneShotRenderedRow): string[] {
|
|
29
24
|
const seen = new Map<string, number>();
|
|
@@ -109,6 +104,7 @@ function domReportTables(
|
|
|
109
104
|
title: string,
|
|
110
105
|
freshness: ReportFreshness,
|
|
111
106
|
incomplete: boolean | string,
|
|
107
|
+
notices: readonly string[],
|
|
112
108
|
): CliReportTables {
|
|
113
109
|
const tables = domTables(rows);
|
|
114
110
|
return {
|
|
@@ -117,7 +113,7 @@ function domReportTables(
|
|
|
117
113
|
table.columns.map((column) => column.header),
|
|
118
114
|
table.rows.map((cells) => table.columns.map((column) => cells.get(column.key))),
|
|
119
115
|
)),
|
|
120
|
-
footer: reportFooterLines({ freshness, incomplete }),
|
|
116
|
+
footer: reportFooterLines({ freshness, incomplete, notes: notices }),
|
|
121
117
|
};
|
|
122
118
|
}
|
|
123
119
|
|
|
@@ -138,8 +134,10 @@ function renderedFailureReason(
|
|
|
138
134
|
return null;
|
|
139
135
|
}
|
|
140
136
|
|
|
137
|
+
/** The rows a report reads: every row of a table that publishes them all, else what the viewport shows. */
|
|
141
138
|
function reportRows(result: PaneScreenshotResult): DesktopPaneShotRenderedRow[] {
|
|
142
|
-
|
|
139
|
+
const rows = result.render.reportRows ?? result.render.rows;
|
|
140
|
+
if (rows.length > 0) return rows;
|
|
143
141
|
if (
|
|
144
142
|
result.render.loadingStateDetected
|
|
145
143
|
|| result.render.errorStateDetected
|
|
@@ -161,15 +159,20 @@ export function buildDomPaneReportFromRender(
|
|
|
161
159
|
result: PaneScreenshotResult,
|
|
162
160
|
): PaneFunctionReport {
|
|
163
161
|
const rows = reportRows(result);
|
|
164
|
-
const hasStructuredRows = result.render.rows.length > 0;
|
|
165
|
-
|
|
166
|
-
const truncationReasons = [...result.render.truncationReasons];
|
|
162
|
+
const hasStructuredRows = (result.render.reportRows ?? result.render.rows).length > 0;
|
|
163
|
+
// A table that reported every row is whole, whatever the viewport cut from the capture.
|
|
164
|
+
const truncationReasons = [...(result.render.reportTruncationReasons ?? result.render.truncationReasons)];
|
|
167
165
|
if (rowsContainEllipsis(rows) && !truncationReasons.includes("one or more cells are visibly clipped")) {
|
|
168
166
|
truncationReasons.push("one or more cells are visibly clipped");
|
|
169
167
|
}
|
|
168
|
+
const truncated = truncationReasons.length > 0
|
|
169
|
+
|| (result.render.reportTruncationReasons ? false : result.render.truncated);
|
|
170
170
|
const failureReason = renderedFailureReason(result, rows);
|
|
171
171
|
const unavailableSymbols = failureReason && result.symbols.length > 0 ? result.symbols : [];
|
|
172
|
-
|
|
172
|
+
// What the view leaves out and how to see more: FXC's other currencies, OMON's strikes outside its window.
|
|
173
|
+
const rendered = renderedReportNotices(result.render.semanticUi ?? []);
|
|
174
|
+
const notices = [...(resolved.pane.reportNotices?.(resolved.instance.settings ?? {}) ?? []), ...rendered.notices];
|
|
175
|
+
const textLines = [resolved.label, "", ...notices, ...(notices.length > 0 ? [""] : [])];
|
|
173
176
|
if (hasStructuredRows) {
|
|
174
177
|
textLines.push(...renderDomTables(rows));
|
|
175
178
|
} else if (result.render.visibleText) {
|
|
@@ -178,9 +181,12 @@ export function buildDomPaneReportFromRender(
|
|
|
178
181
|
textLines.push(failureReason ?? "No rendered values were available.");
|
|
179
182
|
}
|
|
180
183
|
if (failureReason && rows.length > 0) textLines.push("", failureReason);
|
|
181
|
-
|
|
184
|
+
// A pane that knows its feed dates the report; the footer and cell times fill in the rest.
|
|
185
|
+
const declared = { ...resolved.pane.reportFreshness, ...renderedReportFreshness(result.render.semanticUi ?? []) };
|
|
186
|
+
const freshness = deriveRenderedFreshness(declared, {
|
|
182
187
|
footerText: result.render.footerText ?? "",
|
|
183
188
|
cellTimes: rows.flatMap((row) => row.cells.flatMap((cell) => cell.instant ?? [])),
|
|
189
|
+
observed: renderedReportObservations(result.render.semanticUi ?? []),
|
|
184
190
|
});
|
|
185
191
|
|
|
186
192
|
const complete = failureReason === null && !truncated;
|
|
@@ -201,6 +207,7 @@ export function buildDomPaneReportFromRender(
|
|
|
201
207
|
truncationReasons,
|
|
202
208
|
limitation: DOM_LIMITATION,
|
|
203
209
|
...(failureReason ? { reason: failureReason } : {}),
|
|
210
|
+
...(notices.length > 0 ? { metadata: { ...rendered.facts, notices } } : {}),
|
|
204
211
|
freshness,
|
|
205
212
|
},
|
|
206
213
|
text: textLines.join("\n").trimEnd(),
|
|
@@ -209,6 +216,7 @@ export function buildDomPaneReportFromRender(
|
|
|
209
216
|
resolved.label,
|
|
210
217
|
freshness,
|
|
211
218
|
!complete && (failureReason ?? (truncationReasons.length > 0 ? truncationReasons.join("; ") : true)),
|
|
219
|
+
notices,
|
|
212
220
|
),
|
|
213
221
|
};
|
|
214
222
|
}
|
|
@@ -8,9 +8,10 @@ import type {
|
|
|
8
8
|
HeadlessSeriesResult,
|
|
9
9
|
HeadlessSnapshotResult,
|
|
10
10
|
} from "../../types/plugin";
|
|
11
|
-
import {
|
|
11
|
+
import { parseReportTime, type ReportTime } from "../../utils/utc-time";
|
|
12
12
|
import { isRecord } from "../../utils/guards";
|
|
13
13
|
import { latestRegularSessionClose } from "../../market-data/market/freshness";
|
|
14
|
+
import { reportMarketSession, type ReportMarket, type SessionObservation } from "./market-session";
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* The four states of docs/usage.md#how-current-a-report-is, plus `unreported`
|
|
@@ -27,7 +28,10 @@ export interface ReportFreshness {
|
|
|
27
28
|
/** Oldest observation, when it is more than a day older than `asOf`. */
|
|
28
29
|
oldest?: string;
|
|
29
30
|
status: ReportFreshnessStatus;
|
|
31
|
+
/** How far the data is held back; the most of its rows when they differ... */
|
|
30
32
|
delayMinutes?: number;
|
|
33
|
+
/** ...and the least, only when it is less. */
|
|
34
|
+
minDelayMinutes?: number;
|
|
31
35
|
/** How old the stale data is: the newest observation, or the oldest stale row when only some are. */
|
|
32
36
|
ageMinutes?: number;
|
|
33
37
|
/** When only some rows are stale: how many, of the rows that say whether they are... */
|
|
@@ -36,6 +40,10 @@ export interface ReportFreshness {
|
|
|
36
40
|
/** ...and what the rest are. */
|
|
37
41
|
feed?: "live" | "delayed";
|
|
38
42
|
basis?: string;
|
|
43
|
+
/** The local date of the session close `asOf` is, when the newest observation is its venue's latest close. */
|
|
44
|
+
asOfClose?: string;
|
|
45
|
+
/** Where the venues of a quote report stand now, from their session calendars. */
|
|
46
|
+
market?: ReportMarket;
|
|
39
47
|
/** When this report was built. */
|
|
40
48
|
retrievedAt: string;
|
|
41
49
|
}
|
|
@@ -59,11 +67,14 @@ interface FeedSignals {
|
|
|
59
67
|
live: boolean;
|
|
60
68
|
delayed: boolean;
|
|
61
69
|
delayMinutes?: number;
|
|
70
|
+
minDelayMinutes?: number;
|
|
62
71
|
/** A record-wide stale flag (metadata), read only when no row says whether it is stale. */
|
|
63
72
|
stale: boolean;
|
|
64
73
|
/** Rows and series that carry a `stale` flag, and the times of those that are stale. */
|
|
65
74
|
flagged: number;
|
|
66
75
|
staleTimes: Array<ReportTime | null>;
|
|
76
|
+
/** Rows that name the venue whose sessions they follow (`sessionExchange`), with their time. */
|
|
77
|
+
sessions: SessionObservation[];
|
|
67
78
|
}
|
|
68
79
|
|
|
69
80
|
interface Observations {
|
|
@@ -74,7 +85,7 @@ interface Observations {
|
|
|
74
85
|
}
|
|
75
86
|
|
|
76
87
|
function emptySignals(): FeedSignals {
|
|
77
|
-
return { live: false, delayed: false, stale: false, flagged: 0, staleTimes: [] };
|
|
88
|
+
return { live: false, delayed: false, stale: false, flagged: 0, staleTimes: [], sessions: [] };
|
|
78
89
|
}
|
|
79
90
|
|
|
80
91
|
function readFeedSignals(record: Record<string, unknown>, signals: FeedSignals): void {
|
|
@@ -84,9 +95,21 @@ function readFeedSignals(record: Record<string, unknown>, signals: FeedSignals):
|
|
|
84
95
|
if (typeof delay === "number" && Number.isFinite(delay) && delay > 0) {
|
|
85
96
|
signals.delayed = true;
|
|
86
97
|
signals.delayMinutes = Math.max(signals.delayMinutes ?? 0, delay);
|
|
98
|
+
signals.minDelayMinutes = Math.min(signals.minDelayMinutes ?? delay, delay);
|
|
87
99
|
}
|
|
88
100
|
}
|
|
89
101
|
|
|
102
|
+
/** A quote row's venue and reported session, so the report can say whether its market is open. */
|
|
103
|
+
function readSession(record: Record<string, unknown>, time: ReportTime | null, signals: FeedSignals): void {
|
|
104
|
+
const exchange = record.sessionExchange;
|
|
105
|
+
if (typeof exchange !== "string" || !exchange.trim()) return;
|
|
106
|
+
signals.sessions.push({
|
|
107
|
+
exchange,
|
|
108
|
+
...(typeof record.marketState === "string" ? { marketState: record.marketState } : {}),
|
|
109
|
+
time,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
90
113
|
/** A row's or series' own stale flag; worst of across them, counted so a partly stale board says how much. */
|
|
91
114
|
function readUnitStale(record: Record<string, unknown>, time: ReportTime | null, signals: FeedSignals): void {
|
|
92
115
|
if (typeof record.stale !== "boolean") return;
|
|
@@ -147,6 +170,7 @@ function collect(
|
|
|
147
170
|
const time = observedTime(row, now, rowKeys)
|
|
148
171
|
?? (observedKey ? null : observedTime(row, now, UPDATE_KEYS));
|
|
149
172
|
if (!ignoreStaleFlags) readUnitStale(row, time, signals);
|
|
173
|
+
readSession(row, time, signals);
|
|
150
174
|
if (time) observations.units.push(time);
|
|
151
175
|
}
|
|
152
176
|
if (definition.shape === "series") {
|
|
@@ -247,6 +271,7 @@ function resolveFreshness({ declared, signals, observations, now }: FreshnessInp
|
|
|
247
271
|
|
|
248
272
|
let status: ReportFreshnessStatus;
|
|
249
273
|
let delayMinutes: number | undefined;
|
|
274
|
+
let minDelayMinutes: number | undefined;
|
|
250
275
|
let staleAt: ReportTime | null = null;
|
|
251
276
|
let partial: { staleCount: number; observationCount: number; feed?: "live" | "delayed" } | null = null;
|
|
252
277
|
if (declared.status === "not-a-feed") {
|
|
@@ -288,8 +313,16 @@ function resolveFreshness({ declared, signals, observations, now }: FreshnessInp
|
|
|
288
313
|
status = feed ?? "unreported";
|
|
289
314
|
}
|
|
290
315
|
// A partly stale board still says how far its fresh rows are held back.
|
|
291
|
-
if (status === "delayed" || partial?.feed === "delayed")
|
|
316
|
+
if (status === "delayed" || partial?.feed === "delayed") {
|
|
317
|
+
delayMinutes = declared.delayMinutes ?? signals.delayMinutes;
|
|
318
|
+
if (declared.delayMinutes == null && signals.minDelayMinutes != null && delayMinutes != null && signals.minDelayMinutes < delayMinutes) {
|
|
319
|
+
minDelayMinutes = signals.minDelayMinutes;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
292
322
|
}
|
|
323
|
+
const { market, closeDate } = reportMarketSession(signals.sessions, now);
|
|
324
|
+
// The close wording dates the newest observation, so only when that is what `asOf` is.
|
|
325
|
+
const asOfClose = closeDate && asOf && !declaredAsOf ? closeDate : null;
|
|
293
326
|
|
|
294
327
|
return {
|
|
295
328
|
source: declared.source?.trim() || DEFAULT_REPORT_SOURCE,
|
|
@@ -297,9 +330,12 @@ function resolveFreshness({ declared, signals, observations, now }: FreshnessInp
|
|
|
297
330
|
...(asOf && oldestTime && asOf.time - oldestTime.time > OLDEST_GAP_MS ? { oldest: isoTime(oldestTime) } : {}),
|
|
298
331
|
status,
|
|
299
332
|
...(delayMinutes != null && delayMinutes > 0 ? { delayMinutes } : {}),
|
|
333
|
+
...(minDelayMinutes != null && minDelayMinutes > 0 ? { minDelayMinutes } : {}),
|
|
300
334
|
...(status === "stale" && staleAt ? { ageMinutes: Math.max(0, Math.floor((now - staleAt.time) / 60_000)) } : {}),
|
|
301
335
|
...(partial ?? {}),
|
|
302
336
|
...(status === "not-a-feed" && declared.basis ? { basis: declared.basis } : {}),
|
|
337
|
+
...(asOfClose ? { asOfClose } : {}),
|
|
338
|
+
...(market ? { market } : {}),
|
|
303
339
|
retrievedAt: new Date(now).toISOString(),
|
|
304
340
|
};
|
|
305
341
|
}
|
|
@@ -322,14 +358,20 @@ export function deriveHeadlessFreshness(
|
|
|
322
358
|
/**
|
|
323
359
|
* A rendered view has no structured data: the source is the pane's
|
|
324
360
|
* declaration, the times are the cells that carry an instant, and the state
|
|
325
|
-
* is what the pane's own footer says, or not reported.
|
|
361
|
+
* is what the pane's own footer says, or not reported. A pane that dates its
|
|
362
|
+
* figures itself publishes `observed` records, read as a headless report's
|
|
363
|
+
* rows are: quote or rate times, feed, delay, stale flags and venue sessions.
|
|
326
364
|
*/
|
|
327
365
|
export function deriveRenderedFreshness(
|
|
328
366
|
declared: HeadlessPaneFreshness | undefined,
|
|
329
|
-
input: { footerText: string; cellTimes: unknown[] },
|
|
367
|
+
input: { footerText: string; cellTimes: unknown[]; observed?: readonly Record<string, unknown>[] },
|
|
330
368
|
now = Date.now(),
|
|
331
369
|
): ReportFreshness {
|
|
332
|
-
const
|
|
370
|
+
const merged = mergeDeclarations(declared);
|
|
371
|
+
const observed = input.observed?.length
|
|
372
|
+
? collect({ shape: "rows" }, { rows: input.observed as HeadlessPaneRow[] }, now, merged)
|
|
373
|
+
: null;
|
|
374
|
+
const signals = observed?.signals ?? emptySignals();
|
|
333
375
|
const footer = input.footerText;
|
|
334
376
|
const delayed = /\b(\d+)\s*(?:m|min|minutes?)\s+delayed\b/i.exec(footer);
|
|
335
377
|
if (delayed) {
|
|
@@ -344,14 +386,62 @@ export function deriveRenderedFreshness(
|
|
|
344
386
|
.map((value) => parseReportTime(value))
|
|
345
387
|
.filter((time): time is ReportTime => time != null && time.time <= now + FUTURE_TOLERANCE_MS);
|
|
346
388
|
return resolveFreshness({
|
|
347
|
-
declared:
|
|
389
|
+
declared: merged,
|
|
348
390
|
signals,
|
|
349
391
|
// Rendered rows are often a history (a news list, a filing feed), so only the newest is cited.
|
|
350
|
-
observations: { units: [], extra: units },
|
|
392
|
+
observations: { units: observed?.observations.units ?? [], extra: [...(observed?.observations.extra ?? []), ...units] },
|
|
351
393
|
now,
|
|
352
394
|
});
|
|
353
395
|
}
|
|
354
396
|
|
|
397
|
+
const WEEKDAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] as const;
|
|
398
|
+
const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] as const;
|
|
399
|
+
const MINUTES_PER_DAY = 24 * 60;
|
|
400
|
+
const DAY_MS = 24 * 60 * 60_000;
|
|
401
|
+
/** A reopen further ahead than this reads with its date, not just its weekday. */
|
|
402
|
+
const WEEKDAY_ONLY_MS = 6 * DAY_MS;
|
|
403
|
+
|
|
404
|
+
/** `Fri 9 Oct` for a `YYYY-MM-DD` date, with the year when it is not the year of `now`. */
|
|
405
|
+
function dayText(date: string, now: number): string {
|
|
406
|
+
const day = new Date(`${date.slice(0, 10)}T00:00:00Z`);
|
|
407
|
+
if (!Number.isFinite(day.getTime())) return date;
|
|
408
|
+
const year = day.getUTCFullYear() === new Date(now).getUTCFullYear() ? "" : ` ${day.getUTCFullYear()}`;
|
|
409
|
+
return `${WEEKDAYS[day.getUTCDay()]} ${day.getUTCDate()} ${MONTHS[day.getUTCMonth()]}${year}`;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/** `Fri 9 Oct 23:59 UTC`, or `Fri 9 Oct` for a date. */
|
|
413
|
+
function timeText(value: string, now: number): string {
|
|
414
|
+
const time = parseReportTime(value);
|
|
415
|
+
if (!time) return value;
|
|
416
|
+
const iso = new Date(time.time).toISOString();
|
|
417
|
+
return time.dateOnly ? dayText(iso, now) : `${dayText(iso, now)} ${iso.slice(11, 16)} UTC`;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* How long a delay is, in the unit it is stated in: `15 min`, `12 h`, `3 days`.
|
|
422
|
+
* A delay under two hours that is not a whole hour stays in minutes.
|
|
423
|
+
*/
|
|
424
|
+
export function delayLength(minutes: number): string {
|
|
425
|
+
const whole = Math.max(0, Math.round(minutes));
|
|
426
|
+
if (whole >= 2 * MINUTES_PER_DAY && whole % MINUTES_PER_DAY === 0) return `${whole / MINUTES_PER_DAY} days`;
|
|
427
|
+
if (whole >= 60 && whole % 60 === 0) return `${whole / 60} h`;
|
|
428
|
+
if (whole < 120) return `${whole} min`;
|
|
429
|
+
return `${Math.round(whole / 60)} h`;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/** `15 min delayed`, `15-20 min delayed` across venues, or `delayed` when the feed does not say. */
|
|
433
|
+
function delayedText(freshness: Pick<ReportFreshness, "delayMinutes" | "minDelayMinutes">): string {
|
|
434
|
+
const most = freshness.delayMinutes;
|
|
435
|
+
if (!most) return "delayed";
|
|
436
|
+
const least = freshness.minDelayMinutes;
|
|
437
|
+
if (least && least < most) {
|
|
438
|
+
const [low, lowUnit] = delayLength(least).split(" ");
|
|
439
|
+
const [high, highUnit] = delayLength(most).split(" ");
|
|
440
|
+
return lowUnit === highUnit ? `${low}-${high} ${highUnit} delayed` : `up to ${delayLength(most)} delayed`;
|
|
441
|
+
}
|
|
442
|
+
return `${delayLength(most)} delayed`;
|
|
443
|
+
}
|
|
444
|
+
|
|
355
445
|
function ageText(minutes: number): string {
|
|
356
446
|
if (minutes < 120) return `${minutes} min`;
|
|
357
447
|
const hours = Math.floor(minutes / 60);
|
|
@@ -359,27 +449,25 @@ function ageText(minutes: number): string {
|
|
|
359
449
|
return `${Math.floor(hours / 24)} days`;
|
|
360
450
|
}
|
|
361
451
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
function statusText(freshness: ReportFreshness): string {
|
|
452
|
+
/** `short` counts stale rows without their total (`2 stale`), for a narrow capture. */
|
|
453
|
+
function statusText(freshness: ReportFreshness, short = false): string {
|
|
367
454
|
switch (freshness.status) {
|
|
368
455
|
case "live":
|
|
369
|
-
return "
|
|
456
|
+
return "live";
|
|
370
457
|
case "delayed":
|
|
371
|
-
return delayedText(freshness
|
|
458
|
+
return delayedText(freshness);
|
|
372
459
|
case "stale": {
|
|
373
460
|
if (freshness.staleCount != null && freshness.observationCount != null) {
|
|
374
|
-
const rest = freshness.feed === "live" ? "
|
|
375
|
-
|
|
461
|
+
const rest = freshness.feed === "live" ? "live" : freshness.feed === "delayed" ? delayedText(freshness) : null;
|
|
462
|
+
const count = short ? `${freshness.staleCount}` : `${freshness.staleCount} of ${freshness.observationCount}`;
|
|
463
|
+
return `${rest ? `${rest}, ` : ""}${count} stale`;
|
|
376
464
|
}
|
|
377
|
-
return freshness.ageMinutes != null ? `
|
|
465
|
+
return freshness.ageMinutes != null ? `stale (${ageText(freshness.ageMinutes)} old)` : "stale";
|
|
378
466
|
}
|
|
379
467
|
case "not-a-feed":
|
|
380
|
-
return freshness.basis ? `
|
|
468
|
+
return freshness.basis ? `not a live feed (${freshness.basis})` : "not a live feed";
|
|
381
469
|
case "unreported":
|
|
382
|
-
return "
|
|
470
|
+
return "status not reported";
|
|
383
471
|
default: {
|
|
384
472
|
const _exhaustive: never = freshness.status;
|
|
385
473
|
return _exhaustive;
|
|
@@ -388,13 +476,104 @@ function statusText(freshness: ReportFreshness): string {
|
|
|
388
476
|
}
|
|
389
477
|
|
|
390
478
|
/**
|
|
391
|
-
* The
|
|
392
|
-
*
|
|
479
|
+
* The dated as-of: a session close, a UTC time, a date, or when the report was
|
|
480
|
+
* retrieved; with the oldest observation unless `short`.
|
|
481
|
+
*/
|
|
482
|
+
function asOfText(freshness: ReportFreshness, now: number, short = false): string {
|
|
483
|
+
if (!freshness.asOf) return `retrieved ${timeText(freshness.retrievedAt, now)}`;
|
|
484
|
+
const asOf = freshness.asOfClose ? `${dayText(freshness.asOfClose, now)} close` : timeText(freshness.asOf, now);
|
|
485
|
+
return freshness.oldest && !short ? `${asOf} (oldest ${dayText(freshness.oldest, now)})` : asOf;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* When the first venue opens again: its UTC time when that is later the same
|
|
490
|
+
* UTC day, else its weekday at the venue (`Mon`), with the date a week out.
|
|
491
|
+
*/
|
|
492
|
+
function reopenText(market: ReportMarket, now: number): string | null {
|
|
493
|
+
if (!market.reopensAt || !market.reopensOn) return null;
|
|
494
|
+
const at = Date.parse(market.reopensAt);
|
|
495
|
+
if (!Number.isFinite(at)) return null;
|
|
496
|
+
const iso = new Date(at).toISOString();
|
|
497
|
+
if (iso.slice(0, 10) === new Date(now).toISOString().slice(0, 10)) return `${iso.slice(11, 16)} UTC`;
|
|
498
|
+
return at - now > WEEKDAY_ONLY_MS ? dayText(market.reopensOn, now) : WEEKDAYS[new Date(`${market.reopensOn}T00:00:00Z`).getUTCDay()]!;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/** `markets closed until Mon`; `short` is the compact form a narrow capture falls back to (`reopens Mon`). */
|
|
502
|
+
function marketText(market: ReportMarket, now: number, short = false): string {
|
|
503
|
+
const reopen = reopenText(market, now);
|
|
504
|
+
switch (market.state) {
|
|
505
|
+
case "open":
|
|
506
|
+
return short ? "open" : "markets open";
|
|
507
|
+
case "after-hours":
|
|
508
|
+
return "after hours";
|
|
509
|
+
case "pre-market":
|
|
510
|
+
return reopen ? `pre-market, opens ${reopen}` : "pre-market";
|
|
511
|
+
case "closed":
|
|
512
|
+
if (short) return reopen ? `reopens ${reopen}` : "closed";
|
|
513
|
+
return reopen ? `markets closed until ${reopen}` : "markets closed";
|
|
514
|
+
default: {
|
|
515
|
+
const _exhaustive: never = market.state;
|
|
516
|
+
return _exhaustive;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
interface StatusLineDetail {
|
|
522
|
+
/** Leave out the oldest observation. */
|
|
523
|
+
asOf?: "short";
|
|
524
|
+
/** Count stale rows without their total. */
|
|
525
|
+
status?: "short";
|
|
526
|
+
/** `reopens Mon` for `markets closed until Mon`; `none` leaves the market out. */
|
|
527
|
+
market?: "short" | "none";
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* The parts of a report's status line: the dated as-of, how current the data
|
|
532
|
+
* is (`15 min delayed`, `live`), and where its markets stand, when known.
|
|
533
|
+
*/
|
|
534
|
+
function statusLineParts(freshness: ReportFreshness, detail: StatusLineDetail = {}): string[] {
|
|
535
|
+
const now = Date.parse(freshness.retrievedAt);
|
|
536
|
+
return [
|
|
537
|
+
asOfText(freshness, now, detail.asOf === "short"),
|
|
538
|
+
statusText(freshness, detail.status === "short"),
|
|
539
|
+
...(freshness.market && detail.market !== "none" ? [marketText(freshness.market, now, detail.market === "short")] : []),
|
|
540
|
+
];
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function capitalize(text: string): string {
|
|
544
|
+
return text ? `${text[0]!.toUpperCase()}${text.slice(1)}` : text;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* The plain status line of a report or capture, without its source:
|
|
549
|
+
* `Fri 9 Oct close · 15 min delayed · markets closed until Mon`.
|
|
550
|
+
*/
|
|
551
|
+
export function formatStatusLine(freshness: ReportFreshness): string {
|
|
552
|
+
return capitalize(statusLineParts(freshness).join(" · "));
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* The status line from longest to shortest, for a capture to take the first
|
|
557
|
+
* that fits its width. The date and the delay stay to the last; the market
|
|
558
|
+
* part shortens, then the oldest observation and the stale total go, then
|
|
559
|
+
* the market part.
|
|
560
|
+
*/
|
|
561
|
+
export function statusLineVariants(freshness: ReportFreshness): string[] {
|
|
562
|
+
const details: StatusLineDetail[] = [
|
|
563
|
+
{},
|
|
564
|
+
{ market: "short" },
|
|
565
|
+
{ asOf: "short", market: "short" },
|
|
566
|
+
{ asOf: "short", status: "short", market: "short" },
|
|
567
|
+
{ asOf: "short", status: "short", market: "none" },
|
|
568
|
+
];
|
|
569
|
+
return [...new Set(details.map((detail) => capitalize(statusLineParts(freshness, detail).join(" · "))))];
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* The one line every text report ends with, its source then its status line:
|
|
574
|
+
* `Source: Gloom Cloud · Fri 9 Oct close · 15 min delayed · markets closed until Mon`.
|
|
393
575
|
* Without a dated observation it says when the report was retrieved instead.
|
|
394
576
|
*/
|
|
395
577
|
export function formatFreshnessLine(freshness: ReportFreshness): string {
|
|
396
|
-
|
|
397
|
-
? `As of ${formatUtcTime(freshness.asOf)}${freshness.oldest ? ` (oldest ${freshness.oldest.slice(0, 10)})` : ""}`
|
|
398
|
-
: `Retrieved ${formatUtcTime(freshness.retrievedAt)}`;
|
|
399
|
-
return [`Source: ${freshness.source}`, time, statusText(freshness)].join(" | ");
|
|
578
|
+
return [`Source: ${freshness.source}`, ...statusLineParts(freshness)].join(" · ");
|
|
400
579
|
}
|