gloomberb 0.5.1 → 0.6.1
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 +91 -11
- package/package.json +13 -5
- package/src/app.tsx +414 -191
- package/src/architecture/import-boundaries.test.ts +118 -0
- package/src/assets/gloomberb-logo.png +0 -0
- package/src/assets/gloomberb-logo.svg +73 -0
- package/src/brokers/account-cache.ts +157 -0
- package/src/brokers/profile-form.test.ts +73 -0
- package/src/brokers/profile-form.ts +109 -0
- package/src/brokers/remote-broker-adapter.ts +91 -0
- package/src/brokers/sync-broker-instance.test.ts +124 -1
- package/src/brokers/sync-broker-instance.ts +113 -17
- package/src/capabilities/factories.ts +100 -0
- package/src/capabilities/index.ts +4 -0
- package/src/capabilities/registry.test.ts +137 -0
- package/src/capabilities/registry.ts +131 -0
- package/src/capabilities/service-capabilities.ts +27 -0
- package/src/capabilities/types.ts +84 -0
- package/src/cli/commands/ticker.ts +11 -3
- package/src/cli/context.ts +19 -13
- package/src/cli/helpers.ts +1 -0
- package/src/cli/types.ts +2 -2
- package/src/components/chart/chart-controller.ts +4 -24
- package/src/components/chart/chart-data.ts +3 -14
- package/src/components/chart/chart-indicator-selector.tsx +28 -6
- package/src/components/chart/chart-pane-settings.ts +15 -28
- package/src/components/chart/chart-renderer.ts +74 -99
- package/src/components/chart/chart-resolution.ts +10 -10
- package/src/components/chart/chart-scroll.test.ts +6 -5
- package/src/components/chart/chart-scroll.ts +2 -2
- package/src/components/chart/chart-types.ts +0 -1
- package/src/components/chart/chart-viewport.ts +2 -1
- package/src/components/chart/chart.test.ts +37 -0
- package/src/components/chart/comparison-chart-data.ts +3 -17
- package/src/components/chart/comparison-chart-renderer.test.ts +27 -4
- package/src/components/chart/comparison-chart-renderer.ts +1 -1
- package/src/components/chart/comparison-stock-chart.tsx +318 -158
- package/src/components/chart/cursor-motion.ts +3 -2
- package/src/components/chart/indicators/bands.test.ts +0 -14
- package/src/components/chart/indicators/moving-averages.test.ts +0 -15
- package/src/components/chart/indicators/options.ts +3 -3
- package/src/components/chart/indicators/oscillators.test.ts +0 -21
- package/src/components/chart/indicators/types.ts +1 -2
- package/src/components/chart/native/chart-rasterizer.ts +37 -44
- package/src/components/chart/native/kitty-adapter.ts +2 -5
- package/src/components/chart/native/kitty-manager.ts +1 -1
- package/src/components/chart/native/kitty-protocol.ts +0 -1
- package/src/components/chart/native/kitty-support.ts +1 -1
- package/src/components/chart/native/kitty.test.ts +53 -13
- package/src/components/chart/native/renderer-selection.ts +1 -1
- package/src/components/chart/native/surface-manager.ts +1 -1
- package/src/components/chart/native/surface-visibility.ts +48 -0
- package/src/components/chart/static-chart-surface.test.tsx +51 -0
- package/src/components/chart/static-chart-surface.tsx +191 -0
- package/src/components/chart/stock-chart-renderer-switch.test.tsx +242 -35
- package/src/components/chart/stock-chart.tsx +452 -191
- package/src/components/command-bar/__snapshots__/command-bar.test.tsx.snap +4 -4
- package/src/components/command-bar/command-bar.test.tsx +269 -20
- package/src/components/command-bar/command-bar.tsx +830 -353
- package/src/components/command-bar/command-registry.ts +10 -20
- package/src/components/command-bar/helpers.test.ts +1 -1
- package/src/components/command-bar/helpers.ts +9 -11
- package/src/components/command-bar/pane-template-display.ts +4 -8
- package/src/components/command-bar/root-shortcuts.ts +12 -11
- package/src/components/command-bar/view-model.test.ts +8 -42
- package/src/components/command-bar/view-model.ts +8 -36
- package/src/components/command-bar/workflow-ops.test.ts +76 -2
- package/src/components/command-bar/workflow-ops.ts +40 -9
- package/src/components/command-bar/workflow-types.ts +2 -1
- package/src/components/data-table-stack-view.test.tsx +206 -0
- package/src/components/data-table-stack-view.tsx +152 -0
- package/src/components/data-table-view.test.tsx +188 -0
- package/src/components/data-table-view.tsx +279 -0
- package/src/components/feed-data-table-stack-view.test.tsx +225 -0
- package/src/components/feed-data-table-stack-view.tsx +365 -0
- package/src/components/index.ts +26 -31
- package/src/components/layout/detached-pane-shell.tsx +185 -0
- package/src/components/layout/floating-pane.tsx +87 -14
- package/src/components/layout/header.tsx +139 -49
- package/src/components/layout/pane-content.tsx +42 -0
- package/src/components/layout/pane-footer.test.tsx +195 -0
- package/src/components/layout/pane-footer.tsx +425 -0
- package/src/components/layout/pane-header.test.tsx +39 -0
- package/src/components/layout/pane-header.tsx +192 -14
- package/src/components/layout/pane-keyboard-scroll.test.tsx +226 -0
- package/src/components/layout/pane-sizing.ts +9 -10
- package/src/components/layout/pane-title.ts +35 -0
- package/src/components/layout/pane.tsx +52 -11
- package/src/components/layout/shell.test.tsx +315 -107
- package/src/components/layout/shell.tsx +1010 -368
- package/src/components/layout/status-bar.test.tsx +17 -10
- package/src/components/layout/status-bar.tsx +367 -100
- package/src/components/layout/titlebar-overlay.ts +2 -0
- package/src/components/markdown-editor.tsx +29 -14
- package/src/components/markdown-text.tsx +25 -24
- package/src/components/onboarding/onboarding-wizard.test.tsx +8 -3
- package/src/components/onboarding/onboarding-wizard.tsx +347 -473
- package/src/components/pane-settings-dialog.tsx +521 -89
- package/src/components/pane-template-wizard.test.tsx +1 -1
- package/src/components/pane-template-wizard.tsx +38 -37
- package/src/components/price-selector-dialog.tsx +22 -21
- package/src/components/ticker-badge-list.tsx +53 -0
- package/src/components/ticker-badge-text.test.tsx +44 -1
- package/src/components/ticker-badge-text.tsx +78 -38
- package/src/components/ticker-badge.tsx +28 -7
- package/src/components/ticker-list-table-view.tsx +248 -0
- package/src/components/ticker-list-table.test.tsx +47 -1
- package/src/components/ticker-list-table.tsx +212 -41
- package/src/components/toggle-list.tsx +114 -8
- package/src/components/ui/button.tsx +24 -16
- package/src/components/ui/choice-dialog.tsx +89 -0
- package/src/components/ui/data-table.tsx +313 -114
- package/src/components/ui/external-link.tsx +52 -13
- package/src/components/ui/fields.tsx +44 -30
- package/src/components/ui/frame.tsx +24 -57
- package/src/components/ui/index.ts +15 -50
- package/src/components/ui/list-view.tsx +65 -25
- package/src/components/ui/loading.tsx +5 -60
- package/src/components/ui/message-composer.test.tsx +81 -0
- package/src/components/ui/message-composer.tsx +139 -0
- package/src/components/ui/multi-select-dialog.tsx +100 -32
- package/src/components/ui/multi-select.ts +112 -0
- package/src/components/ui/native-select.tsx +120 -0
- package/src/components/ui/page-stack-view.tsx +49 -19
- package/src/components/ui/remote-image.tsx +50 -0
- package/src/components/ui/row-value-cache.test.ts +17 -0
- package/src/components/ui/row-value-cache.ts +41 -0
- package/src/components/ui/shortcut-hint.tsx +55 -0
- package/src/components/ui/status.tsx +12 -67
- package/src/components/ui/table-layout.ts +59 -0
- package/src/components/ui/tabs.tsx +249 -27
- package/src/components/ui/toggle.tsx +19 -127
- package/src/components/ui/ui.test.tsx +266 -53
- package/src/components/use-double-click-activation.ts +7 -2
- package/src/components/use-external-link-footer.ts +79 -0
- package/src/core/app-services.ts +95 -0
- package/src/core/state/app-state.ts +886 -0
- package/src/{state → core/state}/session-persistence.ts +7 -6
- package/src/data/config-layout.ts +225 -0
- package/src/data/config-store-node.ts +390 -0
- package/src/data/config-store.test.ts +72 -0
- package/src/data/config-store.ts +35 -478
- package/src/data/sqlite-json.ts +2 -5
- package/src/data/sqlite-retry.ts +1 -0
- package/src/index.tsx +2 -68
- package/src/market-data/coordinator-subscriptions.test.ts +125 -0
- package/src/market-data/coordinator.test.ts +73 -4
- package/src/market-data/coordinator.ts +190 -24
- package/src/market-data/hooks.test.tsx +1 -1
- package/src/market-data/hooks.tsx +109 -87
- package/src/market-data/query-store.ts +3 -3
- package/src/market-data/request-types.test.ts +0 -14
- package/src/market-data/result-types.ts +1 -0
- package/src/news/aggregator.test.ts +267 -0
- package/src/news/aggregator.ts +452 -0
- package/src/news/hooks.ts +39 -0
- package/src/news/ticker-symbols.ts +21 -0
- package/src/news/types.ts +77 -0
- package/src/notifications/app-notifier.ts +21 -10
- package/src/pane-settings.test.ts +0 -2
- package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +124 -134
- package/src/plugins/builtin/ai/gloomberb-cli.ts +14 -13
- package/src/plugins/builtin/ai/providers.ts +1 -9
- package/src/plugins/builtin/ai/runner.ts +41 -5
- package/src/plugins/builtin/ai/screener-pane.test.tsx +37 -55
- package/src/plugins/builtin/ai/screener-pane.tsx +231 -276
- package/src/plugins/builtin/ai/settings.ts +0 -1
- package/src/plugins/builtin/ai/utils.ts +1 -71
- package/src/plugins/builtin/alerts/alert-engine.test.ts +1 -38
- package/src/plugins/builtin/alerts/index.test.tsx +61 -82
- package/src/plugins/builtin/alerts/index.tsx +263 -155
- package/src/plugins/builtin/alerts/types.ts +7 -0
- package/src/plugins/builtin/analytics/index.test.tsx +13 -8
- package/src/plugins/builtin/analytics/index.tsx +78 -120
- package/src/plugins/builtin/analytics/metrics.test.ts +0 -4
- package/src/plugins/builtin/ask-ai.test.tsx +55 -54
- package/src/plugins/builtin/broker-manager/index.test.tsx +168 -0
- package/src/plugins/builtin/broker-manager/index.tsx +759 -0
- package/src/plugins/builtin/broker-manager/model.test.ts +107 -0
- package/src/plugins/builtin/broker-manager/model.ts +142 -0
- package/src/plugins/builtin/chat-controller.test.ts +129 -0
- package/src/plugins/builtin/chat-controller.ts +85 -14
- package/src/plugins/builtin/chat.test.tsx +283 -36
- package/src/plugins/builtin/chat.tsx +363 -170
- package/src/plugins/builtin/cloud-auth-actions.tsx +72 -0
- package/src/plugins/builtin/cloud-tweets.tsx +896 -0
- package/src/plugins/builtin/comparison-chart.test.tsx +46 -57
- package/src/plugins/builtin/comparison-chart.tsx +11 -11
- package/src/plugins/builtin/correlation/compute.test.ts +0 -17
- package/src/plugins/builtin/correlation/index.tsx +51 -46
- package/src/plugins/builtin/correlation/settings.ts +1 -2
- package/src/plugins/builtin/debug.tsx +99 -70
- package/src/plugins/builtin/earnings/earnings-cache.test.ts +2 -2
- package/src/plugins/builtin/earnings/earnings-cache.ts +12 -3
- package/src/plugins/builtin/earnings/earnings-data.test.ts +42 -11
- package/src/plugins/builtin/earnings/earnings-data.ts +70 -5
- package/src/plugins/builtin/earnings/index.tsx +187 -94
- package/src/plugins/builtin/econ/calendar-source.ts +9 -116
- package/src/plugins/builtin/econ/fred-client.test.ts +1 -14
- package/src/plugins/builtin/econ/fred-series-map.ts +1 -0
- package/src/plugins/builtin/econ/index.tsx +236 -421
- package/src/plugins/builtin/econ/types.ts +1 -0
- package/src/plugins/builtin/fx-matrix/index.tsx +43 -45
- package/src/plugins/builtin/fx-matrix/pairs.ts +1 -0
- package/src/plugins/builtin/help.test.tsx +120 -60
- package/src/plugins/builtin/help.tsx +266 -82
- package/src/plugins/builtin/holders/index.test.ts +45 -0
- package/src/plugins/builtin/holders/index.tsx +949 -0
- package/src/plugins/builtin/insider/index.tsx +207 -253
- package/src/plugins/builtin/insider/insider-data.test.ts +1 -11
- package/src/plugins/builtin/insider/insider-data.ts +1 -0
- package/src/plugins/builtin/layout-manager.tsx +12 -40
- package/src/plugins/builtin/market-movers/index.tsx +70 -144
- package/src/plugins/builtin/market-movers/screener.test.ts +0 -18
- package/src/plugins/builtin/news-wire/breaking-notifications.test.ts +98 -0
- package/src/plugins/builtin/news-wire/breaking-notifications.ts +110 -0
- package/src/plugins/builtin/news-wire/breaking-pane.tsx +157 -0
- package/src/plugins/builtin/news-wire/categories.ts +114 -0
- package/src/plugins/builtin/news-wire/default-feeds.ts +3 -0
- package/src/plugins/builtin/news-wire/digest-store.ts +44 -0
- package/src/plugins/builtin/news-wire/feed-config.test.ts +90 -0
- package/src/plugins/builtin/news-wire/feed-config.ts +216 -0
- package/src/plugins/builtin/news-wire/feed-pane.tsx +21 -0
- package/src/plugins/builtin/news-wire/hash.ts +9 -0
- package/src/plugins/builtin/news-wire/index.tsx +92 -0
- package/src/plugins/builtin/news-wire/industry-pane.tsx +140 -0
- package/src/plugins/builtin/news-wire/news-detail-view.tsx +256 -0
- package/src/plugins/builtin/news-wire/news-footer.ts +28 -0
- package/src/plugins/builtin/news-wire/news-preset-pane.tsx +94 -0
- package/src/plugins/builtin/news-wire/news-query-presets.ts +40 -0
- package/src/plugins/builtin/news-wire/news-table.test.tsx +206 -0
- package/src/plugins/builtin/news-wire/news-table.tsx +296 -0
- package/src/plugins/builtin/news-wire/persisted-articles.ts +92 -0
- package/src/plugins/builtin/news-wire/read-state.test.ts +30 -0
- package/src/plugins/builtin/news-wire/read-state.ts +76 -0
- package/src/plugins/builtin/news-wire/rss-parser.test.ts +184 -0
- package/src/plugins/builtin/news-wire/rss-parser.ts +213 -0
- package/src/plugins/builtin/news-wire/rss-source.test.ts +159 -0
- package/src/plugins/builtin/news-wire/rss-source.ts +181 -0
- package/src/plugins/builtin/news-wire/top-pane.tsx +21 -0
- package/src/plugins/builtin/news.tsx +98 -25
- package/src/plugins/builtin/notes-files.ts +51 -10
- package/src/plugins/builtin/notes.tsx +332 -131
- package/src/plugins/builtin/options.test.tsx +185 -0
- package/src/plugins/builtin/options.tsx +346 -206
- package/src/plugins/builtin/plugin-groups.ts +92 -0
- package/src/plugins/builtin/portfolio-list/header.tsx +72 -37
- package/src/plugins/builtin/portfolio-list/index.tsx +25 -42
- package/src/plugins/builtin/portfolio-list/metrics.ts +1 -1
- package/src/plugins/builtin/portfolio-list/pane.tsx +246 -164
- package/src/plugins/builtin/portfolio-list/settings.ts +19 -51
- package/src/plugins/builtin/portfolio-list/summary.tsx +21 -16
- package/src/plugins/builtin/portfolio-list/table.tsx +66 -25
- package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.test.tsx +1 -1
- package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.ts +1 -1
- package/src/plugins/builtin/portfolio-list.test.ts +60 -1
- package/src/plugins/builtin/portfolio-list.test.tsx +122 -55
- package/src/plugins/builtin/quote-monitor.test.tsx +2 -27
- package/src/plugins/builtin/research/index.tsx +724 -0
- package/src/plugins/builtin/sec.tsx +67 -19
- package/src/plugins/builtin/sectors/index.tsx +42 -80
- package/src/plugins/builtin/sectors/sector-data.ts +1 -0
- package/src/plugins/builtin/ticker-detail/chart-tab.tsx +25 -39
- package/src/plugins/builtin/ticker-detail/financials-tab.tsx +108 -58
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +383 -271
- package/src/plugins/builtin/ticker-detail/pane.tsx +143 -49
- package/src/plugins/builtin/ticker-detail/quote-monitor.tsx +43 -27
- package/src/plugins/builtin/ticker-detail/settings.ts +3 -6
- package/src/plugins/builtin/ticker-detail-chart-tab-switch.test.tsx +82 -10
- package/src/plugins/builtin/ticker-detail.test.tsx +258 -33
- package/src/plugins/builtin/ticker-surface.ts +68 -0
- package/src/plugins/builtin/world-indices/index.tsx +44 -82
- package/src/plugins/builtin/world-indices/indices.ts +1 -0
- package/src/plugins/builtin/yahoo.ts +55 -1
- package/src/plugins/builtin/yield-curve/index.tsx +87 -145
- package/src/plugins/builtin/yield-curve/treasury-data.ts +6 -77
- package/src/plugins/catalog-ui.test.ts +24 -0
- package/src/plugins/catalog-ui.ts +44 -0
- package/src/plugins/catalog.ts +2 -50
- package/src/plugins/ibkr/__snapshots__/trade-tab.test.tsx.snap +5 -5
- package/src/plugins/ibkr/account-cache.test.ts +15 -14
- package/src/plugins/ibkr/account-cache.ts +15 -120
- package/src/plugins/ibkr/broker-adapter.ts +67 -0
- package/src/plugins/ibkr/config.test.ts +0 -18
- package/src/plugins/ibkr/dialogs.tsx +14 -56
- package/src/plugins/ibkr/flex.test.ts +57 -2
- package/src/plugins/ibkr/flex.ts +116 -12
- package/src/plugins/ibkr/gateway-helpers.ts +8 -3
- package/src/plugins/ibkr/gateway-service-native.ts +2381 -0
- package/src/plugins/ibkr/gateway-service.test.ts +47 -9
- package/src/plugins/ibkr/gateway-service.ts +264 -2235
- package/src/plugins/ibkr/gateway-types.ts +27 -0
- package/src/plugins/ibkr/index.test.tsx +3 -11
- package/src/plugins/ibkr/index.tsx +1 -0
- package/src/plugins/ibkr/price-normalization.test.ts +1 -6
- package/src/plugins/ibkr/price-normalization.ts +1 -0
- package/src/plugins/ibkr/trade-badge.tsx +6 -5
- package/src/plugins/ibkr/trade-tab.test.tsx +55 -3
- package/src/plugins/ibkr/trade-tab.tsx +166 -117
- package/src/plugins/ibkr/trading-pane.tsx +117 -83
- package/src/plugins/pane-manager.test.ts +3 -1
- package/src/plugins/pane-manager.ts +93 -19
- package/src/plugins/plugin-runtime.test.tsx +176 -74
- package/src/plugins/plugin-runtime.tsx +228 -10
- package/src/plugins/prediction-markets/cache.ts +49 -0
- package/src/plugins/prediction-markets/categories.ts +0 -10
- package/src/plugins/prediction-markets/chart.test.tsx +1 -1
- package/src/plugins/prediction-markets/chart.tsx +21 -20
- package/src/plugins/prediction-markets/cli.ts +1 -0
- package/src/plugins/prediction-markets/controller-data.ts +135 -89
- package/src/plugins/prediction-markets/controller.ts +37 -108
- package/src/plugins/prediction-markets/detail/book.tsx +23 -22
- package/src/plugins/prediction-markets/detail/outcomes.tsx +33 -32
- package/src/plugins/prediction-markets/detail/overview.tsx +17 -12
- package/src/plugins/prediction-markets/detail/pane.tsx +59 -45
- package/src/plugins/prediction-markets/detail/rules.tsx +22 -7
- package/src/plugins/prediction-markets/detail/shared.tsx +3 -2
- package/src/plugins/prediction-markets/detail/trades.tsx +12 -11
- package/src/plugins/prediction-markets/detail.test.tsx +1 -1
- package/src/plugins/prediction-markets/keyboard.ts +1 -0
- package/src/plugins/prediction-markets/launch.test.ts +1 -1
- package/src/plugins/prediction-markets/launch.ts +1 -1
- package/src/plugins/prediction-markets/metrics.ts +25 -12
- package/src/plugins/prediction-markets/pane.test.tsx +16 -7
- package/src/plugins/prediction-markets/pane.tsx +180 -82
- package/src/plugins/prediction-markets/plugin.test.tsx +37 -45
- package/src/plugins/prediction-markets/services/fetch.ts +27 -10
- package/src/plugins/prediction-markets/services/kalshi-adapter.ts +20 -3
- package/src/plugins/prediction-markets/services/polymarket-adapter.ts +29 -11
- package/src/plugins/prediction-markets/settings.ts +0 -5
- package/src/plugins/prediction-markets/test-helpers.tsx +19 -44
- package/src/plugins/registry.test.ts +307 -0
- package/src/plugins/registry.ts +293 -82
- package/src/react/input.ts +61 -0
- package/src/react/plugins/plugin-slot.tsx +21 -0
- package/src/react/use-raf-callback.ts +44 -0
- package/src/renderers/electrobun/bun/application-menu-click.test.ts +41 -0
- package/src/renderers/electrobun/bun/application-menu-click.ts +55 -0
- package/src/renderers/electrobun/bun/application-menu.ts +118 -0
- package/src/renderers/electrobun/bun/context-menu-click.test.ts +67 -0
- package/src/renderers/electrobun/bun/context-menu-click.ts +61 -0
- package/src/renderers/electrobun/bun/context-menu-normalize.test.ts +52 -0
- package/src/renderers/electrobun/bun/context-menu-normalize.ts +67 -0
- package/src/renderers/electrobun/bun/core-capabilities.ts +328 -0
- package/src/renderers/electrobun/bun/desktop-workspace.test.ts +63 -0
- package/src/renderers/electrobun/bun/desktop-workspace.ts +162 -0
- package/src/renderers/electrobun/bun/index.ts +1149 -0
- package/src/renderers/electrobun/bun/window-frame.test.ts +40 -0
- package/src/renderers/electrobun/bun/window-frame.ts +46 -0
- package/src/renderers/electrobun/shared/protocol.ts +72 -0
- package/src/renderers/electrobun/view/ai-host.ts +93 -0
- package/src/renderers/electrobun/view/app-services.ts +348 -0
- package/src/renderers/electrobun/view/application-menu-bridge.ts +12 -0
- package/src/renderers/electrobun/view/backend-rpc.ts +173 -0
- package/src/renderers/electrobun/view/broker-remote-client.ts +154 -0
- package/src/renderers/electrobun/view/config-host.ts +35 -0
- package/src/renderers/electrobun/view/context-menu.test.ts +140 -0
- package/src/renderers/electrobun/view/context-menu.ts +164 -0
- package/src/renderers/electrobun/view/data-table.tsx +773 -0
- package/src/renderers/electrobun/view/desktop-controls.tsx +651 -0
- package/src/renderers/electrobun/view/desktop-window-bridge.ts +59 -0
- package/src/renderers/electrobun/view/desktop-window-snapshot.test.ts +57 -0
- package/src/renderers/electrobun/view/desktop-window-snapshot.ts +41 -0
- package/src/renderers/electrobun/view/dialog-host.tsx +62 -0
- package/src/renderers/electrobun/view/http-fetch.ts +113 -0
- package/src/renderers/electrobun/view/input-host.tsx +112 -0
- package/src/renderers/electrobun/view/key-event.test.ts +34 -0
- package/src/renderers/electrobun/view/key-event.ts +134 -0
- package/src/renderers/electrobun/view/main.tsx +104 -0
- package/src/renderers/electrobun/view/native-renderer.ts +110 -0
- package/src/renderers/electrobun/view/native-stubs/chart-kitty-support.ts +9 -0
- package/src/renderers/electrobun/view/native-stubs/chart-surface-manager.ts +25 -0
- package/src/renderers/electrobun/view/native-stubs/chart-surface-sync.ts +1 -0
- package/src/renderers/electrobun/view/notes-files.ts +56 -0
- package/src/renderers/electrobun/view/resource-store.test.ts +44 -0
- package/src/renderers/electrobun/view/resource-store.ts +106 -0
- package/src/renderers/electrobun/view/rpc-codec.ts +42 -0
- package/src/renderers/electrobun/view/scrollbar-activity.ts +23 -0
- package/src/renderers/electrobun/view/toast-host.tsx +65 -0
- package/src/renderers/electrobun/view/ui-host.tsx +1509 -0
- package/src/renderers/electrobun/view/update-host.ts +38 -0
- package/src/renderers/opentui/dialog-host.test.tsx +44 -0
- package/src/renderers/opentui/dialog-host.tsx +56 -0
- package/src/renderers/opentui/host.tsx +140 -0
- package/src/renderers/opentui/image-loader.test.ts +25 -0
- package/src/renderers/opentui/image-loader.ts +105 -0
- package/src/renderers/opentui/image-surface.tsx +266 -0
- package/src/renderers/opentui/input-host.tsx +63 -0
- package/src/renderers/opentui/start.tsx +71 -0
- package/src/renderers/opentui/test-utils.tsx +76 -0
- package/src/renderers/opentui/toast-host.tsx +10 -0
- package/src/renderers/opentui/ui-host.tsx +98 -0
- package/src/sources/gloomberb-cloud.test.ts +288 -17
- package/src/sources/gloomberb-cloud.ts +176 -9
- package/src/sources/provider-errors.ts +1 -0
- package/src/sources/provider-router.test.ts +220 -81
- package/src/sources/provider-router.ts +294 -111
- package/src/sources/sec-edgar.ts +19 -6
- package/src/sources/yahoo-finance.test.ts +0 -5
- package/src/sources/yahoo-finance.ts +420 -11
- package/src/state/app-activity.ts +7 -7
- package/src/state/app-bootstrap.ts +114 -40
- package/src/state/app-context-selector.test.tsx +70 -1
- package/src/state/app-context.test.ts +71 -3
- package/src/state/app-context.test.tsx +14 -7
- package/src/state/app-context.tsx +209 -869
- package/src/state/config-save-scheduler.ts +35 -0
- package/src/state/pane-scroll-registry.tsx +254 -0
- package/src/state/persist-scheduler.test.ts +67 -0
- package/src/state/persist-scheduler.ts +69 -0
- package/src/state/selectors-ui.ts +55 -0
- package/src/state/selectors.ts +15 -71
- package/src/state/session-persistence.test.ts +1 -1
- package/src/state/ticker-refresh-queue.ts +52 -3
- package/src/state/use-inline-tickers.ts +18 -16
- package/src/state/use-quote-streaming.test.ts +0 -6
- package/src/state/use-quote-streaming.test.tsx +1 -1
- package/src/state/use-quote-streaming.ts +3 -2
- package/src/state/use-session-snapshot.ts +32 -17
- package/src/test-support/plugin-runtime.ts +149 -0
- package/src/theme/color-utils.ts +1 -0
- package/src/theme/colors.ts +16 -0
- package/src/types/broker.ts +16 -1
- package/src/types/capability-route-source.ts +23 -0
- package/src/types/config.test.ts +23 -0
- package/src/types/config.ts +145 -10
- package/src/types/context-menu.ts +95 -0
- package/src/types/data-provider.ts +34 -3
- package/src/types/desktop-menu.ts +13 -0
- package/src/types/desktop-window.ts +29 -0
- package/src/types/financials.ts +123 -2
- package/src/types/instrument.ts +1 -0
- package/src/types/news-source.ts +9 -0
- package/src/types/persistence.ts +1 -0
- package/src/types/plugin.ts +32 -8
- package/src/ui/ascii-font.ts +85 -0
- package/src/ui/context-menu.test.ts +94 -0
- package/src/ui/context-menu.tsx +433 -0
- package/src/ui/dialog.tsx +55 -0
- package/src/ui/host.tsx +364 -0
- package/src/ui/index.tsx +134 -0
- package/src/ui/toast.tsx +40 -0
- package/src/updater.test.ts +69 -0
- package/src/updater.ts +61 -18
- package/src/utils/api-client.test.ts +99 -9
- package/src/utils/api-client.ts +378 -18
- package/src/utils/back-navigation.ts +6 -0
- package/src/utils/cli-output.ts +1 -0
- package/src/utils/currency-units.test.ts +1 -6
- package/src/utils/currency-units.ts +1 -0
- package/src/utils/debug-log.ts +68 -0
- package/src/utils/dom-screenshot.ts +326 -0
- package/src/utils/exchange-rate-map.ts +1 -0
- package/src/utils/exchanges.ts +214 -0
- package/src/utils/format.test.ts +7 -0
- package/src/utils/format.ts +1 -0
- package/src/utils/fuzzy-search.ts +1 -0
- package/src/utils/html-entities.test.ts +8 -0
- package/src/utils/html-entities.ts +32 -0
- package/src/utils/http-transport.ts +11 -0
- package/src/utils/main-thread-monitor.ts +127 -0
- package/src/utils/perf-marks.ts +54 -0
- package/src/utils/quote-freshness.ts +27 -50
- package/src/utils/quote-resolution.test.ts +5 -5
- package/src/utils/quote-resolution.ts +0 -1
- package/src/utils/quote-time.ts +1 -5
- package/src/utils/sec.test.ts +0 -12
- package/src/utils/selection-clipboard.ts +14 -6
- package/src/utils/shortcut-labels.test.ts +20 -0
- package/src/utils/shortcut-labels.ts +67 -0
- package/src/utils/slugify.ts +1 -0
- package/src/utils/terminal-input-reset.ts +1 -0
- package/src/utils/text-wrap.ts +82 -0
- package/src/utils/throttled-fetch.test.ts +19 -0
- package/src/utils/throttled-fetch.ts +11 -9
- package/src/utils/ticker-list.test.ts +1 -7
- package/src/utils/ticker-list.ts +1 -0
- package/src/utils/timestamp.ts +29 -0
- package/src/utils/tweet-text.test.ts +13 -0
- package/src/utils/tweet-text.ts +18 -0
- package/src/utils/url.ts +11 -0
- package/src/version.ts +1 -1
- package/src/components/chart/chart-control-hint.tsx +0 -39
- package/src/components/detail-feed-view.test.tsx +0 -180
- package/src/components/detail-feed-view.tsx +0 -531
- package/src/components/layout/pixel-mouse-mode.test.ts +0 -122
- package/src/components/layout/pixel-mouse.test.ts +0 -40
- package/src/components/spinner.tsx +0 -2
- package/src/components/tab-bar.tsx +0 -17
- package/src/components/ui/multi-select-chips.tsx +0 -69
- package/src/plugins/builtin/ai/index.test.tsx +0 -139
- package/src/plugins/builtin/ask-ai.tsx +0 -9
- package/src/plugins/builtin/correlation/plugin.test.ts +0 -44
- package/src/plugins/builtin/correlation/settings.test.ts +0 -34
- package/src/plugins/builtin/econ/calendar-source.test.ts +0 -219
- package/src/plugins/builtin/econ/fred-client.ts +0 -79
- package/src/plugins/builtin/fred-settings.ts +0 -10
- package/src/plugins/builtin/options-availability.test.tsx +0 -179
- package/src/plugins/builtin/options-availability.ts +0 -158
- package/src/plugins/builtin/portfolio-list/metrics.test.ts +0 -61
- package/src/plugins/builtin/yield-curve/treasury-data.test.ts +0 -212
- package/src/plugins/prediction-markets/status.ts +0 -24
- package/src/plugins/prediction-markets/table.tsx +0 -87
- package/src/utils/quote-time.test.ts +0 -22
- package/src/utils/selection-clipboard.test.ts +0 -43
- package/src/utils/terminal-input-reset.test.ts +0 -50
|
@@ -1,24 +1,32 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Box, Text, Textarea } from "../../../ui";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState, type Dispatch } from "react";
|
|
2
3
|
import type { RefObject } from "react";
|
|
3
|
-
import { TextAttributes, type
|
|
4
|
-
import {
|
|
4
|
+
import { TextAttributes, type TextareaRenderable } from "../../../ui";
|
|
5
|
+
import { useShortcut } from "../../../react/input";
|
|
5
6
|
import type { PaneProps } from "../../../types/plugin";
|
|
6
7
|
import type { ColumnConfig } from "../../../types/config";
|
|
8
|
+
import type { DataProvider } from "../../../types/data-provider";
|
|
7
9
|
import type { InstrumentSearchResult } from "../../../types/instrument";
|
|
8
10
|
import type { TickerFinancials } from "../../../types/financials";
|
|
9
11
|
import type { TickerRecord } from "../../../types/ticker";
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
+
import {
|
|
13
|
+
useAppDispatch,
|
|
14
|
+
useAppSelector,
|
|
15
|
+
usePaneInstance,
|
|
16
|
+
usePaneInstanceId,
|
|
17
|
+
type AppAction,
|
|
18
|
+
} from "../../../state/app-context";
|
|
19
|
+
import { useAssetData, usePluginPaneState, usePluginState, usePluginTickerActions } from "../../../plugins/plugin-runtime";
|
|
12
20
|
import { useFxRatesMap, useTickerFinancialsMap } from "../../../market-data/hooks";
|
|
13
21
|
import { getSharedMarketDataCoordinator } from "../../../market-data/coordinator";
|
|
14
22
|
import { instrumentFromTicker, quoteSubscriptionTargetFromTicker } from "../../../market-data/request-types";
|
|
15
23
|
import { useQuoteStreaming } from "../../../state/use-quote-streaming";
|
|
16
|
-
import {
|
|
17
|
-
import { Button, EmptyState, Spinner } from "../../../components";
|
|
24
|
+
import { Button, EmptyState, SegmentedControl, Spinner, Tabs, TickerListTableView, usePaneFooter, type DataTableKeyEvent } from "../../../components";
|
|
18
25
|
import { colors } from "../../../theme/colors";
|
|
26
|
+
import { canonicalExchange } from "../../../utils/exchanges";
|
|
19
27
|
import { formatTimeAgo } from "../../../utils/format";
|
|
20
28
|
import { getColumnValue, getSortValue, type ColumnContext } from "../portfolio-list/metrics";
|
|
21
|
-
import {
|
|
29
|
+
import { getSharedRegistry } from "../../registry";
|
|
22
30
|
import { upsertTickerFromSearchResult } from "../../../utils/ticker-search";
|
|
23
31
|
import { buildGloomberbCliInstructions, resolveGloomberbCliCommand } from "./gloomberb-cli";
|
|
24
32
|
import { detectProviders, getAiProvider, resolveDefaultAiProviderId } from "./providers";
|
|
@@ -125,14 +133,14 @@ function ScreenerPromptEditor({
|
|
|
125
133
|
}, [editorKey, focused]);
|
|
126
134
|
|
|
127
135
|
return (
|
|
128
|
-
<
|
|
136
|
+
<Box
|
|
129
137
|
flexGrow={1}
|
|
130
138
|
minHeight={10}
|
|
131
139
|
border
|
|
132
140
|
borderColor={colors.border}
|
|
133
141
|
backgroundColor={colors.panel}
|
|
134
142
|
>
|
|
135
|
-
<
|
|
143
|
+
<Textarea
|
|
136
144
|
key={editorKey}
|
|
137
145
|
ref={textareaRef}
|
|
138
146
|
initialValue={initialValue}
|
|
@@ -144,7 +152,7 @@ function ScreenerPromptEditor({
|
|
|
144
152
|
flexGrow={1}
|
|
145
153
|
wrapText
|
|
146
154
|
/>
|
|
147
|
-
</
|
|
155
|
+
</Box>
|
|
148
156
|
);
|
|
149
157
|
}
|
|
150
158
|
|
|
@@ -199,20 +207,20 @@ function resolveResultSymbol(result: InstrumentSearchResult): string {
|
|
|
199
207
|
|
|
200
208
|
function matchesExchange(result: InstrumentSearchResult, exchange: string): boolean {
|
|
201
209
|
if (!exchange) return true;
|
|
202
|
-
const normalized = exchange
|
|
203
|
-
return result.exchange
|
|
204
|
-
|| (result.primaryExchange
|
|
205
|
-
|| (result.brokerContract?.exchange
|
|
206
|
-
|| (result.brokerContract?.primaryExchange
|
|
210
|
+
const normalized = canonicalExchange(exchange);
|
|
211
|
+
return canonicalExchange(result.exchange) === normalized
|
|
212
|
+
|| canonicalExchange(result.primaryExchange) === normalized
|
|
213
|
+
|| canonicalExchange(result.brokerContract?.exchange) === normalized
|
|
214
|
+
|| canonicalExchange(result.brokerContract?.primaryExchange) === normalized;
|
|
207
215
|
}
|
|
208
216
|
|
|
209
217
|
async function resolveCandidateTicker(
|
|
210
218
|
candidate: { symbol: string; exchange: string; reason: string },
|
|
211
219
|
localTickers: ReadonlyMap<string, TickerRecord>,
|
|
212
|
-
stateDispatch:
|
|
220
|
+
stateDispatch: Dispatch<AppAction>,
|
|
221
|
+
dataProvider: DataProvider | null,
|
|
213
222
|
): Promise<ValidatedScreenerResult | null> {
|
|
214
223
|
const registry = getSharedRegistry();
|
|
215
|
-
const dataProvider = getSharedDataProvider() ?? registry?.dataProvider ?? null;
|
|
216
224
|
if (!registry || !dataProvider) {
|
|
217
225
|
throw new Error("AI screener could not access the ticker repository.");
|
|
218
226
|
}
|
|
@@ -254,7 +262,8 @@ async function resolveCandidateTicker(
|
|
|
254
262
|
async function validateScreenerResults(
|
|
255
263
|
candidates: Array<{ symbol: string; exchange: string; reason: string }>,
|
|
256
264
|
localTickers: ReadonlyMap<string, TickerRecord>,
|
|
257
|
-
stateDispatch:
|
|
265
|
+
stateDispatch: Dispatch<AppAction>,
|
|
266
|
+
dataProvider: DataProvider | null,
|
|
258
267
|
): Promise<{ results: ValidatedScreenerResult[]; warning: string | null }> {
|
|
259
268
|
const resolved: ValidatedScreenerResult[] = [];
|
|
260
269
|
const unresolved: string[] = [];
|
|
@@ -262,7 +271,7 @@ async function validateScreenerResults(
|
|
|
262
271
|
const seen = new Set<string>();
|
|
263
272
|
|
|
264
273
|
for (const candidate of candidates) {
|
|
265
|
-
const result = await resolveCandidateTicker(candidate, localTickers, stateDispatch);
|
|
274
|
+
const result = await resolveCandidateTicker(candidate, localTickers, stateDispatch, dataProvider);
|
|
266
275
|
if (!result) {
|
|
267
276
|
unresolved.push(candidate.symbol);
|
|
268
277
|
continue;
|
|
@@ -313,37 +322,15 @@ function sortScreenerRows(
|
|
|
313
322
|
});
|
|
314
323
|
}
|
|
315
324
|
|
|
316
|
-
function ActionChip({
|
|
317
|
-
label,
|
|
318
|
-
onPress,
|
|
319
|
-
active = false,
|
|
320
|
-
disabled = false,
|
|
321
|
-
}: {
|
|
322
|
-
label: string;
|
|
323
|
-
onPress?: () => void;
|
|
324
|
-
active?: boolean;
|
|
325
|
-
disabled?: boolean;
|
|
326
|
-
}) {
|
|
327
|
-
return (
|
|
328
|
-
<box
|
|
329
|
-
backgroundColor={active ? colors.selected : colors.panel}
|
|
330
|
-
onMouseDown={!disabled && onPress ? (event: any) => {
|
|
331
|
-
event.stopPropagation?.();
|
|
332
|
-
event.preventDefault?.();
|
|
333
|
-
onPress();
|
|
334
|
-
} : undefined}
|
|
335
|
-
>
|
|
336
|
-
<text fg={disabled ? colors.textDim : active ? colors.selectedText : colors.text}>
|
|
337
|
-
{` ${label} `}
|
|
338
|
-
</text>
|
|
339
|
-
</box>
|
|
340
|
-
);
|
|
341
|
-
}
|
|
342
|
-
|
|
343
325
|
export function AiScreenerPane({ focused, width, height }: PaneProps) {
|
|
326
|
+
const dataProvider = useAssetData();
|
|
327
|
+
const { pinTicker } = usePluginTickerActions();
|
|
344
328
|
const paneId = usePaneInstanceId();
|
|
345
329
|
const paneInstance = usePaneInstance();
|
|
346
|
-
const
|
|
330
|
+
const dispatch = useAppDispatch();
|
|
331
|
+
const tickers = useAppSelector((state) => state.tickers);
|
|
332
|
+
const baseCurrency = useAppSelector((state) => state.config.baseCurrency);
|
|
333
|
+
const cachedExchangeRates = useAppSelector((state) => state.exchangeRates);
|
|
347
334
|
const [providers] = useState(() => detectProviders());
|
|
348
335
|
const availableProviders = providers.filter((provider) => provider.available);
|
|
349
336
|
const selectableProviders = availableProviders.length > 0 ? availableProviders : providers;
|
|
@@ -355,15 +342,12 @@ export function AiScreenerPane({ focused, width, height }: PaneProps) {
|
|
|
355
342
|
const [activeTabId, setActiveTabId] = usePluginPaneState<string | null>("activeTabId", null);
|
|
356
343
|
const [cursorSymbol, setCursorSymbol] = usePluginPaneState<string | null>("cursorSymbol", null);
|
|
357
344
|
const [sorts, setSorts] = usePluginPaneState<Record<string, ScreenerSortPreference>>("sorts", {});
|
|
358
|
-
const [hoveredIdx, setHoveredIdx] = useState<number | null>(null);
|
|
359
345
|
const [runState, setRunState] = useState<RunState | null>(null);
|
|
360
346
|
const [now, setNow] = useState(Date.now());
|
|
361
347
|
const [editorState, setEditorState] = useState<ScreenerEditorState | null>(null);
|
|
362
348
|
const [forceConfirmTabId, setForceConfirmTabId] = useState<string | null>(null);
|
|
363
349
|
const runRef = useRef<AiRunController | null>(null);
|
|
364
350
|
const editorTextareaRef = useRef<TextareaRenderable | null>(null);
|
|
365
|
-
const headerScrollRef = useRef<ScrollBoxRenderable>(null);
|
|
366
|
-
const scrollRef = useRef<ScrollBoxRenderable>(null);
|
|
367
351
|
const initializedRef = useRef(false);
|
|
368
352
|
const pendingInitialRunRef = useRef<string | null>(null);
|
|
369
353
|
const lastTabClickRef = useRef<{ tabId: string; at: number } | null>(null);
|
|
@@ -421,25 +405,25 @@ export function AiScreenerPane({ focused, width, height }: PaneProps) {
|
|
|
421
405
|
|
|
422
406
|
const screenerTickers = useMemo(() => (
|
|
423
407
|
(activeTab?.results ?? [])
|
|
424
|
-
.map((result) =>
|
|
408
|
+
.map((result) => tickers.get(result.symbol) ?? null)
|
|
425
409
|
.filter((ticker): ticker is TickerRecord => ticker != null)
|
|
426
|
-
), [activeTab?.results,
|
|
410
|
+
), [activeTab?.results, tickers]);
|
|
427
411
|
const financialsMap = useTickerFinancialsMap(screenerTickers);
|
|
428
412
|
|
|
429
413
|
const trackedCurrencies = useMemo(() => [
|
|
430
|
-
|
|
414
|
+
baseCurrency,
|
|
431
415
|
...screenerTickers.map((ticker) => ticker.metadata.currency),
|
|
432
416
|
...screenerTickers.map((ticker) => financialsMap.get(ticker.metadata.ticker)?.quote?.currency ?? null),
|
|
433
|
-
], [financialsMap, screenerTickers
|
|
417
|
+
], [baseCurrency, financialsMap, screenerTickers]);
|
|
434
418
|
const exchangeRates = useFxRatesMap(trackedCurrencies);
|
|
435
|
-
const effectiveExchangeRates = exchangeRates.size > 1 ||
|
|
419
|
+
const effectiveExchangeRates = exchangeRates.size > 1 || cachedExchangeRates.size === 0
|
|
436
420
|
? exchangeRates
|
|
437
|
-
:
|
|
421
|
+
: cachedExchangeRates;
|
|
438
422
|
const columnContext: ColumnContext = useMemo(() => ({
|
|
439
|
-
baseCurrency
|
|
423
|
+
baseCurrency,
|
|
440
424
|
exchangeRates: effectiveExchangeRates,
|
|
441
425
|
now,
|
|
442
|
-
}), [effectiveExchangeRates, now
|
|
426
|
+
}), [baseCurrency, effectiveExchangeRates, now]);
|
|
443
427
|
|
|
444
428
|
const sortedTickers = useMemo(
|
|
445
429
|
() => sortScreenerRows(screenerTickers, resultMap, financialsMap, activeSort, columnContext, columns),
|
|
@@ -482,18 +466,6 @@ export function AiScreenerPane({ focused, width, height }: PaneProps) {
|
|
|
482
466
|
const isRunningActiveTab = runState?.tabId === activeTab?.id;
|
|
483
467
|
const forceRunArmed = !!activeTab && forceConfirmTabId === activeTab.id;
|
|
484
468
|
|
|
485
|
-
const syncHeaderScroll = useCallback(() => {
|
|
486
|
-
const header = headerScrollRef.current;
|
|
487
|
-
const body = scrollRef.current;
|
|
488
|
-
if (header && body && header.scrollLeft !== body.scrollLeft) {
|
|
489
|
-
header.scrollLeft = body.scrollLeft;
|
|
490
|
-
}
|
|
491
|
-
}, []);
|
|
492
|
-
|
|
493
|
-
const handleBodyScrollActivity = useCallback(() => {
|
|
494
|
-
syncHeaderScroll();
|
|
495
|
-
}, [syncHeaderScroll]);
|
|
496
|
-
|
|
497
469
|
const updateTabs = useCallback((updater: (tabs: AiScreenerTab[]) => AiScreenerTab[]) => {
|
|
498
470
|
setPersistedState((current) => ({
|
|
499
471
|
tabs: updater(normalizeTabs(current)),
|
|
@@ -668,7 +640,7 @@ export function AiScreenerPane({ focused, width, height }: PaneProps) {
|
|
|
668
640
|
rawOutput = await run.done;
|
|
669
641
|
|
|
670
642
|
const parsed = parseScreenerResponse(rawOutput);
|
|
671
|
-
const validated = await validateScreenerResults(parsed.tickers,
|
|
643
|
+
const validated = await validateScreenerResults(parsed.tickers, tickers, dispatch, dataProvider);
|
|
672
644
|
const nextResults = !samePrompt || mode === "force"
|
|
673
645
|
? validated.results
|
|
674
646
|
: mergeScreenerResults(tab.results, validated.results);
|
|
@@ -697,7 +669,7 @@ export function AiScreenerPane({ focused, width, height }: PaneProps) {
|
|
|
697
669
|
}
|
|
698
670
|
setRunState((current) => (current?.tabId === tab.id ? null : current));
|
|
699
671
|
}
|
|
700
|
-
}, [dispatch, providers, setForceConfirmTabId,
|
|
672
|
+
}, [dataProvider, dispatch, providers, setForceConfirmTabId, tabs, tickers, upsertTab]);
|
|
701
673
|
|
|
702
674
|
useEffect(() => {
|
|
703
675
|
if (!pendingInitialRunRef.current) return;
|
|
@@ -721,30 +693,6 @@ export function AiScreenerPane({ focused, width, height }: PaneProps) {
|
|
|
721
693
|
}
|
|
722
694
|
}, [activeTab, forceConfirmTabId]);
|
|
723
695
|
|
|
724
|
-
useEffect(() => {
|
|
725
|
-
if (headerScrollRef.current) {
|
|
726
|
-
headerScrollRef.current.horizontalScrollBar.visible = false;
|
|
727
|
-
}
|
|
728
|
-
syncHeaderScroll();
|
|
729
|
-
}, [columns, syncHeaderScroll]);
|
|
730
|
-
|
|
731
|
-
useEffect(() => {
|
|
732
|
-
const scrollBox = scrollRef.current;
|
|
733
|
-
if (!scrollBox) return;
|
|
734
|
-
const viewportHeight = scrollBox.viewport.height;
|
|
735
|
-
if (safeSelectedIdx < scrollBox.scrollTop) {
|
|
736
|
-
scrollBox.scrollTo(safeSelectedIdx);
|
|
737
|
-
} else if (safeSelectedIdx >= scrollBox.scrollTop + viewportHeight) {
|
|
738
|
-
scrollBox.scrollTo(Math.max(0, safeSelectedIdx - viewportHeight + 1));
|
|
739
|
-
}
|
|
740
|
-
}, [safeSelectedIdx]);
|
|
741
|
-
|
|
742
|
-
useEffect(() => {
|
|
743
|
-
const scrollBox = scrollRef.current;
|
|
744
|
-
if (!scrollBox) return;
|
|
745
|
-
scrollBox.verticalScrollBar.visible = sortedTickers.length > scrollBox.viewport.height;
|
|
746
|
-
}, [sortedTickers.length, width, height]);
|
|
747
|
-
|
|
748
696
|
const cycleTabs = useCallback((direction: -1 | 1) => {
|
|
749
697
|
if (!activeTab || tabs.length <= 1) return;
|
|
750
698
|
const currentIndex = tabs.findIndex((tab) => tab.id === activeTab.id);
|
|
@@ -768,7 +716,19 @@ export function AiScreenerPane({ focused, width, height }: PaneProps) {
|
|
|
768
716
|
});
|
|
769
717
|
}, [activeTab, setSorts, sorts]);
|
|
770
718
|
|
|
771
|
-
|
|
719
|
+
const handleTableKeyDown = useCallback((event: DataTableKeyEvent) => {
|
|
720
|
+
const isEnter = event.name === "enter" || event.name === "return";
|
|
721
|
+
if (!isEnter || !cursorSymbol) return false;
|
|
722
|
+
event.preventDefault?.();
|
|
723
|
+
event.stopPropagation?.();
|
|
724
|
+
pinTicker(cursorSymbol, {
|
|
725
|
+
floating: !!event.shift,
|
|
726
|
+
paneType: "ticker-detail",
|
|
727
|
+
});
|
|
728
|
+
return true;
|
|
729
|
+
}, [cursorSymbol, pinTicker]);
|
|
730
|
+
|
|
731
|
+
useShortcut((event) => {
|
|
772
732
|
if (!focused) return;
|
|
773
733
|
|
|
774
734
|
if (editorState) {
|
|
@@ -794,22 +754,6 @@ export function AiScreenerPane({ focused, width, height }: PaneProps) {
|
|
|
794
754
|
}
|
|
795
755
|
|
|
796
756
|
const key = event.name;
|
|
797
|
-
const isEnter = key === "enter" || key === "return";
|
|
798
|
-
|
|
799
|
-
if (key === "j" || key === "down") {
|
|
800
|
-
const nextTicker = sortedTickers[Math.min(safeSelectedIdx + 1, sortedTickers.length - 1)];
|
|
801
|
-
if (nextTicker) {
|
|
802
|
-
setCursorSymbol(nextTicker.metadata.ticker);
|
|
803
|
-
}
|
|
804
|
-
return;
|
|
805
|
-
}
|
|
806
|
-
if (key === "k" || key === "up") {
|
|
807
|
-
const nextTicker = sortedTickers[Math.max(safeSelectedIdx - 1, 0)];
|
|
808
|
-
if (nextTicker) {
|
|
809
|
-
setCursorSymbol(nextTicker.metadata.ticker);
|
|
810
|
-
}
|
|
811
|
-
return;
|
|
812
|
-
}
|
|
813
757
|
if (event.name === "t") {
|
|
814
758
|
addTab();
|
|
815
759
|
return;
|
|
@@ -840,15 +784,7 @@ export function AiScreenerPane({ focused, width, height }: PaneProps) {
|
|
|
840
784
|
}
|
|
841
785
|
if (event.name === "e") {
|
|
842
786
|
editActiveTab();
|
|
843
|
-
return;
|
|
844
787
|
}
|
|
845
|
-
|
|
846
|
-
if (!isEnter || !cursorSymbol) return;
|
|
847
|
-
const registry = getSharedRegistry();
|
|
848
|
-
registry?.pinTickerFn(cursorSymbol, {
|
|
849
|
-
floating: !!event.shift,
|
|
850
|
-
paneType: "ticker-detail",
|
|
851
|
-
});
|
|
852
788
|
});
|
|
853
789
|
|
|
854
790
|
useEffect(() => () => {
|
|
@@ -858,7 +794,7 @@ export function AiScreenerPane({ focused, width, height }: PaneProps) {
|
|
|
858
794
|
const contentHeight = Math.max(height - 8, 4);
|
|
859
795
|
const warningColor = colors.borderFocused;
|
|
860
796
|
const statusText = activeTab
|
|
861
|
-
? isRunningActiveTab
|
|
797
|
+
? isRunningActiveTab && runState
|
|
862
798
|
? `${runState.mode === "force" ? "Force refreshing" : "Refreshing"} with ${getAiProvider(activeTab.providerId, providers)?.name ?? "AI"}...`
|
|
863
799
|
: activeTab.lastSuccessAt
|
|
864
800
|
? `Last ran ${formatTimeAgo(new Date(activeTab.lastSuccessAt))}`
|
|
@@ -888,97 +824,130 @@ export function AiScreenerPane({ focused, width, height }: PaneProps) {
|
|
|
888
824
|
]
|
|
889
825
|
: activeTab
|
|
890
826
|
? ["No validated results yet."]
|
|
891
|
-
: ["
|
|
827
|
+
: ["Create an AI screener tab to begin."];
|
|
892
828
|
const paddedDetailLines = [...detailLines];
|
|
893
829
|
while (paddedDetailLines.length < DETAIL_FOOTER_LINES) {
|
|
894
830
|
paddedDetailLines.push("");
|
|
895
831
|
}
|
|
832
|
+
const activeProvider = activeTab ? getAiProvider(activeTab.providerId, providers) : null;
|
|
833
|
+
const refreshActiveTab = useCallback(() => {
|
|
834
|
+
if (!activeTab || isRunningActiveTab) return;
|
|
835
|
+
void runTab(activeTab.id, "refresh");
|
|
836
|
+
}, [activeTab, isRunningActiveTab, runTab]);
|
|
837
|
+
const forceRefreshActiveTab = useCallback(() => {
|
|
838
|
+
if (!activeTab || isRunningActiveTab) return;
|
|
839
|
+
if (forceRunArmed) {
|
|
840
|
+
setForceConfirmTabId(null);
|
|
841
|
+
void runTab(activeTab.id, "force");
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
setForceConfirmTabId(activeTab.id);
|
|
845
|
+
}, [activeTab, forceRunArmed, isRunningActiveTab, runTab, setForceConfirmTabId]);
|
|
846
|
+
|
|
847
|
+
usePaneFooter("ai-screener", () => ({
|
|
848
|
+
info: [
|
|
849
|
+
...(editorState ? [{
|
|
850
|
+
id: "provider",
|
|
851
|
+
parts: [{ text: editorProvider?.name ?? editorState.providerId, tone: editorProvider?.available === false ? "warning" as const : "value" as const, bold: true }],
|
|
852
|
+
}] : activeTab ? [{
|
|
853
|
+
id: "provider",
|
|
854
|
+
parts: [{ text: activeProvider?.name ?? activeTab.providerId, tone: activeProvider?.available === false ? "warning" as const : "value" as const, bold: true }],
|
|
855
|
+
}] : []),
|
|
856
|
+
{ id: "status", parts: [{ text: statusText, tone: isRunningActiveTab ? "muted" : "value" }] },
|
|
857
|
+
...(forceRunArmed ? [{ id: "force", parts: [{ text: "force refresh armed", tone: "warning" as const }] }] : []),
|
|
858
|
+
],
|
|
859
|
+
hints: editorState
|
|
860
|
+
? [
|
|
861
|
+
{ id: "save", key: "Ctrl+S", label: "save", onPress: saveEditor },
|
|
862
|
+
{ id: "provider", key: "Ctrl+P", label: "provider", onPress: () => cycleEditorProvider(1), disabled: selectableProviders.length <= 1 },
|
|
863
|
+
]
|
|
864
|
+
: [
|
|
865
|
+
{ id: "new", key: "t", label: "new", onPress: addTab },
|
|
866
|
+
{ id: "close", key: "w", label: "close tab", onPress: activeTab ? () => removeTab(activeTab.id) : undefined, disabled: !activeTab },
|
|
867
|
+
{ id: "refresh", key: "r", label: "efresh", onPress: refreshActiveTab, disabled: !activeTab || isRunningActiveTab },
|
|
868
|
+
{ id: "force-refresh", key: "Shift+R", label: "force refresh", onPress: forceRefreshActiveTab, disabled: !activeTab || isRunningActiveTab },
|
|
869
|
+
{ id: "edit", key: "e", label: "dit", onPress: editActiveTab, disabled: !activeTab },
|
|
870
|
+
],
|
|
871
|
+
}), [
|
|
872
|
+
activeProvider?.available,
|
|
873
|
+
activeProvider?.name,
|
|
874
|
+
activeTab,
|
|
875
|
+
addTab,
|
|
876
|
+
cycleEditorProvider,
|
|
877
|
+
editActiveTab,
|
|
878
|
+
editorProvider?.available,
|
|
879
|
+
editorProvider?.name,
|
|
880
|
+
editorState,
|
|
881
|
+
forceRefreshActiveTab,
|
|
882
|
+
forceRunArmed,
|
|
883
|
+
isRunningActiveTab,
|
|
884
|
+
refreshActiveTab,
|
|
885
|
+
removeTab,
|
|
886
|
+
saveEditor,
|
|
887
|
+
selectableProviders.length,
|
|
888
|
+
statusText,
|
|
889
|
+
]);
|
|
896
890
|
|
|
897
891
|
return (
|
|
898
|
-
<
|
|
899
|
-
<
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
)}
|
|
934
|
-
</box>
|
|
935
|
-
);
|
|
936
|
-
})}
|
|
937
|
-
<text fg={colors.textMuted} onMouseDown={() => { if (!editorState) addTab(); }}>{` + `}</text>
|
|
938
|
-
<box flexGrow={1} />
|
|
939
|
-
<text fg={colors.textMuted}>
|
|
940
|
-
{editorState
|
|
941
|
-
? editorState.mode === "create" ? "creating new screener" : "editing prompt"
|
|
942
|
-
: activeTab
|
|
943
|
-
? `${activeTab.results.length} tickers`
|
|
944
|
-
: "t new"}
|
|
945
|
-
</text>
|
|
946
|
-
</box>
|
|
947
|
-
|
|
948
|
-
<box flexDirection="row" height={1} gap={1}>
|
|
892
|
+
<Box flexDirection="column" width={width} height={height}>
|
|
893
|
+
<Box height={1}>
|
|
894
|
+
<Tabs
|
|
895
|
+
tabs={displayTabs.map((tab) => {
|
|
896
|
+
const isDraft = tab.draft === true;
|
|
897
|
+
return {
|
|
898
|
+
label: truncateWithEllipsis(tab.title, isDraft ? 20 : 18),
|
|
899
|
+
value: tab.id,
|
|
900
|
+
onClose: !editorState && !isDraft ? removeTab : undefined,
|
|
901
|
+
};
|
|
902
|
+
})}
|
|
903
|
+
activeValue={editorState?.mode === "create" ? "__draft__" : activeTab?.id ?? null}
|
|
904
|
+
onSelect={(tabId) => {
|
|
905
|
+
if (editorState || tabId === "__draft__") return;
|
|
906
|
+
const tab = tabs.find((entry) => entry.id === tabId);
|
|
907
|
+
if (!tab) return;
|
|
908
|
+
setActiveTabId(tab.id);
|
|
909
|
+
setCursorSymbol(null);
|
|
910
|
+
const now = Date.now();
|
|
911
|
+
const last = lastTabClickRef.current;
|
|
912
|
+
if (last?.tabId === tab.id && now - last.at <= 350) {
|
|
913
|
+
lastTabClickRef.current = null;
|
|
914
|
+
editTab(tab);
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
lastTabClickRef.current = { tabId: tab.id, at: now };
|
|
918
|
+
}}
|
|
919
|
+
compact
|
|
920
|
+
variant="pill"
|
|
921
|
+
closeMode="active"
|
|
922
|
+
onAdd={editorState ? undefined : addTab}
|
|
923
|
+
/>
|
|
924
|
+
</Box>
|
|
925
|
+
|
|
926
|
+
<Box flexDirection="row" height={1} gap={1}>
|
|
949
927
|
{editorState ? (
|
|
950
928
|
<>
|
|
951
|
-
<Button label="Save" variant="primary"
|
|
952
|
-
<Button label="Cancel" variant="ghost"
|
|
929
|
+
<Button label="Save" variant="primary" onPress={saveEditor} />
|
|
930
|
+
<Button label="Cancel" variant="ghost" onPress={closeEditor} />
|
|
953
931
|
</>
|
|
954
932
|
) : (
|
|
955
933
|
<>
|
|
956
934
|
{isRunningActiveTab ? (
|
|
957
935
|
<>
|
|
958
936
|
<Button label={runState?.mode === "force" ? "Force Refreshing..." : "Refreshing..."} variant="secondary" disabled />
|
|
959
|
-
<Button label="Stop" variant="ghost"
|
|
937
|
+
<Button label="Stop" variant="ghost" onPress={cancelRun} />
|
|
960
938
|
</>
|
|
961
939
|
) : (
|
|
962
940
|
<>
|
|
963
941
|
<Button
|
|
964
942
|
label={primaryRunLabel}
|
|
965
943
|
variant="primary"
|
|
966
|
-
|
|
967
|
-
onPress={activeTab ? () => { void runTab(activeTab.id, "refresh"); } : undefined}
|
|
944
|
+
onPress={refreshActiveTab}
|
|
968
945
|
disabled={!activeTab}
|
|
969
946
|
/>
|
|
970
947
|
<Button
|
|
971
948
|
label={forceRunArmed ? "Confirm Force Refresh" : "Force Refresh"}
|
|
972
949
|
variant={forceRunArmed ? "danger" : "ghost"}
|
|
973
|
-
|
|
974
|
-
onPress={activeTab ? () => {
|
|
975
|
-
if (forceRunArmed) {
|
|
976
|
-
setForceConfirmTabId(null);
|
|
977
|
-
void runTab(activeTab.id, "force");
|
|
978
|
-
return;
|
|
979
|
-
}
|
|
980
|
-
setForceConfirmTabId(activeTab.id);
|
|
981
|
-
} : undefined}
|
|
950
|
+
onPress={forceRefreshActiveTab}
|
|
982
951
|
disabled={!activeTab}
|
|
983
952
|
/>
|
|
984
953
|
</>
|
|
@@ -986,128 +955,116 @@ export function AiScreenerPane({ focused, width, height }: PaneProps) {
|
|
|
986
955
|
<Button
|
|
987
956
|
label="Edit Prompt"
|
|
988
957
|
variant={promptDirty ? "primary" : "secondary"}
|
|
989
|
-
shortcut="e"
|
|
990
958
|
onPress={editActiveTab}
|
|
991
959
|
disabled={!activeTab}
|
|
992
960
|
/>
|
|
993
961
|
</>
|
|
994
962
|
)}
|
|
995
|
-
|
|
996
|
-
<text fg={colors.textMuted}>
|
|
997
|
-
{editorState
|
|
998
|
-
? `${editorProvider?.name ?? editorState.providerId} · Ctrl+S save · Esc cancel`
|
|
999
|
-
: activeTab
|
|
1000
|
-
? `${getAiProvider(activeTab.providerId, providers)?.name ?? activeTab.providerId} · ${forceRunArmed ? "Force refresh armed. Click again to confirm." : statusText}`
|
|
1001
|
-
: statusText}
|
|
1002
|
-
</text>
|
|
1003
|
-
</box>
|
|
963
|
+
</Box>
|
|
1004
964
|
|
|
1005
965
|
{availableProviders.length === 0 && (
|
|
1006
|
-
<
|
|
1007
|
-
<
|
|
1008
|
-
</
|
|
966
|
+
<Box flexDirection="column" paddingX={1} paddingTop={1}>
|
|
967
|
+
<Text fg={colors.textDim}>No AI CLI tools detected. Install `claude`, `gemini`, or `codex` to run screeners.</Text>
|
|
968
|
+
</Box>
|
|
1009
969
|
)}
|
|
1010
970
|
|
|
1011
971
|
{editorState ? (
|
|
1012
972
|
<>
|
|
1013
|
-
<
|
|
1014
|
-
<
|
|
973
|
+
<Box flexDirection="column" paddingX={1} paddingTop={1} gap={1}>
|
|
974
|
+
<Text fg={colors.textDim}>
|
|
1015
975
|
{editorState.mode === "create"
|
|
1016
976
|
? "Describe the companies or setups you want this screener to discover."
|
|
1017
977
|
: "Update the screener prompt or provider. Saving does not rerun it automatically."}
|
|
1018
|
-
</
|
|
1019
|
-
<
|
|
1020
|
-
{
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
))}
|
|
1032
|
-
</box>
|
|
978
|
+
</Text>
|
|
979
|
+
<SegmentedControl
|
|
980
|
+
value={editorState.providerId}
|
|
981
|
+
options={selectableProviders.map((provider) => ({
|
|
982
|
+
value: provider.id,
|
|
983
|
+
label: provider.available ? provider.name : `${provider.name} (missing)`,
|
|
984
|
+
}))}
|
|
985
|
+
onChange={(providerId) => {
|
|
986
|
+
setEditorState((current) => current
|
|
987
|
+
? { ...current, providerId, error: null }
|
|
988
|
+
: current);
|
|
989
|
+
}}
|
|
990
|
+
/>
|
|
1033
991
|
{editorState.error ? (
|
|
1034
|
-
<
|
|
992
|
+
<Text fg={colors.negative}>{editorState.error}</Text>
|
|
1035
993
|
) : (
|
|
1036
|
-
<
|
|
1037
|
-
The AI will return validated ticker ideas with a short reason for each one.
|
|
1038
|
-
</
|
|
994
|
+
<Text fg={colors.textDim}>
|
|
995
|
+
The AI will return validated ticker ideas with a short reason for each one.
|
|
996
|
+
</Text>
|
|
1039
997
|
)}
|
|
1040
|
-
</
|
|
998
|
+
</Box>
|
|
1041
999
|
|
|
1042
|
-
<
|
|
1000
|
+
<Box flexGrow={1} minHeight={contentHeight} padding={1}>
|
|
1043
1001
|
<ScreenerPromptEditor
|
|
1044
1002
|
editorKey={editorState.key}
|
|
1045
1003
|
initialValue={editorState.prompt}
|
|
1046
1004
|
focused={focused}
|
|
1047
1005
|
textareaRef={editorTextareaRef}
|
|
1048
1006
|
/>
|
|
1049
|
-
</
|
|
1007
|
+
</Box>
|
|
1050
1008
|
|
|
1051
|
-
<
|
|
1052
|
-
<
|
|
1053
|
-
</
|
|
1009
|
+
<Box height={1} paddingX={1}>
|
|
1010
|
+
<Text fg={colors.textDim}>{"\u2500".repeat(Math.max(width - 2, 0))}</Text>
|
|
1011
|
+
</Box>
|
|
1054
1012
|
|
|
1055
|
-
<
|
|
1056
|
-
<
|
|
1013
|
+
<Box flexDirection="column" paddingX={1}>
|
|
1014
|
+
<Text fg={colors.textDim}>
|
|
1057
1015
|
{editorProvider?.available === false
|
|
1058
1016
|
? `${editorProvider.name} is not currently installed. Save and switch later.`
|
|
1059
|
-
: "Click a provider chip
|
|
1060
|
-
</
|
|
1061
|
-
</
|
|
1017
|
+
: "Click a provider chip to switch. Save to keep the draft."}
|
|
1018
|
+
</Text>
|
|
1019
|
+
</Box>
|
|
1062
1020
|
</>
|
|
1063
1021
|
) : (
|
|
1064
1022
|
<>
|
|
1065
1023
|
{activeTab?.lastError && (
|
|
1066
|
-
<
|
|
1024
|
+
<Box flexDirection="column" paddingX={1} paddingTop={1}>
|
|
1067
1025
|
{wrapTextLines(activeTab.lastError, detailTextWidth, 2).map((line, index) => (
|
|
1068
|
-
<
|
|
1069
|
-
<
|
|
1070
|
-
</
|
|
1026
|
+
<Box key={`error:${index}`} height={1}>
|
|
1027
|
+
<Text fg={colors.negative}>{line || " "}</Text>
|
|
1028
|
+
</Box>
|
|
1071
1029
|
))}
|
|
1072
|
-
</
|
|
1030
|
+
</Box>
|
|
1073
1031
|
)}
|
|
1074
1032
|
|
|
1075
1033
|
{activeTab?.lastWarning && !activeTab.lastError && (
|
|
1076
|
-
<
|
|
1034
|
+
<Box flexDirection="column" paddingX={1} paddingTop={1}>
|
|
1077
1035
|
{wrapTextLines(activeTab.lastWarning, detailTextWidth, 2).map((line, index) => (
|
|
1078
|
-
<
|
|
1079
|
-
<
|
|
1080
|
-
</
|
|
1036
|
+
<Box key={`warning:${index}`} height={1}>
|
|
1037
|
+
<Text fg={warningColor}>{line || " "}</Text>
|
|
1038
|
+
</Box>
|
|
1081
1039
|
))}
|
|
1082
|
-
</
|
|
1040
|
+
</Box>
|
|
1083
1041
|
)}
|
|
1084
1042
|
|
|
1085
|
-
{summaryLines.length > 0 && !activeTab.lastError && (
|
|
1086
|
-
<
|
|
1043
|
+
{activeTab && summaryLines.length > 0 && !activeTab.lastError && (
|
|
1044
|
+
<Box flexDirection="column" paddingX={1} paddingTop={activeTab.lastWarning ? 0 : 1}>
|
|
1087
1045
|
{summaryLines.map((line, index) => (
|
|
1088
|
-
<
|
|
1089
|
-
<
|
|
1090
|
-
</
|
|
1046
|
+
<Box key={`summary:${index}`} height={1}>
|
|
1047
|
+
<Text fg={colors.textDim}>{line || " "}</Text>
|
|
1048
|
+
</Box>
|
|
1091
1049
|
))}
|
|
1092
|
-
</
|
|
1050
|
+
</Box>
|
|
1093
1051
|
)}
|
|
1094
1052
|
|
|
1095
|
-
<
|
|
1053
|
+
<Box flexGrow={1} minHeight={contentHeight}>
|
|
1096
1054
|
{!activeTab ? (
|
|
1097
|
-
<
|
|
1098
|
-
<EmptyState title="No AI screeners yet." hint="
|
|
1099
|
-
</
|
|
1055
|
+
<Box padding={1} flexGrow={1}>
|
|
1056
|
+
<EmptyState title="No AI screeners yet." hint="Click + to create one." />
|
|
1057
|
+
</Box>
|
|
1100
1058
|
) : isRunningActiveTab && activeTab.results.length === 0 ? (
|
|
1101
|
-
<
|
|
1059
|
+
<Box padding={1} flexGrow={1}>
|
|
1102
1060
|
<Spinner label="Running AI screener..." />
|
|
1103
|
-
</
|
|
1061
|
+
</Box>
|
|
1104
1062
|
) : (
|
|
1105
|
-
<
|
|
1063
|
+
<TickerListTableView
|
|
1064
|
+
focused={focused}
|
|
1106
1065
|
columns={columns}
|
|
1107
1066
|
tickers={sortedTickers}
|
|
1108
1067
|
cursorSymbol={cursorSymbol}
|
|
1109
|
-
hoveredIdx={hoveredIdx}
|
|
1110
|
-
setHoveredIdx={setHoveredIdx}
|
|
1111
1068
|
setCursorSymbol={setCursorSymbol}
|
|
1112
1069
|
resolveCell={(column, ticker, financials) => {
|
|
1113
1070
|
if (column.id === "ticker") {
|
|
@@ -1123,40 +1080,38 @@ export function AiScreenerPane({ focused, width, height }: PaneProps) {
|
|
|
1123
1080
|
return getColumnValue(column, ticker, financials, columnContext);
|
|
1124
1081
|
}}
|
|
1125
1082
|
financialsMap={financialsMap}
|
|
1126
|
-
headerScrollRef={headerScrollRef}
|
|
1127
|
-
scrollRef={scrollRef}
|
|
1128
|
-
syncHeaderScroll={syncHeaderScroll}
|
|
1129
|
-
onBodyScrollActivity={handleBodyScrollActivity}
|
|
1130
1083
|
sortColumnId={activeSort.columnId}
|
|
1131
1084
|
sortDirection={activeSort.direction}
|
|
1132
1085
|
onHeaderClick={handleHeaderClick}
|
|
1086
|
+
onRootKeyDown={handleTableKeyDown}
|
|
1087
|
+
resetScrollKey={activeTab.id}
|
|
1133
1088
|
onRowActivate={(ticker) => {
|
|
1134
|
-
|
|
1089
|
+
pinTicker(ticker.metadata.ticker, { floating: true, paneType: "ticker-detail" });
|
|
1135
1090
|
}}
|
|
1136
1091
|
emptyTitle="No matches yet."
|
|
1137
|
-
emptyHint={promptDirty ? "Prompt changed. Refresh to rerun." : "
|
|
1092
|
+
emptyHint={promptDirty ? "Prompt changed. Refresh to rerun." : "Run this screener. Use PS to customize columns."}
|
|
1138
1093
|
/>
|
|
1139
1094
|
)}
|
|
1140
|
-
</
|
|
1095
|
+
</Box>
|
|
1141
1096
|
|
|
1142
|
-
<
|
|
1143
|
-
<
|
|
1144
|
-
</
|
|
1097
|
+
<Box height={1} paddingX={1}>
|
|
1098
|
+
<Text fg={colors.textDim}>{"\u2500".repeat(Math.max(width - 2, 0))}</Text>
|
|
1099
|
+
</Box>
|
|
1145
1100
|
|
|
1146
|
-
<
|
|
1101
|
+
<Box flexDirection="column" paddingX={1} minHeight={DETAIL_FOOTER_LINES}>
|
|
1147
1102
|
{paddedDetailLines.map((line, index) => (
|
|
1148
|
-
<
|
|
1149
|
-
<
|
|
1103
|
+
<Box key={`detail:${index}`} height={1}>
|
|
1104
|
+
<Text
|
|
1150
1105
|
fg={promptDirty ? warningColor : index === 0 && selectedResult ? colors.text : colors.textDim}
|
|
1151
1106
|
attributes={index === 0 && selectedResult ? TextAttributes.BOLD : 0}
|
|
1152
1107
|
>
|
|
1153
1108
|
{line || " "}
|
|
1154
|
-
</
|
|
1155
|
-
</
|
|
1109
|
+
</Text>
|
|
1110
|
+
</Box>
|
|
1156
1111
|
))}
|
|
1157
|
-
</
|
|
1112
|
+
</Box>
|
|
1158
1113
|
</>
|
|
1159
1114
|
)}
|
|
1160
|
-
</
|
|
1115
|
+
</Box>
|
|
1161
1116
|
);
|
|
1162
1117
|
}
|