gloomberb 0.11.0 → 0.11.2
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/README.md +38 -15
- package/README.zh-CN.md +12 -12
- package/package.json +10 -3
- package/src/api-client/auth.ts +14 -10
- package/src/api-client/data.ts +54 -0
- package/src/api-client/index.test.ts +124 -0
- package/src/api-client/index.ts +139 -4
- package/src/api-client/paths.ts +89 -0
- package/src/api-client/request.ts +15 -2
- package/src/api-client/socket-health.test.ts +1 -0
- package/src/api-client/socket.ts +2 -1
- package/src/api-client/types.ts +163 -0
- package/src/app/pane-runtime/index.tsx +4 -1
- package/src/app/pane-runtime/plugin-bindings.test.ts +117 -0
- package/src/app/pane-runtime/plugin-bindings.ts +25 -4
- package/src/app/pane-runtime/ticker-inspector-runtime.ts +25 -14
- package/src/app/runtime/desktop-deeplink.test.ts +16 -0
- package/src/app/runtime/desktop-deeplink.ts +66 -2
- package/src/app/runtime/desktop-menu.ts +3 -0
- package/src/app/runtime/plugin-bindings.ts +30 -28
- package/src/app/runtime/update.ts +8 -6
- package/src/app.tsx +21 -7
- package/src/architecture/import-boundaries.test.ts +5 -1
- package/src/capabilities/factories.ts +4 -0
- package/src/cli/commands/automation.test.ts +9 -0
- package/src/cli/commands/automation.ts +22 -73
- package/src/cli/index.ts +10 -6
- package/src/cli/registry.ts +1 -1
- package/src/components/chart/composite/composite-chart.test.tsx +45 -2
- package/src/components/chart/composite/composite-chart.tsx +16 -12
- package/src/components/chart/composite/format.test.ts +34 -0
- package/src/components/chart/composite/format.ts +33 -10
- package/src/components/chart/composite/scene.ts +0 -21
- package/src/components/chart/composite/tools.ts +49 -0
- package/src/components/chart/core/axis-format.ts +13 -3
- package/src/components/chart/core/index.test.ts +7 -0
- package/src/components/chart/core/renderer.ts +3 -4
- package/src/components/command-bar/commands/direct/index.ts +4 -0
- package/src/components/command-bar/commands/registry.ts +8 -1
- package/src/components/command-bar/layout-items/current-layout.ts +1 -1
- package/src/components/command-bar/pane-templates/workflow.ts +4 -0
- package/src/components/command-bar/routes/root/shortcut-feedback.ts +3 -0
- package/src/components/command-bar/routes/root/shortcuts.test.ts +19 -0
- package/src/components/command-bar/routes/root/shortcuts.ts +3 -1
- package/src/components/command-bar/surface/index.test.tsx +6 -6
- package/src/components/command-bar/surface/index.tsx +4 -1
- package/src/components/command-bar/surface/pane.test.tsx +66 -2
- package/src/components/command-bar/view-model.test.ts +2 -1
- package/src/components/command-bar/workflow/ops.test.ts +54 -0
- package/src/components/command-bar/workflow/ops.ts +1 -1
- package/src/components/company-logo.test.ts +19 -0
- package/src/components/company-logo.tsx +60 -0
- package/src/components/feed-data-table/stack-view.tsx +7 -1
- package/src/components/index.ts +1 -0
- package/src/components/layout/detached-pane-shell.tsx +85 -15
- package/src/components/layout/header.tsx +5 -5
- package/src/components/layout/pane/title.ts +31 -3
- package/src/components/layout/shell/active-drag.ts +4 -11
- package/src/components/layout/shell/drag/index.ts +4 -20
- package/src/components/layout/shell/drag/runtime.ts +3 -4
- package/src/components/layout/shell/index.test.tsx +17 -3
- package/src/components/layout/shell/index.tsx +89 -31
- package/src/components/layout/shell/menu.test.ts +37 -1
- package/src/components/layout/shell/menu.ts +14 -0
- package/src/components/layout/shell/native/pointer-runtime.ts +22 -8
- package/src/components/layout/shell/pane/actions.ts +0 -5
- package/src/components/layout/shell/pane/management-shortcuts.ts +9 -4
- package/src/components/layout/shell/shortcuts.ts +6 -3
- package/src/components/layout/shell/ticker-link-menu.test.ts +82 -0
- package/src/components/layout/shell/ticker-link-menu.ts +78 -0
- package/src/components/layout/status-bar.test.tsx +62 -61
- package/src/components/layout/status-bar.tsx +53 -75
- package/src/components/onboarding/account-step/form-panel.tsx +4 -2
- package/src/components/onboarding/account-step/index.tsx +1 -1
- package/src/components/onboarding/account-step/signed-in-panel.tsx +1 -1
- package/src/components/onboarding/onboarding-wizard.test.tsx +44 -1
- package/src/components/onboarding/onboarding-wizard.tsx +13 -5
- package/src/components/onboarding/wizard-account.ts +4 -23
- package/src/components/table-view-shared.test.ts +11 -0
- package/src/components/table-view-shared.tsx +26 -0
- package/src/components/ui/data-table/index.tsx +19 -1
- package/src/components/ui/fields.tsx +5 -1
- package/src/core/state/app/layout-reducer.ts +36 -0
- package/src/core/state/app/layout.ts +10 -5
- package/src/core/state/app/state.ts +0 -13
- package/src/core/state/app/types.ts +6 -4
- package/src/data/config/chart-settings.ts +1 -1
- package/src/data/config/layout.ts +2 -0
- package/src/data/config/store/index.test.ts +27 -6
- package/src/data/config/store/normalize.ts +3 -5
- package/src/data/plugin-state-store.ts +11 -0
- package/src/data/sqlite/cache.test.ts +10 -0
- package/src/i18n/es.ts +13 -5
- package/src/i18n/ja.ts +13 -5
- package/src/i18n/ko.ts +13 -5
- package/src/i18n/zh-cn.ts +13 -5
- package/src/i18n/zh-tw.ts +13 -5
- package/src/layout-marketplace/api.test.ts +54 -0
- package/src/layout-marketplace/api.ts +65 -0
- package/src/layout-marketplace/gallery-desktop.test.tsx +262 -0
- package/src/layout-marketplace/gallery-desktop.tsx +375 -0
- package/src/layout-marketplace/gallery-terminal.tsx +376 -0
- package/src/layout-marketplace/gallery.test.tsx +84 -0
- package/src/layout-marketplace/gallery.tsx +323 -0
- package/src/layout-marketplace/geometry.test.ts +47 -0
- package/src/layout-marketplace/geometry.ts +80 -0
- package/src/layout-marketplace/mini-workspace.test.tsx +122 -0
- package/src/layout-marketplace/mini-workspace.tsx +428 -0
- package/src/layout-marketplace/model.test.ts +118 -0
- package/src/layout-marketplace/model.ts +171 -0
- package/src/layout-marketplace/name-dialog.tsx +60 -0
- package/src/layout-marketplace/pane-imagery.ts +85 -0
- package/src/layout-marketplace/pane.tsx +20 -0
- package/src/layout-marketplace/payload.test.ts +246 -0
- package/src/layout-marketplace/payload.ts +624 -0
- package/src/layout-marketplace/use-marketplace.ts +63 -0
- package/src/market-data/coordinator/index.test.ts +2 -2
- package/src/market-data/coordinator/index.ts +1 -1
- package/src/market-data/hooks.tsx +0 -41
- package/src/market-data/market/format.test.ts +7 -0
- package/src/market-data/market/freshness.ts +5 -2
- package/src/market-data/quotes/freshness.test.ts +16 -0
- package/src/news/aggregator.test.ts +92 -3
- package/src/news/aggregator.ts +98 -18
- package/src/news/hooks.test.tsx +2 -0
- package/src/news/hooks.ts +27 -1
- package/src/news/news-model.ts +5 -2
- package/src/news/types.ts +7 -0
- package/src/plugins/broker-sync/broker-sync.test.ts +58 -0
- package/src/plugins/broker-sync/normalize.ts +42 -5
- package/src/plugins/builtin/account-management/pane.tsx +5 -5
- package/src/plugins/builtin/ai/index.tsx +18 -2
- package/src/plugins/builtin/ai/screener/pane.test.tsx +47 -4
- package/src/plugins/builtin/ai/screener/pane.tsx +35 -12
- package/src/plugins/builtin/alerts/alert-engine.test.ts +53 -1
- package/src/plugins/builtin/alerts/alert-engine.ts +34 -1
- package/src/plugins/builtin/alerts/index.test.tsx +49 -6
- package/src/plugins/builtin/alerts/index.tsx +11 -6
- package/src/plugins/builtin/alerts/pane.tsx +51 -9
- package/src/plugins/builtin/alerts/quotes.ts +11 -3
- package/src/plugins/builtin/alerts/types.ts +1 -0
- package/src/plugins/builtin/analytics/index.tsx +11 -1
- package/src/plugins/builtin/analytics/pane-model.ts +6 -4
- package/src/plugins/builtin/broker-manager/index.tsx +3 -0
- package/src/plugins/builtin/buildout/model/index.ts +1 -1
- package/src/plugins/builtin/buildout/pane/index.tsx +17 -18
- package/src/plugins/builtin/cds/client.test.ts +83 -0
- package/src/plugins/builtin/cds/client.ts +67 -0
- package/src/plugins/builtin/cds/index.tsx +41 -0
- package/src/plugins/builtin/cds/model.test.ts +232 -0
- package/src/plugins/builtin/cds/model.ts +401 -0
- package/src/plugins/builtin/cds/pane.test.tsx +132 -0
- package/src/plugins/builtin/cds/pane.tsx +358 -0
- package/src/plugins/builtin/chart-composer/chart-spec.ts +31 -0
- package/src/plugins/builtin/chart-composer/index.tsx +88 -2
- package/src/plugins/builtin/chart-composer/pane-share.test.ts +71 -0
- package/src/plugins/builtin/chart-composer/pane.tsx +103 -15
- package/src/plugins/builtin/chat/content/index.tsx +4 -1
- package/src/plugins/builtin/chat/controller/channels.ts +7 -1
- package/src/plugins/builtin/chat/controller/index.ts +14 -9
- package/src/plugins/builtin/chat/controller/notifications.ts +13 -3
- package/src/plugins/builtin/chat/controller/persistence.ts +0 -3
- package/src/plugins/builtin/chat/controller/realtime.ts +3 -3
- package/src/plugins/builtin/chat/controller/send.ts +1 -3
- package/src/plugins/builtin/chat/controller/session-runtime.ts +18 -11
- package/src/plugins/builtin/chat/controller/state.ts +1 -3
- package/src/plugins/builtin/chat/controller/storage.ts +0 -2
- package/src/plugins/builtin/chat/controller/utils.ts +4 -6
- package/src/plugins/builtin/chat/controller/view.ts +2 -2
- package/src/plugins/builtin/chat/controller.test.ts +142 -14
- package/src/plugins/builtin/chat/message/desktop.test.tsx +88 -0
- package/src/plugins/builtin/chat/message/desktop.tsx +3 -1
- package/src/plugins/builtin/chat/message/profile-popover.test.tsx +1 -29
- package/src/plugins/builtin/chat/profile-popover.ts +53 -22
- package/src/plugins/builtin/chat/sidebar.test.tsx +81 -0
- package/src/plugins/builtin/chat/sidebar.tsx +63 -0
- package/src/plugins/builtin/chat/test-harness.tsx +29 -1
- package/src/plugins/builtin/cloud/auth-actions.tsx +14 -7
- package/src/plugins/builtin/cloud/auth-commands.ts +21 -51
- package/src/plugins/builtin/cloud/auth-dialog.tsx +296 -0
- package/src/{components/onboarding/account-step/model.test.ts → plugins/builtin/cloud/auth-model.test.ts} +1 -1
- package/src/{components/onboarding/account-step/model.ts → plugins/builtin/cloud/auth-model.ts} +37 -3
- package/src/plugins/builtin/cloud/browser.tsx +9 -0
- package/src/plugins/builtin/cloud/buildout-module.tsx +23 -0
- package/src/plugins/builtin/cloud/device-signin.ts +2 -8
- package/src/plugins/builtin/cloud/index.tsx +2 -0
- package/src/plugins/builtin/cloud/plugin.tsx +14 -24
- package/src/plugins/builtin/cloud/upgrade-command.ts +13 -6
- package/src/plugins/builtin/cloud-tweets/pane-share.test.ts +56 -0
- package/src/plugins/builtin/cloud-tweets/pane.tsx +20 -5
- package/src/plugins/builtin/cloud-tweets/registration.ts +50 -6
- package/src/plugins/builtin/composite-plugins.ts +3 -1
- package/src/plugins/builtin/congress-trades/detail.tsx +43 -1
- package/src/plugins/builtin/congress-trades/model.test.ts +85 -0
- package/src/plugins/builtin/congress-trades/model.ts +69 -1
- package/src/plugins/builtin/congress-trades/pane.tsx +51 -2
- package/src/plugins/builtin/connections/index.ts +3 -0
- package/src/plugins/builtin/dividend-yield/client.ts +18 -0
- package/src/plugins/builtin/dividend-yield/pane.tsx +7 -6
- package/src/plugins/builtin/earnings/index.tsx +5 -3
- package/src/plugins/builtin/earnings/model.test.ts +19 -1
- package/src/plugins/builtin/earnings/model.ts +17 -15
- package/src/plugins/builtin/econ/calendar-model.ts +0 -5
- package/src/plugins/builtin/econ/index.test.tsx +6 -1
- package/src/plugins/builtin/fear-greed/charts.tsx +12 -8
- package/src/plugins/builtin/help/index.tsx +2 -2
- package/src/plugins/builtin/holders/thirteenf-match.ts +1 -2
- package/src/plugins/builtin/insider/index.tsx +27 -11
- package/src/plugins/builtin/kelly-sizer/index.tsx +4 -1
- package/src/plugins/builtin/layout-manager/index.tsx +17 -4
- package/src/plugins/builtin/news/index.tsx +11 -6
- package/src/plugins/builtin/news/wire/breaking/notifications.test.ts +2 -0
- package/src/plugins/builtin/news/wire/breaking/pane.test.tsx +2 -0
- package/src/plugins/builtin/news/wire/breaking/pane.tsx +4 -1
- package/src/plugins/builtin/news/wire/feed-config.test.ts +15 -1
- package/src/plugins/builtin/news/wire/feed-config.ts +25 -5
- package/src/plugins/builtin/news/wire/index.tsx +3 -3
- package/src/plugins/builtin/news/wire/industry-pane.tsx +7 -2
- package/src/plugins/builtin/news/wire/news/footer.ts +34 -1
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +4 -1
- package/src/plugins/builtin/news/wire/news/table.tsx +8 -2
- package/src/plugins/builtin/news/wire/rss/source.test.ts +12 -0
- package/src/plugins/builtin/news/wire/rss/source.ts +1 -1
- package/src/plugins/builtin/notes/files.ts +80 -0
- package/src/plugins/builtin/notes/index.tsx +3 -0
- package/src/plugins/builtin/notes/sync.test.ts +49 -0
- package/src/plugins/builtin/notes/sync.ts +98 -0
- package/src/plugins/builtin/options/index.tsx +1 -0
- package/src/plugins/builtin/options/view.test.tsx +25 -3
- package/src/plugins/builtin/options/view.tsx +1 -0
- package/src/plugins/builtin/portfolio-list/account-metrics.test.ts +22 -0
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +12 -7
- package/src/plugins/builtin/portfolio-list/cli/render.ts +5 -3
- package/src/plugins/builtin/portfolio-list/column-values.ts +15 -4
- package/src/plugins/builtin/portfolio-list/header.tsx +13 -3
- package/src/plugins/builtin/portfolio-list/index.tsx +8 -0
- package/src/plugins/builtin/portfolio-list/metrics.test.ts +31 -1
- package/src/plugins/builtin/portfolio-list/pane/index.test.tsx +1 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +10 -7
- package/src/plugins/builtin/portfolio-list/pane/streaming.ts +6 -1
- package/src/plugins/builtin/portfolio-list/position-metrics.ts +25 -6
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +35 -21
- package/src/plugins/builtin/portfolio-list/summary/totals.ts +20 -6
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +5 -12
- package/src/plugins/builtin/research/equity-diagnostic-pane.test.tsx +329 -0
- package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +582 -0
- package/src/plugins/builtin/research/index.test.ts +14 -0
- package/src/plugins/builtin/research/index.tsx +29 -0
- package/src/plugins/builtin/sec/index.tsx +24 -9
- package/src/plugins/builtin/sectors/index.tsx +14 -0
- package/src/plugins/builtin/shared/cloud-upgrade.ts +18 -11
- package/src/plugins/builtin/shared/public-pane.test.ts +12 -0
- package/src/plugins/builtin/shared/public-pane.ts +9 -0
- package/src/plugins/builtin/shared/public-share.ts +23 -0
- package/src/plugins/builtin/shared/ticker-source.ts +32 -0
- package/src/plugins/builtin/shared/ticker-surface.test.ts +27 -0
- package/src/plugins/builtin/shared/ticker-surface.ts +17 -0
- package/src/plugins/builtin/substack/api/cache.ts +7 -3
- package/src/plugins/builtin/substack/api/loaders.ts +36 -12
- package/src/plugins/builtin/substack/api/types.ts +2 -0
- package/src/plugins/builtin/substack/pane-state.ts +1 -1
- package/src/plugins/builtin/substack/pane.tsx +55 -12
- package/src/plugins/builtin/thirteenf/api.test.ts +1 -1
- package/src/plugins/builtin/thirteenf/api.ts +40 -18
- package/src/plugins/builtin/thirteenf/data.ts +15 -7
- package/src/plugins/builtin/thirteenf/pane.tsx +45 -10
- package/src/plugins/builtin/ticker-detail/index.tsx +10 -0
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +12 -4
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +8 -2
- package/src/plugins/builtin/ticker-detail/settings.ts +12 -0
- package/src/plugins/builtin/treasury-auctions/index.tsx +2 -0
- package/src/plugins/builtin/world-indices/index.tsx +2 -0
- package/src/plugins/catalog-browser.test.ts +78 -0
- package/src/plugins/catalog-browser.ts +136 -0
- package/src/plugins/gridlock-notification.ts +2 -1
- package/src/plugins/ibkr/flex/index.test.ts +19 -0
- package/src/plugins/ibkr/flex/index.ts +1 -1
- package/src/plugins/pane-manager/gridlock.ts +87 -3
- package/src/plugins/pane-manager.test.ts +30 -0
- package/src/plugins/pane-manager.ts +6 -1
- package/src/plugins/prediction-markets/chart.tsx +2 -2
- package/src/plugins/prediction-markets/controller/catalog.ts +77 -2
- package/src/plugins/prediction-markets/controller/data.ts +7 -1
- package/src/plugins/prediction-markets/controller/index.ts +9 -2
- package/src/plugins/prediction-markets/controller/keyboard.ts +10 -14
- package/src/plugins/prediction-markets/detail/overview.tsx +21 -0
- package/src/plugins/prediction-markets/detail/pane.tsx +43 -34
- package/src/plugins/prediction-markets/detail/trades.tsx +34 -9
- package/src/plugins/prediction-markets/detail.test.tsx +29 -2
- package/src/plugins/prediction-markets/index.tsx +8 -4
- package/src/plugins/prediction-markets/navigation.ts +0 -1
- package/src/plugins/prediction-markets/pane.test.tsx +77 -0
- package/src/plugins/prediction-markets/pane.tsx +12 -0
- package/src/plugins/prediction-markets/plugin.test.tsx +36 -0
- package/src/plugins/prediction-markets/services/kalshi/adapter.ts +49 -9
- package/src/plugins/prediction-markets/services/polymarket/adapter.ts +31 -0
- package/src/plugins/prediction-markets/test-helpers.tsx +9 -0
- package/src/plugins/registry/index.ts +2 -0
- package/src/plugins/ticker-navigation.test.ts +24 -0
- package/src/plugins/ticker-navigation.ts +40 -0
- package/src/remote/schema.ts +1 -1
- package/src/renderers/browser/app-services.ts +61 -0
- package/src/renderers/browser/cloud-transport.test.ts +68 -0
- package/src/renderers/browser/cloud-transport.ts +48 -0
- package/src/renderers/browser/config-host.ts +58 -0
- package/src/renderers/browser/deeplink-bridge.test.ts +41 -0
- package/src/renderers/browser/deeplink-bridge.ts +22 -0
- package/src/renderers/browser/error-boundary.tsx +23 -0
- package/src/renderers/browser/main.tsx +64 -0
- package/src/renderers/browser/persistence.ts +74 -0
- package/src/renderers/browser/storage.test.ts +66 -0
- package/src/renderers/browser/storage.ts +50 -0
- package/src/renderers/browser/ticker-repository.ts +59 -0
- package/src/renderers/browser/ui-host.tsx +40 -0
- package/src/renderers/cloudflare/worker.test.ts +136 -0
- package/src/renderers/cloudflare/worker.ts +116 -0
- package/src/renderers/electrobun/bun/application-menu/index.ts +3 -2
- package/src/renderers/electrobun/bun/desktop/initialization.ts +1 -0
- package/src/renderers/electrobun/bun/desktop/plugin-state.ts +7 -1
- package/src/renderers/electrobun/view/app-services.ts +14 -3
- package/src/renderers/electrobun/view/build-assets.ts +12 -10
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -1
- package/src/renderers/electrobun/view/data-table/index.tsx +10 -1
- package/src/renderers/electrobun/view/desktop/controls.tsx +2 -0
- package/src/renderers/electrobun/view/dom-ui-host.tsx +143 -0
- package/src/renderers/electrobun/view/host/box.test.tsx +33 -0
- package/src/renderers/electrobun/view/host/box.tsx +11 -6
- package/src/renderers/electrobun/view/host/chart-surface.tsx +7 -1
- package/src/renderers/electrobun/view/host/input.tsx +11 -7
- package/src/renderers/electrobun/view/host/native.ts +0 -5
- package/src/renderers/electrobun/view/main.tsx +2 -0
- package/src/renderers/electrobun/view/remote/persistence.ts +5 -0
- package/src/renderers/electrobun/view/styles.css +107 -7
- package/src/renderers/electrobun/view/toast-host.test.tsx +78 -0
- package/src/renderers/electrobun/view/toast-host.tsx +69 -43
- package/src/renderers/electrobun/view/ui-host.tsx +6 -137
- package/src/renderers/opentui/start.tsx +2 -0
- package/src/renderers/share/layout-view.test.tsx +58 -0
- package/src/renderers/share/layout-view.tsx +133 -0
- package/src/renderers/share/main.tsx +102 -0
- package/src/renderers/share/styles.css +54 -0
- package/src/renderers/share/view.test.tsx +100 -0
- package/src/renderers/share/view.tsx +150 -0
- package/src/shares/api.test.ts +105 -0
- package/src/shares/api.ts +127 -0
- package/src/shares/chart-snapshot.test.ts +34 -0
- package/src/shares/chart-snapshot.ts +28 -0
- package/src/shares/location.test.ts +19 -0
- package/src/shares/location.ts +11 -0
- package/src/shares/pane.test.ts +110 -0
- package/src/shares/pane.ts +54 -0
- package/src/shares/payload.ts +177 -0
- package/src/sources/gloomberb-cloud/index.test.ts +61 -1
- package/src/sources/gloomberb-cloud/index.ts +86 -14
- package/src/sources/gloomberb-cloud/normalizers.test.ts +34 -0
- package/src/sources/gloomberb-cloud/normalizers.ts +5 -5
- package/src/sources/provider-router/history.test.ts +2 -2
- package/src/sources/provider-router/news.test.ts +34 -0
- package/src/sources/provider-router/news.ts +15 -1
- package/src/sources/sec-edgar.test.ts +59 -0
- package/src/sources/sec-edgar.ts +63 -23
- package/src/sources/yahoo-finance/mappers.ts +3 -0
- package/src/sources/yahoo-finance/snapshots.ts +10 -2
- package/src/sources/yahoo-finance/symbols.test.ts +10 -0
- package/src/sources/yahoo-finance/symbols.ts +13 -8
- package/src/sources/yahoo-finance.test.ts +1 -1
- package/src/sources/yahoo-finance.ts +6 -2
- package/src/state/app/bootstrap.ts +6 -7
- package/src/state/app/context/index.test.ts +41 -1
- package/src/state/app/context/index.test.tsx +34 -16
- package/src/state/selectors-ui.ts +0 -8
- package/src/sync/baseline.ts +32 -0
- package/src/sync/controller.test.ts +124 -1
- package/src/sync/controller.ts +54 -12
- package/src/sync/core-contributors.test.ts +44 -0
- package/src/sync/core-contributors.ts +59 -10
- package/src/sync/react.ts +22 -2
- package/src/sync/types.ts +12 -0
- package/src/theme/font-scale.ts +0 -11
- package/src/tickers/search/index.test.ts +27 -0
- package/src/tickers/search/upsert.ts +10 -0
- package/src/types/capability-route-source.ts +2 -1
- package/src/types/config.ts +35 -5
- package/src/types/desktop-menu.ts +1 -0
- package/src/types/plugin.ts +36 -0
- package/src/ui/context-menu.tsx +1 -1
- package/src/ui/host.tsx +4 -0
- package/src/ui/toast.tsx +2 -0
- package/src/utils/back-navigation.ts +0 -4
- package/src/utils/price-history.ts +11 -10
- package/src/utils/sec.test.ts +6 -0
- package/src/utils/sec.ts +1 -1
|
@@ -307,6 +307,15 @@ function collectAccountsByPortfolio(
|
|
|
307
307
|
return output;
|
|
308
308
|
}
|
|
309
309
|
|
|
310
|
+
type SanitizedTickerMetadata = ReturnType<typeof sanitizeTickerMetadata>;
|
|
311
|
+
|
|
312
|
+
/** Only tickers the user actually filed somewhere are worth syncing. */
|
|
313
|
+
function isSyncableTicker(metadata: Pick<SanitizedTickerMetadata, "portfolios" | "watchlists" | "positions">): boolean {
|
|
314
|
+
return metadata.portfolios.length > 0
|
|
315
|
+
|| metadata.watchlists.length > 0
|
|
316
|
+
|| metadata.positions.length > 0;
|
|
317
|
+
}
|
|
318
|
+
|
|
310
319
|
function collectCoreCollectionsPayload(
|
|
311
320
|
config: AppConfig,
|
|
312
321
|
tickers: Map<string, TickerRecord>,
|
|
@@ -316,11 +325,7 @@ function collectCoreCollectionsPayload(
|
|
|
316
325
|
) {
|
|
317
326
|
const records = [...tickers.values()]
|
|
318
327
|
.map((ticker) => sanitizeTickerMetadata(ticker.metadata, financials.get(ticker.metadata.ticker)))
|
|
319
|
-
.filter(
|
|
320
|
-
metadata.portfolios.length > 0 ||
|
|
321
|
-
metadata.watchlists.length > 0 ||
|
|
322
|
-
metadata.positions.length > 0
|
|
323
|
-
));
|
|
328
|
+
.filter(isSyncableTicker);
|
|
324
329
|
|
|
325
330
|
return {
|
|
326
331
|
baseCurrency: config.baseCurrency,
|
|
@@ -375,11 +380,22 @@ function mergeConfigPayload(
|
|
|
375
380
|
config: AppConfig,
|
|
376
381
|
payload: unknown,
|
|
377
382
|
baselineConfig: AppConfig = config,
|
|
383
|
+
lastSyncedPayload?: unknown,
|
|
378
384
|
): AppConfig | null {
|
|
379
385
|
if (!isPlainObject(payload)) return null;
|
|
380
386
|
const next: AppConfig = { ...config };
|
|
387
|
+
// Two guards, both needed. baselineConfig catches edits made while this pull
|
|
388
|
+
// was in flight; lastSyncedPayload catches edits made while the app was not
|
|
389
|
+
// running at all (CLI writes, offline edits), which otherwise look pristine.
|
|
390
|
+
const lastSynced = isPlainObject(lastSyncedPayload) ? lastSyncedPayload : null;
|
|
391
|
+
const localPayload = lastSynced
|
|
392
|
+
? collectCoreConfigPayload(config) as Record<string, unknown>
|
|
393
|
+
: null;
|
|
394
|
+
const matchesLastSynced = (key: string) => (
|
|
395
|
+
!lastSynced || valuesEqual(localPayload?.[key], lastSynced[key])
|
|
396
|
+
);
|
|
381
397
|
const canApply = <K extends keyof AppConfig>(key: K) => (
|
|
382
|
-
valuesEqual(config[key], baselineConfig[key])
|
|
398
|
+
valuesEqual(config[key], baselineConfig[key]) && matchesLastSynced(key as string)
|
|
383
399
|
);
|
|
384
400
|
const assign = <K extends keyof AppConfig>(key: K) => {
|
|
385
401
|
if (key in payload && canApply(key)) {
|
|
@@ -420,7 +436,8 @@ function mergeConfigPayload(
|
|
|
420
436
|
|
|
421
437
|
const layoutStateUntouched = config.layout === baselineConfig.layout
|
|
422
438
|
&& config.layouts === baselineConfig.layouts
|
|
423
|
-
&& config.activeLayoutIndex === baselineConfig.activeLayoutIndex
|
|
439
|
+
&& config.activeLayoutIndex === baselineConfig.activeLayoutIndex
|
|
440
|
+
&& ["layout", "layouts", "activeLayoutIndex"].every(matchesLastSynced);
|
|
424
441
|
if (
|
|
425
442
|
layoutStateUntouched
|
|
426
443
|
&& ["layout", "layouts", "activeLayoutIndex"].every((key) => key in payload)
|
|
@@ -450,12 +467,40 @@ function mergeConfigPayload(
|
|
|
450
467
|
return next;
|
|
451
468
|
}
|
|
452
469
|
|
|
470
|
+
function lastSyncedTickersById(baselinePayload: unknown): Map<string, Record<string, unknown>> | null {
|
|
471
|
+
if (!isPlainObject(baselinePayload) || !Array.isArray(baselinePayload.tickers)) return null;
|
|
472
|
+
const byId = new Map<string, Record<string, unknown>>();
|
|
473
|
+
for (const entry of baselinePayload.tickers) {
|
|
474
|
+
if (isPlainObject(entry) && typeof entry.ticker === "string") byId.set(entry.ticker, entry);
|
|
475
|
+
}
|
|
476
|
+
return byId;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/** Quotes churn on every refresh, so they cannot signal a user edit. */
|
|
480
|
+
function withoutQuote(metadata: Record<string, unknown>): Record<string, unknown> {
|
|
481
|
+
const { quote: _quote, ...rest } = metadata;
|
|
482
|
+
return rest;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function tickerChangedSinceLastSync(
|
|
486
|
+
current: TickerRecord | null | undefined,
|
|
487
|
+
lastSyncedTickers: Map<string, Record<string, unknown>> | null,
|
|
488
|
+
): boolean {
|
|
489
|
+
if (!lastSyncedTickers || !current) return false;
|
|
490
|
+
const local = sanitizeTickerMetadata(current.metadata);
|
|
491
|
+
const baseline = lastSyncedTickers.get(current.metadata.ticker);
|
|
492
|
+
// No baseline entry means this device never uploaded the ticker: filed
|
|
493
|
+
// locally it is unsynced work, unfiled it is not something sync tracks.
|
|
494
|
+
if (!baseline) return isSyncableTicker(local);
|
|
495
|
+
return !valuesEqual(withoutQuote(local), withoutQuote(baseline));
|
|
496
|
+
}
|
|
497
|
+
|
|
453
498
|
export const coreConfigSyncContributor: SyncContributor = {
|
|
454
499
|
id: "core.config",
|
|
455
500
|
schemaVersion: 1,
|
|
456
501
|
collect: ({ state }) => collectCoreConfigPayload(state.config),
|
|
457
|
-
apply: (payload, { baselineState, state, dispatch }) => {
|
|
458
|
-
const nextConfig = mergeConfigPayload(state.config, payload, baselineState.config);
|
|
502
|
+
apply: (payload, { baselinePayload, baselineState, state, dispatch }) => {
|
|
503
|
+
const nextConfig = mergeConfigPayload(state.config, payload, baselineState.config, baselinePayload);
|
|
459
504
|
if (!nextConfig || valuesEqual(nextConfig, state.config)) return;
|
|
460
505
|
dispatch({ type: "SET_CONFIG", config: nextConfig });
|
|
461
506
|
scheduleConfigSave(nextConfig);
|
|
@@ -472,9 +517,10 @@ export const coreCollectionsSyncContributor: SyncContributor = {
|
|
|
472
517
|
state.exchangeRates,
|
|
473
518
|
state.brokerAccounts,
|
|
474
519
|
),
|
|
475
|
-
apply: async (payload, { getState, isCurrent, dispatch, tickerRepository }) => {
|
|
520
|
+
apply: async (payload, { baselinePayload, getState, isCurrent, dispatch, tickerRepository }) => {
|
|
476
521
|
if (!isPlainObject(payload)) return;
|
|
477
522
|
hydrateProfileAnalytics(payload);
|
|
523
|
+
const lastSyncedTickers = lastSyncedTickersById(baselinePayload);
|
|
478
524
|
const incomingRecords: TickerRecord[] = [];
|
|
479
525
|
const rawTickers = Array.isArray(payload.tickers) ? payload.tickers : [];
|
|
480
526
|
for (const rawTicker of rawTickers) {
|
|
@@ -483,6 +529,9 @@ export const coreCollectionsSyncContributor: SyncContributor = {
|
|
|
483
529
|
const current = typeof rawTicker.ticker === "string"
|
|
484
530
|
? getState().tickers.get(rawTicker.ticker)
|
|
485
531
|
: null;
|
|
532
|
+
// Local edits the cloud has never seen (CLI positions, offline changes)
|
|
533
|
+
// win here and are uploaded by the push that follows this pull.
|
|
534
|
+
if (tickerChangedSinceLastSync(current, lastSyncedTickers)) continue;
|
|
486
535
|
const metadata = hydrateTickerMetadata({
|
|
487
536
|
...current?.metadata,
|
|
488
537
|
...rawTicker,
|
package/src/sync/react.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import { useEffect, useSyncExternalStore, type Dispatch } from "react";
|
|
1
|
+
import { useEffect, useMemo, useSyncExternalStore, type Dispatch } from "react";
|
|
2
2
|
import type { AppAction, AppState } from "../core/state/app/state";
|
|
3
3
|
import type { AppTickerRepositoryPort } from "../core/app-service-ports";
|
|
4
4
|
import { apiClient } from "../api-client";
|
|
5
5
|
import type { PluginRegistry } from "../plugins/registry";
|
|
6
6
|
import { subscribeToCloudVerification } from "./auth-transition";
|
|
7
|
+
import { createSyncBaselineStore } from "./baseline";
|
|
7
8
|
import { cloudSyncController } from "./controller";
|
|
8
9
|
|
|
10
|
+
const CLOUD_SYNC_POLL_MS = 15_000;
|
|
11
|
+
|
|
9
12
|
interface CloudSyncRuntimeOptions {
|
|
10
13
|
state: AppState;
|
|
11
14
|
getState: () => AppState;
|
|
@@ -13,6 +16,7 @@ interface CloudSyncRuntimeOptions {
|
|
|
13
16
|
tickerRepository: AppTickerRepositoryPort;
|
|
14
17
|
pluginRegistry: PluginRegistry;
|
|
15
18
|
initialized: boolean;
|
|
19
|
+
appActive?: boolean;
|
|
16
20
|
}
|
|
17
21
|
|
|
18
22
|
export function useCloudSyncRuntime({
|
|
@@ -22,22 +26,38 @@ export function useCloudSyncRuntime({
|
|
|
22
26
|
tickerRepository,
|
|
23
27
|
pluginRegistry,
|
|
24
28
|
initialized,
|
|
29
|
+
appActive = true,
|
|
25
30
|
}: CloudSyncRuntimeOptions): void {
|
|
31
|
+
const baselineStore = useMemo(
|
|
32
|
+
() => createSyncBaselineStore(pluginRegistry.persistence.pluginState),
|
|
33
|
+
[pluginRegistry],
|
|
34
|
+
);
|
|
35
|
+
|
|
26
36
|
useEffect(() => {
|
|
27
37
|
return cloudSyncController.setRuntime({
|
|
28
38
|
getState,
|
|
29
39
|
dispatch,
|
|
30
40
|
tickerRepository,
|
|
41
|
+
baselineStore,
|
|
31
42
|
getContributors: () => pluginRegistry.getEnabledSyncContributors(),
|
|
32
43
|
getTransport: () => pluginRegistry.getActiveSyncTransport(),
|
|
33
44
|
});
|
|
34
|
-
}, [dispatch, getState, pluginRegistry, tickerRepository]);
|
|
45
|
+
}, [baselineStore, dispatch, getState, pluginRegistry, tickerRepository]);
|
|
35
46
|
|
|
36
47
|
useEffect(() => {
|
|
37
48
|
if (!initialized) return;
|
|
38
49
|
void cloudSyncController.requestSync({ reason: "startup" });
|
|
39
50
|
}, [initialized, pluginRegistry]);
|
|
40
51
|
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (!initialized || !appActive) return;
|
|
54
|
+
void cloudSyncController.requestSync({ reason: "foreground" });
|
|
55
|
+
const timer = setInterval(() => {
|
|
56
|
+
void cloudSyncController.requestSync({ reason: "poll" });
|
|
57
|
+
}, CLOUD_SYNC_POLL_MS);
|
|
58
|
+
return () => clearInterval(timer);
|
|
59
|
+
}, [appActive, initialized]);
|
|
60
|
+
|
|
41
61
|
useEffect(() => subscribeToCloudVerification(apiClient, () => {
|
|
42
62
|
if (!initialized) return;
|
|
43
63
|
// Email verification makes the Cloud transport available. Force the first
|
package/src/sync/types.ts
CHANGED
|
@@ -26,6 +26,12 @@ export interface SyncCollectContext {
|
|
|
26
26
|
export interface SyncApplyContext {
|
|
27
27
|
snapshot: SyncSnapshot;
|
|
28
28
|
baselineState: AppState;
|
|
29
|
+
/**
|
|
30
|
+
* The payload this device last pushed for this contributor, or null when it
|
|
31
|
+
* never synced. Anything that differs from it is local work the cloud has
|
|
32
|
+
* never seen (CLI edits, offline edits) and must survive the pull.
|
|
33
|
+
*/
|
|
34
|
+
baselinePayload: unknown;
|
|
29
35
|
state: AppState;
|
|
30
36
|
getState: () => AppState;
|
|
31
37
|
isCurrent: () => boolean;
|
|
@@ -69,6 +75,12 @@ export interface SyncSettings {
|
|
|
69
75
|
lastRoundupEmailAt?: string | null;
|
|
70
76
|
}
|
|
71
77
|
|
|
78
|
+
/** Durable record of the payloads this device last pushed to the cloud. */
|
|
79
|
+
export interface SyncBaselineStore {
|
|
80
|
+
load(): Record<string, unknown> | null;
|
|
81
|
+
save(payloads: Record<string, unknown>): void;
|
|
82
|
+
}
|
|
83
|
+
|
|
72
84
|
export interface RegisteredSyncContributor {
|
|
73
85
|
pluginId: string;
|
|
74
86
|
contributor: SyncContributor;
|
package/src/theme/font-scale.ts
CHANGED
|
@@ -28,10 +28,6 @@ export function clampFontSize(value: unknown): number {
|
|
|
28
28
|
return Math.min(MAX_FONT_SIZE_PX, Math.max(MIN_FONT_SIZE_PX, Math.round(numeric)));
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
export function getFontSize(): number {
|
|
32
|
-
return appliedFontSizePx;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
31
|
function roundToHundredth(value: number): number {
|
|
36
32
|
return Math.round(value * 100) / 100;
|
|
37
33
|
}
|
|
@@ -60,10 +56,3 @@ export function syncFontScale(fontSizePx: unknown): boolean {
|
|
|
60
56
|
appliedToDocument = true;
|
|
61
57
|
return true;
|
|
62
58
|
}
|
|
63
|
-
|
|
64
|
-
export function resetFontScaleForTests(): void {
|
|
65
|
-
appliedFontSizePx = BASE_FONT_SIZE_PX;
|
|
66
|
-
appliedToDocument = false;
|
|
67
|
-
WEB_CELL_WIDTH = BASE_CELL_WIDTH_PX;
|
|
68
|
-
WEB_CELL_HEIGHT = BASE_CELL_HEIGHT_PX;
|
|
69
|
-
}
|
|
@@ -521,6 +521,33 @@ describe("ticker-search utilities", () => {
|
|
|
521
521
|
expect(saved).toHaveLength(1);
|
|
522
522
|
});
|
|
523
523
|
|
|
524
|
+
test("replaces a saved listing when search selects a different exchange", async () => {
|
|
525
|
+
const existing = makeTicker("AAPL", "Apple Inc.");
|
|
526
|
+
existing.metadata.exchange = "NASDAQ";
|
|
527
|
+
existing.metadata.currency = "USD";
|
|
528
|
+
const saved: TickerRecord[] = [];
|
|
529
|
+
const repository = {
|
|
530
|
+
loadTicker: async () => existing,
|
|
531
|
+
createTicker: async (metadata: TickerRecord["metadata"]) => ({ metadata }),
|
|
532
|
+
saveTicker: async (ticker: TickerRecord) => {
|
|
533
|
+
saved.push(ticker);
|
|
534
|
+
},
|
|
535
|
+
};
|
|
536
|
+
|
|
537
|
+
const { ticker } = await upsertTickerFromSearchResult(repository as any, {
|
|
538
|
+
providerId: "test",
|
|
539
|
+
symbol: "AAPL",
|
|
540
|
+
name: "Apple Inc.",
|
|
541
|
+
exchange: "BYMA",
|
|
542
|
+
type: "EQUITY",
|
|
543
|
+
currency: "ARS",
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
expect(ticker.metadata.exchange).toBe("BYMA");
|
|
547
|
+
expect(ticker.metadata.currency).toBe("ARS");
|
|
548
|
+
expect(saved).toHaveLength(1);
|
|
549
|
+
});
|
|
550
|
+
|
|
524
551
|
test("exposes local ticker candidates in saved category", () => {
|
|
525
552
|
expect(createLocalTickerSearchCandidates([makeTicker("TSLA", "Tesla")])).toEqual([
|
|
526
553
|
expect.objectContaining({
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
getSearchResultSymbol,
|
|
9
9
|
shouldReplaceTickerName,
|
|
10
10
|
} from "./result";
|
|
11
|
+
import { canonicalExchange } from "../../utils/exchanges";
|
|
11
12
|
|
|
12
13
|
export async function upsertTickerFromSearchResult(
|
|
13
14
|
tickerRepository: AppTickerRepositoryPort,
|
|
@@ -70,6 +71,15 @@ function mergeTickerMetadataFromSearchResult(metadata: TickerMetadata, result: I
|
|
|
70
71
|
if (nextExchange && !metadata.exchange) {
|
|
71
72
|
metadata.exchange = nextExchange;
|
|
72
73
|
changed = true;
|
|
74
|
+
} else if (
|
|
75
|
+
nextExchange
|
|
76
|
+
&& metadata.exchange
|
|
77
|
+
&& canonicalExchange(nextExchange) !== canonicalExchange(metadata.exchange)
|
|
78
|
+
) {
|
|
79
|
+
metadata.exchange = nextExchange;
|
|
80
|
+
if (nextName) metadata.name = nextName;
|
|
81
|
+
if (nextCurrency) metadata.currency = nextCurrency;
|
|
82
|
+
changed = true;
|
|
73
83
|
}
|
|
74
84
|
if (nextCurrency && !metadata.currency) {
|
|
75
85
|
metadata.currency = nextCurrency;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type { AssetDataProvider } from "./data-provider";
|
|
2
|
-
import type { NewsArticle, NewsQuery } from "../news/types";
|
|
2
|
+
import type { NewsArticle, NewsPage, NewsQuery } from "../news/types";
|
|
3
3
|
import type { CachePolicyMap } from "./persistence";
|
|
4
4
|
|
|
5
5
|
export interface NewsDataProvider {
|
|
6
6
|
supports?(query: NewsQuery): boolean;
|
|
7
7
|
getCachedNews?(query: NewsQuery): NewsArticle[];
|
|
8
8
|
fetchNews(query: NewsQuery): Promise<NewsArticle[]>;
|
|
9
|
+
fetchNewsPage?(query: NewsQuery): Promise<NewsPage>;
|
|
9
10
|
fetchNewsStory?(storyId: string): Promise<NewsArticle | null>;
|
|
10
11
|
}
|
|
11
12
|
|
package/src/types/config.ts
CHANGED
|
@@ -504,7 +504,11 @@ export function removePaneInstances(layout: LayoutConfig, instanceIds: Iterable<
|
|
|
504
504
|
|
|
505
505
|
export function normalizePaneLayout(
|
|
506
506
|
layout: LayoutConfig,
|
|
507
|
-
options?: {
|
|
507
|
+
options?: {
|
|
508
|
+
defaultFollowSourceInstanceId?: string | null;
|
|
509
|
+
/** Last resolved symbol used to pin an orphaned follower; otherwise it remains safely unlinked. */
|
|
510
|
+
resolveOrphanSymbol?: (instanceId: string) => string | null;
|
|
511
|
+
},
|
|
508
512
|
): LayoutConfig {
|
|
509
513
|
const fallbackSourceId = options?.defaultFollowSourceInstanceId ?? null;
|
|
510
514
|
const fallbackAvailable = !!fallbackSourceId && layout.instances.some((instance) => instance.instanceId === fallbackSourceId);
|
|
@@ -530,14 +534,27 @@ export function normalizePaneLayout(
|
|
|
530
534
|
for (;;) {
|
|
531
535
|
const validInstanceIds = new Set(nextLayout.instances.map((instance) => instance.instanceId));
|
|
532
536
|
const removedIds = new Set<string>();
|
|
537
|
+
const orphanBindings = new Map<string, PaneBinding>();
|
|
533
538
|
|
|
534
539
|
for (const instance of nextLayout.instances) {
|
|
535
540
|
if (instance.binding?.kind === "follow" && !validInstanceIds.has(instance.binding.sourceInstanceId)) {
|
|
536
|
-
|
|
541
|
+
// Structural helpers remove the source before the app can resolve its last ticker. Preserve
|
|
542
|
+
// the dangling binding until the runtime normalizes again with a resolver.
|
|
543
|
+
if (!options?.resolveOrphanSymbol) continue;
|
|
544
|
+
const orphanSymbol = options.resolveOrphanSymbol(instance.instanceId)?.trim();
|
|
545
|
+
orphanBindings.set(
|
|
546
|
+
instance.instanceId,
|
|
547
|
+
orphanSymbol ? { kind: "fixed", symbol: orphanSymbol } : { kind: "none" },
|
|
548
|
+
);
|
|
537
549
|
continue;
|
|
538
550
|
}
|
|
539
551
|
|
|
540
|
-
if (
|
|
552
|
+
if (
|
|
553
|
+
isTickerPaneInstance(instance)
|
|
554
|
+
&& instance.paneId !== TICKER_RESEARCH_PANE_ID
|
|
555
|
+
&& instance.binding?.kind !== "follow"
|
|
556
|
+
&& instance.binding?.kind !== "fixed"
|
|
557
|
+
) {
|
|
541
558
|
removedIds.add(instance.instanceId);
|
|
542
559
|
continue;
|
|
543
560
|
}
|
|
@@ -547,8 +564,21 @@ export function normalizePaneLayout(
|
|
|
547
564
|
}
|
|
548
565
|
}
|
|
549
566
|
|
|
550
|
-
if (
|
|
551
|
-
|
|
567
|
+
if (orphanBindings.size > 0) {
|
|
568
|
+
nextLayout = {
|
|
569
|
+
...nextLayout,
|
|
570
|
+
instances: nextLayout.instances.map((instance) => {
|
|
571
|
+
const binding = orphanBindings.get(instance.instanceId);
|
|
572
|
+
return binding ? { ...instance, binding } : instance;
|
|
573
|
+
}),
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
if (removedIds.size > 0) {
|
|
578
|
+
nextLayout = removePaneInstances(nextLayout, removedIds);
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
581
|
+
if (orphanBindings.size === 0) break;
|
|
552
582
|
}
|
|
553
583
|
|
|
554
584
|
const validInstanceIds = new Set(nextLayout.instances.map((instance) => instance.instanceId));
|
|
@@ -4,6 +4,7 @@ export type DesktopApplicationMenuCommand =
|
|
|
4
4
|
| { type: "open-url"; url: string }
|
|
5
5
|
| { type: "check-for-updates" }
|
|
6
6
|
| { type: "toggle-status-bar" }
|
|
7
|
+
| { type: "open-layout-gallery" }
|
|
7
8
|
| { type: "layout-undo" }
|
|
8
9
|
| { type: "layout-redo" }
|
|
9
10
|
| { type: "layout-gridlock" };
|
package/src/types/plugin.ts
CHANGED
|
@@ -44,6 +44,18 @@ export interface PaneProps {
|
|
|
44
44
|
close?: () => void;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
export type PaneSharePrivateFields = true | readonly string[];
|
|
48
|
+
|
|
49
|
+
export interface PanePortableShareDef {
|
|
50
|
+
/** Fields excluded before pane configuration or state leaves the device. */
|
|
51
|
+
private?: {
|
|
52
|
+
title?: boolean;
|
|
53
|
+
params?: PaneSharePrivateFields;
|
|
54
|
+
settings?: PaneSharePrivateFields;
|
|
55
|
+
state?: PaneSharePrivateFields;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
47
59
|
export interface PaneDef {
|
|
48
60
|
id: string;
|
|
49
61
|
name: string;
|
|
@@ -53,7 +65,11 @@ export interface PaneDef {
|
|
|
53
65
|
defaultWidth?: string;
|
|
54
66
|
defaultFloatingSize?: { width: number; height: number };
|
|
55
67
|
defaultMode?: "docked" | "floating";
|
|
68
|
+
/** Pane publishes its selected symbol as pane-state `cursorSymbol`, so ticker panes can follow it. */
|
|
69
|
+
tickerSource?: boolean;
|
|
56
70
|
settings?: PaneSettingsDef | ((context: PaneSettingsContext) => PaneSettingsDef | null);
|
|
71
|
+
/** Portable sharing is public by default; list the few pane-owned fields that must remain local. */
|
|
72
|
+
portableShare?: PanePortableShareDef;
|
|
57
73
|
/** Compact controls surfaced next to the pane title. Toggle keys reference toggle fields in settings. */
|
|
58
74
|
quickSettings?: readonly PaneQuickSettingDef[];
|
|
59
75
|
}
|
|
@@ -177,6 +193,8 @@ export interface PaneTemplateCreateOptions {
|
|
|
177
193
|
symbols?: string[] | null;
|
|
178
194
|
ticker?: TickerRecord | null;
|
|
179
195
|
searchResult?: InstrumentSearchResult | null;
|
|
196
|
+
/** Template-owned, validated data restored from a public pane share. */
|
|
197
|
+
shareData?: unknown;
|
|
180
198
|
}
|
|
181
199
|
|
|
182
200
|
export interface PaneTemplateInstanceConfig {
|
|
@@ -190,6 +208,22 @@ export interface PaneTemplateInstanceConfig {
|
|
|
190
208
|
relativePosition?: "left" | "right" | "above" | "below";
|
|
191
209
|
}
|
|
192
210
|
|
|
211
|
+
export interface PaneTemplatePublicShareContext {
|
|
212
|
+
pane: PaneInstanceConfig;
|
|
213
|
+
paneState: Record<string, unknown>;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export interface PaneTemplatePublicShareSnapshot {
|
|
217
|
+
title: string;
|
|
218
|
+
description?: string;
|
|
219
|
+
data: Record<string, unknown>;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export interface PaneTemplatePublicShareDef {
|
|
223
|
+
serialize(context: PaneTemplatePublicShareContext): PaneTemplatePublicShareSnapshot | null;
|
|
224
|
+
restore(data: Record<string, unknown>): PaneTemplateCreateOptions | null;
|
|
225
|
+
}
|
|
226
|
+
|
|
193
227
|
export interface PaneTemplateDef {
|
|
194
228
|
id: string;
|
|
195
229
|
paneId: string;
|
|
@@ -203,6 +237,8 @@ export interface PaneTemplateDef {
|
|
|
203
237
|
context: PaneTemplateContext,
|
|
204
238
|
options?: PaneTemplateCreateOptions,
|
|
205
239
|
) => PaneTemplateInstanceConfig | null | Promise<PaneTemplateInstanceConfig | null>;
|
|
240
|
+
/** Legacy v1 restoration or an explicit transformed snapshot; normal pane shares use portableShare. */
|
|
241
|
+
publicShare?: PaneTemplatePublicShareDef;
|
|
206
242
|
}
|
|
207
243
|
|
|
208
244
|
export interface WizardStep {
|
package/src/ui/context-menu.tsx
CHANGED
|
@@ -189,7 +189,7 @@ function appContextMenuItems(registry: Pick<PluginRegistry, "openCommandBar"> |
|
|
|
189
189
|
{
|
|
190
190
|
id: "app:layout-actions",
|
|
191
191
|
label: "Layout Actions...",
|
|
192
|
-
onSelect: () => registry?.openCommandBar("
|
|
192
|
+
onSelect: () => registry?.openCommandBar("LMA "),
|
|
193
193
|
},
|
|
194
194
|
contextMenuDivider("app:config-divider"),
|
|
195
195
|
{
|
package/src/ui/host.tsx
CHANGED
|
@@ -336,6 +336,10 @@ export interface UiHost {
|
|
|
336
336
|
capabilities?: {
|
|
337
337
|
nativePaneChrome?: boolean;
|
|
338
338
|
titleBarOverlay?: boolean;
|
|
339
|
+
/** Native drag regions and traffic-light/window-control spacing. */
|
|
340
|
+
nativeWindowChrome?: boolean;
|
|
341
|
+
/** Enables public snapshot sharing controls for this host. */
|
|
342
|
+
publicSharing?: boolean;
|
|
339
343
|
precisePointer?: boolean;
|
|
340
344
|
fractionalViewport?: boolean;
|
|
341
345
|
cellWidthPx?: number;
|
package/src/ui/toast.tsx
CHANGED
|
@@ -31,10 +31,6 @@ export function isPlainEscape(event: BackNavigationEventLike): boolean {
|
|
|
31
31
|
&& !event.shift;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
export function isBackNavigationKey(event: BackNavigationEventLike): boolean {
|
|
35
|
-
return isPlainBackspace(event) || isPlainEscape(event);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
34
|
export function isDetailBackNavigationKey(
|
|
39
35
|
event: BackNavigationEventLike,
|
|
40
36
|
): boolean {
|
|
@@ -3,6 +3,7 @@ import { resolveExchangeTimeZone } from "./exchanges";
|
|
|
3
3
|
import { isTimestampStaleForExchangeSession } from "../market-data/market/freshness";
|
|
4
4
|
|
|
5
5
|
const MAX_CURRENT_INTRADAY_HISTORY_LAG_MS = 18 * 60 * 60 * 1000;
|
|
6
|
+
const MAX_SAME_SESSION_HISTORY_LAG_MS = 30 * 60 * 1000;
|
|
6
7
|
|
|
7
8
|
interface PriceHistoryFreshnessOptions {
|
|
8
9
|
exchange?: string;
|
|
@@ -78,19 +79,19 @@ export function isPriceHistoryStaleForCurrentWindow(
|
|
|
78
79
|
if (!latest) return false;
|
|
79
80
|
|
|
80
81
|
const latestTime = getPricePointTimestamp(latest);
|
|
81
|
-
if (!Number.isFinite(latestTime)
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
if (!Number.isFinite(latestTime)) return false;
|
|
83
|
+
const age = now - latestTime;
|
|
84
|
+
if (age <= MAX_SAME_SESSION_HISTORY_LAG_MS) return false;
|
|
85
|
+
const exchange = options.exchange || "NASDAQ";
|
|
85
86
|
if (
|
|
86
|
-
|
|
87
|
-
&&
|
|
88
|
-
&& !isTimestampStaleForExchangeSession(latestTime, options.exchange, now)
|
|
87
|
+
resolveExchangeTimeZone(exchange)
|
|
88
|
+
&& isTimestampStaleForExchangeSession(latestTime, exchange, now)
|
|
89
89
|
) {
|
|
90
|
-
return
|
|
90
|
+
return true;
|
|
91
91
|
}
|
|
92
|
-
|
|
93
|
-
return
|
|
92
|
+
const hasExchangeSession = Boolean(resolveExchangeTimeZone(exchange));
|
|
93
|
+
if (age <= MAX_CURRENT_INTRADAY_HISTORY_LAG_MS) return hasExchangeSession;
|
|
94
|
+
return !hasExchangeSession;
|
|
94
95
|
}
|
|
95
96
|
|
|
96
97
|
export function normalizeTickerFinancialsPriceHistory(financials: TickerFinancials): TickerFinancials {
|
package/src/utils/sec.test.ts
CHANGED
|
@@ -35,6 +35,12 @@ describe("isUsEquityTicker", () => {
|
|
|
35
35
|
}))).toBe(true);
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
+
test("accepts US-listed ADRs", () => {
|
|
39
|
+
expect(isUsEquityTicker(makeTicker({
|
|
40
|
+
assetCategory: "ADR",
|
|
41
|
+
}))).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
|
|
38
44
|
test("rejects non-equity instruments", () => {
|
|
39
45
|
expect(isUsEquityTicker(makeTicker({
|
|
40
46
|
assetCategory: "OPT",
|
package/src/utils/sec.ts
CHANGED
|
@@ -24,7 +24,7 @@ function isUsExchange(value?: string): boolean {
|
|
|
24
24
|
|
|
25
25
|
function isEquityType(value?: string): boolean {
|
|
26
26
|
const normalized = normalize(value);
|
|
27
|
-
return normalized.length === 0 || normalized === "STK" || normalized === "EQUITY";
|
|
27
|
+
return normalized.length === 0 || normalized === "STK" || normalized === "EQUITY" || normalized === "ADR";
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
export function isUsEquityTicker(ticker: TickerRecord | null | undefined): boolean {
|