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
|
@@ -5,6 +5,8 @@ import {
|
|
|
5
5
|
type CloudEarningsTranscriptPayload,
|
|
6
6
|
} from "../../../api-client";
|
|
7
7
|
import { ApiRequestError } from "../../../api-client/errors";
|
|
8
|
+
import { listingAbroad, type IssuerListingParams, type ListingAbroad } from "../../../api-client/paths";
|
|
9
|
+
import { areDifferentCompanies } from "../../../sources/sec-registrant";
|
|
8
10
|
import type { HeadlessPaneApiClient, PluginPersistence } from "../../../types/plugin";
|
|
9
11
|
|
|
10
12
|
type EarningsCallsApiClient = Pick<
|
|
@@ -16,7 +18,8 @@ const LIST_KIND = "calls";
|
|
|
16
18
|
const TRANSCRIPT_KIND = "transcript";
|
|
17
19
|
const CACHE_SOURCE = "earnings-calls";
|
|
18
20
|
const CACHE_SCHEMA_VERSION = 1;
|
|
19
|
-
|
|
21
|
+
// Version 3: a listing abroad is keyed by its venue and company, not its symbol alone.
|
|
22
|
+
const LIST_CACHE_SCHEMA_VERSION = 3;
|
|
20
23
|
|
|
21
24
|
/** The call list changes as new transcripts publish. */
|
|
22
25
|
const LIST_CACHE_POLICY = {
|
|
@@ -94,19 +97,36 @@ function listOffset(value: number | undefined): number {
|
|
|
94
97
|
return value !== undefined && Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
|
|
95
98
|
}
|
|
96
99
|
|
|
97
|
-
|
|
98
|
-
|
|
100
|
+
/** A listing abroad is its venue and company, so it never shares an entry with a US company of the same symbol. */
|
|
101
|
+
function listKey(ticker: string | null, abroad: ListingAbroad | null, limit: number, offset: number): string {
|
|
102
|
+
const identity = abroad ? [abroad.symbol, abroad.exchange, abroad.name ?? null] : ticker;
|
|
103
|
+
return offset > 0 ? JSON.stringify([identity, limit, offset]) : JSON.stringify([identity, limit]);
|
|
99
104
|
}
|
|
100
105
|
|
|
106
|
+
/**
|
|
107
|
+
* A server that predates venue-aware lookups ignores the venue and lists the
|
|
108
|
+
* calls of the US company with the same symbol: AI in Paris would show C3.ai.
|
|
109
|
+
* Those calls are another company's, so they are dropped, and without the
|
|
110
|
+
* listing's company they cannot be told apart.
|
|
111
|
+
*/
|
|
112
|
+
function listingCalls(calls: CloudEarningsCallPayload[], abroad: ListingAbroad | null): CloudEarningsCallPayload[] {
|
|
113
|
+
if (!abroad) return calls;
|
|
114
|
+
const name = abroad.name;
|
|
115
|
+
return name ? calls.filter((call) => !areDifferentCompanies(name, call.companyName)) : [];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
type EarningsCallsLoadOptions = IssuerListingParams & { force?: boolean; limit?: number; offset?: number };
|
|
119
|
+
|
|
101
120
|
export async function loadEarningsCallsWithClient(
|
|
102
121
|
client: EarningsCallsApiClient,
|
|
103
122
|
ticker: string | null,
|
|
104
|
-
options?:
|
|
123
|
+
options?: EarningsCallsLoadOptions,
|
|
105
124
|
): Promise<EarningsCallsResult> {
|
|
106
125
|
const normalizedTicker = ticker?.trim().toUpperCase() || null;
|
|
126
|
+
const abroad = normalizedTicker ? listingAbroad(normalizedTicker, options?.exchange, options?.name) : null;
|
|
107
127
|
const limit = listLimit(options?.limit);
|
|
108
128
|
const offset = listOffset(options?.offset);
|
|
109
|
-
const key = listKey(normalizedTicker, limit, offset);
|
|
129
|
+
const key = listKey(normalizedTicker, abroad, limit, offset);
|
|
110
130
|
const force = options?.force ?? false;
|
|
111
131
|
// Only the first page is worth keeping. A later page is an artefact of how
|
|
112
132
|
// far somebody scrolled, over a shelf that reorders as transcripts publish,
|
|
@@ -131,11 +151,12 @@ export async function loadEarningsCallsWithClient(
|
|
|
131
151
|
const request = client
|
|
132
152
|
.getCloudEarningsCalls({
|
|
133
153
|
ticker: normalizedTicker ?? undefined,
|
|
154
|
+
...(abroad ? { exchange: abroad.exchange, ...(abroad.name ? { name: abroad.name } : {}) } : {}),
|
|
134
155
|
limit,
|
|
135
156
|
...(offset > 0 ? { offset } : {}),
|
|
136
157
|
})
|
|
137
158
|
.then((payload) => {
|
|
138
|
-
const calls = payload.calls ?? [];
|
|
159
|
+
const calls = listingCalls(payload.calls ?? [], abroad);
|
|
139
160
|
const value = { calls, pending: payload.pending === true, unknownTicker: payload.unknownTicker === true };
|
|
140
161
|
// A list with calls still being produced changes by the minute, so it
|
|
141
162
|
// is not worth keeping; a list of finished transcripts is.
|
|
@@ -190,7 +211,7 @@ export async function loadEarningsCallsWithClient(
|
|
|
190
211
|
|
|
191
212
|
export function loadEarningsCalls(
|
|
192
213
|
ticker: string | null,
|
|
193
|
-
options?:
|
|
214
|
+
options?: EarningsCallsLoadOptions,
|
|
194
215
|
): Promise<EarningsCallsResult> {
|
|
195
216
|
return loadEarningsCallsWithClient(apiClient, ticker, options);
|
|
196
217
|
}
|
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
HeadlessPaneLoadArgs,
|
|
9
9
|
HeadlessRowsResult,
|
|
10
10
|
} from "../../../types/plugin";
|
|
11
|
+
import { resolveHeadlessIssuerListing } from "../shared/headless-market-data";
|
|
11
12
|
import {
|
|
12
13
|
callStatusLabel,
|
|
13
14
|
isPendingTranscript,
|
|
@@ -57,10 +58,10 @@ export interface EarningsCallsHeadlessDependencies {
|
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
const defaultDependencies: EarningsCallsHeadlessDependencies = {
|
|
60
|
-
loadCalls: (ticker, limit, context) => loadEarningsCallsWithClient(
|
|
61
|
+
loadCalls: async (ticker, limit, context) => loadEarningsCallsWithClient(
|
|
61
62
|
context.apiClient,
|
|
62
63
|
ticker,
|
|
63
|
-
{ limit },
|
|
64
|
+
{ limit, ...(ticker ? await resolveHeadlessIssuerListing(context, ticker) : {}) },
|
|
64
65
|
),
|
|
65
66
|
loadTranscript: (callId, context) => loadTranscriptWithClient(context.apiClient, callId),
|
|
66
67
|
};
|
|
@@ -33,6 +33,7 @@ import { nextHeaderSort, type SortDirection } from "../../../utils/sort-values";
|
|
|
33
33
|
import { usePluginPaneState } from "../../runtime";
|
|
34
34
|
import { ProWall, SignInWall } from "../cloud/auth-actions";
|
|
35
35
|
import { usePlanAccess } from "../../../api-client/plan-access";
|
|
36
|
+
import { listingAbroad } from "../../../api-client/paths";
|
|
36
37
|
import { useBoundTicker as useSymbolBinding } from "../shared/ticker-request";
|
|
37
38
|
import {
|
|
38
39
|
callStatusLabel,
|
|
@@ -177,7 +178,7 @@ interface EarningsCallsViewProps {
|
|
|
177
178
|
}
|
|
178
179
|
|
|
179
180
|
export function EarningsCallsPane({ focused, width, height }: EarningsCallsViewProps) {
|
|
180
|
-
const { symbol, exchange } = useSymbolBinding();
|
|
181
|
+
const { symbol, exchange, ticker: boundTicker } = useSymbolBinding();
|
|
181
182
|
const access = usePlanAccess();
|
|
182
183
|
|
|
183
184
|
const [calls, setCalls] = useState<CloudEarningsCallPayload[]>([]);
|
|
@@ -227,6 +228,10 @@ export function EarningsCallsPane({ focused, width, height }: EarningsCallsViewP
|
|
|
227
228
|
const [listPending, setListPending] = useState(false);
|
|
228
229
|
|
|
229
230
|
const ticker = symbol ? symbol.toUpperCase() : null;
|
|
231
|
+
// A listing abroad asks for its own calls by venue and company, never a US namesake's.
|
|
232
|
+
const abroad = ticker ? listingAbroad(ticker, exchange, boundTicker?.metadata.name) : null;
|
|
233
|
+
const listingVenue = abroad?.exchange;
|
|
234
|
+
const listingName = abroad?.name;
|
|
230
235
|
|
|
231
236
|
const fetchCalls = useCallback(
|
|
232
237
|
(force: boolean) => {
|
|
@@ -234,7 +239,7 @@ export function EarningsCallsPane({ focused, width, height }: EarningsCallsViewP
|
|
|
234
239
|
const request = ++listRequestVersion.current;
|
|
235
240
|
setListStatus((current) => (current === "loaded" ? current : "loading"));
|
|
236
241
|
setLoadingMore(false);
|
|
237
|
-
loadEarningsCalls(ticker, { force, limit: CALL_PAGE_SIZE })
|
|
242
|
+
loadEarningsCalls(ticker, { force, limit: CALL_PAGE_SIZE, exchange: listingVenue, name: listingName })
|
|
238
243
|
.then((result) => {
|
|
239
244
|
if (request !== listRequestVersion.current) return;
|
|
240
245
|
setCalls(result.calls);
|
|
@@ -261,7 +266,7 @@ export function EarningsCallsPane({ focused, width, height }: EarningsCallsViewP
|
|
|
261
266
|
setListStatus("error");
|
|
262
267
|
});
|
|
263
268
|
},
|
|
264
|
-
[ticker, access.emailVerified, access.hasProAccess],
|
|
269
|
+
[ticker, listingVenue, listingName, access.emailVerified, access.hasProAccess],
|
|
265
270
|
);
|
|
266
271
|
|
|
267
272
|
// Scrolling to the end of the shelf asks the server for the next page. A
|
|
@@ -271,7 +276,7 @@ export function EarningsCallsPane({ focused, width, height }: EarningsCallsViewP
|
|
|
271
276
|
if (!access.emailVerified || !access.hasProAccess) return;
|
|
272
277
|
const request = listRequestVersion.current;
|
|
273
278
|
setLoadingMore(true);
|
|
274
|
-
loadEarningsCalls(ticker, { limit: CALL_PAGE_SIZE, offset: nextOffset })
|
|
279
|
+
loadEarningsCalls(ticker, { limit: CALL_PAGE_SIZE, offset: nextOffset, exchange: listingVenue, name: listingName })
|
|
275
280
|
.then((result) => {
|
|
276
281
|
if (request !== listRequestVersion.current) return;
|
|
277
282
|
setCalls((current) => appendNewCalls(current, result.calls));
|
|
@@ -289,7 +294,7 @@ export function EarningsCallsPane({ focused, width, height }: EarningsCallsViewP
|
|
|
289
294
|
status: statusOf(error),
|
|
290
295
|
});
|
|
291
296
|
});
|
|
292
|
-
}, [ticker, nextOffset, access.emailVerified, access.hasProAccess]);
|
|
297
|
+
}, [ticker, listingVenue, listingName, nextOffset, access.emailVerified, access.hasProAccess]);
|
|
293
298
|
|
|
294
299
|
const loadMoreFromScroll = useTableLoadMore(
|
|
295
300
|
tableScrollRef,
|
|
@@ -62,41 +62,66 @@ export function impactIndicator(impact: EconImpact): { text: string; color: stri
|
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
/** The UTC day of an instant, so a release sits under the same day on every machine. */
|
|
66
|
+
export function dateKey(d: Date): string {
|
|
67
|
+
return d.toISOString().slice(0, 10);
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
/**
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
71
|
+
* The release's UTC clock time. Rows group by UTC day and the column header
|
|
72
|
+
* names the zone, as `gloomberb econ` prints it; local time would move a 23:00
|
|
73
|
+
* UTC release under another day and read differently on every machine.
|
|
73
74
|
*/
|
|
74
75
|
export function timeLabel(d: Date): string {
|
|
75
|
-
return
|
|
76
|
+
return d.toISOString().slice(11, 16);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** The zone the calendar's times and days are in, for the column header. */
|
|
80
|
+
export const CALENDAR_TIME_ZONE_LABEL = "UTC";
|
|
81
|
+
|
|
82
|
+
/** "Fri Oct 9", the UTC day the calendar groups an instant under. */
|
|
83
|
+
export function shortDayLabel(d: Date): string {
|
|
84
|
+
return `${DAY_NAMES[d.getUTCDay()]!} ${MONTH_NAMES[d.getUTCMonth()]!} ${d.getUTCDate()}`;
|
|
76
85
|
}
|
|
77
86
|
|
|
78
87
|
export function dayLabel(d: Date, today: Date): string {
|
|
79
88
|
const dk = dateKey(d);
|
|
80
89
|
const todayKey = dateKey(today);
|
|
81
|
-
const
|
|
82
|
-
yesterday
|
|
83
|
-
const tomorrow = new Date(today);
|
|
84
|
-
tomorrow.setDate(tomorrow.getDate() + 1);
|
|
90
|
+
const dayMs = 24 * 60 * 60 * 1000;
|
|
91
|
+
const yesterday = dateKey(new Date(today.getTime() - dayMs));
|
|
92
|
+
const tomorrow = dateKey(new Date(today.getTime() + dayMs));
|
|
85
93
|
|
|
86
|
-
const
|
|
87
|
-
const monthName = MONTH_NAMES[d.getMonth()]!;
|
|
88
|
-
const dateNum = d.getDate();
|
|
89
|
-
const suffix = `${dayName} ${monthName} ${dateNum}`;
|
|
94
|
+
const suffix = shortDayLabel(d);
|
|
90
95
|
|
|
91
96
|
if (dk === todayKey) return `TODAY · ${suffix}`;
|
|
92
|
-
if (dk ===
|
|
93
|
-
if (dk ===
|
|
97
|
+
if (dk === tomorrow) return `TOMORROW · ${suffix}`;
|
|
98
|
+
if (dk === yesterday) return `YESTERDAY · ${suffix}`;
|
|
94
99
|
return suffix;
|
|
95
100
|
}
|
|
96
101
|
|
|
102
|
+
/**
|
|
103
|
+
* The calendar runs two weeks ahead. One that lists nothing a week past the
|
|
104
|
+
* start of the window (today, or a later first day asked for) stops short,
|
|
105
|
+
* as a feed that ends with its own week does, and says where it stops rather
|
|
106
|
+
* than reading as a quiet week.
|
|
107
|
+
*/
|
|
108
|
+
const MIN_DAYS_LISTED_AHEAD = 7;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* When the calendar stops short of a week past `windowStart`, the time of its
|
|
112
|
+
* last event; null when it reaches that far or lists nothing.
|
|
113
|
+
*/
|
|
114
|
+
export function shortCalendarEnd(events: readonly EconEvent[], windowStart: number): Date | null {
|
|
115
|
+
let last: Date | null = null;
|
|
116
|
+
for (const event of events) {
|
|
117
|
+
if (!last || event.date.getTime() > last.getTime()) last = event.date;
|
|
118
|
+
}
|
|
119
|
+
return last && last.getTime() < windowStart + MIN_DAYS_LISTED_AHEAD * 86_400_000 ? last : null;
|
|
120
|
+
}
|
|
121
|
+
|
|
97
122
|
/**
|
|
98
123
|
* Events (already in time order; `eventIdx` indexes them) under a header per
|
|
99
|
-
*
|
|
124
|
+
* UTC day, with NOW before the first event still to come. When that event is
|
|
100
125
|
* on a later day, NOW sits above that day's header, since the present is still
|
|
101
126
|
* on the earlier day.
|
|
102
127
|
*/
|
|
@@ -29,7 +29,7 @@ import { Box, Text } from "../../../ui";
|
|
|
29
29
|
import { displayWidth } from "../../../utils/format";
|
|
30
30
|
import { useOpenTickerChoice } from "../shared/ticker-choice";
|
|
31
31
|
import { resolveFredMapping, projectFredHistory, fredHistoryUnits } from "./fred-series-map";
|
|
32
|
-
import { actualColor, timeLabel } from "./calendar-model";
|
|
32
|
+
import { actualColor, CALENDAR_TIME_ZONE_LABEL, timeLabel } from "./calendar-model";
|
|
33
33
|
import type { EconEvent } from "./types";
|
|
34
34
|
|
|
35
35
|
interface EconDetailViewProps {
|
|
@@ -195,7 +195,7 @@ export function EconDetailView({ event, width, height, focused }: EconDetailView
|
|
|
195
195
|
...(event.actual ? [{ id: "actual", label: "Actual", value: event.actual, color: actualColor(event.actual, event.forecast) }] : []),
|
|
196
196
|
...(event.forecast ? [{ id: "forecast", label: "Forecast", value: event.forecast }] : []),
|
|
197
197
|
...(event.prior ? [{ id: "prior", label: "Prior", value: event.prior }] : []),
|
|
198
|
-
{ id: "time", label: "Release", value: timeLabel(event.date) },
|
|
198
|
+
{ id: "time", label: "Release", value: `${timeLabel(event.date)} ${CALENDAR_TIME_ZONE_LABEL}` },
|
|
199
199
|
];
|
|
200
200
|
|
|
201
201
|
const projected = useMemo(
|
|
@@ -24,6 +24,8 @@ import {
|
|
|
24
24
|
attachEconCalendarPersistence,
|
|
25
25
|
actualColor,
|
|
26
26
|
calendarDisplayRows,
|
|
27
|
+
CALENDAR_TIME_ZONE_LABEL,
|
|
28
|
+
dateKey,
|
|
27
29
|
dayLabel,
|
|
28
30
|
formatCountdown,
|
|
29
31
|
formatStaleness,
|
|
@@ -33,6 +35,8 @@ import {
|
|
|
33
35
|
matchesCountry,
|
|
34
36
|
matchesImpact,
|
|
35
37
|
resetEconCalendarPersistence,
|
|
38
|
+
shortCalendarEnd,
|
|
39
|
+
shortDayLabel,
|
|
36
40
|
timeLabel,
|
|
37
41
|
type CountryFilter,
|
|
38
42
|
type DisplayRow,
|
|
@@ -42,6 +46,8 @@ import {
|
|
|
42
46
|
} from "./calendar-model";
|
|
43
47
|
import { usePaneStatusFooter } from "../../../components/layout/pane/status-footer";
|
|
44
48
|
import { handleRefreshKey } from "../../../components/data-table/table-pane";
|
|
49
|
+
import { tableColumnWidth } from "../../../components/ui/table-layout";
|
|
50
|
+
import { useRemoteUiNode } from "../../../remote/semantic-tree";
|
|
45
51
|
|
|
46
52
|
const IMPACT_LABELS: Record<ImpactFilter, string> = {
|
|
47
53
|
all: "All",
|
|
@@ -100,17 +106,28 @@ function EconCalendarPane({ focused, width, height }: PaneProps) {
|
|
|
100
106
|
// Day headers and the NOW marker between the past and the upcoming events.
|
|
101
107
|
const today = new Date(now);
|
|
102
108
|
const rows = calendarDisplayRows(filtered, now);
|
|
109
|
+
// Rows group by UTC day, so today starts at UTC midnight.
|
|
110
|
+
const todayStart = Math.floor(now / 86_400_000) * 86_400_000;
|
|
111
|
+
// A calendar that stops short of a week ahead says where it stops.
|
|
112
|
+
const listedEnd = shortCalendarEnd(events, todayStart);
|
|
113
|
+
const listedAfter = listedEnd ? `No events listed after ${shortDayLabel(listedEnd)}` : null;
|
|
103
114
|
|
|
104
115
|
// Map from eventIdx to flat row index (for scroll tracking)
|
|
105
116
|
const eventIdxToRowIdx = new Map<number, number>();
|
|
106
117
|
let nowRowIdx = -1;
|
|
107
118
|
let nextUpcomingEventIdx = -1;
|
|
108
119
|
let nextUpcomingTime = Number.POSITIVE_INFINITY;
|
|
120
|
+
// The first row of today or later, with the day header and NOW band above it.
|
|
121
|
+
let todayRowIdx = -1;
|
|
109
122
|
for (let r = 0; r < rows.length; r++) {
|
|
110
123
|
const row = rows[r]!;
|
|
111
124
|
if (row.kind === "event") {
|
|
112
125
|
eventIdxToRowIdx.set(row.eventIdx, r);
|
|
113
126
|
const eventTime = row.event.date.getTime();
|
|
127
|
+
if (todayRowIdx < 0 && eventTime >= todayStart) {
|
|
128
|
+
todayRowIdx = r;
|
|
129
|
+
while (todayRowIdx > 0 && rows[todayRowIdx - 1]!.kind !== "event") todayRowIdx -= 1;
|
|
130
|
+
}
|
|
114
131
|
if (eventTime > now && eventTime < nextUpcomingTime) {
|
|
115
132
|
nextUpcomingEventIdx = row.eventIdx;
|
|
116
133
|
nextUpcomingTime = eventTime;
|
|
@@ -125,51 +142,63 @@ function EconCalendarPane({ focused, width, height }: PaneProps) {
|
|
|
125
142
|
const selectedIdx = rememberedIdx >= 0 ? rememberedIdx
|
|
126
143
|
: nextUpcomingEventIdx >= 0 ? nextUpcomingEventIdx : Math.max(0, filtered.length - 1);
|
|
127
144
|
|
|
128
|
-
//
|
|
129
|
-
|
|
145
|
+
// The present at the top of the table: NOW a few rows down for context, but
|
|
146
|
+
// never above today, so a Saturday opens on NOW and Monday rather than on
|
|
147
|
+
// Friday's last releases. Asked for on open, on a filter change and on a
|
|
148
|
+
// forced reload; it holds until the user scrolls or moves the selection.
|
|
149
|
+
const presentRowIdx = nowRowIdx >= 0 ? Math.max(0, todayRowIdx, nowRowIdx - 3) : -1;
|
|
150
|
+
const [presentRequest, setPresentRequest] = useState(1);
|
|
151
|
+
const [presentAnchor, setPresentAnchor] = useState<{ index: number; version: number } | null>(null);
|
|
152
|
+
const servedPresentRequest = useRef(0);
|
|
130
153
|
useEffect(() => {
|
|
131
|
-
if (
|
|
132
|
-
|
|
154
|
+
if (servedPresentRequest.current === presentRequest || filtered.length === 0) return;
|
|
155
|
+
// On open the selection moves to the next release as well.
|
|
156
|
+
if (servedPresentRequest.current === 0 && nextUpcomingEventIdx >= 0) {
|
|
133
157
|
setSelectedKey(filtered[nextUpcomingEventIdx]?.id ?? null);
|
|
134
158
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
141
|
-
initialScrollDone.current = true;
|
|
142
|
-
}, [filtered.length]);
|
|
159
|
+
servedPresentRequest.current = presentRequest;
|
|
160
|
+
setPresentAnchor(presentRowIdx >= 0 ? { index: presentRowIdx, version: presentRequest } : null);
|
|
161
|
+
}, [filtered.length, presentRequest]);
|
|
162
|
+
const returnToPresent = useCallback(() => setPresentRequest((request) => request + 1), []);
|
|
163
|
+
const leavePresent = useCallback(() => setPresentAnchor(null), []);
|
|
143
164
|
|
|
144
165
|
const selectImpactFilter = useCallback((value: ImpactFilter) => {
|
|
145
166
|
setImpactFilter(value);
|
|
146
167
|
setSelectedKey(null);
|
|
147
|
-
|
|
168
|
+
returnToPresent();
|
|
169
|
+
}, [returnToPresent, setImpactFilter, setSelectedKey]);
|
|
148
170
|
const selectCountryFilter = useCallback((value: CountryFilter) => {
|
|
149
171
|
setCountryFilter(value);
|
|
150
172
|
setSelectedKey(null);
|
|
151
|
-
|
|
173
|
+
returnToPresent();
|
|
174
|
+
}, [returnToPresent, setCountryFilter, setSelectedKey]);
|
|
152
175
|
const cycleImpactFilter = useCallback((step: 1 | -1) => {
|
|
153
176
|
setImpactFilter((prev) => FILTER_CYCLE[(FILTER_CYCLE.indexOf(prev) + step + FILTER_CYCLE.length) % FILTER_CYCLE.length]!);
|
|
154
177
|
setSelectedKey(null);
|
|
155
|
-
|
|
178
|
+
returnToPresent();
|
|
179
|
+
}, [returnToPresent, setImpactFilter, setSelectedKey]);
|
|
156
180
|
const cycleCountryFilter = useCallback((step: 1 | -1) => {
|
|
157
181
|
setCountryFilter((prev) => COUNTRY_CYCLE[(COUNTRY_CYCLE.indexOf(prev) + step + COUNTRY_CYCLE.length) % COUNTRY_CYCLE.length]!);
|
|
158
182
|
setSelectedKey(null);
|
|
159
|
-
|
|
183
|
+
returnToPresent();
|
|
184
|
+
}, [returnToPresent, setCountryFilter, setSelectedKey]);
|
|
160
185
|
|
|
161
186
|
// A forced reload goes back to the present once it answers.
|
|
162
187
|
const forcedResult = calendar.data?.forced ? calendar.data : null;
|
|
163
188
|
useEffect(() => {
|
|
164
|
-
if (forcedResult)
|
|
165
|
-
|
|
189
|
+
if (!forcedResult) return;
|
|
190
|
+
setSelectedKey(null);
|
|
191
|
+
returnToPresent();
|
|
192
|
+
}, [forcedResult, returnToPresent, setSelectedKey]);
|
|
166
193
|
|
|
167
194
|
const handleRootKeyDown = useCallback((event: DataTableKeyEvent) => (
|
|
168
195
|
handleRefreshKey(event, () => void calendar.reload(), { stopPropagation: true })
|
|
169
196
|
), [calendar.reload]);
|
|
170
197
|
|
|
171
198
|
const columns = useMemo<EconCalendarColumn[]>(() => {
|
|
172
|
-
|
|
199
|
+
// The header names the zone, so the column is as wide as the header needs.
|
|
200
|
+
const timeLabelText = `TIME (${CALENDAR_TIME_ZONE_LABEL})`;
|
|
201
|
+
const timeWidth = tableColumnWidth({ width: 6, label: timeLabelText });
|
|
173
202
|
const impactWidth = 4;
|
|
174
203
|
const flagWidth = 3;
|
|
175
204
|
const actualWidth = 9;
|
|
@@ -183,7 +212,7 @@ function EconCalendarPane({ focused, width, height }: PaneProps) {
|
|
|
183
212
|
const eventWidth = Math.max(minEventWidth, width - 3 - columnCount - fixedWidth);
|
|
184
213
|
|
|
185
214
|
return [
|
|
186
|
-
{ id: "time", label:
|
|
215
|
+
{ id: "time", label: timeLabelText, width: timeWidth, align: "left" },
|
|
187
216
|
{ id: "impact", label: "IMP", width: impactWidth, align: "left" },
|
|
188
217
|
{ id: "country", label: "CTY", width: flagWidth, align: "left" },
|
|
189
218
|
{ id: "event", label: "EVENT", width: eventWidth, align: "left" },
|
|
@@ -209,11 +238,21 @@ function EconCalendarPane({ focused, width, height }: PaneProps) {
|
|
|
209
238
|
const nextText = nextEvent && nextCountdown ? `next ${nextEvent.event} ${nextCountdown}` : null;
|
|
210
239
|
// The cached first paint is being replaced; its age only counts once a load has answered.
|
|
211
240
|
const showStale = stale && (!loading || calendar.updatedAt !== null);
|
|
241
|
+
// An empty list says it in its empty state instead.
|
|
242
|
+
const listedAfterStatus = listedAfter && !detailEvent && filtered.length > 0 ? listedAfter : null;
|
|
212
243
|
const calendarStatus = useMemo<PaneFooterSegment[]>(() => [
|
|
213
244
|
...(nextText && !detailEvent ? [{ id: "next", parts: [{ text: nextText, tone: "muted" as const }] }] : []),
|
|
245
|
+
...(listedAfterStatus ? [{ id: "listed-after", parts: [{ text: listedAfterStatus, tone: "warning" as const }] }] : []),
|
|
214
246
|
...(showStale ? [{ id: "stale", parts: [{ text: "STALE", tone: "warning" as const }] }] : []),
|
|
215
247
|
...(staleness ? [{ id: "updated", parts: [{ text: staleness, tone: "muted" as const }] }] : []),
|
|
216
|
-
], [detailEvent, nextText, showStale, staleness]);
|
|
248
|
+
], [detailEvent, listedAfterStatus, nextText, showStale, staleness]);
|
|
249
|
+
// A report of the pane (`gloomberb fn ECO`) says it too.
|
|
250
|
+
const listedThrough = listedEnd ? dateKey(listedEnd) : null;
|
|
251
|
+
useRemoteUiNode(listedAfter && listedThrough ? {
|
|
252
|
+
role: "report-notice",
|
|
253
|
+
label: "Listed through",
|
|
254
|
+
getMetadata: () => ({ text: listedAfter, key: "listedThrough", value: listedThrough }),
|
|
255
|
+
} : null);
|
|
217
256
|
usePaneStatusFooter({
|
|
218
257
|
registrationId: "econ-calendar",
|
|
219
258
|
loading,
|
|
@@ -336,9 +375,16 @@ function EconCalendarPane({ focused, width, height }: PaneProps) {
|
|
|
336
375
|
kind: "index",
|
|
337
376
|
selectedIndex: eventIdxToRowIdx.get(selectedIdx) ?? selectedIdx,
|
|
338
377
|
onChange: (_index, row) => {
|
|
378
|
+
leavePresent();
|
|
339
379
|
if (row.kind === "event") setSelectedKey(row.event.id);
|
|
340
380
|
},
|
|
341
381
|
}}
|
|
382
|
+
scrollToIndex={presentAnchor?.index}
|
|
383
|
+
scrollToIndexAlign={presentAnchor ? "start" : "nearest"}
|
|
384
|
+
scrollToIndexVersion={presentAnchor?.version ?? 0}
|
|
385
|
+
onBodyScrollActivity={(source) => {
|
|
386
|
+
if (source === "user") leavePresent();
|
|
387
|
+
}}
|
|
342
388
|
columns={columns}
|
|
343
389
|
items={rows}
|
|
344
390
|
isNavigable={(row) => row.kind === "event"}
|
|
@@ -351,7 +397,7 @@ function EconCalendarPane({ focused, width, height }: PaneProps) {
|
|
|
351
397
|
renderSectionHeader={renderSectionHeader}
|
|
352
398
|
renderCell={renderCell}
|
|
353
399
|
selectedTextOverridesCellColor
|
|
354
|
-
emptyStateTitle={loading ? "Loading economic events..." : "No events"}
|
|
400
|
+
emptyStateTitle={loading ? "Loading economic events..." : listedAfter ?? "No events"}
|
|
355
401
|
emptyStateHint={emptyStateHint}
|
|
356
402
|
showHorizontalScrollbar={false}
|
|
357
403
|
/>
|
|
@@ -49,11 +49,19 @@ function detailEntries(view: StatViewModel): HeadlessPaneEntry[] {
|
|
|
49
49
|
value: view.low,
|
|
50
50
|
formatted: `${format(view.low.value)} ${view.low.date}`,
|
|
51
51
|
},
|
|
52
|
-
|
|
52
|
+
// FRED dates a monthly reading at the month's first day; the text says which period it is.
|
|
53
|
+
{ label: "Period", value: view.latest.date, formatted: view.reading },
|
|
53
54
|
{ label: "Source", value: `FRED ${view.stat.seriesId}` },
|
|
54
55
|
];
|
|
55
56
|
}
|
|
56
57
|
|
|
58
|
+
/** The window the %ile column counts over, in words. */
|
|
59
|
+
const PERCENTILE_WINDOW: Record<StatRangeId, string> = {
|
|
60
|
+
"5Y": "the last 5 years",
|
|
61
|
+
"20Y": "the last 20 years",
|
|
62
|
+
ALL: "the full history",
|
|
63
|
+
};
|
|
64
|
+
|
|
57
65
|
export function projectStatsHeadlessBundle(
|
|
58
66
|
bundle: StatsBundle,
|
|
59
67
|
range: StatRangeId,
|
|
@@ -86,7 +94,7 @@ export function projectStatsHeadlessBundle(
|
|
|
86
94
|
align: "right" as const,
|
|
87
95
|
format: (value: unknown) => formatNumber(Number(value), 0),
|
|
88
96
|
},
|
|
89
|
-
{ key: "asOf", header: "
|
|
97
|
+
{ key: "asOf", header: "Period", format: (_value: unknown, row: Record<string, unknown>) => String(row.reading) },
|
|
90
98
|
],
|
|
91
99
|
rows: categoryViews.map((view) => ({
|
|
92
100
|
id: view.stat.id,
|
|
@@ -97,6 +105,7 @@ export function projectStatsHeadlessBundle(
|
|
|
97
105
|
formattedPrevious: view.previous ? view.stat.formatValue(view.previous.value) : "-",
|
|
98
106
|
percentile: view.percentile,
|
|
99
107
|
asOf: view.latest.date,
|
|
108
|
+
reading: view.reading,
|
|
100
109
|
stale: view.observationStale || view.cacheStale,
|
|
101
110
|
fetchedAt: view.fetchedAt,
|
|
102
111
|
cacheStale: view.cacheStale,
|
|
@@ -112,6 +121,7 @@ export function projectStatsHeadlessBundle(
|
|
|
112
121
|
...(selected ? [{ title: selected.stat.label, entries: detailEntries(selected) }] : []),
|
|
113
122
|
],
|
|
114
123
|
errors: bundle.errors,
|
|
124
|
+
notes: [`%ile is the share of past readings at or below the latest one, over ${PERCENTILE_WINDOW[range]}.`],
|
|
115
125
|
metadata: {
|
|
116
126
|
fetchedAt: bundle.fetchedAt,
|
|
117
127
|
fetchedAtComplete: bundle.fetchedAtComplete,
|
|
@@ -148,7 +148,7 @@ export function EconStatisticsPane({ focused, width, height }: PaneProps) {
|
|
|
148
148
|
const footerInfo = useMemo<PaneFooterSegment[]>(() => {
|
|
149
149
|
if (!selected) return [];
|
|
150
150
|
const info: PaneFooterSegment[] = [
|
|
151
|
-
{ id: "as-of", parts: [{ text: `
|
|
151
|
+
{ id: "as-of", parts: [{ text: `reading for ${selected.reading}`, tone: "muted" }] },
|
|
152
152
|
];
|
|
153
153
|
// The cached first paint is being replaced; its age only counts once that load fails.
|
|
154
154
|
const seeding = resource.loading && resource.updatedAt === null && !selected.refreshError;
|
|
@@ -19,6 +19,8 @@ export interface StatViewModel {
|
|
|
19
19
|
latest: StatPoint;
|
|
20
20
|
/** The latest print's period at the series' own cadence: "Sep 21", "Aug", "Q2". */
|
|
21
21
|
period: string;
|
|
22
|
+
/** What the latest print is a reading of, in full: "Aug 2026", "Q2 2026", "week ending 2026-10-03", "2026-10-09". */
|
|
23
|
+
reading: string;
|
|
22
24
|
/** The print before this one, for the direction of the last move. */
|
|
23
25
|
previous: StatPoint | null;
|
|
24
26
|
yearAgo: StatPoint | null;
|
|
@@ -60,6 +62,20 @@ function formatStatPeriod(date: string, perYear: number): string {
|
|
|
60
62
|
return `${name} ${Number(date.slice(8, 10))}`;
|
|
61
63
|
}
|
|
62
64
|
|
|
65
|
+
/**
|
|
66
|
+
* What the latest reading is a reading of. FRED dates a monthly or quarterly print at the start of
|
|
67
|
+
* its period, so 2026-08-01 is August 2026, not the first of the month; a weekly print is dated by
|
|
68
|
+
* the week's last day, a daily one by its day.
|
|
69
|
+
*/
|
|
70
|
+
function formatStatReading(date: string, perYear: number): string {
|
|
71
|
+
const month = Number(date.slice(5, 7));
|
|
72
|
+
const name = MONTHS[month - 1];
|
|
73
|
+
if (!name) return date;
|
|
74
|
+
if (perYear <= 4) return `Q${Math.ceil(month / 3)} ${date.slice(0, 4)}`;
|
|
75
|
+
if (perYear <= 12) return `${name} ${date.slice(0, 4)}`;
|
|
76
|
+
return perYear <= 52 ? `week ending ${date}` : date;
|
|
77
|
+
}
|
|
78
|
+
|
|
63
79
|
function extreme(points: readonly StatPoint[], pick: "high" | "low"): StatPoint {
|
|
64
80
|
let best = points[0]!;
|
|
65
81
|
for (const point of points) {
|
|
@@ -99,6 +115,7 @@ export function projectStat(
|
|
|
99
115
|
range,
|
|
100
116
|
latest,
|
|
101
117
|
period: formatStatPeriod(latest.date, perYear),
|
|
118
|
+
reading: formatStatReading(latest.date, perYear),
|
|
102
119
|
previous,
|
|
103
120
|
yearAgo,
|
|
104
121
|
changeOnPrevious: previous ? latest.value - previous.value : null,
|
|
@@ -4,7 +4,9 @@ import {
|
|
|
4
4
|
type CloudProxyStatementPayload,
|
|
5
5
|
} from "../../../api-client";
|
|
6
6
|
import { ApiRequestError, isPermanentClientError } from "../../../api-client/errors";
|
|
7
|
+
import type { IssuerListingParams } from "../../../api-client/paths";
|
|
7
8
|
import type { PluginPersistence } from "../../../types/plugin";
|
|
9
|
+
import { secListingKey } from "../../../utils/sec";
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
12
|
* Executive compensation comes from Gloom Cloud's open proxy-statement
|
|
@@ -101,21 +103,30 @@ function loadCached<T>(
|
|
|
101
103
|
return request.promise;
|
|
102
104
|
}
|
|
103
105
|
|
|
106
|
+
interface ProxyLoadOptions {
|
|
107
|
+
force?: boolean;
|
|
108
|
+
/** The listing's venue and company, for a listing outside the US. */
|
|
109
|
+
listing?: IssuerListingParams;
|
|
110
|
+
}
|
|
111
|
+
|
|
104
112
|
export async function loadProxyStatements(
|
|
105
113
|
ticker: string,
|
|
106
|
-
options?:
|
|
114
|
+
options?: ProxyLoadOptions,
|
|
107
115
|
): Promise<ProxyResult<CloudProxyStatementListPayload>> {
|
|
108
|
-
const
|
|
116
|
+
const symbol = ticker.toUpperCase();
|
|
117
|
+
// SAN in Paris is not SAN in New York: a listing abroad is cached under its venue.
|
|
118
|
+
const key = secListingKey(symbol, options?.listing?.exchange);
|
|
109
119
|
return loadCached(LIST_KIND, key, activeListFetches, LIST_CACHE_POLICY, options?.force ?? false,
|
|
110
|
-
() => apiClient.getProxyStatements(
|
|
120
|
+
() => apiClient.getProxyStatements(symbol, options?.listing));
|
|
111
121
|
}
|
|
112
122
|
|
|
113
123
|
export async function loadProxyStatement(
|
|
114
124
|
ticker: string,
|
|
115
125
|
year: number,
|
|
116
|
-
options?:
|
|
126
|
+
options?: ProxyLoadOptions,
|
|
117
127
|
): Promise<ProxyResult<CloudProxyStatementPayload>> {
|
|
118
|
-
const
|
|
128
|
+
const symbol = ticker.toUpperCase();
|
|
129
|
+
const key = `${secListingKey(symbol, options?.listing?.exchange)}:${year}`;
|
|
119
130
|
return loadCached(STATEMENT_KIND, key, activeStatementFetches, STATEMENT_CACHE_POLICY, options?.force ?? false,
|
|
120
|
-
() => apiClient.getProxyStatement(
|
|
131
|
+
() => apiClient.getProxyStatement(symbol, year, options?.listing));
|
|
121
132
|
}
|