gloomberb 0.17.4 → 0.17.6
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 +3 -1
- package/patches/electrobun@1.18.1.patch +80 -0
- package/src/api-client/chat.ts +9 -0
- package/src/api-client/commodity-balances.ts +77 -0
- package/src/api-client/data.ts +35 -3
- package/src/api-client/earnings.ts +10 -0
- package/src/api-client/index.ts +5 -0
- package/src/api-client/insider-signals.ts +75 -0
- package/src/api-client/market-discovery.ts +1 -1
- package/src/api-client/normalizers.ts +1 -0
- package/src/api-client/paths.ts +9 -0
- package/src/api-client/research-activity.ts +10 -3
- package/src/api-client/socket.ts +10 -0
- package/src/api-client/types.ts +25 -1
- package/src/app/pane-runtime/ticker-open-runtime.ts +2 -1
- package/src/app/runtime/broker-import.ts +23 -21
- package/src/app/runtime/plugin-bindings.ts +13 -7
- package/src/app/runtime/update.ts +64 -4
- package/src/app.tsx +2 -0
- package/src/architecture/core-builtin-allowlist.json +2 -10
- package/src/ask-gloom/pane-context.ts +11 -1
- package/src/brokers/broker-portfolio-sync.ts +66 -0
- package/src/brokers/sync-broker-instance.ts +75 -41
- package/src/cli/commands/market.ts +101 -34
- package/src/cli/commands/options-chain.ts +20 -6
- package/src/cli/commands/overview.ts +54 -13
- package/src/cli/commands/search.ts +1 -1
- package/src/cli/commands/system.ts +19 -1
- package/src/cli/commands/ticker.ts +24 -4
- package/src/cli/context.ts +13 -0
- package/src/cli/errors.ts +15 -2
- package/src/cli/fx-pair.ts +77 -0
- package/src/cli/glossary.ts +34 -4
- package/src/cli/history-rows.ts +25 -11
- package/src/cli/index.ts +30 -12
- package/src/cli/listing-arg.ts +61 -138
- package/src/cli/listing-suggestion.ts +3 -21
- package/src/cli/pane-functions/catalog.ts +50 -0
- package/src/cli/pane-functions/freshness.ts +44 -9
- package/src/cli/pane-functions/function-help.ts +9 -9
- package/src/cli/pane-functions/headless.ts +19 -7
- package/src/cli/pane-functions/index.ts +63 -5
- package/src/cli/pane-functions/market-session.ts +18 -3
- package/src/cli/pane-functions/resolver.ts +29 -8
- package/src/cli/pane-functions/screenshot.ts +2 -0
- package/src/cli/report-tables.ts +11 -4
- package/src/cli/result.ts +2 -0
- package/src/cli/series-search.ts +6 -1
- package/src/cli/ticker-resolution.ts +1 -2
- package/src/cli/unknown-command.ts +18 -0
- package/src/components/chart/composite/composite-chart.tsx +80 -19
- package/src/components/chart/composite/format.ts +17 -0
- package/src/components/chart/composite/legend.tsx +26 -15
- package/src/components/chart/composite/panel-labels.ts +38 -0
- package/src/components/chart/composite/panel-series.ts +1 -0
- package/src/components/chart/composite/panel-surface.tsx +25 -7
- package/src/components/chart/composite/price-axis-labels.tsx +16 -3
- package/src/components/chart/composite/scene.ts +60 -2
- package/src/components/chart/composite/series-axis-tags.tsx +67 -0
- package/src/components/chart/composite/tools.ts +2 -2
- package/src/components/chart/composite/types.ts +28 -0
- package/src/components/chart/core/view-reset.ts +46 -0
- package/src/components/command-bar/commands/collection.ts +1 -1
- package/src/components/command-bar/commands/direct/index.ts +48 -1
- package/src/components/command-bar/commands/registry.ts +8 -0
- package/src/components/command-bar/list/model.ts +7 -0
- package/src/components/command-bar/pane-templates/workflow.ts +5 -0
- package/src/components/command-bar/panel/prompt-binding.ts +8 -0
- package/src/components/command-bar/routes/root/command-items.ts +17 -2
- package/src/components/command-bar/routes/root/pick-prefetch.ts +38 -0
- package/src/components/command-bar/routes/root/provider-search.ts +6 -0
- package/src/components/command-bar/routes/root/runtime.ts +3 -0
- package/src/components/command-bar/routes/root/shortcut-feedback.ts +3 -1
- package/src/components/command-bar/routes/root/shortcut-items.ts +1 -1
- package/src/components/command-bar/routes/ticker-search/actions.ts +34 -0
- package/src/components/command-bar/surface/index.tsx +7 -1
- package/src/components/command-bar/workflow/collection-actions.ts +6 -5
- package/src/components/command-bar/workflow/ops.ts +8 -5
- package/src/components/command-bar/workflow/pane-reuse.ts +3 -2
- package/src/components/command-bar/workflow/tickers.ts +1 -1
- package/src/components/form-modal/confirm.tsx +1 -0
- package/src/components/form-modal/request.ts +2 -0
- package/src/components/layout/detached-pane-shell.tsx +6 -1
- package/src/components/layout/pane/content.tsx +11 -8
- package/src/components/layout/pane/sidebar.tsx +24 -11
- package/src/components/layout/pane/ticker-fate-gate.tsx +56 -0
- package/src/components/layout/shell/index.tsx +5 -1
- package/src/components/layout/shell/menu.ts +8 -0
- package/src/components/layout/shell/pane/capture.ts +29 -12
- package/src/components/onboarding/wizard-model.ts +1 -1
- package/src/components/onboarding/wizard-positions.ts +18 -10
- package/src/components/ticker/badge/format.ts +64 -15
- package/src/components/ticker/badge/index.tsx +32 -11
- package/src/components/ticker/badge/list.tsx +4 -3
- package/src/components/ticker/badge/text.tsx +2 -2
- package/src/components/ticker/list-table-view.tsx +8 -1
- package/src/components/ui/confirm-dialog.tsx +30 -4
- package/src/components/ui/data-table/opentui/index.tsx +61 -20
- package/src/components/ui/data-table/opentui/model.ts +104 -0
- package/src/components/ui/fields.tsx +5 -0
- package/src/components/ui/inline-quick-add.tsx +4 -1
- package/src/components/ui/query-bar-keys.tsx +29 -5
- package/src/components/ui/query-bar.tsx +29 -4
- package/src/components/ui/windowed-list.tsx +27 -0
- package/src/data/config/store/normalize.ts +8 -0
- package/src/docs/snapshot.json +248 -5
- package/src/i18n/es.ts +3 -1
- package/src/i18n/ja.ts +3 -1
- package/src/i18n/ko.ts +3 -1
- package/src/i18n/zh-cn.ts +3 -1
- package/src/i18n/zh-tw.ts +3 -1
- package/src/layout-marketplace/cloud.ts +16 -5
- package/src/layout-marketplace/gallery.tsx +4 -4
- package/src/layout-marketplace/linked-sync.ts +9 -3
- package/src/layout-marketplace/linked.ts +24 -3
- package/src/layout-marketplace/model.ts +39 -4
- package/src/market-data/coordinator/events.ts +5 -1
- package/src/market-data/coordinator/index.ts +38 -1
- package/src/market-data/coordinator/quotes.ts +4 -0
- package/src/market-data/futures-contract.ts +53 -0
- package/src/market-data/hooks.tsx +225 -30
- package/src/market-data/market/bar-period.ts +71 -0
- package/src/market-data/market/format.ts +14 -2
- package/src/market-data/market/freshness.ts +28 -1
- package/src/market-data/market/status.ts +152 -13
- package/src/market-data/model-dividend-yield.ts +76 -0
- package/src/market-data/quotes/freshness.ts +30 -1
- package/src/{plugins/builtin/portfolio-list → market-data/quotes}/live-valuation.ts +6 -6
- package/src/market-data/quotes/regular-session-valuation.ts +32 -1
- package/src/plugins/builtin/analytics/metrics.ts +8 -4
- package/src/plugins/builtin/analytics/risk-headless.ts +12 -5
- package/src/plugins/builtin/analytics/risk-model.ts +74 -15
- package/src/plugins/builtin/analytics/risk-pane.tsx +6 -6
- package/src/plugins/builtin/broker-manager/detail.tsx +2 -2
- package/src/plugins/builtin/broker-manager/model.ts +2 -2
- package/src/plugins/builtin/builtin-plugin-meta.ts +0 -1
- package/src/plugins/builtin/chart-composer/editor/controller.ts +14 -10
- package/src/plugins/builtin/chart-composer/formula.ts +233 -0
- package/src/plugins/builtin/chart-composer/headless.ts +27 -2
- package/src/plugins/builtin/chart-composer/index.tsx +3 -2
- package/src/plugins/builtin/chart-composer/pane.tsx +41 -10
- package/src/plugins/builtin/chart-composer/presets.ts +91 -2
- package/src/plugins/builtin/chart-composer/quick-add.tsx +9 -2
- package/src/plugins/builtin/chart-composer/series-catalog.ts +44 -11
- package/src/plugins/builtin/chart-composer/series-expression.ts +1 -1
- package/src/plugins/builtin/chart-composer/settings.ts +16 -4
- package/src/plugins/builtin/chart-composer/studies.ts +1 -1
- package/src/plugins/builtin/chat/content/channel-navigation.ts +17 -6
- package/src/plugins/builtin/chat/content/footer.tsx +5 -2
- package/src/plugins/builtin/chat/content/index.tsx +12 -5
- package/src/plugins/builtin/chat/content/mentions.ts +50 -21
- package/src/plugins/builtin/chat/content/scroll.ts +12 -5
- package/src/plugins/builtin/chat/content/snapshot.ts +11 -0
- package/src/plugins/builtin/chat/content/transcript.tsx +48 -37
- package/src/plugins/builtin/chat/content/whole-message-copy.ts +44 -2
- package/src/plugins/builtin/chat/controller/index.ts +88 -17
- package/src/plugins/builtin/chat/controller/mention-candidates.ts +104 -0
- package/src/plugins/builtin/chat/controller/messages.ts +14 -2
- package/src/plugins/builtin/chat/controller/realtime.ts +9 -0
- package/src/plugins/builtin/chat/controller/state.ts +25 -0
- package/src/plugins/builtin/chat/controller/unread-people.ts +240 -0
- package/src/plugins/builtin/chat/controller/view.ts +4 -0
- package/src/plugins/builtin/chat/conversation-recency.ts +73 -0
- package/src/plugins/builtin/chat/conversations.ts +12 -5
- package/src/plugins/builtin/chat/index.tsx +1 -1
- package/src/plugins/builtin/chat/layout.ts +23 -5
- package/src/plugins/builtin/chat/message/desktop.tsx +2 -0
- package/src/plugins/builtin/chat/share/flow.tsx +18 -9
- package/src/plugins/builtin/chat/sidebar-rows.ts +18 -5
- package/src/plugins/builtin/chat/sidebar.tsx +6 -1
- package/src/plugins/builtin/chat/unread-badge.ts +38 -7
- package/src/plugins/builtin/cloud/askg/manifest.ts +4 -3
- package/src/plugins/builtin/cloud/askg/pane-attachment.ts +2 -3
- package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +16 -5
- package/src/plugins/builtin/commodities-energy/index.tsx +23 -4
- package/src/plugins/builtin/commodity-balances/client.ts +51 -193
- package/src/plugins/builtin/commodity-balances/headless.ts +10 -3
- package/src/plugins/builtin/commodity-balances/model.ts +5 -180
- package/src/plugins/builtin/commodity-balances/pane.tsx +3 -0
- package/src/plugins/builtin/custom-view/index.tsx +1 -1
- package/src/plugins/builtin/custom-view/loader.ts +57 -7
- package/src/plugins/builtin/custom-view/sources.ts +146 -0
- package/src/plugins/builtin/custom-view/view-spec.ts +37 -1
- package/src/plugins/builtin/earnings/board-model.ts +33 -3
- package/src/plugins/builtin/earnings/board-table.ts +59 -14
- package/src/plugins/builtin/earnings/board.tsx +36 -3
- package/src/plugins/builtin/earnings/headless.ts +24 -5
- package/src/plugins/builtin/earnings/history-model.ts +9 -1
- package/src/plugins/builtin/earnings/history-table.ts +16 -5
- package/src/plugins/builtin/earnings/history.tsx +40 -10
- package/src/plugins/builtin/earnings/index.tsx +3 -1
- package/src/plugins/builtin/earnings/no-earnings.ts +52 -0
- package/src/plugins/builtin/econ-statistics/detail.tsx +4 -1
- package/src/plugins/builtin/econ-statistics/headless.ts +5 -0
- package/src/plugins/builtin/econ-statistics/pane.tsx +1 -1
- package/src/plugins/builtin/econ-statistics/view.ts +4 -0
- package/src/plugins/builtin/futures/model.ts +5 -4
- package/src/plugins/builtin/futures/series.ts +38 -1
- package/src/plugins/builtin/futures-curve/client.ts +4 -4
- package/src/plugins/builtin/futures-curve/headless.ts +33 -8
- package/src/plugins/builtin/futures-curve/index.tsx +4 -4
- package/src/plugins/builtin/futures-curve/model.ts +109 -6
- package/src/plugins/builtin/futures-curve/pane.tsx +24 -13
- package/src/plugins/builtin/insider/headless.ts +74 -4
- package/src/plugins/builtin/insider/index.tsx +99 -5
- package/src/plugins/builtin/insider/signals-client.ts +69 -0
- package/src/plugins/builtin/insider/signals-model.ts +146 -0
- package/src/plugins/builtin/insider/signals.tsx +176 -0
- package/src/plugins/builtin/iv-history/client.ts +41 -2
- package/src/plugins/builtin/iv-history/headless.ts +24 -16
- package/src/plugins/builtin/iv-history/screen-pane.tsx +25 -7
- package/src/plugins/builtin/jodi/client.ts +42 -205
- package/src/plugins/builtin/jodi/headless.ts +13 -4
- package/src/plugins/builtin/jodi/model.ts +18 -43
- package/src/plugins/builtin/jodi/pane.tsx +5 -0
- package/src/plugins/builtin/kelly-sizer/headless.ts +1 -1
- package/src/plugins/builtin/kelly-sizer/history.ts +5 -3
- package/src/plugins/builtin/kelly-sizer/pane.tsx +1 -1
- package/src/plugins/builtin/macro-day/release-table.ts +3 -3
- package/src/plugins/builtin/market-movers/client.ts +32 -6
- package/src/plugins/builtin/market-movers/headless.ts +7 -2
- package/src/plugins/builtin/market-movers/model.ts +64 -3
- package/src/plugins/builtin/market-movers/screener.ts +20 -9
- package/src/plugins/builtin/market-valuation/detail.tsx +7 -2
- package/src/plugins/builtin/market-valuation/headless.ts +17 -4
- package/src/plugins/builtin/market-valuation/pane.tsx +7 -3
- package/src/plugins/builtin/market-valuation/view.ts +10 -0
- package/src/plugins/builtin/mna/model.ts +2 -1
- package/src/plugins/builtin/options-positioning/client.ts +10 -0
- package/src/plugins/builtin/options-positioning/headless.ts +88 -13
- package/src/plugins/builtin/options-positioning/index.tsx +2 -2
- package/src/plugins/builtin/options-positioning/model.ts +47 -0
- package/src/plugins/builtin/options-positioning/pane.tsx +55 -26
- package/src/plugins/builtin/options-scenario/charts.tsx +13 -4
- package/src/plugins/builtin/options-scenario/client.ts +111 -17
- package/src/plugins/builtin/options-scenario/collar.ts +141 -0
- package/src/plugins/builtin/options-scenario/csp.ts +1 -1
- package/src/plugins/builtin/options-scenario/freshness.ts +27 -0
- package/src/plugins/builtin/options-scenario/headless.ts +28 -12
- package/src/plugins/builtin/options-scenario/index.tsx +1 -1
- package/src/plugins/builtin/options-scenario/model.ts +55 -5
- package/src/plugins/builtin/options-scenario/pane.tsx +24 -5
- package/src/plugins/builtin/perps/client.ts +77 -6
- package/src/plugins/builtin/perps/headless.ts +25 -14
- package/src/plugins/builtin/perps/index.tsx +1 -1
- package/src/plugins/builtin/perps/model.ts +32 -0
- package/src/plugins/builtin/perps/pane.tsx +5 -4
- package/src/plugins/builtin/portfolio-list/allocation.ts +9 -4
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +39 -20
- package/src/plugins/builtin/portfolio-list/cli/render.ts +5 -3
- package/src/plugins/builtin/portfolio-list/cli/shared.ts +15 -7
- package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +1 -1
- package/src/plugins/builtin/portfolio-list/column-values.ts +4 -3
- package/src/plugins/builtin/portfolio-list/command-bar.ts +1 -1
- package/src/plugins/builtin/portfolio-list/headless.ts +10 -3
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +36 -4
- package/src/plugins/builtin/portfolio-list/pane/remove-ticker.ts +1 -1
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +35 -6
- package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +2 -1
- package/src/plugins/builtin/portfolio-list/settings.ts +22 -0
- package/src/plugins/builtin/portfolio-list/since-last-seen.ts +197 -0
- package/src/plugins/builtin/portfolio-list/summary/totals.ts +1 -1
- package/src/plugins/builtin/portfolio-list/table.tsx +14 -2
- package/src/plugins/builtin/rate-path/model.ts +1 -1
- package/src/plugins/builtin/rate-path/pane.tsx +2 -1
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +14 -5
- package/src/plugins/builtin/research/event-model.ts +41 -49
- package/src/plugins/builtin/research/event-sources.ts +1 -1
- package/src/plugins/builtin/research/headless.ts +1 -1
- package/src/plugins/builtin/research/relative-valuation-model.ts +6 -0
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +3 -3
- package/src/plugins/builtin/reverse-dcf/headless.ts +25 -9
- package/src/plugins/builtin/reverse-dcf/index.tsx +4 -1
- package/src/plugins/builtin/reverse-dcf/model.ts +39 -5
- package/src/plugins/builtin/reverse-dcf/pane.tsx +16 -6
- package/src/plugins/builtin/sectors/client.ts +46 -19
- package/src/plugins/builtin/sectors/sector-model.ts +22 -10
- package/src/plugins/builtin/shared/earnings-report.ts +48 -1
- package/src/plugins/builtin/shared/history-span.ts +55 -0
- package/src/plugins/builtin/shared/report-freshness.ts +20 -11
- package/src/plugins/builtin/shared/series-detail-chart.tsx +5 -2
- package/src/plugins/builtin/shared/series-list-detail.tsx +7 -3
- package/src/plugins/builtin/shared/ticker-surface.ts +16 -2
- package/src/plugins/builtin/shared/use-quote-board.ts +12 -5
- package/src/plugins/builtin/themes/headless.ts +15 -2
- package/src/plugins/builtin/thirteenf/api.ts +60 -43
- package/src/plugins/builtin/thirteenf/data.ts +76 -35
- package/src/plugins/builtin/thirteenf/headless.ts +2 -0
- package/src/plugins/builtin/thirteenf/pane.tsx +10 -2
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +85 -17
- package/src/plugins/builtin/ticker-detail/data-panes/history-bars.ts +92 -0
- package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +26 -8
- package/src/plugins/builtin/ticker-detail/headless-schema.ts +4 -2
- package/src/plugins/builtin/ticker-detail/headless.ts +95 -22
- package/src/plugins/builtin/ticker-detail/overview/model.ts +1 -1
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +1 -1
- package/src/plugins/builtin/world-venue-map/headless.ts +17 -3
- package/src/plugins/builtin/yield-curve/curves.ts +15 -1
- package/src/plugins/builtin/yield-curve/headless.ts +17 -7
- package/src/plugins/builtin/yield-curve/history.ts +2 -0
- package/src/plugins/builtin/yield-curve/index.tsx +23 -18
- package/src/plugins/builtin/yield-curve/series.ts +19 -9
- package/src/plugins/builtin/yield-curve/treasury-data.ts +2 -0
- package/src/plugins/builtin/yield-curve/world.ts +4 -0
- package/src/plugins/registry/host-actions.ts +3 -0
- package/src/{plugins/builtin/portfolio-list → portfolio}/mutations.ts +6 -6
- package/src/portfolio/valuation-basis.ts +4 -21
- package/src/public/__snapshots__/public-api.test.ts.snap +2 -0
- package/src/remote/controller.ts +27 -7
- package/src/remote/schema.ts +25 -9
- package/src/renderers/browser/app-services.ts +7 -1
- package/src/renderers/browser/config-host.ts +55 -2
- package/src/renderers/browser/first-screen.ts +193 -0
- package/src/renderers/browser/main.tsx +59 -7
- package/src/renderers/cloudflare/http-proxy.ts +21 -13
- package/src/renderers/dom/desktop/controls.tsx +2 -0
- package/src/renderers/dom/desktop/query-bar.tsx +1 -1
- package/src/renderers/dom/dom-ui-host.tsx +2 -0
- package/src/renderers/dom/windowed-list.tsx +201 -0
- package/src/renderers/dom/windowed-rows.ts +65 -0
- package/src/renderers/electrobun/bun/application-menu/index.ts +1 -0
- package/src/renderers/electrobun/bun/desktop/backend-requests.ts +3 -7
- package/src/renderers/electrobun/bun/desktop/http-stream.ts +21 -6
- package/src/renderers/electrobun/bun/desktop/update.ts +132 -14
- package/src/renderers/electrobun/shared/protocol.ts +7 -4
- package/src/renderers/electrobun/view/http-fetch.ts +12 -1
- package/src/renderers/electrobun/view/update-host.ts +2 -2
- package/src/shares/bug-report-dialog.tsx +162 -0
- package/src/shares/bug-report-flow.tsx +84 -0
- package/src/shares/bug-report.ts +42 -0
- package/src/shares/capture-flow.ts +19 -0
- package/src/shares/capture-preview.tsx +129 -0
- package/src/shares/chat.ts +10 -1
- package/src/shares/dialog.tsx +16 -91
- package/src/shares/flow.tsx +12 -16
- package/src/shares/personal-pane.ts +35 -1
- package/src/sources/listing-symbols.ts +8 -4
- package/src/state/app/context/index.tsx +13 -0
- package/src/state/app/tab-cursor.ts +85 -0
- package/src/sync/core-contributors.ts +206 -59
- package/src/telemetry/usage-counts.ts +16 -0
- package/src/test-support/config-store.ts +31 -0
- package/src/test-support/moved-symbols.ts +64 -0
- package/src/theme/colors.ts +30 -0
- package/src/tickers/listing-fate.ts +242 -0
- package/src/tickers/open-target.ts +61 -11
- package/src/tickers/search/index.ts +45 -3
- package/src/tickers/search/ranking.ts +19 -2
- package/src/tickers/search/types.ts +3 -0
- package/src/tickers/search/upsert.ts +22 -7
- package/src/tickers/ticker-changes.ts +26 -9
- package/src/time-series/bar-spacing.ts +19 -0
- package/src/time-series/chart-data.ts +37 -13
- package/src/time-series/field-catalog.ts +5 -2
- package/src/time-series/forward-valuation.ts +10 -7
- package/src/time-series/fundamentals.ts +49 -11
- package/src/time-series/hooks.ts +2 -0
- package/src/time-series/live-bars.ts +3 -1
- package/src/time-series/parsed-history-cache.ts +2 -0
- package/src/time-series/price-units.ts +69 -0
- package/src/time-series/resolve.ts +71 -11
- package/src/time-series/session-bar.ts +138 -0
- package/src/time-series/spec.ts +20 -3
- package/src/time-series/studies.ts +211 -12
- package/src/time-series/transforms.ts +2 -0
- package/src/time-series/types.ts +11 -1
- package/src/time-series/valuation-price.ts +13 -0
- package/src/types/config.ts +24 -1
- package/src/types/financials.ts +2 -0
- package/src/types/headless.ts +27 -0
- package/src/types/plugin.ts +7 -0
- package/src/ui/host.tsx +5 -0
- package/src/updater/channel.ts +51 -0
- package/src/updater/github-releases.ts +85 -16
- package/src/updater/index.ts +128 -17
- package/src/utils/asset-category.ts +20 -0
- package/src/utils/datetime-format.ts +7 -1
- package/src/utils/decimal-comma.ts +83 -3
- package/src/utils/fundamentals.ts +7 -2
- package/src/utils/http-proxy-response.ts +66 -9
- package/src/utils/plugin-proxy-hosts.json +1 -5
- package/src/utils/semver.ts +47 -2
- package/src/utils/typo-distance.ts +22 -0
- package/src/plugins/builtin/shared/zip-text.ts +0 -46
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
import { clampFontSize, MIN_FONT_SIZE_PX } from "../../../../theme/font-scale";
|
|
15
15
|
import type { AppAction, AppState } from "../../../../state/app/context";
|
|
16
16
|
import { openBrokerAddFlow } from "../../../../plugins/builtin/broker-manager/add-request";
|
|
17
|
-
import { isManualPortfolio } from "../../../../
|
|
17
|
+
import { isManualPortfolio } from "../../../../portfolio/mutations";
|
|
18
18
|
import { CHART_RENDERER_PREFERENCES } from "../../../chart/core/types";
|
|
19
19
|
import type { Command } from "../registry";
|
|
20
20
|
import type { AppConfig, BrokerInstanceConfig } from "../../../../types/config";
|
|
@@ -29,6 +29,9 @@ import { parseWindowModeCommandArg } from "../../layout-items";
|
|
|
29
29
|
import type { CommandBarRoute } from "../../workflow/types";
|
|
30
30
|
import { recordFunctionOpen } from "../../../../telemetry/usage-counts";
|
|
31
31
|
import { reportTelemetryConfig } from "../../../../telemetry/live-config";
|
|
32
|
+
import { NIGHTLY_CHANNEL_SUMMARY, withUpdateChannel } from "../../../../updater/channel";
|
|
33
|
+
import { isPrereleaseVersion } from "../../../../utils/semver";
|
|
34
|
+
import { VERSION } from "../../../../version";
|
|
32
35
|
|
|
33
36
|
type NotifyFn = (body: string, options?: { type?: "info" | "success" | "error" }) => void;
|
|
34
37
|
|
|
@@ -373,6 +376,50 @@ export function runDirectCommandAction(options: {
|
|
|
373
376
|
void onCheckForUpdates?.();
|
|
374
377
|
closeAll({ revertThemePreview: false });
|
|
375
378
|
return;
|
|
379
|
+
case "release-channel": {
|
|
380
|
+
// The runtime checks again as soon as the channel changes.
|
|
381
|
+
const setChannel = (channel: "stable" | "nightly") => {
|
|
382
|
+
const nextConfig = withUpdateChannel(getState().config, channel);
|
|
383
|
+
dispatch({ type: "SET_CONFIG", config: nextConfig });
|
|
384
|
+
persistConfig(nextConfig);
|
|
385
|
+
notify(`${t(command.label)}: ${channel}`, { type: "success" });
|
|
386
|
+
};
|
|
387
|
+
// The saved setting flips; GLOOMBERB_UPDATE_CHANNEL still wins for the run it is set on.
|
|
388
|
+
// Back to stable asks too, to say a nightly build already running stays.
|
|
389
|
+
if (state.config.updates?.channel === "nightly") {
|
|
390
|
+
openInlineConfirm({
|
|
391
|
+
confirmId: "switch-to-stable",
|
|
392
|
+
title: "Switch to Stable?",
|
|
393
|
+
body: [
|
|
394
|
+
isPrereleaseVersion(VERSION)
|
|
395
|
+
? "Updates come from stable releases only. This nightly build stays until a newer stable release is out."
|
|
396
|
+
: "Updates come from stable releases only.",
|
|
397
|
+
],
|
|
398
|
+
confirmLabel: "Switch to Stable",
|
|
399
|
+
cancelLabel: "Stay on Nightly",
|
|
400
|
+
tone: "default",
|
|
401
|
+
onConfirm: async () => { setChannel("stable"); },
|
|
402
|
+
});
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
openInlineConfirm({
|
|
406
|
+
confirmId: "switch-to-nightly",
|
|
407
|
+
title: "Switch to Nightly?",
|
|
408
|
+
body: [
|
|
409
|
+
`Nightly is ${NIGHTLY_CHANNEL_SUMMARY}. Stable releases still reach you when they are newer.`,
|
|
410
|
+
isPrereleaseVersion(VERSION)
|
|
411
|
+
? "Switch back to Stable at any time."
|
|
412
|
+
: "Switch back to Stable at any time: the nightly build stays until a newer stable release is out.",
|
|
413
|
+
],
|
|
414
|
+
confirmLabel: "Switch to Nightly",
|
|
415
|
+
cancelLabel: "Stay on Stable",
|
|
416
|
+
tone: "default",
|
|
417
|
+
// An opt-in to builds that can break: a stray Enter stays on stable.
|
|
418
|
+
defaultAction: "cancel",
|
|
419
|
+
onConfirm: async () => { setChannel("nightly"); },
|
|
420
|
+
});
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
376
423
|
case "theme":
|
|
377
424
|
setRootThemeBaseId(getState().config.theme);
|
|
378
425
|
setRootQuery(arg ? `TH ${arg}` : "TH ");
|
|
@@ -250,6 +250,14 @@ export const commands: Command[] = [
|
|
|
250
250
|
description: "Check GitHub releases for a newer version",
|
|
251
251
|
category: "Config",
|
|
252
252
|
},
|
|
253
|
+
{
|
|
254
|
+
// Not "Update ...": "update" has to land on Check for Updates first.
|
|
255
|
+
id: "release-channel",
|
|
256
|
+
prefix: "",
|
|
257
|
+
label: "Release Channel",
|
|
258
|
+
description: "Update from stable releases, or from nightly builds as well",
|
|
259
|
+
category: "Config",
|
|
260
|
+
},
|
|
253
261
|
{
|
|
254
262
|
id: "toggle-crash-reports",
|
|
255
263
|
prefix: "",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { CommandBarResultLine } from "../../../types/plugin";
|
|
2
2
|
import type { TickerRecord } from "../../../types/ticker";
|
|
3
3
|
import type { BrokerContractRef, TickerListingRef } from "../../../types/instrument";
|
|
4
|
+
import type { InstrumentRef } from "../../../market-data/request-types";
|
|
4
5
|
import {
|
|
5
6
|
buildSections,
|
|
6
7
|
type CommandBarCategoryPriorities,
|
|
@@ -39,6 +40,12 @@ export interface ResultItem {
|
|
|
39
40
|
listing?: TickerListingRef;
|
|
40
41
|
/** Materialize this exact listing when another command consumes the row. */
|
|
41
42
|
resolveTicker?: () => Promise<TickerRecord>;
|
|
43
|
+
/**
|
|
44
|
+
* What the pane this row opens will load, so the bar can warm it while the
|
|
45
|
+
* row is the pick. Unset when only opening can tell (a broker contract, a
|
|
46
|
+
* saved ticker with several).
|
|
47
|
+
*/
|
|
48
|
+
prefetchInstrument?: InstrumentRef;
|
|
42
49
|
shortcutQuery?: string;
|
|
43
50
|
searchText?: string;
|
|
44
51
|
/** Tints the trailing marker and the section heading with the AI accent. */
|
|
@@ -139,6 +139,11 @@ export function useCommandBarPaneTemplateActions({
|
|
|
139
139
|
{ preserveListingKey: true },
|
|
140
140
|
);
|
|
141
141
|
if (!resolvedTicker) {
|
|
142
|
+
// Typed alone with no active ticker: the pane's own view without one (INS's insider buying).
|
|
143
|
+
if (!trimmedArg && template.shortcut?.openWithoutArg) {
|
|
144
|
+
await openPaneTemplateDirect(template);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
142
147
|
openModeRoute("ticker-search", trimmedArg, {
|
|
143
148
|
action: "pane-template",
|
|
144
149
|
templateId: template.id,
|
|
@@ -52,3 +52,11 @@ function getSnapshot(): CommandBarPromptBinding | null {
|
|
|
52
52
|
export function useCommandBarPromptBinding(): CommandBarPromptBinding | null {
|
|
53
53
|
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
54
54
|
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Calls `listener` with the open screen's query each time the bar publishes
|
|
58
|
+
* its prompt, and with an empty one when it closes. For code outside React.
|
|
59
|
+
*/
|
|
60
|
+
export function subscribeCommandBarQuery(listener: (query: string) => void): () => void {
|
|
61
|
+
return subscribe(() => listener(binding?.query ?? ""));
|
|
62
|
+
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import type { AppState } from "../../../../state/app/context";
|
|
2
2
|
import type { TickerRecord } from "../../../../types/ticker";
|
|
3
|
-
import { isManualPortfolio } from "../../../../
|
|
3
|
+
import { isManualPortfolio } from "../../../../portfolio/mutations";
|
|
4
4
|
import type { Command } from "../../commands/registry";
|
|
5
5
|
import type { ResultItem } from "../../list/model";
|
|
6
6
|
import { canRestartToApply } from "../../../../updater";
|
|
7
|
+
import { NIGHTLY_CHANNEL_SUMMARY, resolveUpdateChannel } from "../../../../updater/channel";
|
|
8
|
+
import { isPrereleaseVersion } from "../../../../utils/semver";
|
|
9
|
+
import { VERSION } from "../../../../version";
|
|
7
10
|
|
|
8
11
|
interface RootCommandItemBuilderOptions {
|
|
9
12
|
activeCollectionId: string | null;
|
|
@@ -157,6 +160,11 @@ export function createRootCommandItemBuilder({
|
|
|
157
160
|
return state.config.telemetry?.attention === true
|
|
158
161
|
? "Currently on: share ticker research counts"
|
|
159
162
|
: "Currently off: opt in to sharing ticker research counts";
|
|
163
|
+
case "release-channel":
|
|
164
|
+
if (resolveUpdateChannel(state.config) === "nightly") return `Currently nightly: ${NIGHTLY_CHANNEL_SUMMARY}`;
|
|
165
|
+
return isPrereleaseVersion(VERSION)
|
|
166
|
+
? "Currently stable: this nightly build stays until a newer stable release"
|
|
167
|
+
: "Currently stable";
|
|
160
168
|
case "font-size-increase":
|
|
161
169
|
case "font-size-decrease":
|
|
162
170
|
return `Currently ${state.config.fontSize ?? 12}px`;
|
|
@@ -176,6 +184,8 @@ export function createRootCommandItemBuilder({
|
|
|
176
184
|
return state.config.telemetry?.usage === false ? "off" : "on";
|
|
177
185
|
case "toggle-attention-counts":
|
|
178
186
|
return state.config.telemetry?.attention === true ? "on" : "off";
|
|
187
|
+
case "release-channel":
|
|
188
|
+
return resolveUpdateChannel(state.config);
|
|
179
189
|
default:
|
|
180
190
|
return command.prefix || undefined;
|
|
181
191
|
}
|
|
@@ -185,8 +195,13 @@ export function createRootCommandItemBuilder({
|
|
|
185
195
|
switch (command.id) {
|
|
186
196
|
case "set-portfolio-position":
|
|
187
197
|
return "edit position update position modify position manual position portfolio position";
|
|
198
|
+
// "update" is a whole word here so it lands on this command before Set
|
|
199
|
+
// Portfolio Position (whose keywords say "update position") and Release
|
|
200
|
+
// Channel, which keeps the word further back.
|
|
188
201
|
case "check-for-updates":
|
|
189
|
-
return state.updateProgress?.phase === "ready" ? "check for updates restart relaunch" : "";
|
|
202
|
+
return state.updateProgress?.phase === "ready" ? "update check for updates restart relaunch" : "update";
|
|
203
|
+
case "release-channel":
|
|
204
|
+
return "nightly stable beta prerelease update updates channel";
|
|
190
205
|
default:
|
|
191
206
|
return "";
|
|
192
207
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
import { getSharedMarketDataCoordinator } from "../../../../market-data/coordinator";
|
|
3
|
+
import type { InstrumentRef } from "../../../../market-data/request-types";
|
|
4
|
+
import { instrumentIdentityKey } from "../../../../utils/instrument-identity";
|
|
5
|
+
import type { ResultItem } from "../../list/model";
|
|
6
|
+
|
|
7
|
+
/** Typing passes through rows faster than this; reading the list and pressing Enter takes longer. */
|
|
8
|
+
const PICK_REST_MS = 200;
|
|
9
|
+
|
|
10
|
+
function warmInstrument(instrument: InstrumentRef): void {
|
|
11
|
+
getSharedMarketDataCoordinator()?.warmTickerGaps(instrument);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Warms the quote and chart of the instrument Enter would open once the pick
|
|
16
|
+
* rests, so its pane paints with data instead of loading. Only the row Enter
|
|
17
|
+
* runs, only an instrument row, and each instrument once per visit to the bar:
|
|
18
|
+
* Gloom Cloud rate-limits the free plan.
|
|
19
|
+
*/
|
|
20
|
+
export function useRootPickPrefetch(
|
|
21
|
+
pick: ResultItem | null | undefined,
|
|
22
|
+
prefetch: (instrument: InstrumentRef) => void = warmInstrument,
|
|
23
|
+
): void {
|
|
24
|
+
const instrument = pick?.prefetchInstrument ?? null;
|
|
25
|
+
const latest = useRef(instrument);
|
|
26
|
+
latest.current = instrument;
|
|
27
|
+
const warmed = useRef(new Set<string>());
|
|
28
|
+
const key = instrument ? instrumentIdentityKey(instrument) : null;
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (!key || warmed.current.has(key)) return;
|
|
32
|
+
const timer = setTimeout(() => {
|
|
33
|
+
warmed.current.add(key);
|
|
34
|
+
if (latest.current) prefetch(latest.current);
|
|
35
|
+
}, PICK_REST_MS);
|
|
36
|
+
return () => clearTimeout(timer);
|
|
37
|
+
}, [key, prefetch]);
|
|
38
|
+
}
|
|
@@ -41,6 +41,8 @@ export function useRootProviderSearch(options: {
|
|
|
41
41
|
}): {
|
|
42
42
|
activeRootProviderResultsKey: string | null;
|
|
43
43
|
orderedRootResults: ResultItem[];
|
|
44
|
+
/** False while the list is the local stand-in for a symbol search that has not answered. */
|
|
45
|
+
rootResultsCurrent: boolean;
|
|
44
46
|
rootSearching: boolean;
|
|
45
47
|
rootSectionOrder: CommandBarSectionOrder;
|
|
46
48
|
} {
|
|
@@ -231,9 +233,13 @@ export function useRootProviderSearch(options: {
|
|
|
231
233
|
].join("\n");
|
|
232
234
|
}, [rootPlainTickerSearchArg, rootProviderResults, rootProviderResultsQuery, rootTickerSearchArg]);
|
|
233
235
|
|
|
236
|
+
// The rows before the answer are for whatever the query matches locally, not the rows Enter will land on.
|
|
237
|
+
const rootResultsCurrent = !rootTickerSearchArg || (rootProviderResultsQuery === rootTickerSearchArg && !!rootProviderResults);
|
|
238
|
+
|
|
234
239
|
return {
|
|
235
240
|
activeRootProviderResultsKey,
|
|
236
241
|
orderedRootResults,
|
|
242
|
+
rootResultsCurrent,
|
|
237
243
|
rootSearching,
|
|
238
244
|
rootSectionOrder,
|
|
239
245
|
};
|
|
@@ -146,6 +146,7 @@ export function useCommandBarRootRuntime({
|
|
|
146
146
|
orderedRootResults: ResultItem[];
|
|
147
147
|
rootGhostSuffix: string | null;
|
|
148
148
|
rootResultModel: RootResultModel;
|
|
149
|
+
rootResultsCurrent: boolean;
|
|
149
150
|
rootSearching: boolean;
|
|
150
151
|
rootSectionOrder: ReturnType<typeof useRootProviderSearch>["rootSectionOrder"];
|
|
151
152
|
rootShortcutFeedback: string | null;
|
|
@@ -282,6 +283,7 @@ export function useCommandBarRootRuntime({
|
|
|
282
283
|
const {
|
|
283
284
|
activeRootProviderResultsKey,
|
|
284
285
|
orderedRootResults,
|
|
286
|
+
rootResultsCurrent,
|
|
285
287
|
rootSearching: tickerSearching,
|
|
286
288
|
rootSectionOrder,
|
|
287
289
|
} = useRootProviderSearch({
|
|
@@ -371,6 +373,7 @@ export function useCommandBarRootRuntime({
|
|
|
371
373
|
orderedRootResults,
|
|
372
374
|
rootGhostSuffix,
|
|
373
375
|
rootResultModel,
|
|
376
|
+
rootResultsCurrent,
|
|
374
377
|
rootSearching,
|
|
375
378
|
rootSectionOrder,
|
|
376
379
|
rootShortcutFeedback,
|
|
@@ -43,7 +43,9 @@ export function buildRootShortcutFeedback({
|
|
|
43
43
|
? `Shortcut: ${rootShortcutIntent.label} for ${symbol} · Tab to accept`
|
|
44
44
|
: `Shortcut: ${rootShortcutIntent.label} for ${symbol}`;
|
|
45
45
|
}
|
|
46
|
-
return
|
|
46
|
+
return rootShortcutIntent.template.shortcut?.openWithoutArg && !rootShortcutIntent.argText
|
|
47
|
+
? `Shortcut: ${rootShortcutIntent.label}`
|
|
48
|
+
: `Shortcut: ${rootShortcutIntent.label} · Enter to choose ticker`;
|
|
47
49
|
}
|
|
48
50
|
if (argKind === "ticker-list") {
|
|
49
51
|
if (rootShortcutIntent.argText) {
|
|
@@ -4,7 +4,7 @@ import type {
|
|
|
4
4
|
PaneTemplateCreateOptions,
|
|
5
5
|
PaneTemplateDef,
|
|
6
6
|
} from "../../../../types/plugin";
|
|
7
|
-
import { isManualPortfolio } from "../../../../
|
|
7
|
+
import { isManualPortfolio } from "../../../../portfolio/mutations";
|
|
8
8
|
import { normalizeTickerInput } from "../../../../tickers/search";
|
|
9
9
|
import {
|
|
10
10
|
getCollectionCommandAction,
|
|
@@ -4,8 +4,12 @@ import type { AppState } from "../../../../state/app/context";
|
|
|
4
4
|
import type { AppTickerRepositoryPort } from "../../../../core/app-service-ports";
|
|
5
5
|
import type { PluginRegistry } from "../../../../plugins/registry";
|
|
6
6
|
import type { InstrumentSearchResult } from "../../../../types/instrument";
|
|
7
|
+
import type { TickerRecord } from "../../../../types/ticker";
|
|
7
8
|
import type { PinTickerOptions } from "../../../../types/plugin";
|
|
8
9
|
import { publicTickerKey } from "../../../../utils/exchanges";
|
|
10
|
+
import { instrumentFromTicker, type InstrumentRef } from "../../../../market-data/request-types";
|
|
11
|
+
import { getSearchResultSymbol } from "../../../../tickers/search/result";
|
|
12
|
+
import { otherListingTickerKey } from "../../../../tickers/search/upsert";
|
|
9
13
|
import {
|
|
10
14
|
rankTickerSearchItems,
|
|
11
15
|
upsertTickerFromSearchResult,
|
|
@@ -37,6 +41,9 @@ export function useCommandBarTickerSearchActions({
|
|
|
37
41
|
tickers,
|
|
38
42
|
}: UseCommandBarTickerSearchActionsOptions) {
|
|
39
43
|
const tickerSearchCacheRef = useRef<Map<string, TickerSearchCandidate[]>>(new Map());
|
|
44
|
+
// Read when a row is built; a ticker saved meanwhile must not rebuild every row.
|
|
45
|
+
const tickersRef = useRef(tickers);
|
|
46
|
+
tickersRef.current = tickers;
|
|
40
47
|
|
|
41
48
|
const resolveSearchTicker = useCallback(async (result: InstrumentSearchResult, tickerSymbol?: string) => {
|
|
42
49
|
const { ticker, created } = await upsertTickerFromSearchResult(tickerRepository, result, { tickerSymbol });
|
|
@@ -82,6 +89,7 @@ export function useCommandBarTickerSearchActions({
|
|
|
82
89
|
category: candidate.category,
|
|
83
90
|
kind: "ticker",
|
|
84
91
|
resolveTicker: async () => candidate.ticker!,
|
|
92
|
+
prefetchInstrument: rowPrefetchInstrument(candidate, tickersRef.current),
|
|
85
93
|
secondaryAction: () => {
|
|
86
94
|
focusTicker(candidate.ticker!.metadata.ticker, { forceNewPane: true, ...tickerSelectionFromSearchResult(candidate.result) });
|
|
87
95
|
closeAll({ revertThemePreview: false });
|
|
@@ -111,6 +119,7 @@ export function useCommandBarTickerSearchActions({
|
|
|
111
119
|
candidate.result!.exchange === "SMART" ? candidate.result!.primaryExchange
|
|
112
120
|
: candidate.result!.exchange || candidate.result!.primaryExchange,
|
|
113
121
|
)),
|
|
122
|
+
prefetchInstrument: rowPrefetchInstrument(candidate, tickersRef.current),
|
|
114
123
|
secondaryAction: () => openTickerResearch(candidate.result!, { forceNewPane: true }),
|
|
115
124
|
action: () => openTickerResearch(candidate.result!),
|
|
116
125
|
};
|
|
@@ -181,6 +190,31 @@ export function useCommandBarTickerSearchActions({
|
|
|
181
190
|
};
|
|
182
191
|
}
|
|
183
192
|
|
|
193
|
+
/**
|
|
194
|
+
* The instrument the pane reads once this row opens: a search listing is
|
|
195
|
+
* pinned by its venue, a saved ticker without one goes by its record. A broker
|
|
196
|
+
* contract, a saved ticker that carries one, or a choice among several stays
|
|
197
|
+
* with the pane, which resolves the route and its venue.
|
|
198
|
+
*/
|
|
199
|
+
function rowPrefetchInstrument(
|
|
200
|
+
candidate: TickerSearchCandidate,
|
|
201
|
+
tickers: ReadonlyMap<string, TickerRecord>,
|
|
202
|
+
): InstrumentRef | undefined {
|
|
203
|
+
const { result, ticker } = candidate;
|
|
204
|
+
if (result) {
|
|
205
|
+
if (result.brokerContract) return undefined;
|
|
206
|
+
// Opened, a listing whose symbol is saved for another venue is saved under its own key.
|
|
207
|
+
const symbol = getSearchResultSymbol(result);
|
|
208
|
+
return {
|
|
209
|
+
symbol: ticker?.metadata.ticker ?? otherListingTickerKey(result, symbol, tickers.get(symbol)) ?? symbol,
|
|
210
|
+
exchange: result.exchange || ticker?.metadata.exchange || "",
|
|
211
|
+
instrument: null,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
if (!ticker || ticker.metadata.broker_contracts?.length) return undefined;
|
|
215
|
+
return instrumentFromTicker(ticker, ticker.metadata.ticker) ?? undefined;
|
|
216
|
+
}
|
|
217
|
+
|
|
184
218
|
function formatTickerSearchDetail(candidate: TickerSearchCandidate): string {
|
|
185
219
|
const metadata = [
|
|
186
220
|
formatInstrumentClass(candidate.instrumentClass),
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
} from "../routes/root/search-providers";
|
|
18
18
|
import { useRouteListState } from "../routing/list-state";
|
|
19
19
|
import { useCommandBarRootRuntime } from "../routes/root/runtime";
|
|
20
|
+
import { useRootPickPrefetch } from "../routes/root/pick-prefetch";
|
|
20
21
|
import { createQuickLookTickerCandidates } from "../routes/ticker-search/results";
|
|
21
22
|
import { useCommandSearchReport } from "../routes/root/search-report";
|
|
22
23
|
import { useAppStateRef } from "../../../state/app/context";
|
|
@@ -77,9 +78,10 @@ export function CommandBar({
|
|
|
77
78
|
titleBarOverlay,
|
|
78
79
|
visibleListStateRef,
|
|
79
80
|
} = useCommandBarEnvironment();
|
|
81
|
+
// Without an updater (the web terminal) neither checking nor a channel means anything.
|
|
80
82
|
const availableCommands = useMemo(() => onCheckForUpdates
|
|
81
83
|
? allAvailableCommands
|
|
82
|
-
: allAvailableCommands.filter((command) => command.id !== "check-for-updates"), [allAvailableCommands, onCheckForUpdates]);
|
|
84
|
+
: allAvailableCommands.filter((command) => command.id !== "check-for-updates" && command.id !== "release-channel"), [allAvailableCommands, onCheckForUpdates]);
|
|
83
85
|
const {
|
|
84
86
|
applyThemePreview,
|
|
85
87
|
clearThemePreview,
|
|
@@ -405,6 +407,7 @@ export function CommandBar({
|
|
|
405
407
|
activeMatch,
|
|
406
408
|
orderedRootResults,
|
|
407
409
|
rootGhostSuffix,
|
|
410
|
+
rootResultsCurrent,
|
|
408
411
|
rootSearching,
|
|
409
412
|
rootSectionOrder,
|
|
410
413
|
rootShortcutFeedback,
|
|
@@ -458,6 +461,9 @@ export function CommandBar({
|
|
|
458
461
|
tickerActionItems,
|
|
459
462
|
writeTickerSearchCache,
|
|
460
463
|
});
|
|
464
|
+
// The row plain Enter runs: the best match while the list is untouched. Not
|
|
465
|
+
// the stand-in rows shown while the search for the typed query is out.
|
|
466
|
+
useRootPickPrefetch(currentRoute || !rootResultsCurrent ? null : orderedRootResults[rootSelectedIdx]);
|
|
461
467
|
const themePickerActive = !currentRoute && activeMatch?.command.id === "theme";
|
|
462
468
|
const themePickerFilter = themePickerActive ? activeMatch.arg : "";
|
|
463
469
|
// A theme picked from the root ("TH dracula", Enter) is the typed shortcut
|
|
@@ -23,11 +23,12 @@ import {
|
|
|
23
23
|
isManualPortfolio,
|
|
24
24
|
resolveManualPositionCurrency,
|
|
25
25
|
setManualPortfolioPosition,
|
|
26
|
-
} from "../../../
|
|
26
|
+
} from "../../../portfolio/mutations";
|
|
27
27
|
import type { CommandBarFieldValue } from "./types";
|
|
28
28
|
import type { WorkflowStringValues } from "./broker";
|
|
29
29
|
import { coerceFieldString } from "../helpers";
|
|
30
30
|
import { slugifyName } from "../../../utils/slugify";
|
|
31
|
+
import { readTypedNumber } from "../../../utils/decimal-comma";
|
|
31
32
|
import { resolveTickerInputOrThrow } from "./ops";
|
|
32
33
|
import { resolveCollectionTicker } from "./collection-ticker";
|
|
33
34
|
|
|
@@ -232,14 +233,14 @@ export function createCommandBarCollectionWorkflowActions(options: {
|
|
|
232
233
|
throw new Error("Choose a manual portfolio.");
|
|
233
234
|
}
|
|
234
235
|
|
|
235
|
-
|
|
236
|
+
// 18,50, 1.234,5 and 123.456 read as typed; 1,234 could be either reading and fails naming both.
|
|
237
|
+
const shares = readTypedNumber(coerceFieldString(values.shares), "Shares");
|
|
236
238
|
if (!Number.isFinite(shares) || shares <= 0) {
|
|
237
239
|
throw new Error("Shares must be greater than 0.");
|
|
238
240
|
}
|
|
239
241
|
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
if (!rawAvgCost || !Number.isFinite(avgCost)) {
|
|
242
|
+
const avgCost = readTypedNumber(coerceFieldString(values.avgCost), "Avg Cost");
|
|
243
|
+
if (!Number.isFinite(avgCost)) {
|
|
243
244
|
throw new Error("Avg Cost must be a valid number.");
|
|
244
245
|
}
|
|
245
246
|
|
|
@@ -2,7 +2,7 @@ import { type LayoutConfig, type PaneBinding, type PaneInstanceConfig } from "..
|
|
|
2
2
|
import type { PaneSettingField, PaneTemplateContext, PaneTemplateCreateOptions, PaneTemplateInstanceConfig, PaneTemplateDef } from "../../../types/plugin";
|
|
3
3
|
import { getFocusedCollectionId, getFocusedTickerSymbol } from "../../../state/app/context";
|
|
4
4
|
import { resolveInstrumentForPane, resolveListingForPane } from "../../../core/state/app/instrument";
|
|
5
|
-
import { resolveTickerOpenTarget } from "../../../tickers/open-target";
|
|
5
|
+
import { resolveTickerOpenTarget, tickerOpenFailureMessage } from "../../../tickers/open-target";
|
|
6
6
|
import { instrumentFromTicker } from "../../../market-data/request-types";
|
|
7
7
|
import { scopedBrokerContractIdentityKey } from "../../../utils/instrument-identity";
|
|
8
8
|
import { tickerSelectionFromSearchResult } from "../../../tickers/selection";
|
|
@@ -115,14 +115,17 @@ async function resolvePaneTemplateOptions(
|
|
|
115
115
|
// nothing was named, so they must not resolve a ticker at all: there is no
|
|
116
116
|
// input to match, and silently falling back to the active ticker would bind a
|
|
117
117
|
// pane the caller deliberately left unbound.
|
|
118
|
+
// INS opens its own view without one (the market's insider buying) when
|
|
119
|
+
// nothing is named and no ticker is active; with one active it opens on it.
|
|
118
120
|
const tickerNamed = !!(resolvedOptions?.symbol?.trim() || resolvedOptions?.arg?.trim() || resolvedOptions?.ticker);
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
+
const opensWithoutTicker = !!template.shortcut?.argOptional || (!!template.shortcut?.openWithoutArg && !baseContext.activeTicker);
|
|
122
|
+
const resolvesTicker = template.shortcut?.argPlaceholder === "ticker" && (tickerNamed || !opensWithoutTicker);
|
|
121
123
|
if (resolvesTicker) {
|
|
122
124
|
if (resolvedOptions?.instrument === null && !resolvedOptions.ticker) {
|
|
123
|
-
const
|
|
125
|
+
const publicQuery = resolvedOptions.symbol ?? resolvedOptions.arg ?? baseContext.activeTicker ?? "";
|
|
126
|
+
const publicTarget = await resolveTickerOpenTarget({ query: publicQuery,
|
|
124
127
|
tickers: state.tickers, tickerRepository: deps.tickerRepository, dataProvider: deps.dataProvider, publicOnly: true });
|
|
125
|
-
if (!publicTarget) throw new Error(
|
|
128
|
+
if (!publicTarget) throw new Error(tickerOpenFailureMessage(publicQuery.trim().toUpperCase()));
|
|
126
129
|
deps.dispatch({ type: "UPDATE_TICKER", ticker: publicTarget.ticker });
|
|
127
130
|
if (publicTarget.created) deps.pluginRegistry.events.emit("ticker:added", { symbol: publicTarget.symbol, ticker: publicTarget.ticker });
|
|
128
131
|
resolvedOptions = { ...resolvedOptions, symbol: publicTarget.symbol, ticker: publicTarget.ticker, listing: resolvedOptions.listing ?? publicTarget.listing };
|
|
@@ -104,7 +104,7 @@ interface OtherSettings {
|
|
|
104
104
|
identity: readonly string[] | undefined;
|
|
105
105
|
/** The template is the only one that opens its pane type. */
|
|
106
106
|
onlyTemplate: boolean;
|
|
107
|
-
/** The spec names a ticker or params, or the form takes no argument
|
|
107
|
+
/** The spec names a ticker or params, or the form takes no argument, an optional one, or opens without one. */
|
|
108
108
|
bareFormOk: boolean;
|
|
109
109
|
}
|
|
110
110
|
|
|
@@ -150,7 +150,8 @@ export function findReusablePaneInstance(
|
|
|
150
150
|
const other: OtherSettings = {
|
|
151
151
|
identity: options.paneDef?.identitySettings,
|
|
152
152
|
onlyTemplate: isOnlyTemplateForPane(template, templates),
|
|
153
|
-
bareFormOk: namesATarget(spec) || !template.shortcut?.argPlaceholder || template.shortcut.argOptional === true
|
|
153
|
+
bareFormOk: namesATarget(spec) || !template.shortcut?.argPlaceholder || template.shortcut.argOptional === true
|
|
154
|
+
|| template.shortcut.openWithoutArg === true,
|
|
154
155
|
};
|
|
155
156
|
const matches = candidates.filter((instance) => showsWhatSpecNames(instance, spec, other));
|
|
156
157
|
return matches.find((instance) => instance.instanceId === options.focusedPaneId) ?? matches[0] ?? null;
|
|
@@ -2,7 +2,7 @@ import type { Dispatch } from "react";
|
|
|
2
2
|
import type { AppAction, AppState } from "../../../state/app/context";
|
|
3
3
|
import type { AppTickerRepositoryPort } from "../../../core/app-service-ports";
|
|
4
4
|
import type { PluginRegistry } from "../../../plugins/registry";
|
|
5
|
-
import { isManualPortfolio } from "../../../
|
|
5
|
+
import { isManualPortfolio } from "../../../portfolio/mutations";
|
|
6
6
|
import type { DataProvider } from "../../../types/data-provider";
|
|
7
7
|
import type { Portfolio, TickerRecord, Watchlist } from "../../../types/ticker";
|
|
8
8
|
import { AmbiguousTickerError, resolveTickerSearch, upsertTickerFromSearchResult } from "../../../tickers/search";
|
|
@@ -137,6 +137,7 @@ export function ConfirmModalContent({
|
|
|
137
137
|
confirmLabel={t(confirm.confirmLabel)}
|
|
138
138
|
cancelLabel={t(confirmCancelLabel(confirm.cancelLabel))}
|
|
139
139
|
confirmVariant={confirm.tone === "default" ? "primary" : "danger"}
|
|
140
|
+
defaultAction={confirm.defaultAction}
|
|
140
141
|
width={contentWidth}
|
|
141
142
|
status={status}
|
|
142
143
|
busy={pending}
|
|
@@ -12,6 +12,8 @@ export interface ConfirmModalOptions {
|
|
|
12
12
|
cancelLabel?: string;
|
|
13
13
|
/** Danger unless said otherwise: most confirms guard a deletion. */
|
|
14
14
|
tone?: "default" | "danger";
|
|
15
|
+
/** What Enter answers: `cancel` for an opt-in a stray Enter must not accept (ConfirmDialog). */
|
|
16
|
+
defaultAction?: "confirm" | "cancel";
|
|
15
17
|
/** Reads what it acts on when it runs, not when the confirm opened. */
|
|
16
18
|
onConfirm: () => void | Promise<void>;
|
|
17
19
|
/** "stay" keeps the confirm open after it ran; "back" closes it, as "close" does. */
|
|
@@ -38,7 +38,8 @@ import {
|
|
|
38
38
|
paneManagementAccelerators,
|
|
39
39
|
resolvePaneManagementShortcut,
|
|
40
40
|
} from "./shell/shortcuts";
|
|
41
|
-
import { menuItemsForFallback, paneFooterMenuItems } from "./shell/menu";
|
|
41
|
+
import { menuItemsForFallback, paneFooterMenuItems, REPORT_BUG_MENU_LABEL } from "./shell/menu";
|
|
42
|
+
import { canReportPaneBug } from "../../shares/bug-report-flow";
|
|
42
43
|
import { useShellPaneCapture } from "./shell/pane/capture";
|
|
43
44
|
import { captureStampFooter, usePaneCaptureStamp } from "./pane/capture-stamp";
|
|
44
45
|
import { pluginContextMenuSlot, withPluginContextMenuItems } from "../../ui/context-menu";
|
|
@@ -252,6 +253,10 @@ export function DetachedPaneShell({ pluginRegistry, desktopWindowBridge }: Detac
|
|
|
252
253
|
}
|
|
253
254
|
items.push({ id: "pane-docs", label: "Docs", onSelect: openDocs });
|
|
254
255
|
items.push({ id: "share-pane", label: "Share", accelerator: accelerators.share, onSelect: sharePane });
|
|
256
|
+
// A popped-out window paints its pane's DOM, so it can always picture it.
|
|
257
|
+
if (canReportPaneBug(true)) {
|
|
258
|
+
items.push({ id: "report-bug", label: REPORT_BUG_MENU_LABEL, onSelect: () => pluginRegistry.reportPaneBug(desktopWindowBridge.paneId) });
|
|
259
|
+
}
|
|
255
260
|
items.push(pluginContextMenuSlot("pane"));
|
|
256
261
|
if (dockPane) {
|
|
257
262
|
items.push({ id: "dock", label: "Dock in Main Window", accelerator: accelerators.toggleFloating, onSelect: dockPane });
|
|
@@ -9,6 +9,7 @@ import { PaneKeyboardScrollController } from "../../../state/pane-scroll-registr
|
|
|
9
9
|
import type { PaneDef } from "../../../types/plugin";
|
|
10
10
|
import { Box } from "../../../ui";
|
|
11
11
|
import { PaneErrorBoundary } from "./error-boundary";
|
|
12
|
+
import { PaneTickerFateGate } from "./ticker-fate-gate";
|
|
12
13
|
|
|
13
14
|
// Under GLOOMBERB_PERF_TRACE every pane commit is reported by pane, which is
|
|
14
15
|
// how a store update that fans out to the whole layout gets attributed. The
|
|
@@ -72,14 +73,16 @@ export const PaneContent = memo(function PaneContent({
|
|
|
72
73
|
const body = useMemo(() => (
|
|
73
74
|
<PaneErrorBoundary paneType={paneType} title={title} onClose={closePane ? closeFailedPane : undefined}>
|
|
74
75
|
<PaneRenderTrace paneId={paneId} paneType={paneType}>
|
|
75
|
-
<
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
76
|
+
<PaneTickerFateGate paneId={paneId}>
|
|
77
|
+
<Component
|
|
78
|
+
paneId={paneId}
|
|
79
|
+
paneType={paneType}
|
|
80
|
+
focused={focused}
|
|
81
|
+
width={width}
|
|
82
|
+
height={height}
|
|
83
|
+
close={onClose ? close : undefined}
|
|
84
|
+
/>
|
|
85
|
+
</PaneTickerFateGate>
|
|
83
86
|
</PaneRenderTrace>
|
|
84
87
|
</PaneErrorBoundary>
|
|
85
88
|
), [Component, close, closeFailedPane, closePane, focused, height, language, onClose, paneId, paneType, theme, title, width]);
|
|
@@ -178,9 +178,11 @@ export function PaneSidebar({
|
|
|
178
178
|
(resize?.onResizeEnd ?? resize?.onResize)?.(next);
|
|
179
179
|
}, [resize, resolveDragWidth, stopLiveDrag]);
|
|
180
180
|
|
|
181
|
-
// The keyboard moves the divider too: `<` and `>` while the pane is focused
|
|
182
|
-
//
|
|
183
|
-
//
|
|
181
|
+
// The keyboard moves the divider too: `<` and `>` while the pane is focused.
|
|
182
|
+
// They are bound and listed in the pane menu, not drawn in the footer: the
|
|
183
|
+
// width of a list is not one of the pane's actions, and the divider shows
|
|
184
|
+
// where it is (on the desktop its tooltip names the keys). Each press is a
|
|
185
|
+
// finished resize, so it persists.
|
|
184
186
|
const resizeRegistrationId = `pane-sidebar-resize:${useId()}`;
|
|
185
187
|
const canNarrow = !!resize && width > resize.min;
|
|
186
188
|
const canWiden = !!resize && width < resize.max;
|
|
@@ -189,14 +191,25 @@ export function PaneSidebar({
|
|
|
189
191
|
const next = Math.min(resize.max, Math.max(resize.min, width + delta));
|
|
190
192
|
if (next !== width) (resize.onResizeEnd ?? resize.onResize)(next);
|
|
191
193
|
};
|
|
192
|
-
usePaneFooter(resizeRegistrationId, () =>
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
{ id: "sidebar-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
194
|
+
usePaneFooter(resizeRegistrationId, () => {
|
|
195
|
+
if (!resize) return null;
|
|
196
|
+
const steps = [
|
|
197
|
+
{ id: "sidebar-narrow", key: NARROW_KEY, title: "Narrow Sidebar", delta: -KEYBOARD_RESIZE_STEP, disabled: !canNarrow },
|
|
198
|
+
{ id: "sidebar-widen", key: WIDEN_KEY, title: "Widen Sidebar", delta: KEYBOARD_RESIZE_STEP, disabled: !canWiden },
|
|
199
|
+
];
|
|
200
|
+
return {
|
|
201
|
+
// After the pane's own actions, which register at order 0.
|
|
202
|
+
order: 1,
|
|
203
|
+
keys: steps.map(({ id, key, delta, disabled }) => ({ id, key, label: "", onPress: () => stepResize(delta), disabled })),
|
|
204
|
+
// A bound that is reached drops out, as a disabled footer hint did.
|
|
205
|
+
menu: steps.filter((step) => !step.disabled).map(({ id, key, title, delta }) => ({
|
|
206
|
+
id: `pane-sidebar:${id}`,
|
|
207
|
+
label: title,
|
|
208
|
+
accelerator: key,
|
|
209
|
+
onSelect: () => stepResize(delta),
|
|
210
|
+
})),
|
|
211
|
+
};
|
|
212
|
+
}, [!!resize, canNarrow, canWiden, resize?.min, resize?.max, width]);
|
|
200
213
|
|
|
201
214
|
const borderWidth = nativePaneChrome ? 0 : width > 1 ? 1 : 0;
|
|
202
215
|
const listWidth = Math.max(width - borderWidth, 1);
|