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
|
@@ -10,6 +10,53 @@ import { WebIcon } from "./icons";
|
|
|
10
10
|
|
|
11
11
|
/** Input width of a text filter that names none, in cells (the terminal's default). */
|
|
12
12
|
const DEFAULT_TEXT_FIELD_CELLS = 20;
|
|
13
|
+
/** Beyond this many choices an inline filter is a strip that scrolls by itself, as the terminal's does. */
|
|
14
|
+
const STRIP_OPTION_LIMIT = 6;
|
|
15
|
+
|
|
16
|
+
function isStrip(item: HostQueryBarItem): boolean {
|
|
17
|
+
return item.kind === "select" && item.inline === true && item.options.length > STRIP_OPTION_LIMIT;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The choices of an inline filter. A long one (expiries, chart ranges) is a
|
|
22
|
+
* strip that scrolls on its own, so the label and the controls beside it stay
|
|
23
|
+
* in view while it brings the chosen one into view.
|
|
24
|
+
*/
|
|
25
|
+
function InlineChoices({ item }: { item: HostQueryBarItem }) {
|
|
26
|
+
const stripRef = useRef<HTMLDivElement | null>(null);
|
|
27
|
+
const strip = isStrip(item);
|
|
28
|
+
const overflow = useHorizontalOverflow(stripRef, [item.options.length, strip]);
|
|
29
|
+
return (
|
|
30
|
+
<div className="gloom-qb-inline" data-item-id={item.id} data-narrowing={item.narrowing ? "true" : undefined}
|
|
31
|
+
data-strip={strip ? "true" : undefined}>
|
|
32
|
+
<span className="gloom-qb-label">{item.label}</span>
|
|
33
|
+
<div
|
|
34
|
+
ref={stripRef}
|
|
35
|
+
className="gloom-qb-view gloom-qb-segments"
|
|
36
|
+
role="radiogroup"
|
|
37
|
+
aria-label={item.label}
|
|
38
|
+
style={strip ? overflow.maskStyle : undefined}
|
|
39
|
+
onWheel={strip ? overflow.onWheel : undefined}
|
|
40
|
+
>
|
|
41
|
+
{item.options.map((option) => (
|
|
42
|
+
<button
|
|
43
|
+
key={option.value}
|
|
44
|
+
type="button"
|
|
45
|
+
role="radio"
|
|
46
|
+
aria-checked={option.selected}
|
|
47
|
+
disabled={option.disabled}
|
|
48
|
+
title={option.hint ? `${option.label} (${option.hint})` : undefined}
|
|
49
|
+
data-active={option.selected ? "true" : undefined}
|
|
50
|
+
onMouseDown={(event) => event.stopPropagation()}
|
|
51
|
+
onClick={() => item.onSelect(option.value)}
|
|
52
|
+
>
|
|
53
|
+
<span className="gloom-qb-text">{option.label}</span>
|
|
54
|
+
</button>
|
|
55
|
+
))}
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
13
60
|
|
|
14
61
|
function Chevron() {
|
|
15
62
|
return <span className="gloom-qb-chevron"><WebIcon name="chevron-down" size={9} /></span>;
|
|
@@ -87,30 +134,7 @@ function FilterChip({ item, open, onOpenChange }: { item: HostQueryBarItem; open
|
|
|
87
134
|
useEffect(() => {
|
|
88
135
|
if (open) triggerRef.current?.scrollIntoView({ block: "nearest", inline: "nearest" });
|
|
89
136
|
}, [open]);
|
|
90
|
-
if (item.kind === "select" && item.inline) {
|
|
91
|
-
return (
|
|
92
|
-
<div className="gloom-qb-inline" data-item-id={item.id} data-narrowing={item.narrowing ? "true" : undefined}>
|
|
93
|
-
<span className="gloom-qb-label">{item.label}</span>
|
|
94
|
-
<div className="gloom-qb-view gloom-qb-segments" role="radiogroup" aria-label={item.label}>
|
|
95
|
-
{item.options.map((option) => (
|
|
96
|
-
<button
|
|
97
|
-
key={option.value}
|
|
98
|
-
type="button"
|
|
99
|
-
role="radio"
|
|
100
|
-
aria-checked={option.selected}
|
|
101
|
-
disabled={option.disabled}
|
|
102
|
-
title={option.hint ? `${option.label} (${option.hint})` : undefined}
|
|
103
|
-
data-active={option.selected ? "true" : undefined}
|
|
104
|
-
onMouseDown={(event) => event.stopPropagation()}
|
|
105
|
-
onClick={() => item.onSelect(option.value)}
|
|
106
|
-
>
|
|
107
|
-
<span className="gloom-qb-text">{option.label}</span>
|
|
108
|
-
</button>
|
|
109
|
-
))}
|
|
110
|
-
</div>
|
|
111
|
-
</div>
|
|
112
|
-
);
|
|
113
|
-
}
|
|
137
|
+
if (item.kind === "select" && item.inline) return <InlineChoices item={item} />;
|
|
114
138
|
if (item.kind === "toggle") {
|
|
115
139
|
return (
|
|
116
140
|
<button
|
|
@@ -190,27 +214,30 @@ export function WebQueryBar({ search, items, view, onClearAll, meta, openRequest
|
|
|
190
214
|
const overflow = useHorizontalOverflow(scrollRef, [items.length, !!search, !!view, !!onClearAll, meta]);
|
|
191
215
|
const searchRef = useRef<HTMLDivElement | null>(null);
|
|
192
216
|
// When an inline choice or the view changes (a shortcut, the next expiry),
|
|
193
|
-
// bring the new selection into a scrolled bar.
|
|
194
|
-
//
|
|
217
|
+
// bring the new selection into a scrolled bar. On mount only a strip that
|
|
218
|
+
// scrolls by itself shows its choice: an expiry chosen far down the strip
|
|
219
|
+
// is what the pane shows, but scrolling the whole bar to a view at the right
|
|
220
|
+
// edge would hide the search when the pane opens.
|
|
195
221
|
const selectionKey = [
|
|
196
222
|
...items.filter((item) => item.inline).map((item) => `${item.id}=${item.options.find((option) => option.selected)?.value ?? ""}`),
|
|
197
223
|
`view=${view?.value ?? ""}`,
|
|
198
224
|
].join("|");
|
|
199
|
-
const previousSelectionKey = useRef(
|
|
225
|
+
const previousSelectionKey = useRef<string | null>(null);
|
|
200
226
|
useEffect(() => {
|
|
201
227
|
const previous = previousSelectionKey.current;
|
|
202
228
|
previousSelectionKey.current = selectionKey;
|
|
203
229
|
if (previous === selectionKey) return;
|
|
204
|
-
const before = new Map(previous.split("|").map((entry) => entry.split("=") as [string, string]));
|
|
230
|
+
const before = previous == null ? null : new Map(previous.split("|").map((entry) => entry.split("=") as [string, string]));
|
|
205
231
|
const bar = scrollRef.current;
|
|
206
232
|
if (!bar) return;
|
|
207
233
|
for (const entry of selectionKey.split("|")) {
|
|
208
234
|
const [id, value] = entry.split("=") as [string, string];
|
|
209
|
-
if (before
|
|
235
|
+
if (before?.get(id) === value) continue;
|
|
210
236
|
const group = id === "view"
|
|
211
237
|
? bar.querySelector(":scope > .gloom-qb-view")
|
|
212
|
-
: bar.
|
|
213
|
-
group?.
|
|
238
|
+
: [...bar.querySelectorAll("[data-item-id]")].find((item) => item.getAttribute("data-item-id") === id);
|
|
239
|
+
if (!before && group?.getAttribute("data-strip") !== "true") continue;
|
|
240
|
+
group?.querySelector<HTMLElement>("button[data-active=true]")?.scrollIntoView?.({ block: "nearest", inline: "nearest" });
|
|
214
241
|
}
|
|
215
242
|
}, [selectionKey]);
|
|
216
243
|
useEffect(() => {
|
|
@@ -280,7 +307,7 @@ export function WebQueryBar({ search, items, view, onClearAll, meta, openRequest
|
|
|
280
307
|
)}
|
|
281
308
|
{searchNode}
|
|
282
309
|
{items.length > 0 && (
|
|
283
|
-
<div className="gloom-qb-filters">
|
|
310
|
+
<div className="gloom-qb-filters" data-strip={items.some(isStrip) ? "true" : undefined}>
|
|
284
311
|
{items.map((item) => (
|
|
285
312
|
<FilterChip key={item.id} item={item} open={openId === item.id} onOpenChange={(open) => setOpen(item.id, open)} />
|
|
286
313
|
))}
|
|
@@ -248,7 +248,7 @@ export const WebBox = forwardRef<HTMLDivElement, Record<string, unknown> & { chi
|
|
|
248
248
|
if (current && !current.shielded) {
|
|
249
249
|
if (dx * dx + dy * dy < 9) return;
|
|
250
250
|
current.shielded = true;
|
|
251
|
-
showDragShield();
|
|
251
|
+
showDragShield(elementRef.current);
|
|
252
252
|
// Captured once it is a drag (a click keeps its usual target), the
|
|
253
253
|
// moves skip hit testing and leave every other element's hover state
|
|
254
254
|
// alone: nothing under the pointer restyles or repaints while a pane,
|
|
@@ -7,14 +7,25 @@ let shield: HTMLElement | null = null;
|
|
|
7
7
|
* It is a single element on purpose: a class on body matched by descendant
|
|
8
8
|
* rules restyled every element with a role (thousands on a busy layout) in
|
|
9
9
|
* the frame the drag began, and again when it ended.
|
|
10
|
+
*
|
|
11
|
+
* A press on a resize edge (a pane's corner, a split) keeps its resize cursor
|
|
12
|
+
* for the drag instead, as a window edge does.
|
|
10
13
|
*/
|
|
11
|
-
export function showDragShield(): void {
|
|
14
|
+
export function showDragShield(pressed?: Element | null): void {
|
|
12
15
|
document.body.classList.add("gloom-dragging");
|
|
13
|
-
if (shield?.isConnected)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
if (!shield?.isConnected) {
|
|
17
|
+
shield = document.createElement("div");
|
|
18
|
+
shield.setAttribute("data-gloom-role", "drag-shield");
|
|
19
|
+
shield.setAttribute("aria-hidden", "true");
|
|
20
|
+
document.body.appendChild(shield);
|
|
21
|
+
}
|
|
22
|
+
shield.style.cursor = resizeCursor(pressed);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function resizeCursor(element: Element | null | undefined): string {
|
|
26
|
+
if (!element || typeof getComputedStyle !== "function") return "";
|
|
27
|
+
const cursor = getComputedStyle(element).cursor;
|
|
28
|
+
return cursor.endsWith("-resize") ? cursor : "";
|
|
18
29
|
}
|
|
19
30
|
|
|
20
31
|
export function hideDragShield(): void {
|
|
@@ -18,7 +18,9 @@ const ANIMATION_CARRIES_SLIDE = typeof navigator !== "undefined"
|
|
|
18
18
|
|
|
19
19
|
const slides = new WeakMap<HTMLElement, Animation>();
|
|
20
20
|
|
|
21
|
+
/** A style value as React writes it: a bare number is pixels. */
|
|
21
22
|
function renderedValue(value: unknown): string {
|
|
23
|
+
if (typeof value === "number") return `${value}px`;
|
|
22
24
|
return typeof value === "string" ? value : "";
|
|
23
25
|
}
|
|
24
26
|
|
|
@@ -36,7 +38,8 @@ function slide(element: HTMLElement, transform: string): void {
|
|
|
36
38
|
/**
|
|
37
39
|
* Draws a box where a drag has it this frame by restyling its element,
|
|
38
40
|
* outside React: an offset on its own compositor layer, with no layout or
|
|
39
|
-
* repaint
|
|
41
|
+
* repaint, and a size, which lays out only the box's subtree. `null` hands
|
|
42
|
+
* the properties back to what React last rendered.
|
|
40
43
|
*/
|
|
41
44
|
export function applyLiveFrame(element: HTMLElement, frame: LiveBoxFrame | null, props: Record<string, unknown>): void {
|
|
42
45
|
const style = element.style;
|
|
@@ -46,9 +49,14 @@ export function applyLiveFrame(element: HTMLElement, frame: LiveBoxFrame | null,
|
|
|
46
49
|
slides.delete(element);
|
|
47
50
|
style.transform = renderedValue(rendered.transform);
|
|
48
51
|
style.willChange = renderedValue(rendered.willChange);
|
|
52
|
+
style.width = renderedValue(rendered.width);
|
|
53
|
+
style.height = renderedValue(rendered.height);
|
|
49
54
|
return;
|
|
50
55
|
}
|
|
56
|
+
if (frame.width !== undefined) style.width = `${frame.width * WEB_CELL_WIDTH}px`;
|
|
57
|
+
if (frame.height !== undefined) style.height = `${frame.height * WEB_CELL_HEIGHT}px`;
|
|
58
|
+
if (frame.dx === undefined && frame.dy === undefined) return;
|
|
51
59
|
// Promoted once, before the first frame of the drag.
|
|
52
60
|
if (style.willChange !== "transform") style.willChange = "transform";
|
|
53
|
-
slide(element, `translate3d(${frame.dx * WEB_CELL_WIDTH}px, ${frame.dy * WEB_CELL_HEIGHT}px, 0)`);
|
|
61
|
+
slide(element, `translate3d(${(frame.dx ?? 0) * WEB_CELL_WIDTH}px, ${(frame.dy ?? 0) * WEB_CELL_HEIGHT}px, 0)`);
|
|
54
62
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/** @jsxImportSource react */
|
|
2
2
|
import { useEffect, useMemo, useSyncExternalStore, type ReactNode } from "react";
|
|
3
|
+
import { flushSync } from "react-dom";
|
|
3
4
|
import {
|
|
4
5
|
createShortcutRegistry,
|
|
5
6
|
InputHostProvider,
|
|
7
|
+
useRegisteredBeforeShortcut,
|
|
6
8
|
useRegisteredShortcut,
|
|
7
9
|
type InputHost,
|
|
8
10
|
type KeyEventLike,
|
|
@@ -76,7 +78,7 @@ function getViewport() {
|
|
|
76
78
|
}
|
|
77
79
|
|
|
78
80
|
export function WebInputHostProvider({ children }: { children: ReactNode }) {
|
|
79
|
-
const shortcutRegistry = useMemo(() => createShortcutRegistry(), []);
|
|
81
|
+
const shortcutRegistry = useMemo(() => createShortcutRegistry({ flushSync }), []);
|
|
80
82
|
|
|
81
83
|
useEffect(() => {
|
|
82
84
|
// Windows fires the Menu key's contextmenu on keyup. When the app used the
|
|
@@ -144,6 +146,9 @@ export function WebInputHostProvider({ children }: { children: ReactNode }) {
|
|
|
144
146
|
useShortcut(handler, options) {
|
|
145
147
|
useRegisteredShortcut(shortcutRegistry, handler, options);
|
|
146
148
|
},
|
|
149
|
+
useBeforeShortcut(listener) {
|
|
150
|
+
useRegisteredBeforeShortcut(shortcutRegistry, listener);
|
|
151
|
+
},
|
|
147
152
|
useViewport() {
|
|
148
153
|
return useSyncExternalStore(subscribeViewport, getViewport, getViewport);
|
|
149
154
|
},
|
|
@@ -753,6 +753,19 @@ html[data-gloom-screenshot="true"] [data-gloom-role="chart-watermark"] {
|
|
|
753
753
|
box-shadow: inset 1px 0 0 var(--qb-hairline);
|
|
754
754
|
}
|
|
755
755
|
.gloom-qb-inline .gloom-qb-view { margin-left: 0; box-shadow: none; align-self: stretch; }
|
|
756
|
+
/* A long inline choice (an expiry strip) scrolls by itself, as the terminal's
|
|
757
|
+
does, so its label and the controls beside it stay in view. */
|
|
758
|
+
.gloom-qb-filters[data-strip="true"],
|
|
759
|
+
.gloom-qb-inline[data-strip="true"] { flex: 0 1 auto; min-width: 0; }
|
|
760
|
+
.gloom-qb-inline[data-strip="true"] .gloom-qb-segments {
|
|
761
|
+
flex: 0 1 auto;
|
|
762
|
+
min-width: 12ch;
|
|
763
|
+
overflow-x: auto;
|
|
764
|
+
scrollbar-width: none;
|
|
765
|
+
}
|
|
766
|
+
.gloom-qb-inline[data-strip="true"] .gloom-qb-segments::-webkit-scrollbar { display: none; }
|
|
767
|
+
.gloom-qb-inline[data-strip="true"] .gloom-qb-segments > button { flex: none; white-space: nowrap; }
|
|
768
|
+
.gloom-qb-filters[data-strip="true"] > :not([data-strip="true"]) { flex: none; }
|
|
756
769
|
.gloom-qb-view button {
|
|
757
770
|
height: 100%;
|
|
758
771
|
padding: 0 8px;
|
|
@@ -105,6 +105,8 @@ export class DesktopBackend {
|
|
|
105
105
|
getWindowKey: this.rpcs.getRpcWindowKey,
|
|
106
106
|
});
|
|
107
107
|
readonly detachedWindows = new DesktopDetachedWindowManager(this);
|
|
108
|
+
/** Each pane type's plugin, for hiding popped-out windows with their plugin. */
|
|
109
|
+
paneOwners: ReadonlyMap<string, string> = new Map();
|
|
108
110
|
|
|
109
111
|
private crashReporterInstalled = false;
|
|
110
112
|
private restartInProgress = false;
|
|
@@ -162,9 +164,11 @@ export class DesktopBackend {
|
|
|
162
164
|
async startServices(nextConfig: AppConfig): Promise<{ config: AppConfig; services: AppServices }> {
|
|
163
165
|
this.setConfig(nextConfig);
|
|
164
166
|
const config = this.requireConfig();
|
|
167
|
+
const { paneOwners, ...backendPlugins } = await loadDesktopBackendPlugins();
|
|
168
|
+
this.paneOwners = paneOwners;
|
|
165
169
|
const services = createAppServices({
|
|
166
170
|
config,
|
|
167
|
-
...
|
|
171
|
+
...backendPlugins,
|
|
168
172
|
});
|
|
169
173
|
this.services = services;
|
|
170
174
|
await services.ready;
|
|
@@ -73,7 +73,18 @@ export class DesktopDetachedWindowManager {
|
|
|
73
73
|
const config = this.backend.config;
|
|
74
74
|
if (!config) return;
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
// A pane whose plugin is switched off stays in the layout, out of sight,
|
|
77
|
+
// as it does in the main window: its window closes without undocking or
|
|
78
|
+
// removing it, and opens again where it was when the plugin is back on.
|
|
79
|
+
const disabledPlugins = new Set(config.disabledPlugins);
|
|
80
|
+
const hidden = (instanceId: string) => {
|
|
81
|
+
const paneType = findPaneInstance(config.layout, instanceId)?.paneId;
|
|
82
|
+
const owner = paneType ? this.backend.paneOwners.get(paneType) : undefined;
|
|
83
|
+
return !!owner && disabledPlugins.has(owner);
|
|
84
|
+
};
|
|
85
|
+
const desiredEntries = new Map(config.layout.detached
|
|
86
|
+
.filter((entry) => !hidden(entry.instanceId))
|
|
87
|
+
.map((entry) => [entry.instanceId, entry] as const));
|
|
77
88
|
for (const [instanceId, entry] of desiredEntries) {
|
|
78
89
|
const existingWindow = this.windows.get(instanceId);
|
|
79
90
|
if (!existingWindow) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, mkdirSync } from "fs";
|
|
2
|
-
import { homedir } from "os";
|
|
2
|
+
import { homedir, hostname } from "os";
|
|
3
3
|
import { getGloomberbDirs } from "../../../../data/config/home";
|
|
4
4
|
import type { AppServices } from "../../../../core/app-services";
|
|
5
5
|
import { restoreExtractedPlugins } from "../../../../cli/restore-plugins";
|
|
@@ -81,6 +81,7 @@ function buildInitializationPayload(
|
|
|
81
81
|
savedResources: loadSavedResources(services, config),
|
|
82
82
|
capabilityManifests: desktopRendererCapabilityManifests(services.pluginRegistry.capabilities),
|
|
83
83
|
desktopPlatform: process.platform,
|
|
84
|
+
hostName: hostname(),
|
|
84
85
|
windowKind: windowTarget.kind,
|
|
85
86
|
paneId: windowTarget.paneId,
|
|
86
87
|
telemetry: {
|
|
@@ -23,6 +23,8 @@ export interface ElectrobunBackendInit {
|
|
|
23
23
|
savedResources?: CachedResourceRecord[];
|
|
24
24
|
capabilityManifests: CapabilityManifest[];
|
|
25
25
|
desktopPlatform: string;
|
|
26
|
+
/** The machine name, which a remote assistant sees this desktop app under. */
|
|
27
|
+
hostName?: string;
|
|
26
28
|
windowKind: "main" | "detached";
|
|
27
29
|
paneId?: string;
|
|
28
30
|
/** What the view needs to send crash reports and usage counts the same way the Bun process would. */
|
|
@@ -441,7 +441,7 @@ function ShotPane({ payload, registry }: { payload: DesktopPaneShotPayload; regi
|
|
|
441
441
|
const height = payload.heightCells;
|
|
442
442
|
// PORT keeps what its basket covers, so a capture never reads as the whole account.
|
|
443
443
|
return (
|
|
444
|
-
<PaneShotFrame paneId={instance.instanceId} title={title} width={width} height={height} preserveStatus={["time-sales", "gpu", "hiring", "apps", "catalysts", "litigation", "perps", "members", "analytics"].includes(pane.id)}>
|
|
444
|
+
<PaneShotFrame paneId={instance.instanceId} title={title} width={width} height={height} statusLine={payload.statusLine === true} preserveStatus={["time-sales", "gpu", "hiring", "apps", "catalysts", "litigation", "perps", "members", "analytics"].includes(pane.id)}>
|
|
445
445
|
{(bodyFrame) => <PaneContent
|
|
446
446
|
component={pane.component}
|
|
447
447
|
paneId={instance.instanceId}
|
|
@@ -1,20 +1,116 @@
|
|
|
1
1
|
/** @jsxImportSource react */
|
|
2
|
-
import type
|
|
2
|
+
import { useEffect, useState, type ReactNode } from "react";
|
|
3
3
|
import { FloatingPaneWrapper } from "../../../components/layout/floating-pane";
|
|
4
|
-
import { PaneFooterProvider, hasPaneFooterContent } from "../../../components/layout/pane/footer";
|
|
4
|
+
import { PaneFooterProvider, hasPaneFooterContent, type PaneFooterSegment } from "../../../components/layout/pane/footer";
|
|
5
5
|
import { resolvePaneBodyFrame } from "../../../components/layout/pane/sizing";
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
declare global {
|
|
8
|
+
interface Window {
|
|
9
|
+
/**
|
|
10
|
+
* Set by a capture that carries a status line: shows the first of the
|
|
11
|
+
* variants (longest first) that fits the footer, cut in the middle when
|
|
12
|
+
* none does, and resolves to the text shown.
|
|
13
|
+
*/
|
|
14
|
+
__GLOOM_CLI_SHOT_FIT_STATUS__?: (variants: string[]) => Promise<string>;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Holds the footer row from the first frame, while the status line is still
|
|
20
|
+
* being worked out from what the pane loaded, so the body never resizes.
|
|
21
|
+
*/
|
|
22
|
+
const PENDING_STATUS = "\u00a0";
|
|
23
|
+
|
|
24
|
+
function nextFrames(count: number): Promise<void> {
|
|
25
|
+
return new Promise((resolve) => {
|
|
26
|
+
const step = (left: number) => (left <= 0 ? resolve() : requestAnimationFrame(() => step(left - 1)));
|
|
27
|
+
step(count);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Keeps the start and the end of a line that does not fit, `keep` characters in all. */
|
|
32
|
+
function middleEllipsis(text: string, keep: number): string {
|
|
33
|
+
if (keep >= text.length) return text;
|
|
34
|
+
const head = Math.ceil(keep / 2);
|
|
35
|
+
const tail = Math.max(0, keep - head);
|
|
36
|
+
return `${text.slice(0, head).trimEnd()}…${tail > 0 ? text.slice(text.length - tail).trimStart() : ""}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The first variant that fits, else the last one cut in the middle as little
|
|
41
|
+
* as it must be. `show` puts a text on screen and resolves once it is drawn.
|
|
42
|
+
*/
|
|
43
|
+
async function fitStatusLine(
|
|
44
|
+
variants: readonly string[],
|
|
45
|
+
show: (text: string) => Promise<void>,
|
|
46
|
+
overflows: () => boolean,
|
|
47
|
+
): Promise<string> {
|
|
48
|
+
for (const variant of variants) {
|
|
49
|
+
await show(variant);
|
|
50
|
+
if (!overflows()) return variant;
|
|
51
|
+
}
|
|
52
|
+
const last = variants.at(-1) ?? "";
|
|
53
|
+
let low = 1;
|
|
54
|
+
let high = last.length - 1;
|
|
55
|
+
let best = middleEllipsis(last, 1);
|
|
56
|
+
while (low <= high) {
|
|
57
|
+
const keep = Math.floor((low + high) / 2);
|
|
58
|
+
const text = middleEllipsis(last, keep);
|
|
59
|
+
await show(text);
|
|
60
|
+
if (overflows()) {
|
|
61
|
+
high = keep - 1;
|
|
62
|
+
} else {
|
|
63
|
+
best = text;
|
|
64
|
+
low = keep + 1;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
await show(best);
|
|
68
|
+
return best;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function footerOverflows(): boolean {
|
|
72
|
+
const footer = document.querySelector('[data-gloom-role="pane-footer"]');
|
|
73
|
+
if (!footer) return false;
|
|
74
|
+
return [footer, ...footer.querySelectorAll("*")].some((element) => (
|
|
75
|
+
element instanceof HTMLElement && element.scrollWidth > element.clientWidth + 1
|
|
76
|
+
));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function useShotStatusLine(enabled: boolean): string {
|
|
80
|
+
const [text, setText] = useState(PENDING_STATUS);
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
if (!enabled) return;
|
|
83
|
+
window.__GLOOM_CLI_SHOT_FIT_STATUS__ = (variants) => fitStatusLine(variants, async (next) => {
|
|
84
|
+
setText(next);
|
|
85
|
+
await nextFrames(2);
|
|
86
|
+
}, footerOverflows);
|
|
87
|
+
return () => {
|
|
88
|
+
delete window.__GLOOM_CLI_SHOT_FIT_STATUS__;
|
|
89
|
+
};
|
|
90
|
+
}, [enabled]);
|
|
91
|
+
return text;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Static captures retain warnings and optional source status without action
|
|
96
|
+
* shortcuts. With `statusLine` the footer also carries the capture's dated
|
|
97
|
+
* status line (`Fri 9 Oct close · 15 min delayed · markets closed until Mon`),
|
|
98
|
+
* inside the requested size: the body gives up the footer row for it.
|
|
99
|
+
*/
|
|
100
|
+
export function PaneShotFrame({ paneId, title, width, height, preserveStatus = false, statusLine = false, children }: {
|
|
9
101
|
paneId: string;
|
|
10
102
|
title: string;
|
|
11
103
|
width: number;
|
|
12
104
|
height: number;
|
|
13
105
|
preserveStatus?: boolean;
|
|
106
|
+
statusLine?: boolean;
|
|
14
107
|
children: (frame: ReturnType<typeof resolvePaneBodyFrame>) => ReactNode;
|
|
15
108
|
}) {
|
|
109
|
+
const statusText = useShotStatusLine(statusLine);
|
|
16
110
|
return <PaneFooterProvider>{(registeredFooter) => {
|
|
17
|
-
const
|
|
111
|
+
const status: PaneFooterSegment[] = statusLine ? [{ id: "shot-status", parts: [{ text: statusText, tone: "label" }] }] : [];
|
|
112
|
+
const info = registeredFooter.info.filter((segment) => preserveStatus || segment.icon === "warning");
|
|
113
|
+
const footer = { info: [...info, ...status], hints: [], menu: [], keys: [] };
|
|
18
114
|
const bodyFrame = resolvePaneBodyFrame({
|
|
19
115
|
width,
|
|
20
116
|
height,
|
|
@@ -210,7 +210,10 @@ async function boot() {
|
|
|
210
210
|
});
|
|
211
211
|
|
|
212
212
|
const remoteControlAdapter = init.windowKind === "main"
|
|
213
|
-
? {
|
|
213
|
+
? {
|
|
214
|
+
registerHandler: setElectrobunRemoteRequestHandler,
|
|
215
|
+
terminalDevice: { kind: "desktop" as const, name: init.hostName?.trim() || "Gloom desktop" },
|
|
216
|
+
}
|
|
214
217
|
: undefined;
|
|
215
218
|
measurePerfAsync("startup.electrobun.root-render", async () => {
|
|
216
219
|
root.render(
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { flushSync } from "@opentui/react";
|
|
1
2
|
import { useMemo, type ReactNode } from "react";
|
|
2
3
|
import { useNativeRenderer } from "../../ui";
|
|
3
4
|
import {
|
|
4
5
|
createShortcutRegistry,
|
|
5
6
|
InputHostProvider,
|
|
7
|
+
useRegisteredBeforeShortcut,
|
|
6
8
|
useRegisteredShortcut,
|
|
7
9
|
type InputHost,
|
|
8
10
|
} from "../../react/input";
|
|
@@ -10,7 +12,7 @@ import { toKeyEventLike, useKeyboard, useTerminalDimensions } from "./host";
|
|
|
10
12
|
|
|
11
13
|
export function OpenTuiInputHostProvider({ children }: { children: ReactNode }) {
|
|
12
14
|
const renderer = useNativeRenderer();
|
|
13
|
-
const shortcutRegistry = useMemo(() => createShortcutRegistry(), []);
|
|
15
|
+
const shortcutRegistry = useMemo(() => createShortcutRegistry({ flushSync }), []);
|
|
14
16
|
|
|
15
17
|
useKeyboard((event) => {
|
|
16
18
|
const shortcutEvent = toKeyEventLike(event);
|
|
@@ -22,6 +24,9 @@ export function OpenTuiInputHostProvider({ children }: { children: ReactNode })
|
|
|
22
24
|
useShortcut(handler, options) {
|
|
23
25
|
useRegisteredShortcut(shortcutRegistry, handler, options);
|
|
24
26
|
},
|
|
27
|
+
useBeforeShortcut(listener) {
|
|
28
|
+
useRegisteredBeforeShortcut(shortcutRegistry, listener);
|
|
29
|
+
},
|
|
25
30
|
useViewport() {
|
|
26
31
|
const dimensions = useTerminalDimensions();
|
|
27
32
|
return { width: dimensions.width, height: dimensions.height };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { hostname } from "os";
|
|
1
2
|
import { getGloomberbDirs } from "../../data/config/home";
|
|
2
3
|
import { existsSync, mkdirSync } from "fs";
|
|
3
4
|
import { App } from "../../app";
|
|
@@ -38,6 +39,8 @@ import {
|
|
|
38
39
|
import { currentTelemetryConfig } from "../../telemetry/live-config";
|
|
39
40
|
import { flushUsageCounts, installUsageCounter, usageCountsEnabled } from "../../telemetry/usage-counts";
|
|
40
41
|
import { attentionCountsEnabled, installAttentionCounter } from "../../telemetry/attention-counts";
|
|
42
|
+
import { starPromptEnvironmentAllows } from "../../app/star-prompt/model";
|
|
43
|
+
import { allowStarPrompt } from "../../app/star-prompt/runtime";
|
|
41
44
|
|
|
42
45
|
// Declared here rather than sniffed: the desktop view and the hosted browser
|
|
43
46
|
// app are both browser contexts but differ in what plugins may do.
|
|
@@ -56,6 +59,7 @@ export async function startOpenTuiApp({ externalPlugins, cliLaunchRequest }: Sta
|
|
|
56
59
|
const appLog = debugLog.createLogger("app");
|
|
57
60
|
appLog.info("Gloomberb starting");
|
|
58
61
|
const remoteControlAdapter: RemoteControlAdapter = {
|
|
62
|
+
terminalDevice: { kind: "tui", name: hostname() },
|
|
59
63
|
startServer: ({ dataDir, handle }) => {
|
|
60
64
|
let closed = false;
|
|
61
65
|
const serverPromise: Promise<RemoteControlServer> = startRemoteControlServer({
|
|
@@ -121,6 +125,11 @@ export async function startOpenTuiApp({ externalPlugins, cliLaunchRequest }: Sta
|
|
|
121
125
|
officialPluginIds: loadOfficialPluginIds,
|
|
122
126
|
});
|
|
123
127
|
const stopAttention = installAttentionCounter(() => attentionCountsEnabled(currentTelemetryConfig(config), process.env));
|
|
128
|
+
allowStarPrompt(starPromptEnvironmentAllows({
|
|
129
|
+
env: process.env,
|
|
130
|
+
stdinIsTTY: process.stdin.isTTY,
|
|
131
|
+
stdoutIsTTY: process.stdout.isTTY,
|
|
132
|
+
}));
|
|
124
133
|
host = await measurePerfAsync("startup.opentui.create-host", () => createOpenTuiHost());
|
|
125
134
|
const renderer = host.renderer;
|
|
126
135
|
renderer.once("destroy", stopAttention);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { PaneRuntimeState } from "../core/state/app/types";
|
|
2
|
+
import { foldPaneLayout } from "../data/config/store/fold-panes";
|
|
2
3
|
import { publicTickerBindingSymbol } from "../tickers/selection";
|
|
3
4
|
import { pinFollowingPane } from "../layout/pane-follow";
|
|
4
5
|
import {
|
|
@@ -641,28 +642,37 @@ export function materializeMarketplaceLayout(
|
|
|
641
642
|
`${paneId.slice(0, 80)}:shared-${crypto.randomUUID().replaceAll("-", "").slice(0, 20)}`
|
|
642
643
|
),
|
|
643
644
|
): { layout: LayoutConfig; paneState: Record<string, PaneRuntimeState> } {
|
|
644
|
-
|
|
645
|
+
// A layout or pane shared before SIV and SOVR folded into SI and CDX still
|
|
646
|
+
// names the old panes. It opens the way a saved layout migrates.
|
|
647
|
+
const folded = foldPaneLayout(payload.layout);
|
|
648
|
+
const source = {
|
|
649
|
+
layout: folded.layout,
|
|
650
|
+
paneState: Object.fromEntries(Object.entries(payload.paneState).flatMap(([id, state]) => (
|
|
651
|
+
folded.removed.has(id) ? [] : [[folded.idMap.get(id) ?? id, state]]
|
|
652
|
+
))),
|
|
653
|
+
};
|
|
654
|
+
const ids = new Map(source.layout.instances.map((instance, index) => [
|
|
645
655
|
instance.instanceId,
|
|
646
656
|
createId(instance.paneId, index),
|
|
647
657
|
]));
|
|
648
658
|
if (new Set(ids.values()).size !== ids.size) throw new Error("Could not create unique pane ids for this layout.");
|
|
649
659
|
const layout: LayoutConfig = {
|
|
650
|
-
dockRoot: mapDockNode(
|
|
651
|
-
instances:
|
|
660
|
+
dockRoot: mapDockNode(source.layout.dockRoot, ids),
|
|
661
|
+
instances: source.layout.instances.map((instance) => ({
|
|
652
662
|
...structuredClone(instance),
|
|
653
663
|
instanceId: ids.get(instance.instanceId)!,
|
|
654
664
|
...(instance.binding ? { binding: mapBinding(instance.binding, ids) } : {}),
|
|
655
665
|
})),
|
|
656
|
-
floating:
|
|
666
|
+
floating: source.layout.floating.map((entry) => ({
|
|
657
667
|
...entry,
|
|
658
668
|
instanceId: ids.get(entry.instanceId) ?? entry.instanceId,
|
|
659
669
|
})),
|
|
660
|
-
detached:
|
|
670
|
+
detached: source.layout.detached.map((entry) => ({
|
|
661
671
|
...entry,
|
|
662
672
|
instanceId: ids.get(entry.instanceId) ?? entry.instanceId,
|
|
663
673
|
})),
|
|
664
674
|
};
|
|
665
|
-
const paneState = Object.fromEntries(Object.entries(
|
|
675
|
+
const paneState = Object.fromEntries(Object.entries(source.paneState).flatMap(([id, state]) => {
|
|
666
676
|
const materializedId = ids.get(id);
|
|
667
677
|
return materializedId ? [[materializedId, structuredClone(state)]] : [];
|
|
668
678
|
}));
|