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
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
// the browser catalog (catalog-browser.ts). Keep this file import-free so the
|
|
3
3
|
// browser catalog can read it without pulling in native-only modules.
|
|
4
4
|
//
|
|
5
|
-
//
|
|
6
|
-
// them
|
|
5
|
+
// Screeners & Movers, Ownership & Insiders and Earnings ship fewer panes on
|
|
6
|
+
// the web, so each catalog gives them their own description rather than
|
|
7
|
+
// advertising panes they lack.
|
|
7
8
|
|
|
8
9
|
export const applicationPluginMeta = {
|
|
9
10
|
id: "application",
|
|
@@ -20,11 +21,44 @@ export const portfolioPluginMeta = {
|
|
|
20
21
|
toggleable: true,
|
|
21
22
|
};
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
// Ticker Research's successors. Every module keeps the `ticker-research`
|
|
25
|
+
// state namespace: these four declare it, Alt Data and Quant default to it,
|
|
26
|
+
// and Credit & Bonds and Earnings compose theirs with an override.
|
|
27
|
+
// `ticker-research` itself now stands for all eight in disabledPlugins (see
|
|
28
|
+
// ownership.ts). The core keeps the name people know.
|
|
29
|
+
|
|
30
|
+
export const tickerCorePluginMeta = {
|
|
31
|
+
id: "ticker-core",
|
|
32
|
+
stateId: "ticker-research",
|
|
25
33
|
name: "Ticker Research",
|
|
26
34
|
version: "1.0.0",
|
|
27
|
-
description: "Company
|
|
35
|
+
description: "Company overview, charts, financials, quotes and returns, analyst research and estimates, KPIs and segments, valuation, dividends, executives, and time and sales.",
|
|
36
|
+
toggleable: true,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const optionsVolatilityPluginMeta = {
|
|
40
|
+
id: "options-volatility",
|
|
41
|
+
stateId: "ticker-research",
|
|
42
|
+
name: "Options & Volatility",
|
|
43
|
+
version: "1.0.0",
|
|
44
|
+
description: "Option chains and positioning, a pricing calculator, scenarios, the volatility surface, and realized and implied volatility history.",
|
|
45
|
+
toggleable: true,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const ownershipPluginMeta = {
|
|
49
|
+
id: "ownership",
|
|
50
|
+
stateId: "ticker-research",
|
|
51
|
+
name: "Ownership & Insiders",
|
|
52
|
+
version: "1.0.0",
|
|
53
|
+
toggleable: true,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const filingsPluginMeta = {
|
|
57
|
+
id: "filings",
|
|
58
|
+
stateId: "ticker-research",
|
|
59
|
+
name: "Filings & Events",
|
|
60
|
+
version: "1.0.0",
|
|
61
|
+
description: "SEC filings, risk factors, 8-K events, catalysts and litigation, and M&A and distress.",
|
|
28
62
|
toggleable: true,
|
|
29
63
|
};
|
|
30
64
|
|
|
@@ -36,16 +70,92 @@ export const newsPluginMeta = {
|
|
|
36
70
|
toggleable: true,
|
|
37
71
|
};
|
|
38
72
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
73
|
+
// Market Overview's successors. Its modules keep the `market-overview` state
|
|
74
|
+
// namespace; Alt Data and Quant default to `ticker-research`, where their
|
|
75
|
+
// Ticker Research modules keep theirs, and compose their Market Overview
|
|
76
|
+
// modules with a `market-overview` override. `market-overview` itself
|
|
77
|
+
// now stands for all six in disabledPlugins (see ownership.ts).
|
|
78
|
+
|
|
79
|
+
export const globalMarketsPluginMeta = {
|
|
80
|
+
id: "global-markets",
|
|
81
|
+
stateId: "market-overview",
|
|
82
|
+
name: "Global Markets",
|
|
83
|
+
version: "1.0.0",
|
|
84
|
+
description: "World indices, the world map with chokepoint transits, sector and thematic performance, the FX matrix, and relative rotation.",
|
|
85
|
+
toggleable: true,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export const screenersPluginMeta = {
|
|
89
|
+
id: "screeners",
|
|
90
|
+
stateId: "market-overview",
|
|
91
|
+
name: "Screeners & Movers",
|
|
92
|
+
version: "1.0.0",
|
|
93
|
+
toggleable: true,
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export const futuresCommoditiesPluginMeta = {
|
|
97
|
+
id: "futures-commodities",
|
|
98
|
+
stateId: "market-overview",
|
|
99
|
+
name: "Futures & Commodities",
|
|
100
|
+
version: "1.0.0",
|
|
101
|
+
description: "Futures board and curves, CFTC positioning, and EIA petroleum and gas storage.",
|
|
102
|
+
toggleable: true,
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export const cryptoPluginMeta = {
|
|
106
|
+
id: "crypto",
|
|
107
|
+
stateId: "market-overview",
|
|
108
|
+
name: "Crypto & Perps",
|
|
109
|
+
version: "1.0.0",
|
|
110
|
+
description: "Top crypto assets by market cap, and perpetual funding, open interest and premiums.",
|
|
111
|
+
toggleable: true,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export const altDataPluginMeta = {
|
|
115
|
+
id: "alt-data",
|
|
116
|
+
stateId: "ticker-research",
|
|
117
|
+
name: "Supply Chain & Alt Data",
|
|
118
|
+
version: "1.0.0",
|
|
119
|
+
description: "Supply chains and exposure, government awards, hiring, apps and job postings, social mentions, research attention, GPU prices, and power queues.",
|
|
120
|
+
toggleable: true,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export const quantPluginMeta = {
|
|
124
|
+
id: "quant",
|
|
125
|
+
stateId: "ticker-research",
|
|
126
|
+
name: "Quant",
|
|
127
|
+
version: "1.0.0",
|
|
128
|
+
description: "Correlation matrix and relationship graphs, backtests, seasonality, and moves on macro release days.",
|
|
129
|
+
toggleable: true,
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// Macro's successors keep its state namespace, so the pane state, settings and
|
|
133
|
+
// caches saved while they were one plugin stay theirs; the Ticker Research
|
|
134
|
+
// modules Credit & Bonds and Earnings hold keep `ticker-research`. `macro` itself now
|
|
135
|
+
// stands for all three in disabledPlugins (see ownership.ts).
|
|
136
|
+
|
|
137
|
+
export const ratesMacroPluginMeta = {
|
|
138
|
+
id: "rates-macro",
|
|
139
|
+
stateId: "macro",
|
|
140
|
+
name: "Rates & Macro",
|
|
141
|
+
version: "1.0.0",
|
|
142
|
+
description: "Economic calendar and statistics, consumer prices, yield curve, rate path, money markets, central bank rates, volatility, and market valuation.",
|
|
143
|
+
toggleable: true,
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export const creditPluginMeta = {
|
|
147
|
+
id: "credit",
|
|
148
|
+
stateId: "macro",
|
|
149
|
+
name: "Credit & Bonds",
|
|
42
150
|
version: "1.0.0",
|
|
151
|
+
description: "Single-name, index and sovereign CDS, credit spreads, Treasury auctions, a bond calculator, credit documents and covenants, and debt maturities.",
|
|
43
152
|
toggleable: true,
|
|
44
153
|
};
|
|
45
154
|
|
|
46
|
-
export const
|
|
47
|
-
id: "
|
|
48
|
-
|
|
155
|
+
export const earningsPluginMeta = {
|
|
156
|
+
id: "earnings",
|
|
157
|
+
stateId: "macro",
|
|
158
|
+
name: "Earnings",
|
|
49
159
|
version: "1.0.0",
|
|
50
160
|
toggleable: true,
|
|
51
161
|
};
|
|
@@ -1145,7 +1145,7 @@ export function ChartComposerPane({ paneId, focused, width, height }: PaneProps)
|
|
|
1145
1145
|
}
|
|
1146
1146
|
}, [follows, ownedIds, savedIds, setSpec, spec, stored, target, unlinkedFrom, updateSettings]);
|
|
1147
1147
|
if (follows && !target && ownedIds.length > 0) {
|
|
1148
|
-
return <EmptyState title={error ?? "
|
|
1148
|
+
return <EmptyState title={error ?? "Select a ticker."} />;
|
|
1149
1149
|
}
|
|
1150
1150
|
return (
|
|
1151
1151
|
<ChartComposerSurface
|
|
@@ -3,9 +3,10 @@ import type { ChatChannel, ChatChannelState } from "../../../api-client";
|
|
|
3
3
|
import type { AppConfig } from "../../../types/config";
|
|
4
4
|
import { t } from "../../../i18n";
|
|
5
5
|
import { chatController } from "./controller";
|
|
6
|
-
import { formatChannelLabel } from "./channel-labels";
|
|
7
6
|
import { describeConversationStartError, knownConversationRefusal } from "./direct-messages";
|
|
8
7
|
import { buildChatUserByUsername } from "./content/user-map";
|
|
8
|
+
import { chatConversationRow, chatNewDmRow, openChatConversation, openChatNewDm } from "./command-bar";
|
|
9
|
+
import { buildChatConversations, selectChatConversations } from "./conversations";
|
|
9
10
|
|
|
10
11
|
export const DEFAULT_CHAT_CHANNEL_ID = "everyone";
|
|
11
12
|
export const LAST_VISITED_CHAT_CHANNEL_KEY = "lastChatChannelId";
|
|
@@ -42,20 +43,6 @@ export function getPreferredChatOpenChannelId(
|
|
|
42
43
|
return normalizeChannelId(unreadConversation?.channelId ?? unreadStates[0]?.channelId ?? getLastVisitedChatChannelId(config));
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
function conversationDetail(channel: ChatChannel): string {
|
|
46
|
-
if (channel.kind === "direct") {
|
|
47
|
-
const displayName = channel.dmUser?.displayName?.trim();
|
|
48
|
-
const username = channel.dmUser?.username?.trim();
|
|
49
|
-
return [displayName && username ? displayName : null, t("Direct message")].filter(Boolean).join(" - ");
|
|
50
|
-
}
|
|
51
|
-
const members = (channel.members ?? [])
|
|
52
|
-
.map((member) => member.username ? `@${member.username}` : member.displayName)
|
|
53
|
-
.filter(Boolean)
|
|
54
|
-
.slice(0, 4);
|
|
55
|
-
const suffix = (channel.members?.length ?? 0) > members.length ? ` +${(channel.members?.length ?? 0) - members.length}` : "";
|
|
56
|
-
return members.length > 0 ? `${t("Group chat")} - ${members.join(", ")}${suffix}` : t("Group chat");
|
|
57
|
-
}
|
|
58
|
-
|
|
59
46
|
export function parseDmUsernames(value: string): string[] {
|
|
60
47
|
const usernames = new Set<string>();
|
|
61
48
|
for (const rawPart of value.split(/[\s,]+/)) {
|
|
@@ -106,18 +93,40 @@ export async function openDmTargetFromCommand(ctx: GloomPluginContext, usernames
|
|
|
106
93
|
} catch (error) {
|
|
107
94
|
throw new Error(describeConversationStartError(error, usernames, userByUsername));
|
|
108
95
|
}
|
|
109
|
-
|
|
96
|
+
openChatConversation(ctx, channel.id);
|
|
110
97
|
}
|
|
111
98
|
|
|
99
|
+
/**
|
|
100
|
+
* `DM` in the command bar. Bare, it lists your DMs and groups, unread first,
|
|
101
|
+
* then the latest, and ends with New DM, which opens the Chat pane's dialog.
|
|
102
|
+
* With names after it, the conversations those names find come first, then
|
|
103
|
+
* the row that starts or opens a DM with exactly what was typed.
|
|
104
|
+
*/
|
|
112
105
|
export function buildDmCommandResults(ctx: GloomPluginContext, arg: string): CommandResultDef[] {
|
|
106
|
+
const list = chatController.listConversations();
|
|
107
|
+
const conversations = list
|
|
108
|
+
? buildChatConversations(list.states, list.userId)
|
|
109
|
+
.filter((conversation) => conversation.kind === "direct" || conversation.kind === "group")
|
|
110
|
+
: [];
|
|
111
|
+
const open = (channelId: string) => openChatConversation(ctx, channelId);
|
|
112
|
+
const category = "Chat";
|
|
113
113
|
const trimmed = arg.trim();
|
|
114
114
|
if (trimmed) {
|
|
115
115
|
const usernames = parseDmUsernames(trimmed);
|
|
116
116
|
const valid = hasOnlyDmUsernameArgs(trimmed) && usernames.length > 0;
|
|
117
|
+
const found = selectChatConversations(conversations, { kind: "search", text: trimmed });
|
|
118
|
+
const rows: CommandResultDef[] = found.map((conversation) => ({
|
|
119
|
+
...chatConversationRow(conversation, open),
|
|
120
|
+
category,
|
|
121
|
+
}));
|
|
122
|
+
// The DM you already share is the row above; a second one would open it too.
|
|
123
|
+
const alreadyShared = usernames.length === 1
|
|
124
|
+
&& found.some((conversation) => conversation.kind === "direct" && conversation.username === usernames[0]);
|
|
125
|
+
if (alreadyShared) return rows;
|
|
117
126
|
const label = usernames.length <= 1
|
|
118
127
|
? `DM ${usernames[0] ? `@${usernames[0]}` : trimmed}`
|
|
119
128
|
: `Group ${usernames.map((username) => `@${username}`).join(", ")}`;
|
|
120
|
-
return [{
|
|
129
|
+
return [...rows, {
|
|
121
130
|
id: `start:${valid ? usernames.join(",") : trimmed}`,
|
|
122
131
|
label,
|
|
123
132
|
detail: valid
|
|
@@ -125,40 +134,29 @@ export function buildDmCommandResults(ctx: GloomPluginContext, arg: string): Com
|
|
|
125
134
|
? t("Start or open direct message")
|
|
126
135
|
: t("Start group chat")
|
|
127
136
|
: t("Use @username, or multiple usernames for a group chat"),
|
|
128
|
-
category
|
|
129
|
-
|
|
137
|
+
category,
|
|
138
|
+
// No tag: it starts a conversation, so it must not read as one.
|
|
139
|
+
right: "",
|
|
130
140
|
disabled: !valid,
|
|
131
141
|
execute: () => openDmTargetFromCommand(ctx, usernames),
|
|
132
142
|
}];
|
|
133
143
|
}
|
|
134
144
|
|
|
135
|
-
|
|
136
|
-
.filter((channel) => channel.kind === "direct" || channel.kind === "group");
|
|
137
|
-
if (conversations.length === 0) {
|
|
145
|
+
if (!list) {
|
|
138
146
|
return [{
|
|
139
147
|
id: "empty",
|
|
140
148
|
label: t("No DMs yet"),
|
|
141
149
|
detail: t("Type DM @username to start one"),
|
|
142
|
-
category
|
|
150
|
+
category,
|
|
143
151
|
right: "DM",
|
|
144
152
|
disabled: true,
|
|
145
153
|
execute: () => {},
|
|
146
154
|
}];
|
|
147
155
|
}
|
|
148
156
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
detail: conversationDetail(channel),
|
|
153
|
-
category: t("Conversations"),
|
|
154
|
-
right: channel.kind === "group" ? t("Group") : t("DM"),
|
|
155
|
-
keywords: [
|
|
156
|
-
channel.name,
|
|
157
|
-
channel.dmUser?.username ?? "",
|
|
158
|
-
...(channel.members ?? []).map((member) => member.username ?? member.displayName),
|
|
159
|
-
],
|
|
160
|
-
execute: () => openChatChannelFromCommand(ctx, channel.id),
|
|
161
|
-
}));
|
|
157
|
+
const listed = selectChatConversations(conversations, { kind: "listing" })
|
|
158
|
+
.map((conversation) => ({ ...chatConversationRow(conversation, open), category }));
|
|
159
|
+
return [...listed, { ...chatNewDmRow(() => openChatNewDm(ctx)), category }];
|
|
162
160
|
}
|
|
163
161
|
|
|
164
162
|
/**
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { t, tf } from "../../../i18n";
|
|
2
|
+
import type {
|
|
3
|
+
CommandBarResultDef,
|
|
4
|
+
CommandBarSearchProvider,
|
|
5
|
+
GloomPluginContext,
|
|
6
|
+
} from "../../../types/plugin";
|
|
7
|
+
import { chatController } from "./controller";
|
|
8
|
+
import {
|
|
9
|
+
buildChatConversations,
|
|
10
|
+
parseChatConversationQuery,
|
|
11
|
+
selectChatConversations,
|
|
12
|
+
type ChatConversation,
|
|
13
|
+
type ChatConversationKind,
|
|
14
|
+
type ChatConversationList,
|
|
15
|
+
} from "./conversations";
|
|
16
|
+
import { requestChatPaneAction } from "./pane-requests";
|
|
17
|
+
|
|
18
|
+
const CHAT_PANE_TEMPLATE_ID = "new-chat-pane";
|
|
19
|
+
/**
|
|
20
|
+
* Pane template value: show the conversation in a Chat pane that is already
|
|
21
|
+
* open (switching it over) rather than in a pane of its own.
|
|
22
|
+
*/
|
|
23
|
+
export const CHAT_REUSE_PANE_VALUE = "reusePane";
|
|
24
|
+
const CHAT_SEARCH_PROVIDER_ID = "chat:conversations";
|
|
25
|
+
|
|
26
|
+
type OpenChatPane = Pick<GloomPluginContext, "createPaneFromTemplate">;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Shows a conversation with its composer ready: in the open Chat pane, which
|
|
30
|
+
* switches to it, or in a new one when none is open.
|
|
31
|
+
*/
|
|
32
|
+
export function openChatConversation(ctx: OpenChatPane, channelId: string): void {
|
|
33
|
+
requestChatPaneAction({ action: "compose", channelId });
|
|
34
|
+
ctx.createPaneFromTemplate(CHAT_PANE_TEMPLATE_ID, {
|
|
35
|
+
arg: channelId,
|
|
36
|
+
values: { [CHAT_REUSE_PANE_VALUE]: "1" },
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The Chat pane's own New DM dialog, in the open Chat pane or a new one. */
|
|
41
|
+
export function openChatNewDm(ctx: OpenChatPane): void {
|
|
42
|
+
requestChatPaneAction({ action: "new-dm" });
|
|
43
|
+
ctx.createPaneFromTemplate(CHAT_PANE_TEMPLATE_ID, { values: { [CHAT_REUSE_PANE_VALUE]: "1" } });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const BADGES: Record<ChatConversationKind, string> = {
|
|
47
|
+
channel: "CHAT",
|
|
48
|
+
team: "TEAM",
|
|
49
|
+
direct: "DM",
|
|
50
|
+
group: "GROUP",
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const DETAILS: Record<ChatConversationKind, string> = {
|
|
54
|
+
channel: "Channel",
|
|
55
|
+
team: "Team channel",
|
|
56
|
+
direct: "Direct message",
|
|
57
|
+
group: "Group chat",
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
function unreadLabel(count: number): string {
|
|
61
|
+
return tf("{count} unread", { count: count > 99 ? "99+" : String(count) });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* One conversation as a command-bar row: a tag that tells channels from DMs,
|
|
66
|
+
* the name the Chat pane shows, and the unread count on the right. `right` is
|
|
67
|
+
* empty rather than unset so a command's shortcut never stands in for it.
|
|
68
|
+
*/
|
|
69
|
+
export function chatConversationRow(
|
|
70
|
+
conversation: ChatConversation,
|
|
71
|
+
open: (channelId: string) => void,
|
|
72
|
+
): CommandBarResultDef {
|
|
73
|
+
return {
|
|
74
|
+
id: `conversation:${conversation.channelId}`,
|
|
75
|
+
label: conversation.title,
|
|
76
|
+
...(conversation.name ? { name: conversation.name } : {}),
|
|
77
|
+
badge: BADGES[conversation.kind],
|
|
78
|
+
detail: t(DETAILS[conversation.kind]),
|
|
79
|
+
right: conversation.unreadCount > 0 ? unreadLabel(conversation.unreadCount) : "",
|
|
80
|
+
keywords: [...conversation.words, "chat", "messages"],
|
|
81
|
+
execute: () => open(conversation.channelId),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function chatNewDmRow(open: () => void): CommandBarResultDef {
|
|
86
|
+
return {
|
|
87
|
+
id: "new-dm",
|
|
88
|
+
label: t("New DM"),
|
|
89
|
+
detail: t("Start a direct or group chat"),
|
|
90
|
+
right: "",
|
|
91
|
+
keywords: ["new", "dm", "direct", "message", "group", "chat"],
|
|
92
|
+
execute: open,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
interface ChatSearchSource {
|
|
97
|
+
listConversations(): ChatConversationList | null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* The conversations that answer the command bar's text, from the chat state
|
|
102
|
+
* the Chat pane already keeps: nothing is asked of the server. Signed out or
|
|
103
|
+
* unverified there is nothing, and the Chat pane command is all there is.
|
|
104
|
+
*/
|
|
105
|
+
export function buildChatSearchRows(
|
|
106
|
+
ctx: OpenChatPane,
|
|
107
|
+
query: string,
|
|
108
|
+
source: ChatSearchSource = chatController,
|
|
109
|
+
): CommandBarResultDef[] {
|
|
110
|
+
const list = source.listConversations();
|
|
111
|
+
if (!list) return [];
|
|
112
|
+
const parsed = parseChatConversationQuery(query);
|
|
113
|
+
const conversations = selectChatConversations(buildChatConversations(list.states, list.userId), parsed);
|
|
114
|
+
const rows = conversations.map((conversation) => chatConversationRow(conversation, (channelId) => {
|
|
115
|
+
openChatConversation(ctx, channelId);
|
|
116
|
+
}));
|
|
117
|
+
return parsed.kind === "listing" ? [...rows, chatNewDmRow(() => openChatNewDm(ctx))] : rows;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Channels and DMs in the command bar: typing part of a channel or a person
|
|
122
|
+
* finds the conversation, "chat" and "messages" list them, and the empty bar
|
|
123
|
+
* shows the unread and latest ones. Answered from memory on every keystroke.
|
|
124
|
+
*/
|
|
125
|
+
export function createChatSearchProvider(
|
|
126
|
+
ctx: OpenChatPane,
|
|
127
|
+
source: ChatSearchSource = chatController,
|
|
128
|
+
): CommandBarSearchProvider {
|
|
129
|
+
const match = (query: string) => buildChatSearchRows(ctx, query, source);
|
|
130
|
+
return {
|
|
131
|
+
id: CHAT_SEARCH_PROVIDER_ID,
|
|
132
|
+
category: "Chat",
|
|
133
|
+
minQueryLength: 0,
|
|
134
|
+
shortcuts: ["CHAT"],
|
|
135
|
+
match,
|
|
136
|
+
provide: async (query) => match(query),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
@@ -1,13 +1,33 @@
|
|
|
1
1
|
export type ChatComposerCommand =
|
|
2
2
|
| { kind: "direct"; username: string; draft: string }
|
|
3
|
-
| { kind: "group"; usernames: string[]; name: string | undefined }
|
|
3
|
+
| { kind: "group"; usernames: string[]; name: string | undefined }
|
|
4
|
+
| DiscordComposerCommand;
|
|
5
|
+
|
|
6
|
+
/** `usage` is anything after /discord that is not a subcommand: it answers with the usage instead of going to the channel as text. */
|
|
7
|
+
export type DiscordComposerCommand =
|
|
8
|
+
| { kind: "discord"; action: "connect" | "unlink" | "usage" }
|
|
9
|
+
| { kind: "discord"; action: "mirror"; enabled: boolean };
|
|
4
10
|
|
|
5
11
|
const USERNAME_PATTERN = "[A-Za-z][A-Za-z0-9_]{2,29}";
|
|
6
12
|
const DIRECT_COMMAND_RE = new RegExp(`^/dm\\s+@?(${USERNAME_PATTERN})(?:\\s+([\\s\\S]+))?$`, "i");
|
|
7
13
|
const GROUP_COMMAND_RE = /^\/group\s+(.+)$/i;
|
|
14
|
+
const DISCORD_COMMAND_RE = /^\/discord(?:\s+([\s\S]*))?$/i;
|
|
8
15
|
const GROUP_USERNAME_RE = new RegExp(`@(${USERNAME_PATTERN})`, "g");
|
|
9
16
|
|
|
17
|
+
function parseDiscordArguments(rest: string): DiscordComposerCommand {
|
|
18
|
+
const [subcommand, value, ...extra] = rest.toLowerCase().split(/\s+/).filter(Boolean);
|
|
19
|
+
if (!subcommand) return { kind: "discord", action: "connect" };
|
|
20
|
+
if (subcommand === "unlink" && value === undefined) return { kind: "discord", action: "unlink" };
|
|
21
|
+
if (subcommand === "mirror" && extra.length === 0 && (value === "on" || value === "off")) {
|
|
22
|
+
return { kind: "discord", action: "mirror", enabled: value === "on" };
|
|
23
|
+
}
|
|
24
|
+
return { kind: "discord", action: "usage" };
|
|
25
|
+
}
|
|
26
|
+
|
|
10
27
|
export function parseChatComposerCommand(content: string): ChatComposerCommand | null {
|
|
28
|
+
const discordCommand = content.match(DISCORD_COMMAND_RE);
|
|
29
|
+
if (discordCommand) return parseDiscordArguments(discordCommand[1] ?? "");
|
|
30
|
+
|
|
11
31
|
const directCommand = content.match(DIRECT_COMMAND_RE);
|
|
12
32
|
if (directCommand) {
|
|
13
33
|
return {
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
COMPOSER_ACTION_WIDTH,
|
|
6
6
|
formatInlinePreview,
|
|
7
7
|
} from "../layout";
|
|
8
|
-
import { parseChatComposerCommand } from "../composer-commands";
|
|
8
|
+
import { parseChatComposerCommand, type DiscordComposerCommand } from "../composer-commands";
|
|
9
9
|
import { t, tf } from "../../../../i18n";
|
|
10
10
|
import type { ChatContentController } from "./types";
|
|
11
11
|
import { getComposerCursorOffset, moveComposerCursorToOffset } from "./composer-cursor";
|
|
@@ -45,6 +45,7 @@ export function useChatComposerRuntime({
|
|
|
45
45
|
draftAttachmentCount = 0,
|
|
46
46
|
onPastedImagePaths,
|
|
47
47
|
onConversationStartError,
|
|
48
|
+
onDiscordCommand,
|
|
48
49
|
}: {
|
|
49
50
|
applyingExternalDraftRef: MutableRef<boolean>;
|
|
50
51
|
blurInput: () => void;
|
|
@@ -77,6 +78,8 @@ export function useChatComposerRuntime({
|
|
|
77
78
|
onPastedImagePaths?: (paths: string[], pasted: string) => void;
|
|
78
79
|
/** A `/dm` or `/group` command the server refused, with the names it asked for. */
|
|
79
80
|
onConversationStartError?: (error: unknown, usernames: string[]) => void;
|
|
81
|
+
/** Runs a /discord command. Without it the command is sent as text. */
|
|
82
|
+
onDiscordCommand?: (command: DiscordComposerCommand) => void;
|
|
80
83
|
}) {
|
|
81
84
|
const replyToRef = useRef(replyTo);
|
|
82
85
|
const draftAttachmentCountRef = useRef(draftAttachmentCount);
|
|
@@ -262,6 +265,11 @@ export function useChatComposerRuntime({
|
|
|
262
265
|
}).catch((error: unknown) => onConversationStartError?.(error, [composerCommand.username]));
|
|
263
266
|
return;
|
|
264
267
|
}
|
|
268
|
+
if (composerCommand?.kind === "discord" && onDiscordCommand) {
|
|
269
|
+
replaceComposerDraft("");
|
|
270
|
+
onDiscordCommand(composerCommand);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
265
273
|
if (composerCommand?.kind === "group") {
|
|
266
274
|
void controller.openGroupChannel({
|
|
267
275
|
usernames: composerCommand.usernames,
|
|
@@ -293,8 +301,10 @@ export function useChatComposerRuntime({
|
|
|
293
301
|
inputValueRef,
|
|
294
302
|
onChannelChange,
|
|
295
303
|
onConversationStartError,
|
|
304
|
+
onDiscordCommand,
|
|
296
305
|
expandDirectSection,
|
|
297
306
|
persistDraft,
|
|
307
|
+
replaceComposerDraft,
|
|
298
308
|
setEditingMessage,
|
|
299
309
|
setFollowMessages,
|
|
300
310
|
setSelectedIdx,
|
|
@@ -6,6 +6,7 @@ import type { InlineTickerCatalogEntry } from "../../../../state/hooks/inline-ti
|
|
|
6
6
|
import type { ContextMenuItem } from "../../../../types/context-menu";
|
|
7
7
|
import { useRendererHost, useUiCapabilities } from "../../../../ui";
|
|
8
8
|
import { useOptionalDialog, type PromptContext } from "../../../../ui/dialog";
|
|
9
|
+
import { chatAuthorName, isDiscordGhost } from "../ghost-user";
|
|
9
10
|
import { chatMessageOpenTargets, type ChatOpenTarget } from "./open-targets";
|
|
10
11
|
import { openChatImageViewer } from "../attachments/desktop";
|
|
11
12
|
|
|
@@ -105,7 +106,7 @@ export function useChatFooter({
|
|
|
105
106
|
attachments: selectedMessage.attachments,
|
|
106
107
|
index: target.index,
|
|
107
108
|
caption: selectedMessage.content,
|
|
108
|
-
author: selectedMessage.user
|
|
109
|
+
author: chatAuthorName(selectedMessage.user),
|
|
109
110
|
});
|
|
110
111
|
return;
|
|
111
112
|
}
|
|
@@ -135,7 +136,8 @@ export function useChatFooter({
|
|
|
135
136
|
if (target) openTarget(target);
|
|
136
137
|
}, [dialog, openTarget, openTargets]);
|
|
137
138
|
|
|
138
|
-
|
|
139
|
+
// A Discord ghost has no card to show.
|
|
140
|
+
const author = selectedMessage && !isDiscordGhost(selectedMessage.user) ? selectedMessage.user : null;
|
|
139
141
|
// A card opened from the keyboard follows the selection: moving on closes it.
|
|
140
142
|
const keyboardProfileRef = useRef(false);
|
|
141
143
|
const toggleAuthorProfile = useCallback(() => {
|
|
@@ -215,7 +217,7 @@ export function useChatFooter({
|
|
|
215
217
|
else if (canSend) hints.push({ id: "reply", key: "Enter", label: "reply", title: "Reply", onPress: () => latest.current.beginReplyTo(latest.current.selectedIdx, { deferFocus: true }) });
|
|
216
218
|
if (canEdit) hints.push({ id: "edit", key: "e", label: "dit", title: "Edit Message", onPress: () => { latest.current.beginEditMessage(latest.current.selectedIdx, { deferFocus: true }); } });
|
|
217
219
|
if (openTargets.length > 0) hints.push({ id: "open", key: "o", label: "pen", title: openTargetTitle, onPress: () => { void latest.current.openSelectedTargets(); } });
|
|
218
|
-
// Every author has a card, if only their name and a way to write to them.
|
|
220
|
+
// Every author but a Discord ghost has a card, if only their name and a way to write to them.
|
|
219
221
|
if (author) hints.push({ id: "profile", key: "p", label: "rofile", title: "Show Profile", onPress: () => latest.current.toggleAuthorProfile() });
|
|
220
222
|
} else if (!composing && canSend) {
|
|
221
223
|
hints.push({ id: "compose", key: "i", label: " compose", title: "Compose", onPress: () => queueMicrotask(() => latest.current.focusComposer()) });
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Box, Text, useContextMenu, useUiCapabilities } from "../../../../ui";
|
|
1
|
+
import { Box, Text, useContextMenu, useRendererHost, useUiCapabilities } from "../../../../ui";
|
|
2
2
|
import { useState, useEffect, useRef, useCallback, useMemo, useSyncExternalStore } from "react";
|
|
3
3
|
import { PageStackView } from "../../../../components/ui";
|
|
4
4
|
import { type ScrollBoxRenderable, type TextareaRenderable } from "../../../../ui";
|
|
@@ -33,12 +33,15 @@ import {
|
|
|
33
33
|
} from "./layout-metrics";
|
|
34
34
|
import { buildChatUserByUsername } from "./user-map";
|
|
35
35
|
import { useChatComposerRuntime } from "./composer-runtime";
|
|
36
|
+
import { runDiscordCommand } from "../discord-command";
|
|
37
|
+
import type { DiscordComposerCommand } from "../composer-commands";
|
|
36
38
|
import { useChatMessageSelection } from "./selection-runtime";
|
|
37
39
|
import type { ChatAttachment, ChatMessage, ChatUserSummary } from "../../../../api-client";
|
|
38
40
|
import { DesktopChatDropOverlay, DesktopChatDropTarget } from "../attachments/desktop";
|
|
39
41
|
import { uploadFromTransferFile, type TransferFile } from "../attachments/transfer";
|
|
40
42
|
import { readChatImageFiles } from "../attachments/files";
|
|
41
43
|
import { NewDmDialog } from "./new-dm-dialog";
|
|
44
|
+
import { takeChatPaneRequest, usePendingChatPaneRequest } from "../pane-requests";
|
|
42
45
|
import {
|
|
43
46
|
describeConversationStartError,
|
|
44
47
|
directMessageAction,
|
|
@@ -93,6 +96,7 @@ export function ChatContent({
|
|
|
93
96
|
channelIdRef.current = channelId;
|
|
94
97
|
const initialSnapshot = controller.getSnapshot(channelId);
|
|
95
98
|
const { nativePaneChrome } = useUiCapabilities();
|
|
99
|
+
const rendererHost = useRendererHost();
|
|
96
100
|
const [inputFocused, setInputFocused] = useState(false);
|
|
97
101
|
const [selectedIdx, setSelectedIdx] = useState(-1);
|
|
98
102
|
const [hoveredIdx, setHoveredIdx] = useState<number | null>(null);
|
|
@@ -461,6 +465,10 @@ export function ChatContent({
|
|
|
461
465
|
if (controller.attachToChannel(channelIdRef.current, files.map(uploadFromTransferFile)) > 0) focusInput();
|
|
462
466
|
}, [canAttach, controller, focusInput, imagesAvailable, notify]);
|
|
463
467
|
|
|
468
|
+
const handleDiscordCommand = useCallback((command: DiscordComposerCommand) => {
|
|
469
|
+
void runDiscordCommand(command, { openUrl: (url) => rendererHost.openExternal(url) }).then(notify);
|
|
470
|
+
}, [notify, rendererHost]);
|
|
471
|
+
|
|
464
472
|
// The terminal: a pasted or dropped path to an image file attaches the file
|
|
465
473
|
// and leaves the composer. A path that is no file stays as typed.
|
|
466
474
|
const handlePastedImagePaths = useCallback((paths: string[], pasted: string) => {
|
|
@@ -543,6 +551,7 @@ export function ChatContent({
|
|
|
543
551
|
draftAttachmentCount: draftAttachments.length,
|
|
544
552
|
onPastedImagePaths: !nativePaneChrome && canAttach ? handlePastedImagePaths : undefined,
|
|
545
553
|
onConversationStartError: reportConversationStartError,
|
|
554
|
+
onDiscordCommand: handleDiscordCommand,
|
|
546
555
|
});
|
|
547
556
|
replaceComposerDraftRef.current = replaceComposerDraft;
|
|
548
557
|
|
|
@@ -558,6 +567,17 @@ export function ChatContent({
|
|
|
558
567
|
if (canSend) focusComposer();
|
|
559
568
|
}, [canSend, closeNewDmDialog, closeProfilePopover, expandDirectSection, focusComposer, selectSidebarChannel, setSidebarFocused]);
|
|
560
569
|
|
|
570
|
+
// Asked for from outside the pane (the command bar): once this pane has the
|
|
571
|
+
// keyboard on that conversation, the composer takes it, or New DM opens.
|
|
572
|
+
const pendingPaneRequest = usePendingChatPaneRequest();
|
|
573
|
+
useEffect(() => {
|
|
574
|
+
if (!pendingPaneRequest || !focused || commandBarOpen) return;
|
|
575
|
+
const request = takeChatPaneRequest(channelId);
|
|
576
|
+
if (!request || !canSend) return;
|
|
577
|
+
if (request.action === "new-dm") openNewDmDialog();
|
|
578
|
+
else showConversation(channelId);
|
|
579
|
+
}, [canSend, channelId, commandBarOpen, focused, openNewDmDialog, pendingPaneRequest, showConversation]);
|
|
580
|
+
|
|
561
581
|
const openConversationFromDialog = useCallback(async (usernames: string[]) => {
|
|
562
582
|
const channel = usernames.length === 1
|
|
563
583
|
? await controller.openDirectChannel({ username: usernames[0] })
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ChatChannel, ChatMessage } from "../../../../api-client";
|
|
2
|
+
import { isDiscordGhost } from "../ghost-user";
|
|
2
3
|
|
|
3
4
|
export interface ChatMentionSuggestion {
|
|
4
5
|
username: string;
|
|
@@ -49,7 +50,7 @@ export function buildRecentMentionSuggestions({
|
|
|
49
50
|
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
50
51
|
const user = messages[index]?.user;
|
|
51
52
|
const username = normalizeUsername(user?.username);
|
|
52
|
-
if (!user || !username || user.id === currentUserId) continue;
|
|
53
|
+
if (!user || !username || user.id === currentUserId || isDiscordGhost(user)) continue;
|
|
53
54
|
const key = username.toLowerCase();
|
|
54
55
|
if (seen.has(key)) continue;
|
|
55
56
|
seen.add(key);
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { ChatChannel, ChatMessage, ChatUserSummary } from "../../../../api-client";
|
|
2
|
+
import { isDiscordGhost } from "../ghost-user";
|
|
2
3
|
|
|
4
|
+
/** Who a name can open a profile for or start a conversation with: Discord ghosts are left out. */
|
|
3
5
|
export function buildChatUserByUsername(
|
|
4
6
|
channels: ChatChannel[],
|
|
5
7
|
messages: ChatMessage[],
|
|
@@ -7,7 +9,7 @@ export function buildChatUserByUsername(
|
|
|
7
9
|
const map = new Map<string, ChatUserSummary>();
|
|
8
10
|
for (const message of messages) {
|
|
9
11
|
const username = message.user.username?.toLowerCase();
|
|
10
|
-
if (username) map.set(username, message.user);
|
|
12
|
+
if (username && !isDiscordGhost(message.user)) map.set(username, message.user);
|
|
11
13
|
}
|
|
12
14
|
for (const channel of channels) {
|
|
13
15
|
for (const member of channel.members ?? []) {
|