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
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { cloneLayout, createBlankLayout, type SavedLayout } from "../../../types/config";
|
|
1
|
+
import { cloneLayout, createBlankLayout, getPlacedPaneInstanceIds, type SavedLayout } from "../../../types/config";
|
|
2
|
+
import { movePaneBack, movePaneToOwnLayout, paneMoveBackTarget } from "../../../layout/pane-layout-move";
|
|
2
3
|
import {
|
|
3
4
|
clonePaneStateMap,
|
|
4
5
|
cloneSavedLayout,
|
|
@@ -6,11 +7,12 @@ import {
|
|
|
6
7
|
moveHistoryIndex,
|
|
7
8
|
movedIndex,
|
|
8
9
|
removeHistoryIndex,
|
|
10
|
+
resolveTickerForPane,
|
|
9
11
|
setHistoryForIndex,
|
|
10
12
|
syncConfigActiveLayoutState,
|
|
11
13
|
withFocusedPane,
|
|
12
14
|
} from "./layout";
|
|
13
|
-
import type { AppAction, AppState } from "./types";
|
|
15
|
+
import type { AppAction, AppState, PaneRuntimeState } from "./types";
|
|
14
16
|
|
|
15
17
|
const MAX_LAYOUT_HISTORY = 50;
|
|
16
18
|
|
|
@@ -293,7 +295,8 @@ export function reduceLayoutAction(state: AppState, action: AppAction): AppState
|
|
|
293
295
|
state.paneState,
|
|
294
296
|
state.focusedPaneId,
|
|
295
297
|
);
|
|
296
|
-
const source = currentConfig.layouts[action.index]!;
|
|
298
|
+
const { id: _id, ...source } = currentConfig.layouts[action.index]!;
|
|
299
|
+
// A copy is a layout of its own: sharing the id would send Move Back to either.
|
|
297
300
|
const duplicate: SavedLayout = {
|
|
298
301
|
...cloneSavedLayout(source),
|
|
299
302
|
name: `${source.name} Copy`,
|
|
@@ -313,6 +316,89 @@ export function reduceLayoutAction(state: AppState, action: AppAction): AppState
|
|
|
313
316
|
});
|
|
314
317
|
}
|
|
315
318
|
|
|
319
|
+
case "MOVE_PANE_TO_NEW_LAYOUT": {
|
|
320
|
+
const resolveSymbol = (instanceId: string) => resolveTickerForPane(state, instanceId);
|
|
321
|
+
const currentConfig = syncConfigActiveLayoutState(state.config, state.paneState, state.focusedPaneId);
|
|
322
|
+
const sourceIndex = currentConfig.activeLayoutIndex;
|
|
323
|
+
const sourceEntry = currentConfig.layouts[sourceIndex]!;
|
|
324
|
+
const sourceLayoutId = sourceEntry.id ?? action.sourceLayoutId;
|
|
325
|
+
const move = movePaneToOwnLayout(state.config.layout, action.paneId, sourceLayoutId, resolveSymbol);
|
|
326
|
+
if (!move) return state;
|
|
327
|
+
|
|
328
|
+
const { [action.paneId]: savedPaneState, ...sourcePaneState } = sourceEntry.paneState ?? {};
|
|
329
|
+
// The live object, so the pane's state keeps its identity across the move.
|
|
330
|
+
const livePaneState = (state.paneState[action.paneId] ?? savedPaneState) as PaneRuntimeState | undefined;
|
|
331
|
+
const movedPaneState: Record<string, PaneRuntimeState> = livePaneState ? { [action.paneId]: livePaneState } : {};
|
|
332
|
+
const previous = state.previousFocusedPaneId;
|
|
333
|
+
const sourceFocus = previous && getPlacedPaneInstanceIds(move.source).includes(previous) ? previous : null;
|
|
334
|
+
const layouts: SavedLayout[] = currentConfig.layouts.map((entry, index) => (
|
|
335
|
+
index === sourceIndex
|
|
336
|
+
? { ...entry, id: sourceLayoutId, layout: move.source, paneState: sourcePaneState, focusedPaneId: sourceFocus }
|
|
337
|
+
: entry
|
|
338
|
+
));
|
|
339
|
+
layouts.push({
|
|
340
|
+
name: availableLayoutName(action.name, layouts),
|
|
341
|
+
layout: move.target,
|
|
342
|
+
paneState: movedPaneState,
|
|
343
|
+
focusedPaneId: action.paneId,
|
|
344
|
+
});
|
|
345
|
+
return withFocusedPane({
|
|
346
|
+
...state,
|
|
347
|
+
layoutHistory: setHistoryForIndex(state.layoutHistory, layouts.length - 1, { past: [], future: [] }),
|
|
348
|
+
}, {
|
|
349
|
+
...currentConfig,
|
|
350
|
+
layout: cloneLayout(move.target),
|
|
351
|
+
layouts,
|
|
352
|
+
activeLayoutIndex: layouts.length - 1,
|
|
353
|
+
}, {
|
|
354
|
+
paneState: movedPaneState,
|
|
355
|
+
focusedPaneId: action.paneId,
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
case "MOVE_PANE_BACK": {
|
|
360
|
+
const target = paneMoveBackTarget(state.config, action.paneId);
|
|
361
|
+
if (!target) return state;
|
|
362
|
+
const resolveSymbol = (instanceId: string) => resolveTickerForPane(state, instanceId);
|
|
363
|
+
const currentConfig = syncConfigActiveLayoutState(state.config, state.paneState, state.focusedPaneId);
|
|
364
|
+
const activeIndex = currentConfig.activeLayoutIndex;
|
|
365
|
+
const activeEntry = currentConfig.layouts[activeIndex]!;
|
|
366
|
+
const targetEntry = currentConfig.layouts[target.index]!;
|
|
367
|
+
const move = movePaneBack(state.config.layout, targetEntry.layout, action.paneId, target.original, resolveSymbol);
|
|
368
|
+
if (!move) return state;
|
|
369
|
+
|
|
370
|
+
const { [action.paneId]: savedPaneState, ...activePaneState } = activeEntry.paneState ?? {};
|
|
371
|
+
const livePaneState = (state.paneState[action.paneId] ?? savedPaneState) as PaneRuntimeState | undefined;
|
|
372
|
+
const { [action.paneId]: _stale, ...targetPaneState } = targetEntry.paneState ?? {};
|
|
373
|
+
if (livePaneState) targetPaneState[action.paneId] = livePaneState;
|
|
374
|
+
let layouts: SavedLayout[] = currentConfig.layouts.map((entry, index) => {
|
|
375
|
+
if (index === target.index) return { ...entry, layout: move.target, paneState: targetPaneState, focusedPaneId: action.paneId };
|
|
376
|
+
if (index === activeIndex) return { ...entry, layout: move.source, paneState: activePaneState, focusedPaneId: null };
|
|
377
|
+
return entry;
|
|
378
|
+
});
|
|
379
|
+
// A layout the pane leaves empty was only there for it, unless a team layout is linked to it.
|
|
380
|
+
let layoutHistory = state.layoutHistory;
|
|
381
|
+
let nextIndex = target.index;
|
|
382
|
+
if (getPlacedPaneInstanceIds(move.source).length === 0 && !activeEntry.origin) {
|
|
383
|
+
layouts = layouts.filter((_, index) => index !== activeIndex);
|
|
384
|
+
layoutHistory = removeHistoryIndex(layoutHistory, activeIndex);
|
|
385
|
+
if (activeIndex < target.index) nextIndex -= 1;
|
|
386
|
+
}
|
|
387
|
+
const next = layouts[nextIndex]!;
|
|
388
|
+
return withFocusedPane({ ...state, layoutHistory }, {
|
|
389
|
+
...currentConfig,
|
|
390
|
+
layout: cloneLayout(next.layout),
|
|
391
|
+
layouts,
|
|
392
|
+
activeLayoutIndex: nextIndex,
|
|
393
|
+
}, {
|
|
394
|
+
paneState: {
|
|
395
|
+
...clonePaneStateMap(targetPaneState),
|
|
396
|
+
...(livePaneState ? { [action.paneId]: livePaneState } : {}),
|
|
397
|
+
},
|
|
398
|
+
focusedPaneId: action.paneId,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
316
402
|
default:
|
|
317
403
|
return undefined;
|
|
318
404
|
}
|
|
@@ -73,6 +73,9 @@ export function appReducer(state: AppState, action: AppAction): AppState {
|
|
|
73
73
|
case "SET_KEYBINDINGS":
|
|
74
74
|
return { ...state, config: updateKeybindingsConfig(state.config, action.keybindings) };
|
|
75
75
|
|
|
76
|
+
case "SET_STAR_PROMPT":
|
|
77
|
+
return { ...state, config: { ...state.config, starPrompt: action.starPrompt } };
|
|
78
|
+
|
|
76
79
|
case "SET_TICKERS":
|
|
77
80
|
return { ...state, tickers: action.tickers };
|
|
78
81
|
|
|
@@ -214,11 +217,11 @@ export function appReducer(state: AppState, action: AppAction): AppState {
|
|
|
214
217
|
case "SET_UPDATE_NOTICE":
|
|
215
218
|
return { ...state, updateNotice: action.notice };
|
|
216
219
|
|
|
217
|
-
case "
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
220
|
+
case "SET_DISABLED_PLUGINS": {
|
|
221
|
+
const config = { ...state.config, disabledPlugins: action.disabledPlugins };
|
|
222
|
+
return Object.prototype.hasOwnProperty.call(action, "focusedPaneId")
|
|
223
|
+
? withFocusedPane(state, config, { focusedPaneId: action.focusedPaneId ?? null })
|
|
224
|
+
: { ...state, config };
|
|
222
225
|
}
|
|
223
226
|
|
|
224
227
|
case "SET_INPUT_CAPTURED":
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { BrokerAccount } from "../../../types/trading";
|
|
2
|
-
import type { AppConfig, KeybindingsConfig, LayoutConfig, LayoutOrigin, OnboardingProgress } from "../../../types/config";
|
|
2
|
+
import type { AppConfig, KeybindingsConfig, LayoutConfig, LayoutOrigin, OnboardingProgress, StarPromptConfig } from "../../../types/config";
|
|
3
3
|
import type { DesktopSharedStateSnapshot } from "../../../types/desktop-window";
|
|
4
4
|
import type { Quote, TickerFinancials } from "../../../types/financials";
|
|
5
5
|
import type { TickerRecord } from "../../../types/ticker";
|
|
@@ -99,6 +99,8 @@ export interface AppState {
|
|
|
99
99
|
export type AppAction =
|
|
100
100
|
| { type: "SET_CONFIG"; config: AppConfig }
|
|
101
101
|
| { type: "SET_KEYBINDINGS"; keybindings: KeybindingsConfig | undefined }
|
|
102
|
+
/** Only the star prompt's own record, so a background write never touches layouts or a theme preview. */
|
|
103
|
+
| { type: "SET_STAR_PROMPT"; starPrompt: StarPromptConfig | undefined }
|
|
102
104
|
| {
|
|
103
105
|
type: "SET_ONBOARDING_STATE";
|
|
104
106
|
complete: boolean;
|
|
@@ -137,7 +139,8 @@ export type AppAction =
|
|
|
137
139
|
| { type: "SET_UPDATE_PROGRESS"; progress: UpdateProgress | null }
|
|
138
140
|
| { type: "SET_UPDATE_CHECK_IN_PROGRESS"; checking: boolean }
|
|
139
141
|
| { type: "SET_UPDATE_NOTICE"; notice: string | null }
|
|
140
|
-
|
|
142
|
+
/** Plugins switched on or off; `focusedPaneId` moves focus off a pane that just went hidden. */
|
|
143
|
+
| { type: "SET_DISABLED_PLUGINS"; disabledPlugins: string[]; focusedPaneId?: string | null }
|
|
141
144
|
| { type: "SET_INPUT_CAPTURED"; captured: boolean }
|
|
142
145
|
/** @deprecated Only writes the unused `AppState.exchangeRates`. Use `useFxRatesMap`. */
|
|
143
146
|
| { type: "SET_EXCHANGE_RATE"; currency: string; rate: number }
|
|
@@ -172,6 +175,14 @@ export type AppAction =
|
|
|
172
175
|
| { type: "DELETE_LAYOUT"; index: number }
|
|
173
176
|
| { type: "RENAME_LAYOUT"; index: number; name: string }
|
|
174
177
|
| { type: "DUPLICATE_LAYOUT"; index: number }
|
|
178
|
+
/**
|
|
179
|
+
* Moves a pane out of the active layout into a new layout of its own, named
|
|
180
|
+
* `name` (made unique), which becomes active. `sourceLayoutId` names the
|
|
181
|
+
* layout it left when that one has no id yet, so Move Back can find it.
|
|
182
|
+
*/
|
|
183
|
+
| { type: "MOVE_PANE_TO_NEW_LAYOUT"; paneId: string; name: string; sourceLayoutId: string }
|
|
184
|
+
/** Returns a pane Move to New Layout moved to the layout it came from, which becomes active. */
|
|
185
|
+
| { type: "MOVE_PANE_BACK"; paneId: string }
|
|
175
186
|
| { type: "FOCUS_PANE"; paneId: string }
|
|
176
187
|
| { type: "FOCUS_NEXT"; paneOrder: string[] }
|
|
177
188
|
| { type: "FOCUS_PREV"; paneOrder: string[] }
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
DetachedPlacementMemory,
|
|
3
|
+
DockedPlacementMemory,
|
|
3
4
|
FloatingPlacementMemory,
|
|
4
5
|
LayoutConfig,
|
|
5
6
|
PaneBinding,
|
|
6
7
|
PaneInstanceConfig,
|
|
8
|
+
PaneMovedFrom,
|
|
9
|
+
PaneMovedLink,
|
|
7
10
|
PanePlacementMemory,
|
|
8
11
|
} from "../../types/config";
|
|
9
12
|
import type { BrokerContractRef, TickerListingRef } from "../../types/instrument";
|
|
@@ -93,30 +96,59 @@ function sanitizeDetachedPlacementMemory(value: unknown): DetachedPlacementMemor
|
|
|
93
96
|
return { x, y, width, height };
|
|
94
97
|
}
|
|
95
98
|
|
|
99
|
+
function sanitizeDockedPlacementMemory(raw: unknown): DockedPlacementMemory | undefined {
|
|
100
|
+
if (!raw || typeof raw !== "object") return undefined;
|
|
101
|
+
const rawPath = (raw as { path?: unknown }).path;
|
|
102
|
+
const path = Array.isArray(rawPath)
|
|
103
|
+
? rawPath.filter((segment): segment is 0 | 1 => segment === 0 || segment === 1)
|
|
104
|
+
: undefined;
|
|
105
|
+
const anchorInstanceId = typeof (raw as { anchorInstanceId?: unknown }).anchorInstanceId === "string"
|
|
106
|
+
? (raw as { anchorInstanceId: string }).anchorInstanceId
|
|
107
|
+
: undefined;
|
|
108
|
+
const position = ["left", "right", "above", "below"].includes(String((raw as { position?: unknown }).position))
|
|
109
|
+
? (raw as { position: "left" | "right" | "above" | "below" }).position
|
|
110
|
+
: undefined;
|
|
111
|
+
if (!path && !anchorInstanceId && !position) return undefined;
|
|
112
|
+
return {
|
|
113
|
+
path,
|
|
114
|
+
anchorInstanceId,
|
|
115
|
+
position,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function sanitizePaneMovedFrom(value: unknown): PaneMovedFrom | undefined {
|
|
120
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
|
|
121
|
+
const raw = value as Record<string, unknown>;
|
|
122
|
+
if (typeof raw.layoutId !== "string" || !raw.layoutId) return undefined;
|
|
123
|
+
const docked = sanitizeDockedPlacementMemory(raw.docked);
|
|
124
|
+
const rawRatio = (raw.docked as { ratio?: unknown } | undefined)?.ratio;
|
|
125
|
+
const ratio = typeof rawRatio === "number" && rawRatio > 0 && rawRatio < 1 ? rawRatio : undefined;
|
|
126
|
+
const floating = docked ? undefined : sanitizeFloatingPlacementMemory(raw.floating);
|
|
127
|
+
const links = Array.isArray(raw.links)
|
|
128
|
+
? raw.links.flatMap((entry): PaneMovedLink[] => {
|
|
129
|
+
if (!entry || typeof entry !== "object") return [];
|
|
130
|
+
const link = entry as Record<string, unknown>;
|
|
131
|
+
if (typeof link.instanceId !== "string" || typeof link.sourceInstanceId !== "string" || typeof link.symbol !== "string") return [];
|
|
132
|
+
return [{
|
|
133
|
+
instanceId: link.instanceId,
|
|
134
|
+
sourceInstanceId: link.sourceInstanceId,
|
|
135
|
+
symbol: link.symbol,
|
|
136
|
+
...(typeof link.title === "string" ? { title: link.title } : {}),
|
|
137
|
+
}];
|
|
138
|
+
})
|
|
139
|
+
: [];
|
|
140
|
+
return {
|
|
141
|
+
layoutId: raw.layoutId,
|
|
142
|
+
...(docked ? { docked: ratio === undefined ? docked : { ...docked, ratio } } : {}),
|
|
143
|
+
...(floating ? { floating } : {}),
|
|
144
|
+
...(links.length > 0 ? { links } : {}),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
96
148
|
function sanitizePlacementMemory(value: unknown): PanePlacementMemory | undefined {
|
|
97
149
|
if (!value || typeof value !== "object") return undefined;
|
|
98
150
|
|
|
99
|
-
const docked = ((
|
|
100
|
-
const raw = (value as PanePlacementMemory).docked;
|
|
101
|
-
if (!raw || typeof raw !== "object") return undefined;
|
|
102
|
-
const rawPath = (raw as { path?: unknown }).path;
|
|
103
|
-
const path = Array.isArray(rawPath)
|
|
104
|
-
? rawPath.filter((segment): segment is 0 | 1 => segment === 0 || segment === 1)
|
|
105
|
-
: undefined;
|
|
106
|
-
const anchorInstanceId = typeof (raw as { anchorInstanceId?: unknown }).anchorInstanceId === "string"
|
|
107
|
-
? (raw as { anchorInstanceId: string }).anchorInstanceId
|
|
108
|
-
: undefined;
|
|
109
|
-
const position = ["left", "right", "above", "below"].includes(String((raw as { position?: unknown }).position))
|
|
110
|
-
? (raw as { position: "left" | "right" | "above" | "below" }).position
|
|
111
|
-
: undefined;
|
|
112
|
-
if (!path && !anchorInstanceId && !position) return undefined;
|
|
113
|
-
return {
|
|
114
|
-
path,
|
|
115
|
-
anchorInstanceId,
|
|
116
|
-
position,
|
|
117
|
-
};
|
|
118
|
-
})();
|
|
119
|
-
|
|
151
|
+
const docked = sanitizeDockedPlacementMemory((value as PanePlacementMemory).docked);
|
|
120
152
|
const floating = sanitizeFloatingPlacementMemory((value as PanePlacementMemory).floating);
|
|
121
153
|
const detached = sanitizeDetachedPlacementMemory((value as PanePlacementMemory).detached);
|
|
122
154
|
if (!docked && !floating && !detached) return undefined;
|
|
@@ -176,6 +208,7 @@ function sanitizePaneInstances(
|
|
|
176
208
|
seen.add(instanceId);
|
|
177
209
|
const binding = sanitizePaneBinding(entry.binding);
|
|
178
210
|
const settings = sanitizePaneSettings(entry.settings);
|
|
211
|
+
const movedFrom = sanitizePaneMovedFrom(entry.movedFrom);
|
|
179
212
|
return {
|
|
180
213
|
instanceId,
|
|
181
214
|
paneId,
|
|
@@ -189,6 +222,7 @@ function sanitizePaneInstances(
|
|
|
189
222
|
settings: migrateChartPaneSettings(originalPaneId, binding, settings, chartMigration),
|
|
190
223
|
placementMemory: sanitizePlacementMemory(entry.placementMemory),
|
|
191
224
|
locked: entry.locked === true ? true : undefined,
|
|
225
|
+
...(movedFrom ? { movedFrom } : {}),
|
|
192
226
|
};
|
|
193
227
|
});
|
|
194
228
|
return instances;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import {
|
|
2
|
+
removePaneInstances,
|
|
3
|
+
type DockLayoutNode,
|
|
4
|
+
type LayoutConfig,
|
|
5
|
+
type PaneInstanceConfig,
|
|
6
|
+
} from "../../../types/config";
|
|
7
|
+
import { isRecord } from "../../../utils/guards";
|
|
8
|
+
|
|
9
|
+
const SHORT_VOLUME_PANE_ID = "short-volume";
|
|
10
|
+
const SHORT_INTEREST_PANE_ID = "short-interest";
|
|
11
|
+
const SOVR_PANE_ID = "sovr-board";
|
|
12
|
+
const CDX_PANE_ID = "cdx-board";
|
|
13
|
+
|
|
14
|
+
interface FoldedLayout {
|
|
15
|
+
layout: LayoutConfig;
|
|
16
|
+
idMap: Map<string, string>;
|
|
17
|
+
removed: Set<string>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function asLayout(value: unknown): LayoutConfig | null {
|
|
21
|
+
if (!isRecord(value) || !Array.isArray(value.instances)) return null;
|
|
22
|
+
return {
|
|
23
|
+
dockRoot: (value.dockRoot ?? null) as DockLayoutNode | null,
|
|
24
|
+
instances: value.instances as PaneInstanceConfig[],
|
|
25
|
+
floating: Array.isArray(value.floating) ? value.floating as LayoutConfig["floating"] : [],
|
|
26
|
+
detached: Array.isArray(value.detached) ? value.detached as LayoutConfig["detached"] : [],
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function interestInstanceId(instanceId: string): string {
|
|
31
|
+
if (instanceId === SHORT_VOLUME_PANE_ID || instanceId.startsWith(`${SHORT_VOLUME_PANE_ID}:`)) {
|
|
32
|
+
return `${SHORT_INTEREST_PANE_ID}${instanceId.slice(SHORT_VOLUME_PANE_ID.length)}`;
|
|
33
|
+
}
|
|
34
|
+
return instanceId;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function mapNode(node: DockLayoutNode | null, idMap: Map<string, string>): DockLayoutNode | null {
|
|
38
|
+
if (!node) return null;
|
|
39
|
+
if (node.kind === "pane") return { ...node, instanceId: idMap.get(node.instanceId) ?? node.instanceId };
|
|
40
|
+
return { ...node, first: mapNode(node.first, idMap)!, second: mapNode(node.second, idMap)! };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Daily short volume becomes the short interest pane's volume tab, and sovereign CDS the index board's Sovereign tab. */
|
|
44
|
+
export function foldPaneLayout(layout: LayoutConfig): FoldedLayout {
|
|
45
|
+
const removed = new Set<string>();
|
|
46
|
+
const idMap = new Map<string, string>();
|
|
47
|
+
const claimed = new Set(layout.instances.map((instance) => instance.instanceId));
|
|
48
|
+
let hasIndexBoard = layout.instances.some((instance) => instance.paneId === CDX_PANE_ID);
|
|
49
|
+
|
|
50
|
+
const instances = layout.instances.flatMap((instance) => {
|
|
51
|
+
if (instance.paneId === SOVR_PANE_ID) {
|
|
52
|
+
if (hasIndexBoard) {
|
|
53
|
+
removed.add(instance.instanceId);
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
hasIndexBoard = true;
|
|
57
|
+
return [{
|
|
58
|
+
...instance,
|
|
59
|
+
paneId: CDX_PANE_ID,
|
|
60
|
+
params: { ...(instance.params ?? {}), tab: "sovereign" },
|
|
61
|
+
}];
|
|
62
|
+
}
|
|
63
|
+
if (instance.paneId !== SHORT_VOLUME_PANE_ID) return [instance];
|
|
64
|
+
const targetId = interestInstanceId(instance.instanceId);
|
|
65
|
+
const taken = targetId !== instance.instanceId && claimed.has(targetId);
|
|
66
|
+
if (taken) {
|
|
67
|
+
removed.add(instance.instanceId);
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
if (targetId !== instance.instanceId) {
|
|
71
|
+
claimed.add(targetId);
|
|
72
|
+
idMap.set(instance.instanceId, targetId);
|
|
73
|
+
}
|
|
74
|
+
return [{
|
|
75
|
+
...instance,
|
|
76
|
+
instanceId: targetId,
|
|
77
|
+
paneId: SHORT_INTEREST_PANE_ID,
|
|
78
|
+
params: { ...(instance.params ?? {}), tab: "volume" },
|
|
79
|
+
}];
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const mapped: LayoutConfig = {
|
|
83
|
+
dockRoot: mapNode(layout.dockRoot, idMap),
|
|
84
|
+
instances: instances.map((instance) => {
|
|
85
|
+
const binding = instance.binding?.kind === "follow"
|
|
86
|
+
? { ...instance.binding, sourceInstanceId: idMap.get(instance.binding.sourceInstanceId) ?? instance.binding.sourceInstanceId }
|
|
87
|
+
: instance.binding;
|
|
88
|
+
const anchor = instance.placementMemory?.docked?.anchorInstanceId;
|
|
89
|
+
const placementMemory = anchor
|
|
90
|
+
? {
|
|
91
|
+
...instance.placementMemory,
|
|
92
|
+
docked: { ...instance.placementMemory?.docked, anchorInstanceId: idMap.get(anchor) ?? anchor },
|
|
93
|
+
}
|
|
94
|
+
: instance.placementMemory;
|
|
95
|
+
return { ...instance, binding, placementMemory };
|
|
96
|
+
}),
|
|
97
|
+
floating: layout.floating.map((entry) => ({ ...entry, instanceId: idMap.get(entry.instanceId) ?? entry.instanceId })),
|
|
98
|
+
detached: (layout.detached ?? []).map((entry) => ({ ...entry, instanceId: idMap.get(entry.instanceId) ?? entry.instanceId })),
|
|
99
|
+
};
|
|
100
|
+
return { layout: removePaneInstances(mapped, removed), idMap, removed };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function foldPaneState(
|
|
104
|
+
value: unknown,
|
|
105
|
+
idMap: Map<string, string>,
|
|
106
|
+
removed: Set<string>,
|
|
107
|
+
): unknown {
|
|
108
|
+
if (!isRecord(value)) return value;
|
|
109
|
+
return Object.fromEntries(
|
|
110
|
+
Object.entries(value)
|
|
111
|
+
.filter(([instanceId]) => !removed.has(instanceId))
|
|
112
|
+
.map(([instanceId, entry]) => [idMap.get(instanceId) ?? instanceId, entry]),
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function foldFocus(value: unknown, idMap: Map<string, string>, removed: Set<string>): unknown {
|
|
117
|
+
if (typeof value !== "string") return value;
|
|
118
|
+
if (removed.has(value)) return null;
|
|
119
|
+
return idMap.get(value) ?? value;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function foldLayoutField(value: unknown): FoldedLayout | null {
|
|
123
|
+
const layout = asLayout(value);
|
|
124
|
+
if (!layout) return null;
|
|
125
|
+
return foldPaneLayout(layout);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Rewrite the open layout and every saved tab. */
|
|
129
|
+
export function foldSavedPanes(saved: Record<string, unknown>): Record<string, unknown> {
|
|
130
|
+
const open = foldLayoutField(saved.layout);
|
|
131
|
+
const layouts = Array.isArray(saved.layouts)
|
|
132
|
+
? saved.layouts.map((entry) => {
|
|
133
|
+
if (!isRecord(entry)) return entry;
|
|
134
|
+
const folded = foldLayoutField(entry.layout);
|
|
135
|
+
if (!folded) return entry;
|
|
136
|
+
return {
|
|
137
|
+
...entry,
|
|
138
|
+
layout: folded.layout,
|
|
139
|
+
paneState: foldPaneState(entry.paneState, folded.idMap, folded.removed),
|
|
140
|
+
focusedPaneId: foldFocus(entry.focusedPaneId, folded.idMap, folded.removed),
|
|
141
|
+
};
|
|
142
|
+
})
|
|
143
|
+
: saved.layouts;
|
|
144
|
+
return {
|
|
145
|
+
...saved,
|
|
146
|
+
...(open ? { layout: open.layout } : {}),
|
|
147
|
+
layouts,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
type LegacyChartMigrationContext,
|
|
18
18
|
} from "../chart-settings";
|
|
19
19
|
import { sanitizeLayout } from "../layout";
|
|
20
|
+
import { foldSavedPanes } from "./fold-panes";
|
|
20
21
|
import { sanitizeSavedPaneState } from "./pane-state";
|
|
21
22
|
import { isRecord } from "../../../utils/guards";
|
|
22
23
|
|
|
@@ -26,6 +27,7 @@ const ONBOARDING_BACKFILL_CONFIG_VERSION = 21;
|
|
|
26
27
|
const UNREACHABLE_PANE_CLEANUP_CONFIG_VERSION = 22;
|
|
27
28
|
const MARKET_OVERVIEW_ABSORBED_CONFIG_VERSION = 23;
|
|
28
29
|
const IPO_CALENDAR_ABSORBED_CONFIG_VERSION = 24;
|
|
30
|
+
const FOLDED_PANES_CONFIG_VERSION = 25;
|
|
29
31
|
|
|
30
32
|
/** What a migration may ask of the machine the config is loaded on. */
|
|
31
33
|
export interface ConfigMigrationHost {
|
|
@@ -80,6 +82,11 @@ const CONFIG_MIGRATIONS: readonly ConfigMigration[] = [
|
|
|
80
82
|
toVersion: IPO_CALENDAR_ABSORBED_CONFIG_VERSION,
|
|
81
83
|
migrate: keepAbsorbedPluginsOff("macro", ["ipo-calendar"]),
|
|
82
84
|
},
|
|
85
|
+
{
|
|
86
|
+
name: "fold-short-volume-and-sovereign-cds",
|
|
87
|
+
toVersion: FOLDED_PANES_CONFIG_VERSION,
|
|
88
|
+
migrate: foldSavedPanes,
|
|
89
|
+
},
|
|
83
90
|
];
|
|
84
91
|
|
|
85
92
|
export function migrateSavedConfig(
|
|
@@ -102,14 +102,16 @@ async function loadConfigState(dataDir: string): Promise<{ config: AppConfig; ne
|
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
/** The save in flight or queued last for each config file; it never rejects. */
|
|
106
|
+
const saveQueues = new Map<string, Promise<void>>();
|
|
107
|
+
let tempFileCounter = 0;
|
|
108
108
|
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
async function writeConfigFile(configPath: string, contents: string): Promise<void> {
|
|
110
|
+
await mkdir(dirname(configPath), { recursive: true });
|
|
111
|
+
// Unique per call: a clock tick is not, and two saves can land in one.
|
|
112
|
+
const tempPath = `${configPath}.${process.pid}.${Date.now()}.${tempFileCounter++}.tmp`;
|
|
111
113
|
try {
|
|
112
|
-
await writeFile(tempPath,
|
|
114
|
+
await writeFile(tempPath, contents, "utf-8");
|
|
113
115
|
await rename(tempPath, configPath);
|
|
114
116
|
} catch (error) {
|
|
115
117
|
await rm(tempPath, { force: true }).catch(() => {});
|
|
@@ -117,6 +119,25 @@ export async function saveConfig(config: AppConfig): Promise<void> {
|
|
|
117
119
|
}
|
|
118
120
|
}
|
|
119
121
|
|
|
122
|
+
/**
|
|
123
|
+
* Saves to one file run one after another in call order, so the last call is
|
|
124
|
+
* the one on disk and none is lost. A failed save rejects only its own caller
|
|
125
|
+
* and the next one still runs.
|
|
126
|
+
*/
|
|
127
|
+
export async function saveConfig(config: AppConfig): Promise<void> {
|
|
128
|
+
const configPath = configFileFor(config.dataDir);
|
|
129
|
+
// Written as it was when called, however the caller's object changes while
|
|
130
|
+
// earlier saves are still running.
|
|
131
|
+
const contents = JSON.stringify(normalizeConfigForSave(config), null, 2);
|
|
132
|
+
const result = (saveQueues.get(configPath) ?? Promise.resolve()).then(() => writeConfigFile(configPath, contents));
|
|
133
|
+
const settled = result.catch(() => {});
|
|
134
|
+
saveQueues.set(configPath, settled);
|
|
135
|
+
void settled.then(() => {
|
|
136
|
+
if (saveQueues.get(configPath) === settled) saveQueues.delete(configPath);
|
|
137
|
+
});
|
|
138
|
+
return result;
|
|
139
|
+
}
|
|
140
|
+
|
|
120
141
|
export async function initDataDir(dataDir: string): Promise<AppConfig> {
|
|
121
142
|
configLog.info(`Initializing data directory: ${dataDir}`);
|
|
122
143
|
await mkdir(dataDir, { recursive: true });
|
|
@@ -21,8 +21,30 @@ import { isLanguagePreference } from "../../../i18n/languages";
|
|
|
21
21
|
import { clampFontSize } from "../../../theme/font-scale";
|
|
22
22
|
import { isLayoutConfig, sanitizeLayout } from "../layout";
|
|
23
23
|
import { migrateSavedConfig, type ConfigMigrationHost } from "./migrations";
|
|
24
|
+
import { encodeBuiltinDisabledPluginIds, expandBuiltinPluginGroups } from "../../../plugins/ownership";
|
|
24
25
|
import { sanitizeSavedPaneState } from "./pane-state";
|
|
25
26
|
import { isRecord } from "../../../utils/guards";
|
|
27
|
+
import { debugLog } from "../../../utils/debug-log";
|
|
28
|
+
import { DEFAULT_THEME, themes } from "../../../theme/themes";
|
|
29
|
+
import { sanitizeStarPrompt } from "../../../app/star-prompt/model";
|
|
30
|
+
|
|
31
|
+
const configLog = debugLog.createLogger("config");
|
|
32
|
+
const reportedUnknownThemes = new Set<string>();
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* A theme id this build does not know is kept, not replaced: a newer build on
|
|
36
|
+
* another device may have saved it, and writing the default back would undo
|
|
37
|
+
* that choice everywhere through sync. The app draws the default meanwhile,
|
|
38
|
+
* and says so once.
|
|
39
|
+
*/
|
|
40
|
+
function sanitizeTheme(value: unknown, fallback: string): string {
|
|
41
|
+
if (typeof value !== "string") return fallback;
|
|
42
|
+
if (!themes[value] && !reportedUnknownThemes.has(value)) {
|
|
43
|
+
reportedUnknownThemes.add(value);
|
|
44
|
+
configLog.warn(`Unknown theme "${value}" in config; showing ${DEFAULT_THEME}. gloomberb config themes lists the ids.`);
|
|
45
|
+
}
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
26
48
|
|
|
27
49
|
export function normalizeLoadedConfig(
|
|
28
50
|
saved: Record<string, unknown>,
|
|
@@ -42,7 +64,10 @@ export function normalizeLoadedConfig(
|
|
|
42
64
|
: entry
|
|
43
65
|
));
|
|
44
66
|
|
|
45
|
-
|
|
67
|
+
// After the migrations, which read retired group ids as they were saved.
|
|
68
|
+
const disabledPlugins = expandBuiltinPluginGroups(
|
|
69
|
+
sanitizeUniqueStringList(candidate.disabledPlugins ?? defaults.disabledPlugins),
|
|
70
|
+
);
|
|
46
71
|
const onboardingProgress = sanitizeOnboardingProgress(candidate.onboardingProgress);
|
|
47
72
|
const onboardingComplete = onboardingProgress
|
|
48
73
|
? false
|
|
@@ -64,9 +89,10 @@ export function normalizeLoadedConfig(
|
|
|
64
89
|
disabledPlugins,
|
|
65
90
|
seededPlugins: sanitizeUniqueStringList(candidate.seededPlugins),
|
|
66
91
|
...(candidate.portfolioCurrenciesAdopted === true ? { portfolioCurrenciesAdopted: true } : {}),
|
|
92
|
+
...(candidate.presentationMode === true ? { presentationMode: true } : {}),
|
|
67
93
|
disabledSources: sanitizeUniqueStringList(candidate.disabledSources ?? defaults.disabledSources),
|
|
68
94
|
pluginConfig: sanitizePluginConfig(candidate.pluginConfig),
|
|
69
|
-
theme:
|
|
95
|
+
theme: sanitizeTheme(candidate.theme, defaults.theme),
|
|
70
96
|
chartPreferences: sanitizeChartPreferences(candidate.chartPreferences, defaults.chartPreferences),
|
|
71
97
|
valueFlashingEnabled: typeof candidate.valueFlashingEnabled === "boolean" ? candidate.valueFlashingEnabled : defaults.valueFlashingEnabled,
|
|
72
98
|
fontSize: sanitizeFontSize(candidate.fontSize, defaults.fontSize),
|
|
@@ -78,6 +104,7 @@ export function normalizeLoadedConfig(
|
|
|
78
104
|
lastLaunchedVersion: typeof candidate.lastLaunchedVersion === "string" ? candidate.lastLaunchedVersion : undefined,
|
|
79
105
|
...withKeybindings(sanitizeKeybindings(candidate.keybindings)),
|
|
80
106
|
...withTelemetry(sanitizeTelemetry(candidate.telemetry)),
|
|
107
|
+
...withStarPrompt(sanitizeStarPrompt(candidate.starPrompt)),
|
|
81
108
|
};
|
|
82
109
|
|
|
83
110
|
const needsSave =
|
|
@@ -122,7 +149,8 @@ export function normalizeConfigForSave(config: AppConfig): AppConfig {
|
|
|
122
149
|
layouts,
|
|
123
150
|
activeLayoutIndex,
|
|
124
151
|
brokerInstances: sanitizeBrokerInstances(config.brokerInstances),
|
|
125
|
-
|
|
152
|
+
// Older apps read the retired group ids, so a group that is all off is saved as one.
|
|
153
|
+
disabledPlugins: encodeBuiltinDisabledPluginIds(sanitizeUniqueStringList(config.disabledPlugins)),
|
|
126
154
|
seededPlugins: sanitizeUniqueStringList(config.seededPlugins),
|
|
127
155
|
disabledSources: sanitizeUniqueStringList(config.disabledSources),
|
|
128
156
|
pluginConfig: sanitizePluginConfig(config.pluginConfig),
|
|
@@ -138,6 +166,8 @@ export function normalizeConfigForSave(config: AppConfig): AppConfig {
|
|
|
138
166
|
Object.assign(persisted, withKeybindings(sanitizeKeybindings(config.keybindings)));
|
|
139
167
|
delete persisted.telemetry;
|
|
140
168
|
Object.assign(persisted, withTelemetry(sanitizeTelemetry(config.telemetry)));
|
|
169
|
+
delete persisted.starPrompt;
|
|
170
|
+
Object.assign(persisted, withStarPrompt(sanitizeStarPrompt(config.starPrompt)));
|
|
141
171
|
|
|
142
172
|
return persisted;
|
|
143
173
|
}
|
|
@@ -150,6 +180,10 @@ function withTelemetry(telemetry: TelemetryConfig | undefined): Pick<AppConfig,
|
|
|
150
180
|
return telemetry ? { telemetry } : {};
|
|
151
181
|
}
|
|
152
182
|
|
|
183
|
+
function withStarPrompt(starPrompt: AppConfig["starPrompt"]): Pick<AppConfig, "starPrompt"> {
|
|
184
|
+
return starPrompt ? { starPrompt } : {};
|
|
185
|
+
}
|
|
186
|
+
|
|
153
187
|
/** Only the switches that are set survive; an empty object is the same as none. */
|
|
154
188
|
function sanitizeTelemetry(value: unknown): TelemetryConfig | undefined {
|
|
155
189
|
if (!isRecord(value)) return undefined;
|