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
package/src/{components/onboarding/account-step/model.ts → plugins/builtin/cloud/auth-model.ts}
RENAMED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* transitions that gate a network call
|
|
4
|
-
* username derivation) can be exercised
|
|
2
|
+
* Shared email/password auth logic for every sign-in surface (onboarding,
|
|
3
|
+
* auth dialog), kept out of React so the transitions that gate a network call
|
|
4
|
+
* (validation, retry classification, username derivation) can be exercised
|
|
5
|
+
* without rendering.
|
|
5
6
|
*/
|
|
7
|
+
import { apiClient, type AuthUser } from "../../../api-client";
|
|
6
8
|
import { t } from "../../../i18n";
|
|
9
|
+
import { chatController } from "../chat/controller";
|
|
7
10
|
|
|
8
11
|
export type AccountSub = "choose" | "signup" | "login" | "qr" | "signed-in";
|
|
9
12
|
|
|
@@ -100,6 +103,37 @@ export function classifyAccountError(error: unknown, mode: AccountMode): Account
|
|
|
100
103
|
};
|
|
101
104
|
}
|
|
102
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Runs the sign-up or sign-in sequence every surface shares: sign-up derives a
|
|
108
|
+
* username the user never typed (retrying once when the derivation collides),
|
|
109
|
+
* unverified accounts get a verification email, and the captured session is
|
|
110
|
+
* handed to the chat controller so it reaches plugin persistence.
|
|
111
|
+
*/
|
|
112
|
+
export async function performEmailAuth(mode: AccountMode, email: string, password: string): Promise<AuthUser> {
|
|
113
|
+
let user: AuthUser;
|
|
114
|
+
if (mode === "signup") {
|
|
115
|
+
try {
|
|
116
|
+
const username = deriveUsernameFromEmail(email);
|
|
117
|
+
user = await apiClient.signUp(email, username, username, password);
|
|
118
|
+
} catch (error) {
|
|
119
|
+
if (!isUsernameConflictError(error)) throw error;
|
|
120
|
+
const retryUsername = deriveUsernameFromEmail(email, 1);
|
|
121
|
+
user = await apiClient.signUp(email, retryUsername, retryUsername, password);
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
user = await apiClient.signIn(email, password);
|
|
125
|
+
}
|
|
126
|
+
if (!user.emailVerified) {
|
|
127
|
+
await apiClient.sendVerification().catch(() => {});
|
|
128
|
+
}
|
|
129
|
+
const sessionToken = apiClient.getSessionToken();
|
|
130
|
+
if (sessionToken) {
|
|
131
|
+
chatController.adoptSession(sessionToken, user);
|
|
132
|
+
}
|
|
133
|
+
await chatController.refreshSession().catch(() => {});
|
|
134
|
+
return user;
|
|
135
|
+
}
|
|
136
|
+
|
|
103
137
|
export interface AccountFormState {
|
|
104
138
|
mode: AccountMode;
|
|
105
139
|
email: string;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ChatContent } from "../chat/content";
|
|
2
|
+
import { createChatPane } from "../chat/pane";
|
|
3
|
+
import { ChatStatusWidget } from "../chat/status-widget";
|
|
4
|
+
import { createGloomberbCloudPlugin } from "./plugin";
|
|
5
|
+
|
|
6
|
+
export const browserGloomberbCloudPlugin = createGloomberbCloudPlugin({
|
|
7
|
+
ChatPane: createChatPane(ChatContent),
|
|
8
|
+
ChatStatusWidget,
|
|
9
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { BuildoutPane } from "../buildout/pane";
|
|
2
|
+
import type { PluginModule } from "../plugin-module";
|
|
3
|
+
|
|
4
|
+
export const buildoutModule: PluginModule = {
|
|
5
|
+
panes: [{
|
|
6
|
+
id: "buildout",
|
|
7
|
+
name: "TheBuildout",
|
|
8
|
+
icon: "T",
|
|
9
|
+
component: BuildoutPane,
|
|
10
|
+
defaultPosition: "right",
|
|
11
|
+
defaultMode: "floating",
|
|
12
|
+
defaultFloatingSize: { width: 110, height: 34 },
|
|
13
|
+
}],
|
|
14
|
+
paneTemplates: [{
|
|
15
|
+
id: "buildout-pane",
|
|
16
|
+
paneId: "buildout",
|
|
17
|
+
label: "TheBuildout",
|
|
18
|
+
description: "Open TheBuildout infrastructure intelligence.",
|
|
19
|
+
keywords: ["tbo", "buildout", "thebuildout", "infrastructure", "sites", "intel"],
|
|
20
|
+
shortcut: { prefix: "TBO" },
|
|
21
|
+
createInstance: () => ({ placement: "floating" }),
|
|
22
|
+
}],
|
|
23
|
+
};
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
* adopted through the same persistence and API-client path boot restoration
|
|
5
5
|
* uses, so the rest of the app sees a normal signed-in session.
|
|
6
6
|
*/
|
|
7
|
-
import { hostname } from "os";
|
|
8
7
|
import {
|
|
9
8
|
apiClient,
|
|
10
9
|
type AuthUser,
|
|
@@ -53,13 +52,8 @@ const START_RETRY_MAX_MS = 30_000;
|
|
|
53
52
|
const POLL_BACKOFF_FACTOR = 2;
|
|
54
53
|
|
|
55
54
|
function defaultClientName(): string {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if (name && name !== "localhost") return name;
|
|
59
|
-
} catch {
|
|
60
|
-
// Browser-bundled builds have no hostname; fall through to the generic name.
|
|
61
|
-
}
|
|
62
|
-
return "Gloomberb TUI";
|
|
55
|
+
const name = typeof process !== "undefined" ? process.env.HOSTNAME?.trim() : "";
|
|
56
|
+
return name && name !== "localhost" ? name : "Gloomberb";
|
|
63
57
|
}
|
|
64
58
|
|
|
65
59
|
function defaultClientPlatform(): string | undefined {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ChatContent } from "../chat/content";
|
|
2
|
+
import { buildoutModule } from "./buildout-module";
|
|
2
3
|
import { createChatPane } from "../chat/pane";
|
|
3
4
|
import { ChatStatusWidget } from "../chat/status-widget";
|
|
4
5
|
import { createGloomberbCloudPlugin } from "./plugin";
|
|
@@ -8,4 +9,5 @@ const ChatPane = createChatPane(ChatContent);
|
|
|
8
9
|
export const gloomberbCloudPlugin = createGloomberbCloudPlugin({
|
|
9
10
|
ChatPane,
|
|
10
11
|
ChatStatusWidget,
|
|
12
|
+
extraModules: [buildoutModule],
|
|
11
13
|
});
|
|
@@ -3,7 +3,6 @@ import type { GloomPlugin, PaneProps } from "../../../types/plugin";
|
|
|
3
3
|
import { apiClient } from "../../../api-client";
|
|
4
4
|
import { createGloomberbCloudCapabilities, createGloomberbCloudProvider } from "../../../sources/gloomberb-cloud";
|
|
5
5
|
import { AccountManagementPane } from "../account-management/pane";
|
|
6
|
-
import { BuildoutPane } from "../buildout/pane";
|
|
7
6
|
import { chatController } from "../chat/controller";
|
|
8
7
|
import {
|
|
9
8
|
buildDmCommandResults,
|
|
@@ -23,10 +22,12 @@ import { composeBuiltinPlugin, type PluginModule } from "../plugin-module";
|
|
|
23
22
|
import { registerCloudAuthCommands } from "./auth-commands";
|
|
24
23
|
import { registerCloudUpgradeCommand } from "./upgrade-command";
|
|
25
24
|
import { CloudUpgradeStatusWidget } from "./upgrade-status-widget";
|
|
25
|
+
import { createPublicPaneShare } from "../shared/public-pane";
|
|
26
26
|
|
|
27
27
|
interface GloomberbCloudPluginComponents {
|
|
28
28
|
ChatPane: (props: PaneProps) => ReactNode;
|
|
29
29
|
ChatStatusWidget: ComponentType;
|
|
30
|
+
extraModules?: readonly PluginModule[];
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
function createCloudDataModule(): PluginModule {
|
|
@@ -59,6 +60,9 @@ function createChatModule(
|
|
|
59
60
|
defaultPosition: "right",
|
|
60
61
|
defaultMode: "floating",
|
|
61
62
|
defaultFloatingSize: { width: 80, height: 30 },
|
|
63
|
+
portableShare: {
|
|
64
|
+
private: { title: true, params: true, settings: true, state: true },
|
|
65
|
+
},
|
|
62
66
|
}],
|
|
63
67
|
paneTemplates: [{
|
|
64
68
|
id: "new-chat-pane",
|
|
@@ -96,7 +100,9 @@ function createChatModule(
|
|
|
96
100
|
},
|
|
97
101
|
setup(ctx) {
|
|
98
102
|
chatController.attachPersistence(ctx.persistence, ctx.resume);
|
|
99
|
-
chatController.setNotifier(ctx.notify)
|
|
103
|
+
chatController.setNotifier(ctx.notify, (channelId, messageId) => {
|
|
104
|
+
ctx.createPaneFromTemplate("new-chat-pane", { arg: channelId, values: { messageId } });
|
|
105
|
+
});
|
|
100
106
|
ctx.registerCommand({
|
|
101
107
|
id: "direct-message",
|
|
102
108
|
label: "DM",
|
|
@@ -135,6 +141,9 @@ const accountModule: PluginModule = {
|
|
|
135
141
|
defaultPosition: "right",
|
|
136
142
|
defaultMode: "floating",
|
|
137
143
|
defaultFloatingSize: { width: 72, height: 36 },
|
|
144
|
+
portableShare: {
|
|
145
|
+
private: { title: true, params: true, settings: true, state: true },
|
|
146
|
+
},
|
|
138
147
|
}],
|
|
139
148
|
paneTemplates: [{
|
|
140
149
|
id: "account-management-pane",
|
|
@@ -154,27 +163,6 @@ const accountModule: PluginModule = {
|
|
|
154
163
|
},
|
|
155
164
|
};
|
|
156
165
|
|
|
157
|
-
const buildoutModule: PluginModule = {
|
|
158
|
-
panes: [{
|
|
159
|
-
id: "buildout",
|
|
160
|
-
name: "TheBuildout",
|
|
161
|
-
icon: "T",
|
|
162
|
-
component: BuildoutPane,
|
|
163
|
-
defaultPosition: "right",
|
|
164
|
-
defaultMode: "floating",
|
|
165
|
-
defaultFloatingSize: { width: 110, height: 34 },
|
|
166
|
-
}],
|
|
167
|
-
paneTemplates: [{
|
|
168
|
-
id: "buildout-pane",
|
|
169
|
-
paneId: "buildout",
|
|
170
|
-
label: "TheBuildout",
|
|
171
|
-
description: "Open TheBuildout infrastructure intelligence.",
|
|
172
|
-
keywords: ["tbo", "buildout", "thebuildout", "infrastructure", "sites", "intel"],
|
|
173
|
-
shortcut: { prefix: "TBO" },
|
|
174
|
-
createInstance: () => ({ placement: "floating" }),
|
|
175
|
-
}],
|
|
176
|
-
};
|
|
177
|
-
|
|
178
166
|
const congressTradesModule: PluginModule = {
|
|
179
167
|
panes: [{
|
|
180
168
|
id: CONGRESS_TRADES_PANE_ID,
|
|
@@ -193,6 +181,7 @@ const congressTradesModule: PluginModule = {
|
|
|
193
181
|
keywords: ["congress", "house", "trades", "ptr", "stock", "disclosures"],
|
|
194
182
|
shortcut: { prefix: "CG" },
|
|
195
183
|
createInstance: () => ({ placement: "floating" }),
|
|
184
|
+
publicShare: createPublicPaneShare("Congress Trades"),
|
|
196
185
|
}],
|
|
197
186
|
};
|
|
198
187
|
|
|
@@ -203,6 +192,7 @@ const twitterModule: PluginModule = {
|
|
|
203
192
|
export function createGloomberbCloudPlugin({
|
|
204
193
|
ChatPane,
|
|
205
194
|
ChatStatusWidget,
|
|
195
|
+
extraModules = [],
|
|
206
196
|
}: GloomberbCloudPluginComponents): GloomPlugin {
|
|
207
197
|
return composeBuiltinPlugin({
|
|
208
198
|
id: "gloomberb-cloud",
|
|
@@ -215,7 +205,7 @@ export function createGloomberbCloudPlugin({
|
|
|
215
205
|
createCloudDataModule(),
|
|
216
206
|
createChatModule(ChatPane, ChatStatusWidget),
|
|
217
207
|
accountModule,
|
|
218
|
-
|
|
208
|
+
...extraModules,
|
|
219
209
|
congressTradesModule,
|
|
220
210
|
twitterModule,
|
|
221
211
|
],
|
|
@@ -3,6 +3,7 @@ import type { GloomPluginContext } from "../../../types/plugin";
|
|
|
3
3
|
import { requestAccountManagementTab } from "../account-management/navigation";
|
|
4
4
|
import { openCloudUpgradeUrl } from "../shared/cloud-upgrade";
|
|
5
5
|
import { resolvePlanAccess } from "../shared/plan-access";
|
|
6
|
+
import { requestAuthDialog } from "./auth-dialog";
|
|
6
7
|
|
|
7
8
|
export function registerCloudUpgradeCommand(ctx: GloomPluginContext): void {
|
|
8
9
|
ctx.registerCommand({
|
|
@@ -15,14 +16,20 @@ export function registerCloudUpgradeCommand(ctx: GloomPluginContext): void {
|
|
|
15
16
|
// Paying subscribers have nothing to upgrade to; trial accounts still do.
|
|
16
17
|
hidden: () => resolvePlanAccess(apiClient.getCurrentUser()).isPayingPro,
|
|
17
18
|
execute: () => {
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
const openUpgrade = () => {
|
|
20
|
+
if (openCloudUpgradeUrl()) return;
|
|
21
|
+
// No renderer-backed opener yet: fall back to the in-app billing surface.
|
|
22
|
+
requestAccountManagementTab("pro");
|
|
23
|
+
ctx.showPane("account-management");
|
|
24
|
+
};
|
|
25
|
+
if (!apiClient.isSignedIn()) {
|
|
26
|
+
// Upgrading needs an account first; continue to checkout once signed in.
|
|
27
|
+
if (!requestAuthDialog({ mode: "signup", onSignedIn: openUpgrade })) {
|
|
28
|
+
ctx.openCommandBar("Sign Up");
|
|
29
|
+
}
|
|
20
30
|
return;
|
|
21
31
|
}
|
|
22
|
-
|
|
23
|
-
// No renderer-backed opener yet: fall back to the in-app billing surface.
|
|
24
|
-
requestAccountManagementTab("pro");
|
|
25
|
-
ctx.showPane("account-management");
|
|
32
|
+
openUpgrade();
|
|
26
33
|
},
|
|
27
34
|
});
|
|
28
35
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import type {
|
|
3
|
+
GloomPluginContext,
|
|
4
|
+
PaneTemplateContext,
|
|
5
|
+
PaneTemplateDef,
|
|
6
|
+
} from "../../../types/plugin";
|
|
7
|
+
import { createFeed } from "./model";
|
|
8
|
+
import { registerTwitterFeedFeature } from "./registration";
|
|
9
|
+
|
|
10
|
+
const context: PaneTemplateContext = {
|
|
11
|
+
config: {} as PaneTemplateContext["config"],
|
|
12
|
+
layout: { dockRoot: null, instances: [], floating: [], detached: [] },
|
|
13
|
+
focusedPaneId: null,
|
|
14
|
+
activeTicker: null,
|
|
15
|
+
activeCollectionId: null,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function twitterTemplate(): PaneTemplateDef {
|
|
19
|
+
let template: PaneTemplateDef | null = null;
|
|
20
|
+
registerTwitterFeedFeature({
|
|
21
|
+
registerTickerResearchTab() {},
|
|
22
|
+
registerPane() {},
|
|
23
|
+
registerCommand() {},
|
|
24
|
+
registerPaneTemplate(value) { template = value; },
|
|
25
|
+
} as unknown as GloomPluginContext);
|
|
26
|
+
if (!template) throw new Error("X Feed template was not registered");
|
|
27
|
+
return template;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
describe("X Feed pane sharing", () => {
|
|
31
|
+
test("restores the active advanced-search query", async () => {
|
|
32
|
+
const template = twitterTemplate();
|
|
33
|
+
const first = createFeed("$SPY", "Latest");
|
|
34
|
+
const active = createFeed("$NVDA OR $AMD", "Top");
|
|
35
|
+
const shared = template.publicShare?.serialize({
|
|
36
|
+
pane: {
|
|
37
|
+
instanceId: "twitter-feed:private-query-id",
|
|
38
|
+
paneId: "twitter-feed",
|
|
39
|
+
params: { query: "old", queryType: "Latest" },
|
|
40
|
+
},
|
|
41
|
+
paneState: {
|
|
42
|
+
pluginState: {
|
|
43
|
+
"gloomberb-cloud": {
|
|
44
|
+
feeds: { feeds: [first, active] },
|
|
45
|
+
activeFeedId: active.id,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
expect(shared?.data).toEqual({ query: "$NVDA OR $AMD", queryType: "Top" });
|
|
52
|
+
const options = template.publicShare?.restore(shared!.data);
|
|
53
|
+
const instance = await template.createInstance?.(context, options ?? undefined);
|
|
54
|
+
expect(instance?.params).toEqual({ query: "$NVDA OR $AMD", queryType: "Top" });
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -60,11 +60,16 @@ export function TwitterTickerTab({ focused, width, height }: TickerResearchTabPr
|
|
|
60
60
|
export function TwitterFeedPane({ focused, width, height }: PaneProps) {
|
|
61
61
|
const paneId = usePaneInstanceId();
|
|
62
62
|
const paneInstance = usePaneInstance();
|
|
63
|
-
const [
|
|
63
|
+
const [legacyPersistedState, setLegacyPersistedState] = usePluginState<PersistedTwitterFeedState>(
|
|
64
64
|
`twitter-feed:${paneId}`,
|
|
65
65
|
EMPTY_FEED_STATE,
|
|
66
66
|
{ schemaVersion: TWEET_SEARCH_SCHEMA_VERSION },
|
|
67
67
|
);
|
|
68
|
+
const [panePersistedState, setPanePersistedState] = usePluginPaneState<PersistedTwitterFeedState | null>(
|
|
69
|
+
"feeds",
|
|
70
|
+
null,
|
|
71
|
+
);
|
|
72
|
+
const persistedState = panePersistedState ?? legacyPersistedState;
|
|
68
73
|
const [launchRequest, setLaunchRequest] = usePluginState<TwitterFeedLaunchRequest | null>(
|
|
69
74
|
TWITTER_FEED_LAUNCH_STATE_KEY,
|
|
70
75
|
null,
|
|
@@ -77,6 +82,14 @@ export function TwitterFeedPane({ focused, width, height }: PaneProps) {
|
|
|
77
82
|
const initializedRef = useRef(false);
|
|
78
83
|
const feeds = useMemo(() => normalizeFeeds(persistedState), [persistedState]);
|
|
79
84
|
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
if (panePersistedState !== null) return;
|
|
87
|
+
const legacyFeeds = normalizeFeeds(legacyPersistedState);
|
|
88
|
+
if (legacyFeeds.length === 0) return;
|
|
89
|
+
setPanePersistedState({ feeds: legacyFeeds });
|
|
90
|
+
setLegacyPersistedState(EMPTY_FEED_STATE);
|
|
91
|
+
}, [legacyPersistedState, panePersistedState, setLegacyPersistedState, setPanePersistedState]);
|
|
92
|
+
|
|
80
93
|
const focusSearch = useCallback(() => {
|
|
81
94
|
setSearchFocused(true);
|
|
82
95
|
setSearchFocusToken((current) => current + 1);
|
|
@@ -87,8 +100,10 @@ export function TwitterFeedPane({ focused, width, height }: PaneProps) {
|
|
|
87
100
|
}, []);
|
|
88
101
|
|
|
89
102
|
const updateFeeds = useCallback((updater: (feeds: TwitterFeed[]) => TwitterFeed[]) => {
|
|
90
|
-
|
|
91
|
-
|
|
103
|
+
setPanePersistedState((current) => ({
|
|
104
|
+
feeds: updater(normalizeFeeds(current ?? persistedState)),
|
|
105
|
+
}));
|
|
106
|
+
}, [persistedState, setPanePersistedState]);
|
|
92
107
|
|
|
93
108
|
const addFeed = useCallback((query = "", queryType: CloudTweetQueryType = "Latest") => {
|
|
94
109
|
const feed = createFeed(query, queryType);
|
|
@@ -146,10 +161,10 @@ export function TwitterFeedPane({ focused, width, height }: PaneProps) {
|
|
|
146
161
|
const seedQuery = typeof paneInstance?.params?.query === "string" ? paneInstance.params.query : "";
|
|
147
162
|
const seedType = paneInstance?.params?.queryType === "Top" ? "Top" : "Latest";
|
|
148
163
|
const feed = createFeed(seedQuery, seedType);
|
|
149
|
-
|
|
164
|
+
setPanePersistedState({ feeds: [feed] });
|
|
150
165
|
setActiveFeedId(feed.id);
|
|
151
166
|
if (!seedQuery.trim()) focusSearch();
|
|
152
|
-
}, [feeds.length, focusSearch, paneInstance?.params?.query, paneInstance?.params?.queryType, setActiveFeedId,
|
|
167
|
+
}, [feeds.length, focusSearch, paneInstance?.params?.query, paneInstance?.params?.queryType, setActiveFeedId, setPanePersistedState]);
|
|
153
168
|
|
|
154
169
|
useEffect(() => {
|
|
155
170
|
if (!launchRequest) return;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { GloomPluginContext } from "../../../types/plugin";
|
|
2
|
+
import { canonicalExchange } from "../../../utils/exchanges";
|
|
2
3
|
import {
|
|
3
4
|
TWITTER_FEED_LAUNCH_SCHEMA_VERSION,
|
|
4
5
|
TWITTER_FEED_LAUNCH_STATE_KEY,
|
|
5
6
|
TWITTER_FEED_PANE_ID,
|
|
7
|
+
normalizeFeeds,
|
|
6
8
|
type TwitterFeedLaunchRequest,
|
|
7
9
|
} from "./model";
|
|
8
10
|
import {
|
|
@@ -10,13 +12,21 @@ import {
|
|
|
10
12
|
TwitterTickerTab,
|
|
11
13
|
} from "./pane";
|
|
12
14
|
|
|
15
|
+
function record(value: unknown): value is Record<string, unknown> {
|
|
16
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
17
|
+
}
|
|
18
|
+
|
|
13
19
|
export function registerTwitterFeedFeature(ctx: GloomPluginContext): void {
|
|
14
20
|
ctx.registerTickerResearchTab({
|
|
15
21
|
id: "ticker-tweets",
|
|
16
22
|
name: "Tweets",
|
|
17
23
|
order: 38,
|
|
18
24
|
component: TwitterTickerTab,
|
|
19
|
-
isVisible: ({ ticker }) =>
|
|
25
|
+
isVisible: ({ ticker }) => {
|
|
26
|
+
if (!ticker) return false;
|
|
27
|
+
const exchange = canonicalExchange(ticker.metadata.exchange);
|
|
28
|
+
return !exchange || ["NASDAQ", "NYSE", "AMEX", "ARCA", "BATS", "OTC", "PINK"].includes(exchange);
|
|
29
|
+
},
|
|
20
30
|
});
|
|
21
31
|
|
|
22
32
|
ctx.registerPane({
|
|
@@ -36,16 +46,50 @@ export function registerTwitterFeedFeature(ctx: GloomPluginContext): void {
|
|
|
36
46
|
description: "Open an X advanced-search feed.",
|
|
37
47
|
keywords: ["twitter", "x", "tweet", "tweets", "feed", "social"],
|
|
38
48
|
createInstance: (_context, options) => {
|
|
39
|
-
const
|
|
49
|
+
const shared = record(options?.shareData) ? options.shareData : null;
|
|
50
|
+
const query = typeof shared?.query === "string"
|
|
51
|
+
? shared.query.trim()
|
|
52
|
+
: options?.values?.query?.trim() || options?.arg?.trim() || "";
|
|
53
|
+
const queryType = shared?.queryType === "Top" || options?.values?.queryType === "Top"
|
|
54
|
+
? "Top"
|
|
55
|
+
: "Latest";
|
|
40
56
|
return {
|
|
41
57
|
title: "X Feed",
|
|
42
58
|
placement: "floating",
|
|
43
|
-
params: {
|
|
44
|
-
query,
|
|
45
|
-
queryType: options?.values?.queryType === "Top" ? "Top" : "Latest",
|
|
46
|
-
},
|
|
59
|
+
params: { query, queryType },
|
|
47
60
|
};
|
|
48
61
|
},
|
|
62
|
+
publicShare: {
|
|
63
|
+
serialize: ({ pane, paneState }) => {
|
|
64
|
+
const pluginState = record(paneState.pluginState)
|
|
65
|
+
? paneState.pluginState["gloomberb-cloud"]
|
|
66
|
+
: null;
|
|
67
|
+
const feedsState = record(pluginState) ? pluginState.feeds : null;
|
|
68
|
+
const feeds = normalizeFeeds(feedsState);
|
|
69
|
+
const activeFeedId = record(pluginState) && typeof pluginState.activeFeedId === "string"
|
|
70
|
+
? pluginState.activeFeedId
|
|
71
|
+
: null;
|
|
72
|
+
const active = feeds.find((feed) => feed.id === activeFeedId) ?? feeds[0];
|
|
73
|
+
const query = active?.query.trim()
|
|
74
|
+
|| (typeof pane.params?.query === "string" ? pane.params.query.trim() : "");
|
|
75
|
+
if (!query) return null;
|
|
76
|
+
return {
|
|
77
|
+
title: pane.title?.trim() || "X Feed",
|
|
78
|
+
data: {
|
|
79
|
+
query,
|
|
80
|
+
queryType: active?.queryType === "Top" || pane.params?.queryType === "Top" ? "Top" : "Latest",
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
},
|
|
84
|
+
restore: (data) => (
|
|
85
|
+
Object.keys(data).every((key) => key === "query" || key === "queryType")
|
|
86
|
+
&& typeof data.query === "string"
|
|
87
|
+
&& data.query.trim().length > 0
|
|
88
|
+
&& (data.queryType === "Latest" || data.queryType === "Top")
|
|
89
|
+
? { shareData: { query: data.query.trim(), queryType: data.queryType } }
|
|
90
|
+
: null
|
|
91
|
+
),
|
|
92
|
+
},
|
|
49
93
|
});
|
|
50
94
|
|
|
51
95
|
ctx.registerCommand({
|
|
@@ -7,6 +7,7 @@ import { brokerManagerModule } from "./broker-manager";
|
|
|
7
7
|
import { changelogModule } from "./changelog";
|
|
8
8
|
import { connectionsModule } from "./connections";
|
|
9
9
|
import { correlationModule } from "./correlation";
|
|
10
|
+
import { cdsModule } from "./cds";
|
|
10
11
|
import { creditConditionsModule } from "./credit-conditions";
|
|
11
12
|
import { economicCalendarModule } from "./econ";
|
|
12
13
|
import { earningsModule } from "./earnings";
|
|
@@ -89,7 +90,7 @@ export const macroPlugin = composeBuiltinPlugin({
|
|
|
89
90
|
id: "macro",
|
|
90
91
|
name: "Macro",
|
|
91
92
|
version: "1.0.0",
|
|
92
|
-
description: "Economic calendar, rates, volatility, credit spreads, Treasury auctions, earnings, IPOs, and live financial TV.",
|
|
93
|
+
description: "Economic calendar, rates, volatility, credit spreads, single-name CDS, Treasury auctions, earnings, IPOs, and live financial TV.",
|
|
93
94
|
toggleable: true,
|
|
94
95
|
modules: [
|
|
95
96
|
macroSharedResourcesModule,
|
|
@@ -97,6 +98,7 @@ export const macroPlugin = composeBuiltinPlugin({
|
|
|
97
98
|
yieldCurveModule,
|
|
98
99
|
volatilityModule,
|
|
99
100
|
creditConditionsModule,
|
|
101
|
+
cdsModule,
|
|
100
102
|
treasuryAuctionsModule,
|
|
101
103
|
earningsModule,
|
|
102
104
|
ipoCalendarModule,
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
-
import { Box, ScrollBox, Text, TextAttributes, useRendererHost } from "../../../ui";
|
|
2
|
+
import { Box, ScrollBox, Text, TextAttributes, useRendererHost, type ScrollBoxRenderable } from "../../../ui";
|
|
3
3
|
import {
|
|
4
4
|
DataTableView,
|
|
5
5
|
usePaneFooter,
|
|
6
|
+
useTableLoadMore,
|
|
6
7
|
type DataTableKeyEvent,
|
|
7
8
|
} from "../../../components";
|
|
8
9
|
import { useInlineTickerOpener } from "../../../state/hooks/inline-tickers";
|
|
@@ -19,6 +20,9 @@ import {
|
|
|
19
20
|
CONGRESS_MEMBER_FILING_LIMIT,
|
|
20
21
|
CONGRESS_MEMBER_TRADE_LIMIT,
|
|
21
22
|
CONGRESS_TRADES_PANE_ID,
|
|
23
|
+
canLoadMoreCongress,
|
|
24
|
+
mergeCongressPages,
|
|
25
|
+
nextCongressPage,
|
|
22
26
|
buildMemberTradeColumns,
|
|
23
27
|
formatAmountRange,
|
|
24
28
|
formatLag,
|
|
@@ -121,6 +125,8 @@ export function MemberTradesDetail({
|
|
|
121
125
|
direction: "desc",
|
|
122
126
|
});
|
|
123
127
|
const fetchGenRef = useRef(0);
|
|
128
|
+
const [loadingMore, setLoadingMore] = useState(false);
|
|
129
|
+
const tradeScrollRef = useRef<ScrollBoxRenderable | null>(null);
|
|
124
130
|
|
|
125
131
|
const load = useCallback((refresh = false) => {
|
|
126
132
|
fetchGenRef.current += 1;
|
|
@@ -150,6 +156,40 @@ export function MemberTradesDetail({
|
|
|
150
156
|
});
|
|
151
157
|
}, [filingLimit, member.memberName, member.stateDistrict]);
|
|
152
158
|
|
|
159
|
+
const loadMore = useCallback(() => {
|
|
160
|
+
if (!detailPayload || loadingMore || status !== "loaded") return;
|
|
161
|
+
const nextRequest = nextCongressPage(detailPayload);
|
|
162
|
+
if (!nextRequest) return;
|
|
163
|
+
const gen = fetchGenRef.current;
|
|
164
|
+
setLoadingMore(true);
|
|
165
|
+
apiClient.getCloudCongressHouse({
|
|
166
|
+
...nextRequest,
|
|
167
|
+
member: member.memberName,
|
|
168
|
+
limit: CONGRESS_MEMBER_TRADE_LIMIT,
|
|
169
|
+
filingLimit: Math.max(CONGRESS_MEMBER_FILING_LIMIT, filingLimit),
|
|
170
|
+
})
|
|
171
|
+
.then((payload) => {
|
|
172
|
+
if (fetchGenRef.current !== gen) return;
|
|
173
|
+
const merged = detailPayload ? mergeCongressPages(detailPayload, payload) : payload;
|
|
174
|
+
const exactMemberTrades = merged.trades.filter((trade) => (
|
|
175
|
+
trade.memberName === member.memberName
|
|
176
|
+
&& trade.stateDistrict === member.stateDistrict
|
|
177
|
+
));
|
|
178
|
+
setDetailPayload(merged);
|
|
179
|
+
setTrades(exactMemberTrades.length > 0 ? exactMemberTrades : merged.trades);
|
|
180
|
+
})
|
|
181
|
+
.finally(() => {
|
|
182
|
+
if (fetchGenRef.current !== gen) return;
|
|
183
|
+
setLoadingMore(false);
|
|
184
|
+
});
|
|
185
|
+
}, [detailPayload, filingLimit, loadingMore, member.memberName, member.stateDistrict, status]);
|
|
186
|
+
|
|
187
|
+
const onTradeScroll = useTableLoadMore(
|
|
188
|
+
tradeScrollRef,
|
|
189
|
+
!!detailPayload && status === "loaded" && !loadingMore && canLoadMoreCongress(detailPayload),
|
|
190
|
+
loadMore,
|
|
191
|
+
);
|
|
192
|
+
|
|
153
193
|
useEffect(() => {
|
|
154
194
|
fetchGenRef.current += 1;
|
|
155
195
|
setTrades(initialTrades);
|
|
@@ -282,6 +322,8 @@ export function MemberTradesDetail({
|
|
|
282
322
|
renderCell={renderCongressTradeCell}
|
|
283
323
|
emptyStateTitle={emptyTitle}
|
|
284
324
|
showHorizontalScrollbar={false}
|
|
325
|
+
scrollRef={tradeScrollRef}
|
|
326
|
+
onBodyScrollActivity={onTradeScroll}
|
|
285
327
|
/>
|
|
286
328
|
</Box>
|
|
287
329
|
);
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import type { CloudCongressHousePayload } from "../../../api-client";
|
|
3
|
+
import { canLoadMoreCongress, congressPageAfterEmpty, mergeCongressPages, nextCongressPage } from "./model";
|
|
4
|
+
|
|
5
|
+
function payload(overrides: Partial<CloudCongressHousePayload> = {}): CloudCongressHousePayload {
|
|
6
|
+
return {
|
|
7
|
+
asOf: "2026-01-01T00:00:00.000Z",
|
|
8
|
+
chamber: "house",
|
|
9
|
+
source: "house-clerk",
|
|
10
|
+
year: 2026,
|
|
11
|
+
indexUpdatedAt: null,
|
|
12
|
+
filingsScanned: 20,
|
|
13
|
+
filingCount: 80,
|
|
14
|
+
filingOffset: 0,
|
|
15
|
+
hasMore: false,
|
|
16
|
+
hasMoreFilings: true,
|
|
17
|
+
nextOffset: 20,
|
|
18
|
+
nextFilingOffset: 20,
|
|
19
|
+
trades: [],
|
|
20
|
+
members: [],
|
|
21
|
+
...overrides,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe("congress paging", () => {
|
|
26
|
+
test("walks remaining trades, then more filings, then the previous year", () => {
|
|
27
|
+
expect(nextCongressPage(payload({ hasMore: true, nextOffset: 40 }))).toEqual({
|
|
28
|
+
year: 2026,
|
|
29
|
+
offset: 40,
|
|
30
|
+
filingOffset: 0,
|
|
31
|
+
});
|
|
32
|
+
expect(nextCongressPage(payload({ hasMore: false, hasMoreFilings: true, nextFilingOffset: 20 }))).toEqual({
|
|
33
|
+
year: 2026,
|
|
34
|
+
offset: 0,
|
|
35
|
+
filingOffset: 20,
|
|
36
|
+
});
|
|
37
|
+
expect(nextCongressPage(payload({ hasMore: false, hasMoreFilings: false, year: 2026 }))).toEqual({
|
|
38
|
+
year: 2025,
|
|
39
|
+
offset: 0,
|
|
40
|
+
filingOffset: 0,
|
|
41
|
+
});
|
|
42
|
+
expect(nextCongressPage(payload({
|
|
43
|
+
hasMore: false,
|
|
44
|
+
hasMoreFilings: undefined,
|
|
45
|
+
filingOffset: 0,
|
|
46
|
+
filingsScanned: 20,
|
|
47
|
+
filingCount: 80,
|
|
48
|
+
}))).toEqual({
|
|
49
|
+
year: 2026,
|
|
50
|
+
offset: 0,
|
|
51
|
+
filingOffset: 20,
|
|
52
|
+
});
|
|
53
|
+
expect(canLoadMoreCongress(payload({ hasMore: false, hasMoreFilings: false, year: 2008 }))).toBe(false);
|
|
54
|
+
expect(nextCongressPage(congressPageAfterEmpty(payload({
|
|
55
|
+
hasMore: true,
|
|
56
|
+
year: 2026,
|
|
57
|
+
filingsScanned: 20,
|
|
58
|
+
filingCount: 80,
|
|
59
|
+
})))).toEqual({
|
|
60
|
+
year: 2025,
|
|
61
|
+
offset: 0,
|
|
62
|
+
filingOffset: 0,
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("appends unique trades and members from the next page", () => {
|
|
67
|
+
const merged = mergeCongressPages(
|
|
68
|
+
payload({
|
|
69
|
+
trades: [{ id: "t1" } as CloudCongressHousePayload["trades"][number]],
|
|
70
|
+
members: [{ id: "m1" } as CloudCongressHousePayload["members"][number]],
|
|
71
|
+
}),
|
|
72
|
+
payload({
|
|
73
|
+
year: 2025,
|
|
74
|
+
trades: [
|
|
75
|
+
{ id: "t1" } as CloudCongressHousePayload["trades"][number],
|
|
76
|
+
{ id: "t2" } as CloudCongressHousePayload["trades"][number],
|
|
77
|
+
],
|
|
78
|
+
members: [{ id: "m2" } as CloudCongressHousePayload["members"][number]],
|
|
79
|
+
}),
|
|
80
|
+
);
|
|
81
|
+
expect(merged.year).toBe(2025);
|
|
82
|
+
expect(merged.trades.map((trade) => trade.id)).toEqual(["t1", "t2"]);
|
|
83
|
+
expect(merged.members.map((member) => member.id)).toEqual(["m1", "m2"]);
|
|
84
|
+
});
|
|
85
|
+
});
|