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
|
@@ -54,7 +54,13 @@ import {
|
|
|
54
54
|
type ResolvedRegistryPaneQuickSetting,
|
|
55
55
|
type ResolvedRegistryPaneSettings,
|
|
56
56
|
} from "./pane-settings";
|
|
57
|
-
import {
|
|
57
|
+
import {
|
|
58
|
+
RegistryResumeStateListeners,
|
|
59
|
+
bindPluginContextNamespaces,
|
|
60
|
+
createPluginPaneSettingsState,
|
|
61
|
+
createPluginResumeState,
|
|
62
|
+
type NamespacedPluginContext,
|
|
63
|
+
} from "./plugin-state";
|
|
58
64
|
import { createPluginSetupCommand, isPluginConfigured } from "./setup-command";
|
|
59
65
|
import {
|
|
60
66
|
bindSharedRegistry,
|
|
@@ -76,6 +82,12 @@ interface PluginRegistryOptions {
|
|
|
76
82
|
}
|
|
77
83
|
|
|
78
84
|
export type { WindowEditMode } from "./host-actions";
|
|
85
|
+
|
|
86
|
+
/** A switched-off plugin, named for a "Turn on" offer. */
|
|
87
|
+
export interface DisabledPluginOwner {
|
|
88
|
+
id: string;
|
|
89
|
+
name: string;
|
|
90
|
+
}
|
|
79
91
|
export {
|
|
80
92
|
getSharedMarketData,
|
|
81
93
|
getSharedRegistry,
|
|
@@ -167,6 +179,7 @@ export class PluginRegistry implements PluginRuntimeAccess {
|
|
|
167
179
|
bindSharedRegistry(this, marketData);
|
|
168
180
|
this.capabilities = new CapabilityRegistry({
|
|
169
181
|
isPluginEnabled: (pluginId) => !this.getConfig().disabledPlugins.includes(pluginId),
|
|
182
|
+
pluginName: (pluginId) => this.plugins.get(pluginId)?.name,
|
|
170
183
|
isCapabilityEnabled: (capability, pluginId) => {
|
|
171
184
|
const disabledSources = this.getConfig().disabledSources ?? [];
|
|
172
185
|
return !disabledSources.includes(capability.sourceId ?? capability.id) && !this.getConfig().disabledPlugins.includes(pluginId);
|
|
@@ -407,6 +420,41 @@ export class PluginRegistry implements PluginRuntimeAccess {
|
|
|
407
420
|
return this.contributions.paneTemplatesMap.owners.get(templateId);
|
|
408
421
|
}
|
|
409
422
|
|
|
423
|
+
/**
|
|
424
|
+
* The plugin that owns this pane type when it is switched off. A pane of a
|
|
425
|
+
* switched-off plugin stays hidden, so callers offer to turn it on instead
|
|
426
|
+
* of adding one.
|
|
427
|
+
*/
|
|
428
|
+
getDisabledPaneOwner(
|
|
429
|
+
paneType: string,
|
|
430
|
+
disabledPlugins: readonly string[] = this.getConfig().disabledPlugins,
|
|
431
|
+
): DisabledPluginOwner | null {
|
|
432
|
+
return this.disabledOwner(this.getPanePluginId(paneType), disabledPlugins);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/** The same for a template: its own plugin, or the one owning the pane it opens. */
|
|
436
|
+
getDisabledPaneTemplateOwner(
|
|
437
|
+
templateId: string,
|
|
438
|
+
disabledPlugins: readonly string[] = this.getConfig().disabledPlugins,
|
|
439
|
+
): DisabledPluginOwner | null {
|
|
440
|
+
const paneType = this.paneTemplates.get(templateId)?.paneId;
|
|
441
|
+
return this.disabledOwner(this.getPaneTemplatePluginId(templateId), disabledPlugins)
|
|
442
|
+
?? (paneType ? this.getDisabledPaneOwner(paneType, disabledPlugins) : null);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/** The plugin behind a research tab when it is switched off, which hides the tab. */
|
|
446
|
+
getDisabledTickerResearchTabOwner(
|
|
447
|
+
tabId: string,
|
|
448
|
+
disabledPlugins: readonly string[] = this.getConfig().disabledPlugins,
|
|
449
|
+
): DisabledPluginOwner | null {
|
|
450
|
+
return this.disabledOwner(this.getTickerResearchTabPluginId(tabId), disabledPlugins);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
private disabledOwner(pluginId: string | undefined, disabledPlugins: readonly string[]): DisabledPluginOwner | null {
|
|
454
|
+
if (!pluginId || !disabledPlugins.includes(pluginId)) return null;
|
|
455
|
+
return { id: pluginId, name: this.plugins.get(pluginId)?.name ?? pluginId };
|
|
456
|
+
}
|
|
457
|
+
|
|
410
458
|
getBrokerPluginId(brokerType: string): string | undefined {
|
|
411
459
|
return this.contributions.brokersMap.owners.get(brokerType);
|
|
412
460
|
}
|
|
@@ -429,10 +477,42 @@ export class PluginRegistry implements PluginRuntimeAccess {
|
|
|
429
477
|
}
|
|
430
478
|
}
|
|
431
479
|
|
|
480
|
+
/**
|
|
481
|
+
* The state parts of a plugin's context. `namespaceKey` is the plugin's id,
|
|
482
|
+
* or the namespace a composed module keeps; both resolve the way a render
|
|
483
|
+
* context's id does.
|
|
484
|
+
*/
|
|
485
|
+
private createNamespacedContext(namespaceKey: string): NamespacedPluginContext {
|
|
486
|
+
const stateId = this.stateNamespace(namespaceKey);
|
|
487
|
+
return {
|
|
488
|
+
persistence: createPluginPersistence(
|
|
489
|
+
this.persistence.pluginState,
|
|
490
|
+
this.persistence.resources,
|
|
491
|
+
`plugin:${stateId}`,
|
|
492
|
+
stateId,
|
|
493
|
+
),
|
|
494
|
+
resume: createPluginResumeState({
|
|
495
|
+
pluginId: stateId,
|
|
496
|
+
getResumeState: (key, version) => this.getResumeState(namespaceKey, key, version),
|
|
497
|
+
setResumeState: (key, value, version) => this.setResumeState(namespaceKey, key, value, version),
|
|
498
|
+
deleteResumeState: (key) => this.deleteResumeState(namespaceKey, key),
|
|
499
|
+
getPaneRuntimeState: (paneId) => this.getPaneRuntimeState(paneId),
|
|
500
|
+
updatePaneRuntimeState: (paneId, patch) => this.updatePaneRuntimeState(paneId, patch),
|
|
501
|
+
}),
|
|
502
|
+
teamState: createPluginTeamState(stateId),
|
|
503
|
+
configState: {
|
|
504
|
+
get: (key) => this.getConfigState(namespaceKey, key),
|
|
505
|
+
set: (key, value) => this.setConfigState(namespaceKey, key, value),
|
|
506
|
+
delete: (key) => this.deleteConfigState(namespaceKey, key),
|
|
507
|
+
keys: () => this.getConfigStateKeys(namespaceKey),
|
|
508
|
+
},
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
|
|
432
512
|
private createContext(pluginId: string): GloomPluginContext {
|
|
433
513
|
const contributions = this.contributions;
|
|
434
514
|
const items = contributions.getOrCreatePluginItems(pluginId);
|
|
435
|
-
return {
|
|
515
|
+
return bindPluginContextNamespaces({
|
|
436
516
|
registerPane: (pane) => contributions.registerPane(pluginId, pane),
|
|
437
517
|
registerPaneTemplate: (template) => contributions.registerPaneTemplate(pluginId, template, true),
|
|
438
518
|
registerCommand: (command) => contributions.registerCommand(pluginId, command),
|
|
@@ -468,33 +548,13 @@ export class PluginRegistry implements PluginRuntimeAccess {
|
|
|
468
548
|
marketData: this.marketData,
|
|
469
549
|
connectionHealth: this.connectionHealth,
|
|
470
550
|
tickerRepository: this.tickerRepository,
|
|
471
|
-
|
|
472
|
-
this.persistence.pluginState,
|
|
473
|
-
this.persistence.resources,
|
|
474
|
-
`plugin:${this.stateNamespace(pluginId)}`,
|
|
475
|
-
this.stateNamespace(pluginId),
|
|
476
|
-
),
|
|
551
|
+
...this.createNamespacedContext(pluginId),
|
|
477
552
|
log: debugLog.createLogger(pluginId),
|
|
478
|
-
resume: createPluginResumeState({
|
|
479
|
-
pluginId: this.stateNamespace(pluginId),
|
|
480
|
-
getResumeState: (key, version) => this.getResumeState(pluginId, key, version),
|
|
481
|
-
setResumeState: (key, value, version) => this.setResumeState(pluginId, key, value, version),
|
|
482
|
-
deleteResumeState: (key) => this.deleteResumeState(pluginId, key),
|
|
483
|
-
getPaneRuntimeState: (paneId) => this.getPaneRuntimeState(paneId),
|
|
484
|
-
updatePaneRuntimeState: (paneId, patch) => this.updatePaneRuntimeState(paneId, patch),
|
|
485
|
-
}),
|
|
486
|
-
teamState: createPluginTeamState(this.stateNamespace(pluginId)),
|
|
487
553
|
paneSettings: createPluginPaneSettingsState({
|
|
488
554
|
getLayout: () => this.getLayout(),
|
|
489
555
|
updateLayout: (layout) => this.updateLayout(layout),
|
|
490
556
|
resolvePaneTarget: (paneId) => this.resolvePaneTarget(paneId),
|
|
491
557
|
}),
|
|
492
|
-
configState: {
|
|
493
|
-
get: (key) => this.getConfigState(pluginId, key),
|
|
494
|
-
set: (key, value) => this.setConfigState(pluginId, key, value),
|
|
495
|
-
delete: (key) => this.deleteConfigState(pluginId, key),
|
|
496
|
-
keys: () => this.getConfigStateKeys(pluginId),
|
|
497
|
-
},
|
|
498
558
|
createBrokerInstance: (brokerType, label, values) => this.createBrokerInstance(brokerType, label, values),
|
|
499
559
|
updateBrokerInstance: this.updateBrokerInstance,
|
|
500
560
|
syncBrokerInstance: this.syncBrokerInstance,
|
|
@@ -519,7 +579,7 @@ export class PluginRegistry implements PluginRuntimeAccess {
|
|
|
519
579
|
// Every plugin event is also a host event; TypeScript cannot see that through the generic key.
|
|
520
580
|
emit: (event, payload) => this.events.emit(event, payload as HostEvents[typeof event]),
|
|
521
581
|
notify: (notification) => this.notify(notification),
|
|
522
|
-
};
|
|
582
|
+
}, (stateId) => this.createNamespacedContext(stateId));
|
|
523
583
|
}
|
|
524
584
|
|
|
525
585
|
private registryLog = debugLog.createLogger("registry");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { PaneRuntimeState } from "../../core/state/app/state";
|
|
2
2
|
import { deletePaneSetting, setPaneSetting } from "../../pane-settings";
|
|
3
3
|
import type { LayoutConfig } from "../../types/config";
|
|
4
|
-
import type { PluginPaneSettingsState, PluginResumeState } from "../../types/plugin";
|
|
4
|
+
import type { GloomPluginContext, PluginPaneSettingsState, PluginResumeState } from "../../types/plugin";
|
|
5
5
|
import { deletePluginPaneStateValue, setPluginPaneStateValue } from "../../layout/pane-state";
|
|
6
6
|
|
|
7
7
|
export class RegistryResumeStateListeners {
|
|
@@ -35,6 +35,30 @@ export class RegistryResumeStateListeners {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/** The parts of a plugin context that read and write under its state namespace. */
|
|
39
|
+
export type NamespacedPluginContext = Pick<GloomPluginContext, "persistence" | "resume" | "configState" | "teamState">;
|
|
40
|
+
|
|
41
|
+
const namespacedContexts = new WeakMap<GloomPluginContext, (stateId: string) => NamespacedPluginContext>();
|
|
42
|
+
|
|
43
|
+
/** Lets `pluginContextInNamespace` rebuild this context's state parts for another namespace. */
|
|
44
|
+
export function bindPluginContextNamespaces(
|
|
45
|
+
context: GloomPluginContext,
|
|
46
|
+
createNamespaced: (stateId: string) => NamespacedPluginContext,
|
|
47
|
+
): GloomPluginContext {
|
|
48
|
+
namespacedContexts.set(context, createNamespaced);
|
|
49
|
+
return context;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The same plugin context with its persistence, resume, config and team state
|
|
54
|
+
* under another namespace. Contributions still register to the plugin that
|
|
55
|
+
* owns the context. A context the registry did not create comes back as is.
|
|
56
|
+
*/
|
|
57
|
+
export function pluginContextInNamespace(context: GloomPluginContext, stateId: string): GloomPluginContext {
|
|
58
|
+
const createNamespaced = namespacedContexts.get(context);
|
|
59
|
+
return createNamespaced ? { ...context, ...createNamespaced(stateId) } : context;
|
|
60
|
+
}
|
|
61
|
+
|
|
38
62
|
interface PluginResumeStateOptions {
|
|
39
63
|
pluginId: string;
|
|
40
64
|
getResumeState: <T = unknown>(key: string, schemaVersion?: number) => T | null;
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
createContext,
|
|
3
3
|
createElement,
|
|
4
4
|
useContext,
|
|
5
|
+
useMemo,
|
|
5
6
|
type ReactNode,
|
|
6
7
|
} from "react";
|
|
7
8
|
import type { BrokerAdapter } from "../../types/broker";
|
|
@@ -67,6 +68,34 @@ export function withPluginRender<P>(
|
|
|
67
68
|
);
|
|
68
69
|
}
|
|
69
70
|
|
|
71
|
+
function PluginStateNamespace({ stateId, children }: { stateId: string; children: ReactNode }) {
|
|
72
|
+
const outer = useContext(PluginRenderContext);
|
|
73
|
+
const runtime = outer?.runtime;
|
|
74
|
+
const value = useMemo(() => (runtime ? { pluginId: stateId, runtime } : null), [runtime, stateId]);
|
|
75
|
+
if (!value) return children;
|
|
76
|
+
return <PluginRenderContext value={value}>{children}</PluginRenderContext>;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Renders a component under another state namespace of the plugin render
|
|
81
|
+
* context it is already in. A built-in module that moved to another plugin
|
|
82
|
+
* keeps reading and writing the pane and plugin state it already has.
|
|
83
|
+
*/
|
|
84
|
+
export function withPluginStateNamespace<P>(
|
|
85
|
+
stateId: string,
|
|
86
|
+
component: (props: P) => ReactNode,
|
|
87
|
+
): (props: P) => ReactNode {
|
|
88
|
+
const scoped = (props: P) => (
|
|
89
|
+
<PluginStateNamespace stateId={stateId}>
|
|
90
|
+
{createElement(component as (props: any) => ReactNode, props)}
|
|
91
|
+
</PluginStateNamespace>
|
|
92
|
+
);
|
|
93
|
+
// Names the namespace in React's tools, and to tests that check a moved
|
|
94
|
+
// module still reads the state it had.
|
|
95
|
+
scoped.displayName = `PluginStateNamespace(${stateId})`;
|
|
96
|
+
return scoped;
|
|
97
|
+
}
|
|
98
|
+
|
|
70
99
|
export function usePluginRenderContext(): PluginRenderContextValue {
|
|
71
100
|
const context = useContext(PluginRenderContext);
|
|
72
101
|
if (!context) {
|
package/src/react/input.ts
CHANGED
|
@@ -57,9 +57,20 @@ interface ShortcutEntry extends ShortcutRegistration {
|
|
|
57
57
|
|
|
58
58
|
export interface ShortcutRegistry {
|
|
59
59
|
register(registration: ShortcutRegistration): () => void;
|
|
60
|
+
/**
|
|
61
|
+
* Runs `listener` for every key, before any handler is picked for it. State
|
|
62
|
+
* it sets has rendered by the time the handlers run (when the host can
|
|
63
|
+
* flush), so they read it instead of what the last frame held.
|
|
64
|
+
*/
|
|
65
|
+
beforeDispatch(listener: (event: KeyEventLike) => void): () => void;
|
|
60
66
|
dispatch(event: KeyEventLike): void;
|
|
61
67
|
}
|
|
62
68
|
|
|
69
|
+
export interface ShortcutRegistryOptions {
|
|
70
|
+
/** The renderer's synchronous render flush, which `beforeDispatch` listeners run inside. */
|
|
71
|
+
flushSync?: (run: () => void) => void;
|
|
72
|
+
}
|
|
73
|
+
|
|
63
74
|
const SHORTCUT_PHASES: ReadonlyArray<ShortcutEntry["phase"]> = [
|
|
64
75
|
"capture",
|
|
65
76
|
"before",
|
|
@@ -88,9 +99,10 @@ function orderShortcutEntries(entries: ShortcutEntry[]): ShortcutEntry[] {
|
|
|
88
99
|
});
|
|
89
100
|
}
|
|
90
101
|
|
|
91
|
-
export function createShortcutRegistry(): ShortcutRegistry {
|
|
102
|
+
export function createShortcutRegistry({ flushSync }: ShortcutRegistryOptions = {}): ShortcutRegistry {
|
|
92
103
|
let nextOrder = 1;
|
|
93
104
|
const entries = new Map<number, ShortcutEntry>();
|
|
105
|
+
const beforeListeners = new Set<(event: KeyEventLike) => void>();
|
|
94
106
|
|
|
95
107
|
return {
|
|
96
108
|
register(registration) {
|
|
@@ -104,7 +116,20 @@ export function createShortcutRegistry(): ShortcutRegistry {
|
|
|
104
116
|
entries.delete(order);
|
|
105
117
|
};
|
|
106
118
|
},
|
|
119
|
+
beforeDispatch(listener) {
|
|
120
|
+
beforeListeners.add(listener);
|
|
121
|
+
return () => {
|
|
122
|
+
beforeListeners.delete(listener);
|
|
123
|
+
};
|
|
124
|
+
},
|
|
107
125
|
dispatch(event) {
|
|
126
|
+
if (beforeListeners.size > 0) {
|
|
127
|
+
const run = () => {
|
|
128
|
+
for (const listener of [...beforeListeners]) listener(event);
|
|
129
|
+
};
|
|
130
|
+
if (flushSync) flushSync(run);
|
|
131
|
+
else run();
|
|
132
|
+
}
|
|
108
133
|
const eligible = orderShortcutEntries(
|
|
109
134
|
[...entries.values()].filter((entry) => (
|
|
110
135
|
entry.isEnabled() && shouldDeliverShortcut(event, entry.allowsEditable())
|
|
@@ -151,11 +176,24 @@ export function useRegisteredShortcut(
|
|
|
151
176
|
}), [options?.phase, options?.scope, registry]);
|
|
152
177
|
}
|
|
153
178
|
|
|
179
|
+
export function useRegisteredBeforeShortcut(
|
|
180
|
+
registry: ShortcutRegistry,
|
|
181
|
+
listener: (event: KeyEventLike) => void,
|
|
182
|
+
): void {
|
|
183
|
+
const listenerRef = useRef(listener);
|
|
184
|
+
listenerRef.current = listener;
|
|
185
|
+
useLayoutEffect(
|
|
186
|
+
() => registry.beforeDispatch((event) => listenerRef.current(event)),
|
|
187
|
+
[registry],
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
154
191
|
export interface InputHost {
|
|
155
192
|
useShortcut(
|
|
156
193
|
handler: (event: KeyEventLike) => void,
|
|
157
194
|
options?: ShortcutOptions,
|
|
158
195
|
): void;
|
|
196
|
+
useBeforeShortcut(listener: (event: KeyEventLike) => void): void;
|
|
159
197
|
useViewport(): { width: number; height: number };
|
|
160
198
|
}
|
|
161
199
|
|
|
@@ -186,6 +224,15 @@ export function useShortcut(
|
|
|
186
224
|
useInputHost().useShortcut(handler, options);
|
|
187
225
|
}
|
|
188
226
|
|
|
227
|
+
/**
|
|
228
|
+
* Runs before every key reaches any shortcut handler, whatever its phase or
|
|
229
|
+
* scope, for state the handlers read: set it here and it has rendered by the
|
|
230
|
+
* time they run. Not for acting on the key.
|
|
231
|
+
*/
|
|
232
|
+
export function useBeforeShortcut(listener: (event: KeyEventLike) => void): void {
|
|
233
|
+
useInputHost().useBeforeShortcut(listener);
|
|
234
|
+
}
|
|
235
|
+
|
|
189
236
|
export function shouldDeliverShortcut(
|
|
190
237
|
event: KeyEventLike,
|
|
191
238
|
allowEditable: boolean,
|
package/src/remote/app-host.tsx
CHANGED
|
@@ -23,6 +23,11 @@ export function useRemoteControlHandler(): RemoteControlHandler | null {
|
|
|
23
23
|
export interface RemoteControlAdapter {
|
|
24
24
|
startServer?(options: { dataDir: string; handle: RemoteControlHandler }): void | (() => void | Promise<void>);
|
|
25
25
|
registerHandler?(handler: RemoteControlHandler | null): void | (() => void);
|
|
26
|
+
/**
|
|
27
|
+
* Set where a remote assistant may drive this app through Gloom Cloud: what
|
|
28
|
+
* kind of app it is and the machine name it shows under.
|
|
29
|
+
*/
|
|
30
|
+
terminalDevice?: { kind: "desktop" | "tui"; name: string };
|
|
26
31
|
}
|
|
27
32
|
|
|
28
33
|
interface RemoteControlHostProps {
|
package/src/remote/controller.ts
CHANGED
|
@@ -30,6 +30,7 @@ import type {
|
|
|
30
30
|
} from "./types";
|
|
31
31
|
import type { RemoteUiRegistry } from "./semantic-tree";
|
|
32
32
|
import { commandBarResultsFromNodes, isCommandBarInputNode } from "./command-bar";
|
|
33
|
+
import { findCommandBarResult, findMatchingUiNode } from "./matching";
|
|
33
34
|
import { REMOTE_AGENT_HELP, remoteControlSchema } from "./schema";
|
|
34
35
|
import {
|
|
35
36
|
fail,
|
|
@@ -48,7 +49,6 @@ import {
|
|
|
48
49
|
} from "./layout-helpers";
|
|
49
50
|
import { createRemoteResources } from "./resources";
|
|
50
51
|
import { dismissTopmostDialog, isDialogOpen as isAnyDialogOpen } from "../ui/dialog-stack";
|
|
51
|
-
import { findFormNode } from "./form";
|
|
52
52
|
import { asRecord } from "../utils/guards";
|
|
53
53
|
import { runAutomated } from "../telemetry/usage-counts";
|
|
54
54
|
|
|
@@ -247,20 +247,7 @@ export function createAppRemoteController({
|
|
|
247
247
|
};
|
|
248
248
|
|
|
249
249
|
const activateCommandBarResult = async (input: Record<string, unknown>): Promise<unknown> => {
|
|
250
|
-
const
|
|
251
|
-
const index = optionalNumber(input, "index");
|
|
252
|
-
const itemId = optionalString(input, "itemId");
|
|
253
|
-
const label = optionalString(input, "label");
|
|
254
|
-
const results = commandBarResultsFromNodes(uiRegistry?.snapshot() ?? []);
|
|
255
|
-
const result = nodeId
|
|
256
|
-
? results.find((entry) => entry.nodeId === nodeId)
|
|
257
|
-
: itemId
|
|
258
|
-
? results.find((entry) => entry.itemId === itemId)
|
|
259
|
-
: label
|
|
260
|
-
? results.find((entry) => entry.label === label)
|
|
261
|
-
: typeof index === "number"
|
|
262
|
-
? results.find((entry) => entry.index === index)
|
|
263
|
-
: results.find((entry) => entry.selected) ?? results[0];
|
|
250
|
+
const result = findCommandBarResult(commandBarResultsFromNodes(uiRegistry?.snapshot() ?? []), input);
|
|
264
251
|
if (!result) throw new Error("No matching command-bar result is visible.");
|
|
265
252
|
if (!result.actions.includes("activate")) {
|
|
266
253
|
throw new Error(`Command-bar result "${result.nodeId}" does not expose activate.`);
|
|
@@ -270,45 +257,8 @@ export function createAppRemoteController({
|
|
|
270
257
|
};
|
|
271
258
|
|
|
272
259
|
const invokeMatchingUiNode = async (input: Record<string, unknown>): Promise<unknown> => {
|
|
273
|
-
const role = optionalString(input, "role");
|
|
274
|
-
const label = optionalString(input, "label");
|
|
275
|
-
const contains = optionalString(input, "contains");
|
|
276
|
-
const index = optionalNumber(input, "index");
|
|
277
260
|
const action = optionalString(input, "action") ?? "press";
|
|
278
|
-
const
|
|
279
|
-
const nodes = uiRegistry?.snapshot() ?? [];
|
|
280
|
-
// A dialog over the form (a listing picker, a sign-in) keeps its controls
|
|
281
|
-
// out of reach, as it does from the mouse.
|
|
282
|
-
const formCovered = findFormNode(nodes)?.metadata?.covered === true;
|
|
283
|
-
const candidates = nodes.filter((node) => {
|
|
284
|
-
if (formCovered && node.metadata?.scope === "form") return false;
|
|
285
|
-
if (role && node.role !== role) return false;
|
|
286
|
-
if (label && node.label !== label && node.metadata?.item && typeof node.metadata.item === "object") {
|
|
287
|
-
const item = node.metadata.item as Record<string, unknown>;
|
|
288
|
-
if (item.label !== label && item.id !== label) return false;
|
|
289
|
-
} else if (label && node.label !== label) {
|
|
290
|
-
return false;
|
|
291
|
-
}
|
|
292
|
-
if (contains) {
|
|
293
|
-
const haystack = [
|
|
294
|
-
node.label,
|
|
295
|
-
node.role,
|
|
296
|
-
JSON.stringify(node.metadata ?? {}),
|
|
297
|
-
].filter((entry): entry is string => typeof entry === "string").join(" ").toLowerCase();
|
|
298
|
-
if (!haystack.includes(contains.toLowerCase())) return false;
|
|
299
|
-
}
|
|
300
|
-
for (const [key, value] of Object.entries(metadataFilter)) {
|
|
301
|
-
if (node.metadata?.[key] !== value) return false;
|
|
302
|
-
}
|
|
303
|
-
if (!node.actions.includes(action)) return false;
|
|
304
|
-
if (node.disabled) return false;
|
|
305
|
-
return true;
|
|
306
|
-
});
|
|
307
|
-
// An open form or confirm covers every pane, so its own controls come
|
|
308
|
-
// first: its Cancel, not a pane's Cancel behind it.
|
|
309
|
-
const inForm = candidates.filter((node) => node.metadata?.scope === "form");
|
|
310
|
-
const matches = inForm.length > 0 ? inForm : candidates;
|
|
311
|
-
const node = typeof index === "number" ? matches[index] : matches[0];
|
|
261
|
+
const node = findMatchingUiNode(uiRegistry?.snapshot() ?? [], input);
|
|
312
262
|
if (!node) throw new Error("No matching semantic UI node is visible.");
|
|
313
263
|
const result = await uiRegistry?.invoke(node.id, action, input.input);
|
|
314
264
|
return getAfterMutationSummary({ invokedNode: node, result });
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { optionalNumber, optionalString } from "./controller-utils";
|
|
2
|
+
import { findFormNode } from "./form";
|
|
3
|
+
import type { RemoteUiNodeSnapshot } from "./types";
|
|
4
|
+
import { asRecord } from "../utils/guards";
|
|
5
|
+
|
|
6
|
+
/** The visible command-bar row commandBar.activateResult picks for this input. */
|
|
7
|
+
export function findCommandBarResult<T extends { nodeId: string; itemId?: unknown; label?: unknown; index?: unknown; selected: boolean }>(
|
|
8
|
+
results: readonly T[],
|
|
9
|
+
input: Record<string, unknown>,
|
|
10
|
+
): T | undefined {
|
|
11
|
+
const nodeId = optionalString(input, "nodeId");
|
|
12
|
+
const index = optionalNumber(input, "index");
|
|
13
|
+
const itemId = optionalString(input, "itemId");
|
|
14
|
+
const label = optionalString(input, "label");
|
|
15
|
+
return nodeId
|
|
16
|
+
? results.find((entry) => entry.nodeId === nodeId)
|
|
17
|
+
: itemId
|
|
18
|
+
? results.find((entry) => entry.itemId === itemId)
|
|
19
|
+
: label
|
|
20
|
+
? results.find((entry) => entry.label === label)
|
|
21
|
+
: typeof index === "number"
|
|
22
|
+
? results.find((entry) => entry.index === index)
|
|
23
|
+
: results.find((entry) => entry.selected) ?? results[0];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** The semantic UI node ui.invokeMatching acts on for this input, or undefined. */
|
|
27
|
+
export function findMatchingUiNode(
|
|
28
|
+
nodes: RemoteUiNodeSnapshot[],
|
|
29
|
+
input: Record<string, unknown>,
|
|
30
|
+
): RemoteUiNodeSnapshot | undefined {
|
|
31
|
+
const role = optionalString(input, "role");
|
|
32
|
+
const label = optionalString(input, "label");
|
|
33
|
+
const contains = optionalString(input, "contains");
|
|
34
|
+
const index = optionalNumber(input, "index");
|
|
35
|
+
const action = optionalString(input, "action") ?? "press";
|
|
36
|
+
const metadataFilter = asRecord(input.metadata);
|
|
37
|
+
// A dialog over the form (a listing picker, a sign-in) keeps its controls
|
|
38
|
+
// out of reach, as it does from the mouse.
|
|
39
|
+
const formCovered = findFormNode(nodes)?.metadata?.covered === true;
|
|
40
|
+
const candidates = nodes.filter((node) => {
|
|
41
|
+
if (formCovered && node.metadata?.scope === "form") return false;
|
|
42
|
+
if (role && node.role !== role) return false;
|
|
43
|
+
if (label && node.label !== label && node.metadata?.item && typeof node.metadata.item === "object") {
|
|
44
|
+
const item = node.metadata.item as Record<string, unknown>;
|
|
45
|
+
if (item.label !== label && item.id !== label) return false;
|
|
46
|
+
} else if (label && node.label !== label) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
if (contains) {
|
|
50
|
+
const haystack = [
|
|
51
|
+
node.label,
|
|
52
|
+
node.role,
|
|
53
|
+
JSON.stringify(node.metadata ?? {}),
|
|
54
|
+
].filter((entry): entry is string => typeof entry === "string").join(" ").toLowerCase();
|
|
55
|
+
if (!haystack.includes(contains.toLowerCase())) return false;
|
|
56
|
+
}
|
|
57
|
+
for (const [key, value] of Object.entries(metadataFilter)) {
|
|
58
|
+
if (node.metadata?.[key] !== value) return false;
|
|
59
|
+
}
|
|
60
|
+
if (!node.actions.includes(action)) return false;
|
|
61
|
+
if (node.disabled) return false;
|
|
62
|
+
return true;
|
|
63
|
+
});
|
|
64
|
+
// An open form or confirm covers every pane, so its own controls come
|
|
65
|
+
// first: its Cancel, not a pane's Cancel behind it.
|
|
66
|
+
const inForm = candidates.filter((node) => node.metadata?.scope === "form");
|
|
67
|
+
const matches = inForm.length > 0 ? inForm : candidates;
|
|
68
|
+
return typeof index === "number" ? matches[index] : matches[0];
|
|
69
|
+
}
|
package/src/remote/schema.ts
CHANGED
|
@@ -115,7 +115,7 @@ export const REMOTE_OPERATIONS: RemoteOperationSchema[] = [
|
|
|
115
115
|
op("pane.close", "Close a pane instance or pane type.", "{ paneId: string }", "local-write", paneIdInput),
|
|
116
116
|
op(
|
|
117
117
|
"pane.createFromTemplate",
|
|
118
|
-
"
|
|
118
|
+
"Open a pane from a registered template. It only opens the pane: it adds nothing to a portfolio or watchlist.",
|
|
119
119
|
"{ templateId: string, options?: object }",
|
|
120
120
|
"local-write",
|
|
121
121
|
objectSchema({ templateId: requiredStringSchema, options: openObjectSchema }, ["templateId"]),
|
|
@@ -11,6 +11,8 @@ type RemoteUiAction = (input?: unknown) => unknown | Promise<unknown>;
|
|
|
11
11
|
|
|
12
12
|
export interface RemoteUiNodeRegistration {
|
|
13
13
|
role: string;
|
|
14
|
+
/** The pane instance the node is drawn in, set from RemoteUiPaneScope. */
|
|
15
|
+
paneId?: string;
|
|
14
16
|
label?: string;
|
|
15
17
|
disabled?: boolean;
|
|
16
18
|
actions?: Record<string, RemoteUiAction | undefined>;
|
|
@@ -56,6 +58,7 @@ export function createRemoteUiRegistry(): RemoteUiRegistry {
|
|
|
56
58
|
.map(([action]) => action)
|
|
57
59
|
.sort(),
|
|
58
60
|
metadata: registration.getMetadata?.() ?? registration.metadata,
|
|
61
|
+
...(registration.paneId ? { paneId: registration.paneId } : {}),
|
|
59
62
|
};
|
|
60
63
|
});
|
|
61
64
|
},
|
|
@@ -110,6 +113,13 @@ export function useRemoteUiScope(): string | null {
|
|
|
110
113
|
return useContext(RemoteUiScopeContext);
|
|
111
114
|
}
|
|
112
115
|
|
|
116
|
+
const RemoteUiPaneContext = createContext<string | null>(null);
|
|
117
|
+
|
|
118
|
+
/** Tags the nodes drawn below with the pane instance they belong to. */
|
|
119
|
+
export function RemoteUiPaneScope({ paneId, children }: { paneId: string; children: ReactNode }) {
|
|
120
|
+
return <RemoteUiPaneContext value={paneId}>{children}</RemoteUiPaneContext>;
|
|
121
|
+
}
|
|
122
|
+
|
|
113
123
|
let nextRemoteNodeSequence = 0;
|
|
114
124
|
|
|
115
125
|
/**
|
|
@@ -120,9 +130,12 @@ let nextRemoteNodeSequence = 0;
|
|
|
120
130
|
*/
|
|
121
131
|
export function useRemoteUiNode(registration: RemoteUiNodeRegistration | null | undefined): string | null {
|
|
122
132
|
const registry = useRemoteUiRegistry();
|
|
133
|
+
const paneId = useContext(RemoteUiPaneContext);
|
|
123
134
|
const nodeIdRef = useRef<string | null>(null);
|
|
124
135
|
const registrationRef = useRef(registration);
|
|
125
136
|
registrationRef.current = registration;
|
|
137
|
+
const paneIdRef = useRef(paneId);
|
|
138
|
+
paneIdRef.current = paneId;
|
|
126
139
|
const dynamicRegistrationRef = useRef<RemoteUiNodeRegistration | null>(null);
|
|
127
140
|
const registered = registration != null;
|
|
128
141
|
if (registered && !nodeIdRef.current) {
|
|
@@ -130,6 +143,7 @@ export function useRemoteUiNode(registration: RemoteUiNodeRegistration | null |
|
|
|
130
143
|
nodeIdRef.current = `ui:${nextRemoteNodeSequence.toString(36)}`;
|
|
131
144
|
dynamicRegistrationRef.current = {
|
|
132
145
|
get role() { return registrationRef.current?.role ?? "unknown"; },
|
|
146
|
+
get paneId() { return registrationRef.current?.paneId ?? paneIdRef.current ?? undefined; },
|
|
133
147
|
get label() { return registrationRef.current?.label; },
|
|
134
148
|
get disabled() { return registrationRef.current?.disabled; },
|
|
135
149
|
get actions() { return registrationRef.current?.actions; },
|
package/src/remote/types.ts
CHANGED
|
@@ -172,6 +172,8 @@ export interface RemoteControlSchema {
|
|
|
172
172
|
export interface RemoteUiNodeSnapshot {
|
|
173
173
|
id: string;
|
|
174
174
|
role: string;
|
|
175
|
+
/** The pane instance the node is drawn in; absent outside panes. */
|
|
176
|
+
paneId?: string;
|
|
175
177
|
label?: string;
|
|
176
178
|
disabled?: boolean;
|
|
177
179
|
actions: string[];
|
|
@@ -88,6 +88,20 @@ const ICONS: Record<IconName, ReactNode> = {
|
|
|
88
88
|
<path d="M2 9.1 4.7 6.6l1.6 1.5 1.8-2 2.4 2.5" stroke="currentColor" strokeWidth="1.1" strokeLinecap="round" strokeLinejoin="round" />
|
|
89
89
|
</>
|
|
90
90
|
),
|
|
91
|
+
// A hand with the thumb raised, on the 24-unit grid; the thumbs down is the
|
|
92
|
+
// same hand turned over.
|
|
93
|
+
"thumbs-up": (
|
|
94
|
+
<g transform="scale(0.5)">
|
|
95
|
+
<rect x="3" y="10" width="4" height="10" rx="1" stroke="currentColor" strokeWidth="1.8" strokeLinejoin="round" />
|
|
96
|
+
<path d="M7 10l3.5-6.5a1.8 1.8 0 0 1 3.3 1.2L13 9.5h5.6a2 2 0 0 1 2 2.4l-1.4 6.5a2 2 0 0 1-2 1.6H7" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" />
|
|
97
|
+
</g>
|
|
98
|
+
),
|
|
99
|
+
"thumbs-down": (
|
|
100
|
+
<g transform="scale(0.5) rotate(180 12 12)">
|
|
101
|
+
<rect x="3" y="10" width="4" height="10" rx="1" stroke="currentColor" strokeWidth="1.8" strokeLinejoin="round" />
|
|
102
|
+
<path d="M7 10l3.5-6.5a1.8 1.8 0 0 1 3.3 1.2L13 9.5h5.6a2 2 0 0 1 2 2.4l-1.4 6.5a2 2 0 0 1-2 1.6H7" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" />
|
|
103
|
+
</g>
|
|
104
|
+
),
|
|
91
105
|
restore: (
|
|
92
106
|
<>
|
|
93
107
|
<rect x="2" y="4" width="6" height="6" rx="0.8" stroke="currentColor" strokeWidth="1.1" />
|