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
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
import type { SyncBrokerInstanceResult } from "../../brokers/sync-broker-instance";
|
|
9
9
|
import type { AppTickerRepositoryPort } from "../../core/app-service-ports";
|
|
10
10
|
import type { PluginRegistry } from "../../plugins/registry";
|
|
11
|
-
import { adoptHeldPortfolioCurrencies } from "../../
|
|
11
|
+
import { adoptHeldPortfolioCurrencies } from "../../portfolio/mutations";
|
|
12
12
|
import { saveConfigImmediately } from "../../state/config-save-scheduler";
|
|
13
13
|
import type { AppAction, AppState } from "../../state/app/context";
|
|
14
14
|
import type { AppConfig } from "../../types/config";
|
|
@@ -18,6 +18,7 @@ import { getBrokerInstance } from "../../utils/broker-instances";
|
|
|
18
18
|
export interface AppBrokerImportRuntime {
|
|
19
19
|
importBrokerPositions: (
|
|
20
20
|
instanceId: string,
|
|
21
|
+
/** What the sync reads. It lands on the app's tickers as they are when it commits, whatever was passed. */
|
|
21
22
|
tickerMap?: Map<string, TickerRecord>,
|
|
22
23
|
options?: BrokerImportOptions,
|
|
23
24
|
) => Promise<SyncBrokerInstanceResult>;
|
|
@@ -52,6 +53,13 @@ export function useBrokerImportRuntime({
|
|
|
52
53
|
stateRef: { current: AppState };
|
|
53
54
|
tickerRepository: AppTickerRepositoryPort;
|
|
54
55
|
}): AppBrokerImportRuntime {
|
|
56
|
+
/**
|
|
57
|
+
* Commits a sync: carried onto the app as it is now (a pull or an edit while
|
|
58
|
+
* the broker answered stays) and handed to the app before anything is
|
|
59
|
+
* written. With a wait in between, a pull landing in it would be overwritten
|
|
60
|
+
* by this older copy and pushed from here as an edit. The writes start in
|
|
61
|
+
* the same tick, so the copy of anyone writing after lands on disk after it.
|
|
62
|
+
*/
|
|
55
63
|
const applyBrokerImportResult = useCallback(async (
|
|
56
64
|
instanceId: string,
|
|
57
65
|
result: SyncBrokerInstanceResult,
|
|
@@ -62,31 +70,27 @@ export function useBrokerImportRuntime({
|
|
|
62
70
|
},
|
|
63
71
|
) => {
|
|
64
72
|
throwIfBrokerImportCancelled(options?.signal);
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const liveConfig = stateRef.current.config;
|
|
73
|
+
const live = stateRef.current;
|
|
74
|
+
result.rebase(live);
|
|
68
75
|
const nextConfig = options?.callerOwnsConfig
|
|
69
|
-
? (result.config !== baseConfig ? result.config :
|
|
70
|
-
: rebaseBrokerSyncConfig(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
76
|
+
? (result.config !== baseConfig ? result.config : live.config)
|
|
77
|
+
: rebaseBrokerSyncConfig(live.config, baseConfig, result.config, instanceId);
|
|
78
|
+
const configChanged = nextConfig !== live.config;
|
|
79
|
+
|
|
80
|
+
dispatch({ type: "SET_BROKER_ACCOUNTS", instanceId, accounts: result.brokerAccounts });
|
|
81
|
+
if (configChanged) dispatch({ type: "SET_CONFIG", config: nextConfig });
|
|
82
|
+
for (const ticker of [...result.addedTickers, ...result.updatedTickers]) {
|
|
83
|
+
dispatch({ type: "UPDATE_TICKER", ticker });
|
|
77
84
|
}
|
|
85
|
+
await Promise.all([result.commit(), configChanged ? saveConfigImmediately(nextConfig) : undefined]);
|
|
78
86
|
|
|
87
|
+
if (configChanged) pluginRegistry.events.emit("config:changed", { config: nextConfig });
|
|
79
88
|
for (const ticker of result.addedTickers) {
|
|
80
|
-
throwIfBrokerImportCancelled(options?.signal);
|
|
81
89
|
pluginRegistry.events.emit("ticker:added", { symbol: ticker.metadata.ticker, ticker });
|
|
82
90
|
}
|
|
83
|
-
for (const ticker of [...result.addedTickers, ...result.updatedTickers]) {
|
|
84
|
-
throwIfBrokerImportCancelled(options?.signal);
|
|
85
|
-
dispatch({ type: "UPDATE_TICKER", ticker: { ...ticker } });
|
|
86
|
-
}
|
|
87
91
|
|
|
88
92
|
for (const position of result.positions) {
|
|
89
|
-
|
|
93
|
+
if (options?.signal?.aborted) break;
|
|
90
94
|
// Skip market data for broker option symbols; position marks are already available.
|
|
91
95
|
// Position data (markPrice, marketValue, unrealizedPnl) is used directly.
|
|
92
96
|
if (options?.refreshImportedTickers !== false && position.assetCategory !== "OPT") {
|
|
@@ -120,8 +124,6 @@ export function useBrokerImportRuntime({
|
|
|
120
124
|
throwIfBrokerImportCancelled(options?.signal);
|
|
121
125
|
options?.onCommitStart?.();
|
|
122
126
|
commitStarted = true;
|
|
123
|
-
await result.commit();
|
|
124
|
-
throwIfBrokerImportCancelled(options?.signal);
|
|
125
127
|
await applyBrokerImportResult(instanceId, result, baseConfig, {
|
|
126
128
|
...options,
|
|
127
129
|
callerOwnsConfig: options?.config !== undefined,
|
|
@@ -155,7 +157,7 @@ export function useBrokerImportRuntime({
|
|
|
155
157
|
deferPersistence: true,
|
|
156
158
|
onResult: async (result, instance, previousConfig) => {
|
|
157
159
|
if (!getBrokerInstance(stateRef.current.config.brokerInstances, instance.id)) return;
|
|
158
|
-
|
|
160
|
+
// Startup syncs run beside the first pull: each lands on what it left.
|
|
159
161
|
await applyBrokerImportResult(instance.id, result, previousConfig, {
|
|
160
162
|
refreshImportedTickers: false,
|
|
161
163
|
});
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
clearPersistedBrokerAccounts,
|
|
4
4
|
getBrokerAccountCacheSourceKey,
|
|
5
5
|
} from "../../brokers/account-cache";
|
|
6
|
-
import { findPortfolioHeir } from "../../brokers/broker-portfolio-sync";
|
|
6
|
+
import { clearLeftoverBrokerHoldings, findPortfolioHeir, knownBrokerHoldings } from "../../brokers/broker-portfolio-sync";
|
|
7
7
|
import type { AppTickerRepositoryPort } from "../../core/app-service-ports";
|
|
8
8
|
import type { MarketDataCoordinator } from "../../market-data/coordinator";
|
|
9
9
|
import { instrumentFromTicker } from "../../market-data/request-types";
|
|
@@ -216,15 +216,20 @@ export function bindPluginRegistryRuntimeAccess({
|
|
|
216
216
|
return [heirId ? { ...portfolio, brokerInstanceId: heirId } : portfolio];
|
|
217
217
|
});
|
|
218
218
|
const nextTickers = new Map(stateRef.current.tickers);
|
|
219
|
+
const nextBrokerInstances = stateRef.current.config.brokerInstances.filter((entry) => entry.id !== instanceId);
|
|
220
|
+
// What older removals and pulls from other devices left behind goes as well.
|
|
221
|
+
const known = knownBrokerHoldings({ portfolios: nextPortfolios, brokerInstances: nextBrokerInstances });
|
|
219
222
|
|
|
220
223
|
for (const ticker of stateRef.current.tickers.values()) {
|
|
221
|
-
|
|
224
|
+
// A position here without a profile came from another device's sync of this one.
|
|
225
|
+
const nextPositions = ticker.metadata.positions.filter((position) => position.brokerInstanceId !== instanceId
|
|
226
|
+
&& !(removedPortfolioIds.has(position.portfolio) && !position.brokerInstanceId));
|
|
222
227
|
const heldPortfolioIds = new Set(nextPositions.map((position) => position.portfolio));
|
|
223
228
|
const nextPortfolioRefs = ticker.metadata.portfolios.filter((portfolioId) =>
|
|
224
229
|
!removedPortfolioIds.has(portfolioId) && (!heirs.has(portfolioId) || heldPortfolioIds.has(portfolioId)));
|
|
225
230
|
const nextBrokerContracts = (ticker.metadata.broker_contracts ?? []).filter((contract) => contract.brokerInstanceId !== instanceId);
|
|
226
231
|
|
|
227
|
-
const
|
|
232
|
+
const removedTicker: TickerRecord = {
|
|
228
233
|
...ticker,
|
|
229
234
|
metadata: {
|
|
230
235
|
...ticker.metadata,
|
|
@@ -233,12 +238,13 @@ export function bindPluginRegistryRuntimeAccess({
|
|
|
233
238
|
broker_contracts: nextBrokerContracts,
|
|
234
239
|
},
|
|
235
240
|
};
|
|
241
|
+
const nextTicker = clearLeftoverBrokerHoldings(removedTicker, known) ?? removedTicker;
|
|
236
242
|
|
|
237
243
|
const shouldDeleteTicker =
|
|
238
|
-
|
|
239
|
-
&&
|
|
244
|
+
nextTicker.metadata.positions.length === 0
|
|
245
|
+
&& nextTicker.metadata.portfolios.length === 0
|
|
240
246
|
&& nextTicker.metadata.watchlists.length === 0
|
|
241
|
-
&&
|
|
247
|
+
&& (nextTicker.metadata.broker_contracts ?? []).length === 0
|
|
242
248
|
&& nextTicker.metadata.tags.length === 0
|
|
243
249
|
&& Object.keys(nextTicker.metadata.custom).length === 0;
|
|
244
250
|
|
|
@@ -256,7 +262,7 @@ export function bindPluginRegistryRuntimeAccess({
|
|
|
256
262
|
|
|
257
263
|
const nextConfig = {
|
|
258
264
|
...stateRef.current.config,
|
|
259
|
-
brokerInstances:
|
|
265
|
+
brokerInstances: nextBrokerInstances,
|
|
260
266
|
portfolios: nextPortfolios,
|
|
261
267
|
};
|
|
262
268
|
|
|
@@ -7,13 +7,17 @@ import {
|
|
|
7
7
|
canRestartToApply,
|
|
8
8
|
canSelfUpdate,
|
|
9
9
|
checkForUpdateDetailed,
|
|
10
|
+
discardStagedUpdate,
|
|
10
11
|
performUpdate,
|
|
11
12
|
type ReleaseInfo,
|
|
12
13
|
type UpdateCheckResult,
|
|
13
14
|
type UpdateProgress,
|
|
14
15
|
} from "../../updater";
|
|
16
|
+
import { resolveUpdateChannel, stagedUpdateFitsChannel } from "../../updater/channel";
|
|
17
|
+
import type { UpdateChannel } from "../../types/config";
|
|
15
18
|
import { VERSION } from "../../version";
|
|
16
19
|
import { debugLog } from "../../utils/debug-log";
|
|
20
|
+
import { isPrereleaseVersion } from "../../utils/semver";
|
|
17
21
|
import { reportCrash } from "../../telemetry/crash-reports";
|
|
18
22
|
import { runAutomated } from "../../telemetry/usage-counts";
|
|
19
23
|
|
|
@@ -41,6 +45,7 @@ export function useAppUpdateRuntime({
|
|
|
41
45
|
pluginRegistry,
|
|
42
46
|
stateRef,
|
|
43
47
|
updateAvailable,
|
|
48
|
+
updateChannel = "stable",
|
|
44
49
|
updateCheckInProgress,
|
|
45
50
|
updateProgress,
|
|
46
51
|
}: {
|
|
@@ -50,6 +55,8 @@ export function useAppUpdateRuntime({
|
|
|
50
55
|
pluginRegistry: PluginRegistry;
|
|
51
56
|
stateRef: { current: AppState };
|
|
52
57
|
updateAvailable: ReleaseInfo | null;
|
|
58
|
+
/** The channel in effect (`resolveUpdateChannel`); a change checks again at once. */
|
|
59
|
+
updateChannel?: UpdateChannel;
|
|
53
60
|
updateCheckInProgress: boolean;
|
|
54
61
|
updateProgress: unknown;
|
|
55
62
|
}): {
|
|
@@ -61,11 +68,24 @@ export function useAppUpdateRuntime({
|
|
|
61
68
|
runUpdateCommand: () => void;
|
|
62
69
|
} {
|
|
63
70
|
const restartingRef = useRef(false);
|
|
71
|
+
/** The tag of a release whose download found nothing to take; the hourly check does not offer it again. */
|
|
72
|
+
const declinedTagRef = useRef<string | null>(null);
|
|
64
73
|
|
|
65
74
|
const startUpdate = useCallback((release: ReleaseInfo) => {
|
|
66
75
|
dispatch({ type: "SET_UPDATE_PROGRESS", progress: { phase: "downloading", percent: 0 } });
|
|
67
76
|
void performUpdate(release, (progress) => {
|
|
68
77
|
logUpdateFailure(progress);
|
|
78
|
+
if (progress.phase === "done") {
|
|
79
|
+
// The download had nothing to take (that build is the installed one):
|
|
80
|
+
// an answer like a check's, so it passes as a notice like one. The
|
|
81
|
+
// release goes before the progress, so the effect below never starts
|
|
82
|
+
// it again, and the hourly check does not offer it again.
|
|
83
|
+
declinedTagRef.current = release.tagName;
|
|
84
|
+
dispatch({ type: "SET_UPDATE_AVAILABLE", release: null });
|
|
85
|
+
dispatch({ type: "SET_UPDATE_PROGRESS", progress: null });
|
|
86
|
+
dispatch({ type: "SET_UPDATE_NOTICE", notice: progress.message ?? null });
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
69
89
|
dispatch({ type: "SET_UPDATE_PROGRESS", progress });
|
|
70
90
|
});
|
|
71
91
|
}, [dispatch]);
|
|
@@ -97,7 +117,8 @@ export function useAppUpdateRuntime({
|
|
|
97
117
|
// failed check like any other: the hourly one must not end as an
|
|
98
118
|
// unhandled rejection, and a manual one must clear its progress state.
|
|
99
119
|
// The timeout is still reported, as it was when it went unhandled.
|
|
100
|
-
const
|
|
120
|
+
const channel = resolveUpdateChannel(stateRef.current.config);
|
|
121
|
+
const result = await checkForUpdateDetailed(VERSION, channel).catch((error: unknown): UpdateCheckResult => {
|
|
101
122
|
const message = error instanceof Error ? error.message : "";
|
|
102
123
|
if (message.startsWith("RPC request timed out")) {
|
|
103
124
|
reportCrash(error, { kind: "unhandled-rejection" });
|
|
@@ -108,7 +129,7 @@ export function useAppUpdateRuntime({
|
|
|
108
129
|
if (result.kind === "error") updateLog.warn("update check failed", { error: result.error });
|
|
109
130
|
|
|
110
131
|
if (!manual) {
|
|
111
|
-
if (result.kind === "available") {
|
|
132
|
+
if (result.kind === "available" && result.release.tagName !== declinedTagRef.current) {
|
|
112
133
|
dispatch({ type: "SET_UPDATE_AVAILABLE", release: result.release });
|
|
113
134
|
}
|
|
114
135
|
return;
|
|
@@ -123,7 +144,7 @@ export function useAppUpdateRuntime({
|
|
|
123
144
|
|
|
124
145
|
if (result.kind === "current") {
|
|
125
146
|
dispatch({ type: "SET_UPDATE_AVAILABLE", release: null });
|
|
126
|
-
dispatch({ type: "SET_UPDATE_NOTICE", notice: `Already on v${VERSION}` });
|
|
147
|
+
dispatch({ type: "SET_UPDATE_NOTICE", notice: result.notice ?? `Already on v${VERSION}` });
|
|
127
148
|
return;
|
|
128
149
|
}
|
|
129
150
|
|
|
@@ -147,6 +168,44 @@ export function useAppUpdateRuntime({
|
|
|
147
168
|
return () => { clearInterval(interval); };
|
|
148
169
|
}, [enabled, isDetachedWindow, runUpdateCheck]);
|
|
149
170
|
|
|
171
|
+
// A channel switch checks again at once and says what it found: nightly
|
|
172
|
+
// starts on today's build, stable says when this nightly build stays. An
|
|
173
|
+
// update already downloading or waiting for a restart is left to the effect
|
|
174
|
+
// below, which takes it back once it is ready if the channel rules it out.
|
|
175
|
+
const checkedChannelRef = useRef(updateChannel);
|
|
176
|
+
useEffect(() => {
|
|
177
|
+
if (!enabled || isDetachedWindow || checkedChannelRef.current === updateChannel) return;
|
|
178
|
+
checkedChannelRef.current = updateChannel;
|
|
179
|
+
if (updateInFlight(stateRef.current.updateProgress)) return;
|
|
180
|
+
// What the other channel offered, or how its download failed, is not this one's answer.
|
|
181
|
+
if (stateRef.current.updateProgress) dispatch({ type: "SET_UPDATE_PROGRESS", progress: null });
|
|
182
|
+
dispatch({ type: "SET_UPDATE_AVAILABLE", release: null });
|
|
183
|
+
void runUpdateCheck(true);
|
|
184
|
+
}, [dispatch, enabled, isDetachedWindow, runUpdateCheck, stateRef, updateChannel]);
|
|
185
|
+
|
|
186
|
+
// A downloaded update the channel does not allow (a nightly build waiting
|
|
187
|
+
// for a restart after the switch back to stable, or one that finished
|
|
188
|
+
// downloading after it) is taken back rather than installed by the restart,
|
|
189
|
+
// and stable checks again.
|
|
190
|
+
const discardingRef = useRef(false);
|
|
191
|
+
useEffect(() => {
|
|
192
|
+
if (!enabled || isDetachedWindow || discardingRef.current) return;
|
|
193
|
+
const { updateProgress: progress, updateAvailable: staged } = stateRef.current;
|
|
194
|
+
if (progress?.phase !== "ready" || stagedUpdateFitsChannel(staged, updateChannel)) return;
|
|
195
|
+
discardingRef.current = true;
|
|
196
|
+
void discardStagedUpdate().then((error) => {
|
|
197
|
+
discardingRef.current = false;
|
|
198
|
+
if (error) {
|
|
199
|
+
updateLog.warn("could not take back the staged update", { error, version: staged?.version });
|
|
200
|
+
dispatch({ type: "SET_UPDATE_NOTICE", notice: `Could not take back v${staged?.version ?? "?"}: ${error}` });
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
dispatch({ type: "SET_UPDATE_PROGRESS", progress: null });
|
|
204
|
+
dispatch({ type: "SET_UPDATE_AVAILABLE", release: null });
|
|
205
|
+
void runUpdateCheck(true);
|
|
206
|
+
});
|
|
207
|
+
}, [dispatch, enabled, isDetachedWindow, runUpdateCheck, stateRef, updateAvailable, updateChannel, updateProgress]);
|
|
208
|
+
|
|
150
209
|
// First launch on a new version: show that release's notes, then remember the version.
|
|
151
210
|
useEffect(() => {
|
|
152
211
|
if (!enabled || isDetachedWindow) return;
|
|
@@ -156,7 +215,8 @@ export function useAppUpdateRuntime({
|
|
|
156
215
|
const nextConfig = { ...config, lastLaunchedVersion: VERSION };
|
|
157
216
|
dispatch({ type: "SET_CONFIG", config: nextConfig });
|
|
158
217
|
void saveConfigImmediately(nextConfig).catch(() => {});
|
|
159
|
-
|
|
218
|
+
// Nightly builds come daily and the changelog lists stable releases only.
|
|
219
|
+
if (!isUpgrade || isPrereleaseVersion(VERSION)) return;
|
|
160
220
|
// Opened by the app, not the user: usage counts leave it out.
|
|
161
221
|
void runAutomated(() => pluginRegistry.createPaneFromTemplateAsync("changelog-pane", {
|
|
162
222
|
values: { version: VERSION },
|
package/src/app.tsx
CHANGED
|
@@ -50,6 +50,7 @@ import { bindPluginRegistryRuntimeAccess } from "./app/runtime/plugin-bindings";
|
|
|
50
50
|
import { useAppStartupRuntime } from "./app/runtime/startup";
|
|
51
51
|
import { useTickerRefreshRuntime } from "./app/runtime/ticker-refresh";
|
|
52
52
|
import { useAppUpdateRuntime } from "./app/runtime/update";
|
|
53
|
+
import { resolveUpdateChannel } from "./updater/channel";
|
|
53
54
|
import { createCoreSyncContributors } from "./sync/core-contributors";
|
|
54
55
|
import { useCloudSyncRuntime } from "./sync/react";
|
|
55
56
|
import { RemoteControlHost, type RemoteControlAdapter } from "./remote/app-host";
|
|
@@ -278,6 +279,7 @@ function AppInner({
|
|
|
278
279
|
pluginRegistry,
|
|
279
280
|
stateRef,
|
|
280
281
|
updateAvailable: state.updateAvailable,
|
|
282
|
+
updateChannel: resolveUpdateChannel(state.config),
|
|
281
283
|
updateCheckInProgress: state.updateCheckInProgress,
|
|
282
284
|
updateProgress: state.updateProgress,
|
|
283
285
|
});
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
"src/app.tsx": ["cloud"],
|
|
4
4
|
"src/app/global-shortcuts.ts": ["help"],
|
|
5
5
|
"src/app/pane-runtime/plugin-bindings.ts": ["broker-manager","layout-manager"],
|
|
6
|
-
"src/app/runtime/broker-import.ts": ["portfolio-list"],
|
|
7
6
|
"src/app/runtime/desktop-deeplink.ts": ["account-management","chat"],
|
|
8
7
|
"src/app/runtime/startup.ts": ["chat"],
|
|
9
8
|
"src/cli/commands/market.ts": ["holders","insider","research","sec"],
|
|
@@ -19,21 +18,14 @@
|
|
|
19
18
|
"src/cli/pane-functions/resolver.ts": ["chart-composer"],
|
|
20
19
|
"src/cli/pane-functions/screenshot-evidence.ts": ["attention","awards","catalysts","company-attention","company-kpis","credit-documents","exposure","market-heatmap","options-calculator","options-scenario","power","realized-vol","supply-chain","vol-surface","volatility","world-venue-map"],
|
|
21
20
|
"src/cli/pane-functions/screenshot.ts": ["chart-composer","econ-statistics","market-valuation","portfolio-list","ticker-detail","time-sales"],
|
|
22
|
-
"src/components/command-bar/commands/
|
|
23
|
-
"src/components/command-bar/commands/direct/index.ts": ["broker-manager","portfolio-list"],
|
|
24
|
-
"src/components/command-bar/routes/root/command-items.ts": ["portfolio-list"],
|
|
21
|
+
"src/components/command-bar/commands/direct/index.ts": ["broker-manager"],
|
|
25
22
|
"src/components/command-bar/routes/root/help-items.ts": ["help"],
|
|
26
|
-
"src/components/command-bar/routes/root/shortcut-items.ts": ["portfolio-list"],
|
|
27
23
|
"src/components/command-bar/workflow/builtin.ts": ["portfolio-list"],
|
|
28
|
-
"src/components/command-bar/workflow/collection-actions.ts": ["portfolio-list"],
|
|
29
24
|
"src/components/command-bar/workflow/ops.ts": ["correlation","portfolio-list","ticker-detail"],
|
|
30
|
-
"src/components/command-bar/workflow/tickers.ts": ["portfolio-list"],
|
|
31
25
|
"src/components/form-modal/routes.ts": ["portfolio-list"],
|
|
32
26
|
"src/components/index.ts": ["portfolio-list"],
|
|
33
27
|
"src/components/layout/shell/index.tsx": ["cloud"],
|
|
34
28
|
"src/components/onboarding/use-onboarding-upgrade.ts": ["chat"],
|
|
35
|
-
"src/components/onboarding/wizard-model.ts": ["portfolio-list"],
|
|
36
|
-
"src/components/onboarding/wizard-positions.ts": ["portfolio-list"],
|
|
37
29
|
"src/layout/desks.ts": ["chart-composer"],
|
|
38
30
|
"src/plugins/catalog-browser.ts": ["alerts","analytics","attention","awards","backtest","bond-calculator","catalysts","cds","central-bank-rates","changelog","chart-composer","clinical-trials","cloud","comment-letters","commodities-energy","commodity-balances","company-attention","company-kpis","connections","correlation","cot","cpi","credit-boards","credit-conditions","credit-documents","crypto-board","debt-maturities","dividend-yield","docs","doe","earnings-calls","earnings-ripple","econ","econ-statistics","equity-screener","estimate-revisions","executives","exposure","fear-greed","filing-events","fixed-income","futures","futures-curve","fx-matrix","gpu","help","insider","ipo-calendar","iv-history","jobs","jodi","kelly-sizer","layout-manager","macro-day","market-halts","market-heatmap","market-valuation","members","mna","money-markets","news","openfda","options","options-calculator","options-positioning","options-scenario","pe-band","perps","portfolio-list","power","primary-dealers","rate-path","realized-vol","relative-rotation","research","research-search","revenue-breakdown","reverse-dcf","risk-factors","scanner","seasonality","sec","sectors","short-interest","short-volume","social-mentions","supply-chain","ticker-detail","time-sales","trace-bonds","treasury-auctions","treasury-daily","vol-surface","volatility","world-indices","world-venue-map","yield-curve"],
|
|
39
31
|
"src/plugins/catalog-ui.ts": ["alerts","clinical-trials","cloud","comment-letters","custom-view","fear-greed","ipo-calendar","market-halts","market-heatmap","members","news","notes","openfda","research-search"],
|
|
@@ -43,5 +35,5 @@
|
|
|
43
35
|
"src/renderers/electrobun/view/cli-pane-shot-entry.tsx": ["econ-statistics","market-valuation","time-sales"],
|
|
44
36
|
"src/renderers/electrobun/view/main.tsx": ["notes"],
|
|
45
37
|
"src/renderers/electrobun/view/remote/connection-health.ts": ["connections"],
|
|
46
|
-
"src/sync/core-contributors.ts": ["analytics"
|
|
38
|
+
"src/sync/core-contributors.ts": ["analytics"]
|
|
47
39
|
}
|
|
@@ -41,7 +41,7 @@ function scalarArgs(source: ArgSource, privateFields: PaneSharePrivateFields | u
|
|
|
41
41
|
* that opens this pane (a chart opened with `GP` stays `GP`), else the first
|
|
42
42
|
* template that opens it.
|
|
43
43
|
*/
|
|
44
|
-
function functionCode(registry: Pick<PluginRegistry, "paneTemplates">, paneType: string, title: string): string | null {
|
|
44
|
+
export function functionCode(registry: Pick<PluginRegistry, "paneTemplates">, paneType: string, title: string): string | null {
|
|
45
45
|
const prefixes = [...registry.paneTemplates.values()]
|
|
46
46
|
.filter((template) => template.paneId === paneType && template.shortcut?.prefix)
|
|
47
47
|
.map((template) => template.shortcut!.prefix.toUpperCase());
|
|
@@ -50,6 +50,16 @@ function functionCode(registry: Pick<PluginRegistry, "paneTemplates">, paneType:
|
|
|
50
50
|
return prefixes[0] ?? null;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
/**
|
|
54
|
+
* A pane named by its function and what it shows, such as `DES AAPL`: its
|
|
55
|
+
* title, led by its function code unless the title already starts with it.
|
|
56
|
+
*/
|
|
57
|
+
export function paneFunctionLabel(pane: { title: string; code: string | null; paneType: string }): string {
|
|
58
|
+
const title = pane.title.trim() || pane.paneType;
|
|
59
|
+
if (!pane.code) return title;
|
|
60
|
+
return title.split(/\s+/)[0]?.toUpperCase() === pane.code ? title : `${pane.code} ${title}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
53
63
|
export interface PaneAttachmentSource {
|
|
54
64
|
registry: Pick<PluginRegistry, "paneTemplates" | "brokers" | "getPanePluginId" | "getBrokerPluginId">;
|
|
55
65
|
pane: Pick<ResolvedPane, "instance" | "def">;
|
|
@@ -181,10 +181,17 @@ export function clearBrokerInstanceTickerData(
|
|
|
181
181
|
brokerPortfolioIds: Set<string>,
|
|
182
182
|
/** Portfolios this profile just took over: whatever another profile imported there goes too. */
|
|
183
183
|
takenOverPortfolioIds: ReadonlySet<string> = new Set(),
|
|
184
|
+
/**
|
|
185
|
+
* Portfolios this sync writes. A position there without a profile came from
|
|
186
|
+
* another device's sync of the same account, so this one replaces it too;
|
|
187
|
+
* one entered by hand stays.
|
|
188
|
+
*/
|
|
189
|
+
syncedPortfolioIds: ReadonlySet<string> = new Set(),
|
|
184
190
|
): TickerRecord | null {
|
|
185
191
|
const positions = ticker.metadata.positions.filter((position) =>
|
|
186
192
|
position.brokerInstanceId !== instanceId
|
|
187
193
|
&& !(takenOverPortfolioIds.has(position.portfolio) && position.brokerInstanceId)
|
|
194
|
+
&& !(syncedPortfolioIds.has(position.portfolio) && !position.brokerInstanceId && position.broker !== "manual")
|
|
188
195
|
);
|
|
189
196
|
const remainingPositionPortfolios = new Set(positions.map((position) => position.portfolio));
|
|
190
197
|
const portfolios = ticker.metadata.portfolios.filter((portfolioId) =>
|
|
@@ -211,6 +218,65 @@ export function clearBrokerInstanceTickerData(
|
|
|
211
218
|
};
|
|
212
219
|
}
|
|
213
220
|
|
|
221
|
+
/** The profile a broker portfolio id was made for: `broker:<profile>:<account>`. */
|
|
222
|
+
function brokerInstanceIdOfPortfolio(portfolioId: string): string | undefined {
|
|
223
|
+
return isBrokerPortfolioId(portfolioId) ? portfolioId.split(":")[1] || undefined : undefined;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** The broker portfolios and profiles that still exist somewhere this device knows of. */
|
|
227
|
+
export interface KnownBrokerHoldings {
|
|
228
|
+
portfolioIds: ReadonlySet<string>;
|
|
229
|
+
instanceIds: ReadonlySet<string>;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export function knownBrokerHoldings(
|
|
233
|
+
...sources: Array<{ portfolios?: ReadonlyArray<{ id: string }>; brokerInstances?: ReadonlyArray<{ id: string }> }>
|
|
234
|
+
): KnownBrokerHoldings {
|
|
235
|
+
return {
|
|
236
|
+
portfolioIds: new Set(sources.flatMap((source) => source.portfolios?.map((portfolio) => portfolio.id) ?? [])),
|
|
237
|
+
instanceIds: new Set(sources.flatMap((source) => source.brokerInstances?.map((instance) => instance.id) ?? [])),
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* A broker portfolio that is gone, along with the profile it was made for. One
|
|
243
|
+
* that still has its profile stays: that profile restores it on its next sync.
|
|
244
|
+
*/
|
|
245
|
+
function isGoneBrokerPortfolio(portfolioId: string, known: KnownBrokerHoldings): boolean {
|
|
246
|
+
if (!isBrokerPortfolioId(portfolioId) || known.portfolioIds.has(portfolioId)) return false;
|
|
247
|
+
const instanceId = brokerInstanceIdOfPortfolio(portfolioId);
|
|
248
|
+
return !instanceId || !known.instanceIds.has(instanceId);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function isLeftoverBrokerPosition(position: TickerPosition, known: KnownBrokerHoldings): boolean {
|
|
252
|
+
return isGoneBrokerPortfolio(position.portfolio, known)
|
|
253
|
+
&& !(position.brokerInstanceId && known.instanceIds.has(position.brokerInstanceId));
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* The ticker without what removed broker profiles left on it: positions and
|
|
258
|
+
* links in portfolios that are gone with their profile, and contracts of
|
|
259
|
+
* profiles that are gone. A pull from another device brings such positions
|
|
260
|
+
* without the profile that imported them, as a profile never leaves its
|
|
261
|
+
* device, so nothing else ever clears them. Null when there is none.
|
|
262
|
+
*/
|
|
263
|
+
export function clearLeftoverBrokerHoldings(ticker: TickerRecord, known: KnownBrokerHoldings): TickerRecord | null {
|
|
264
|
+
const { metadata } = ticker;
|
|
265
|
+
const positions = metadata.positions.filter((position) => !isLeftoverBrokerPosition(position, known));
|
|
266
|
+
const held = new Set(positions.map((position) => position.portfolio));
|
|
267
|
+
const portfolios = metadata.portfolios.filter((portfolioId) => held.has(portfolioId) || !isGoneBrokerPortfolio(portfolioId, known));
|
|
268
|
+
const contracts = metadata.broker_contracts ?? [];
|
|
269
|
+
const brokerContracts = contracts.filter((contract) => !contract.brokerInstanceId || known.instanceIds.has(contract.brokerInstanceId));
|
|
270
|
+
if (
|
|
271
|
+
positions.length === metadata.positions.length
|
|
272
|
+
&& portfolios.length === metadata.portfolios.length
|
|
273
|
+
&& brokerContracts.length === contracts.length
|
|
274
|
+
) {
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
return { ...ticker, metadata: { ...metadata, positions, portfolios, broker_contracts: brokerContracts } };
|
|
278
|
+
}
|
|
279
|
+
|
|
214
280
|
function inferBrokerAccountId(position: TickerPosition, portfolioId: string, instanceId: string): string | undefined {
|
|
215
281
|
if (position.brokerAccountId) return position.brokerAccountId;
|
|
216
282
|
const prefix = `broker:${instanceId}:`;
|
|
@@ -7,9 +7,11 @@ import type { TickerRecord } from "../types/ticker";
|
|
|
7
7
|
import { getBrokerInstance } from "../utils/broker-instances";
|
|
8
8
|
import {
|
|
9
9
|
clearBrokerInstanceTickerData,
|
|
10
|
+
clearLeftoverBrokerHoldings,
|
|
10
11
|
ensureBrokerPortfolio,
|
|
11
12
|
findReusableBrokerPortfolioId,
|
|
12
13
|
isHeldByOtherSignedInProfile,
|
|
14
|
+
knownBrokerHoldings,
|
|
13
15
|
removeStaleBrokerPortfolios,
|
|
14
16
|
resolvePortfolioBrokerId,
|
|
15
17
|
} from "./broker-portfolio-sync";
|
|
@@ -33,6 +35,12 @@ export interface SyncBrokerInstanceArgs {
|
|
|
33
35
|
deferPersistence?: boolean;
|
|
34
36
|
}
|
|
35
37
|
|
|
38
|
+
/** The app as it is when a sync commits, which may be seconds after the sync read it. */
|
|
39
|
+
interface BrokerSyncCommitTarget {
|
|
40
|
+
tickers: ReadonlyMap<string, TickerRecord>;
|
|
41
|
+
config: Pick<AppConfig, "portfolios" | "brokerInstances">;
|
|
42
|
+
}
|
|
43
|
+
|
|
36
44
|
export interface SyncBrokerInstanceResult {
|
|
37
45
|
config: AppConfig;
|
|
38
46
|
tickers: Map<string, TickerRecord>;
|
|
@@ -41,6 +49,15 @@ export interface SyncBrokerInstanceResult {
|
|
|
41
49
|
portfolioIds: string[];
|
|
42
50
|
addedTickers: TickerRecord[];
|
|
43
51
|
updatedTickers: TickerRecord[];
|
|
52
|
+
/**
|
|
53
|
+
* Carries the sync onto the app as it is now, which may be seconds after the
|
|
54
|
+
* sync read it, so what changed while the broker answered (a pull from
|
|
55
|
+
* another device, an edit) stays; `tickers`, `addedTickers` and
|
|
56
|
+
* `updatedTickers` then hold the sync over it. Hand them to the app before
|
|
57
|
+
* anything waits: a pull landing in a wait would be overwritten in turn.
|
|
58
|
+
*/
|
|
59
|
+
rebase(onto: BrokerSyncCommitTarget): void;
|
|
60
|
+
/** Writes the sync's tickers and broker accounts. */
|
|
44
61
|
commit(): Promise<void>;
|
|
45
62
|
}
|
|
46
63
|
|
|
@@ -310,62 +327,78 @@ export async function syncBrokerInstance({
|
|
|
310
327
|
: []);
|
|
311
328
|
|
|
312
329
|
nextConfig = removeStaleBrokerPortfolios(nextConfig, instance.id, currentPortfolioIds);
|
|
313
|
-
const cleanedTickers = new Map<string, TickerRecord>();
|
|
314
|
-
for (const ticker of tickers.values()) {
|
|
315
|
-
const cleanedTicker = clearBrokerInstanceTickerData(ticker, instance.id, brokerPortfolioIds, takenOverPortfolioIds);
|
|
316
|
-
if (!cleanedTicker) continue;
|
|
317
|
-
tickers.set(cleanedTicker.metadata.ticker, cleanedTicker);
|
|
318
|
-
cleanedTickers.set(cleanedTicker.metadata.ticker, cleanedTicker);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
330
|
nextConfig = await maybePersistResolvedBrokerConfig(nextConfig, instance, broker, persistResolvedBrokerConfig);
|
|
322
331
|
throwIfBrokerImportCancelled(signal);
|
|
323
332
|
nextConfig = markBrokerInstanceSynced(nextConfig, instance.id, syncedAt);
|
|
333
|
+
const syncedConfig = nextConfig;
|
|
334
|
+
|
|
335
|
+
// What the sync does to the tickers, given them: it runs on the tickers it
|
|
336
|
+
// read, and again on the live ones when it commits after the broker answered.
|
|
337
|
+
const writeTickers = (source: ReadonlyMap<string, TickerRecord>, liveConfig?: BrokerSyncCommitTarget["config"]) => {
|
|
338
|
+
const tickers = new Map(source);
|
|
339
|
+
// Only a portfolio and a profile gone everywhere this device knows of make a leftover.
|
|
340
|
+
const known = knownBrokerHoldings(config, syncedConfig, ...(liveConfig ? [liveConfig] : []));
|
|
341
|
+
const cleanedTickers = new Map<string, TickerRecord>();
|
|
342
|
+
for (const ticker of tickers.values()) {
|
|
343
|
+
const cleared = clearBrokerInstanceTickerData(
|
|
344
|
+
ticker, instance.id, brokerPortfolioIds, takenOverPortfolioIds, currentPortfolioIds,
|
|
345
|
+
);
|
|
346
|
+
const cleanedTicker = clearLeftoverBrokerHoldings(cleared ?? ticker, known) ?? cleared;
|
|
347
|
+
if (!cleanedTicker) continue;
|
|
348
|
+
tickers.set(cleanedTicker.metadata.ticker, cleanedTicker);
|
|
349
|
+
cleanedTickers.set(cleanedTicker.metadata.ticker, cleanedTicker);
|
|
350
|
+
}
|
|
324
351
|
|
|
325
|
-
|
|
326
|
-
|
|
352
|
+
const addedTickers = new Map<string, TickerRecord>();
|
|
353
|
+
const updatedTickers = new Map<string, TickerRecord>();
|
|
354
|
+
|
|
355
|
+
for (const position of positions) {
|
|
356
|
+
throwIfBrokerImportCancelled(signal);
|
|
357
|
+
const portfolioId = findReusableBrokerPortfolioId(syncedConfig, instance, position.accountId, portfolioBrokerId);
|
|
358
|
+
if (heldElsewhere.has(portfolioId)) continue;
|
|
359
|
+
const { ticker, created } = upsertBrokerPositionTicker({
|
|
360
|
+
tickers,
|
|
361
|
+
instance,
|
|
362
|
+
portfolioId,
|
|
363
|
+
position,
|
|
364
|
+
});
|
|
365
|
+
if (created) {
|
|
366
|
+
addedTickers.set(position.ticker, ticker);
|
|
367
|
+
} else {
|
|
368
|
+
cleanedTickers.delete(ticker.metadata.ticker);
|
|
369
|
+
if (!addedTickers.has(position.ticker)) {
|
|
370
|
+
updatedTickers.set(position.ticker, ticker);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
327
373
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
const { ticker, created } = upsertBrokerPositionTicker({
|
|
333
|
-
tickers,
|
|
334
|
-
instance,
|
|
335
|
-
portfolioId,
|
|
336
|
-
position,
|
|
337
|
-
});
|
|
338
|
-
if (created) {
|
|
339
|
-
addedTickers.set(position.ticker, ticker);
|
|
340
|
-
} else {
|
|
341
|
-
cleanedTickers.delete(ticker.metadata.ticker);
|
|
342
|
-
if (!addedTickers.has(position.ticker)) {
|
|
374
|
+
tickers.set(position.ticker, ticker);
|
|
375
|
+
if (addedTickers.has(position.ticker)) {
|
|
376
|
+
addedTickers.set(position.ticker, ticker);
|
|
377
|
+
} else {
|
|
343
378
|
updatedTickers.set(position.ticker, ticker);
|
|
344
379
|
}
|
|
345
380
|
}
|
|
346
381
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
updatedTickers.set(position.ticker, ticker);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
for (const ticker of cleanedTickers.values()) {
|
|
356
|
-
if (!addedTickers.has(ticker.metadata.ticker) && !updatedTickers.has(ticker.metadata.ticker)) {
|
|
357
|
-
updatedTickers.set(ticker.metadata.ticker, ticker);
|
|
382
|
+
for (const ticker of cleanedTickers.values()) {
|
|
383
|
+
if (!addedTickers.has(ticker.metadata.ticker) && !updatedTickers.has(ticker.metadata.ticker)) {
|
|
384
|
+
updatedTickers.set(ticker.metadata.ticker, ticker);
|
|
385
|
+
}
|
|
358
386
|
}
|
|
359
|
-
|
|
387
|
+
return { tickers, addedTickers: [...addedTickers.values()], updatedTickers: [...updatedTickers.values()] };
|
|
388
|
+
};
|
|
360
389
|
|
|
390
|
+
let written = writeTickers(tickers);
|
|
361
391
|
let committed = false;
|
|
392
|
+
const rebase = (onto: BrokerSyncCommitTarget) => {
|
|
393
|
+
if (!committed) written = writeTickers(onto.tickers, onto.config);
|
|
394
|
+
};
|
|
362
395
|
const commit = async () => {
|
|
363
396
|
if (committed) return;
|
|
364
397
|
throwIfBrokerImportCancelled(signal);
|
|
365
398
|
if (resources) {
|
|
366
399
|
persistBrokerAccounts(resources, instance, broker, brokerAccounts);
|
|
367
400
|
}
|
|
368
|
-
await Promise.all([...addedTickers
|
|
401
|
+
await Promise.all([...written.addedTickers, ...written.updatedTickers].map((ticker) => (
|
|
369
402
|
tickerRepository.saveTicker(ticker)
|
|
370
403
|
)));
|
|
371
404
|
committed = true;
|
|
@@ -377,12 +410,13 @@ export async function syncBrokerInstance({
|
|
|
377
410
|
|
|
378
411
|
return {
|
|
379
412
|
config: nextConfig,
|
|
380
|
-
tickers,
|
|
413
|
+
get tickers() { return written.tickers; },
|
|
381
414
|
brokerAccounts,
|
|
382
415
|
positions,
|
|
383
416
|
portfolioIds,
|
|
384
|
-
|
|
385
|
-
|
|
417
|
+
get addedTickers() { return written.addedTickers; },
|
|
418
|
+
get updatedTickers() { return written.updatedTickers; },
|
|
419
|
+
rebase,
|
|
386
420
|
commit,
|
|
387
421
|
};
|
|
388
422
|
}
|