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,4 +1,4 @@
|
|
|
1
|
-
import { RGBA } from "
|
|
1
|
+
import { RGBA } from "../../ui";
|
|
2
2
|
import type { ChartAxisMode, ChartColors, ChartIndicatorOverlays, Pixel, PixelBuffer, ChartRenderMode } from "./chart-types";
|
|
3
3
|
import type { ProjectedChartPoint } from "./chart-data";
|
|
4
4
|
import { formatCompactMarketPriceWithCurrency, formatMarketPrice, resolveAssetDisplayKind } from "../../utils/market-format";
|
|
@@ -29,6 +29,27 @@ const LAYER_DATA = 2;
|
|
|
29
29
|
const LAYER_OVERLAY = 2;
|
|
30
30
|
const LAYER_CROSSHAIR = 3;
|
|
31
31
|
|
|
32
|
+
function normalizeCount(value: number, min: number): number {
|
|
33
|
+
if (!Number.isFinite(value)) return min;
|
|
34
|
+
return Math.max(Math.floor(value), min);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function normalizeRenderDimensions(opts: RenderChartOptions) {
|
|
38
|
+
const width = normalizeCount(opts.width, 1);
|
|
39
|
+
const height = normalizeCount(opts.height, 1);
|
|
40
|
+
const volumeHeight = opts.showVolume
|
|
41
|
+
? Math.min(normalizeCount(opts.volumeHeight, 0), Math.max(height - 1, 0))
|
|
42
|
+
: 0;
|
|
43
|
+
const showVolume = opts.showVolume && volumeHeight > 0;
|
|
44
|
+
return {
|
|
45
|
+
width,
|
|
46
|
+
height,
|
|
47
|
+
showVolume,
|
|
48
|
+
volumeHeight,
|
|
49
|
+
chartRows: Math.max(height - volumeHeight, 1),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
32
53
|
// ---------------------------------------------------------------------------
|
|
33
54
|
// Braille character mapping
|
|
34
55
|
// Each terminal cell maps to a 2×4 dot grid. Unicode braille = U+2800 + bits.
|
|
@@ -70,14 +91,15 @@ export interface ResolvedChartPalette extends ChartColors {
|
|
|
70
91
|
|
|
71
92
|
type VolumeTrendMode = "previousClose" | "openClose";
|
|
72
93
|
|
|
73
|
-
function makeChunk(text: string, fgColor
|
|
74
|
-
|
|
94
|
+
function makeChunk(text: string, fgColor?: string, bgColor?: string): StyledChunk {
|
|
95
|
+
const chunk: StyledChunk = {
|
|
75
96
|
__isChunk: true,
|
|
76
97
|
text,
|
|
77
|
-
fg: RGBA.fromHex(fgColor),
|
|
78
|
-
bg: RGBA.fromHex(bgColor),
|
|
79
98
|
attributes: 0,
|
|
80
99
|
};
|
|
100
|
+
if (fgColor) chunk.fg = RGBA.fromHex(fgColor);
|
|
101
|
+
if (bgColor) chunk.bg = RGBA.fromHex(bgColor);
|
|
102
|
+
return chunk;
|
|
81
103
|
}
|
|
82
104
|
|
|
83
105
|
function blendHex(a: string, b: string, ratio: number): string {
|
|
@@ -125,11 +147,13 @@ export function resolveChartPalette(
|
|
|
125
147
|
// ---------------------------------------------------------------------------
|
|
126
148
|
|
|
127
149
|
export function createPixelBuffer(width: number, heightPixels: number): PixelBuffer {
|
|
150
|
+
const bufferWidth = normalizeCount(width, 0);
|
|
151
|
+
const bufferHeight = normalizeCount(heightPixels, 0);
|
|
128
152
|
const pixels: (Pixel | null)[][] = [];
|
|
129
|
-
for (let y = 0; y <
|
|
130
|
-
pixels.push(new Array(
|
|
153
|
+
for (let y = 0; y < bufferHeight; y++) {
|
|
154
|
+
pixels.push(new Array(bufferWidth).fill(null));
|
|
131
155
|
}
|
|
132
|
-
return { width, height:
|
|
156
|
+
return { width: bufferWidth, height: bufferHeight, pixels };
|
|
133
157
|
}
|
|
134
158
|
|
|
135
159
|
function setPixel(buf: PixelBuffer, x: number, y: number, color: string, layer: number) {
|
|
@@ -413,7 +437,7 @@ function drawOhlcChart(
|
|
|
413
437
|
// Volume bars
|
|
414
438
|
// ---------------------------------------------------------------------------
|
|
415
439
|
|
|
416
|
-
|
|
440
|
+
function drawVolumeBars(
|
|
417
441
|
buf: PixelBuffer,
|
|
418
442
|
points: ProjectedChartPoint[],
|
|
419
443
|
yTop: number,
|
|
@@ -483,7 +507,7 @@ export function drawGridLines(
|
|
|
483
507
|
// Braille buffer → styled terminal output
|
|
484
508
|
// ---------------------------------------------------------------------------
|
|
485
509
|
|
|
486
|
-
export function bufferToBrailleLines(buf: PixelBuffer
|
|
510
|
+
export function bufferToBrailleLines(buf: PixelBuffer): StyledContent[] {
|
|
487
511
|
const lines: StyledContent[] = [];
|
|
488
512
|
const termCols = Math.ceil(buf.width / 2);
|
|
489
513
|
const termRows = Math.ceil(buf.height / 4);
|
|
@@ -491,8 +515,8 @@ export function bufferToBrailleLines(buf: PixelBuffer, bgColor: string): StyledC
|
|
|
491
515
|
for (let row = 0; row < termRows; row++) {
|
|
492
516
|
const chunks: StyledChunk[] = [];
|
|
493
517
|
let runChar = "";
|
|
494
|
-
let runFg
|
|
495
|
-
let runBg
|
|
518
|
+
let runFg: string | undefined;
|
|
519
|
+
let runBg: string | undefined;
|
|
496
520
|
let runLen = 0;
|
|
497
521
|
|
|
498
522
|
const flushRun = () => {
|
|
@@ -529,13 +553,13 @@ export function bufferToBrailleLines(buf: PixelBuffer, bgColor: string): StyledC
|
|
|
529
553
|
}
|
|
530
554
|
|
|
531
555
|
let char: string;
|
|
532
|
-
let cellFg: string;
|
|
533
|
-
let cellBg: string;
|
|
556
|
+
let cellFg: string | undefined;
|
|
557
|
+
let cellBg: string | undefined;
|
|
534
558
|
|
|
535
559
|
if (topLayer < 0) {
|
|
536
560
|
char = " ";
|
|
537
|
-
cellFg =
|
|
538
|
-
cellBg =
|
|
561
|
+
cellFg = undefined;
|
|
562
|
+
cellBg = undefined;
|
|
539
563
|
} else {
|
|
540
564
|
// Only show dots from the highest layer — keeps lines thin in mixed
|
|
541
565
|
// cells (e.g. area chart where line and fill overlap).
|
|
@@ -550,7 +574,7 @@ export function bufferToBrailleLines(buf: PixelBuffer, bgColor: string): StyledC
|
|
|
550
574
|
}
|
|
551
575
|
|
|
552
576
|
cellFg = topColor;
|
|
553
|
-
cellBg =
|
|
577
|
+
cellBg = undefined;
|
|
554
578
|
}
|
|
555
579
|
|
|
556
580
|
if (char === runChar && cellFg === runFg && cellBg === runBg) {
|
|
@@ -599,7 +623,7 @@ function formatPercentAxisValue(value: number): string {
|
|
|
599
623
|
return `${prefix}${value.toFixed(decimals)}%`;
|
|
600
624
|
}
|
|
601
625
|
|
|
602
|
-
|
|
626
|
+
function formatPriceWithCurrency(
|
|
603
627
|
value: number,
|
|
604
628
|
currency = "USD",
|
|
605
629
|
assetCategory?: string,
|
|
@@ -722,12 +746,6 @@ export function formatCursorAxisValue(
|
|
|
722
746
|
|
|
723
747
|
const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
724
748
|
|
|
725
|
-
export function formatDate(date: Date | string | number): string {
|
|
726
|
-
const d = date instanceof Date ? date : new Date(date);
|
|
727
|
-
if (isNaN(d.getTime())) return "—";
|
|
728
|
-
return `${MONTHS[d.getMonth()]} ${d.getFullYear()}`;
|
|
729
|
-
}
|
|
730
|
-
|
|
731
749
|
export function formatDateShort(date: Date | string | number): string {
|
|
732
750
|
const d = date instanceof Date ? date : new Date(date);
|
|
733
751
|
if (isNaN(d.getTime())) return "—";
|
|
@@ -901,24 +919,25 @@ function writeAxisLabel(axis: string[], start: number, label: string) {
|
|
|
901
919
|
}
|
|
902
920
|
|
|
903
921
|
export function buildTimeAxis(dates: Array<Date | string | number>, width: number): string {
|
|
904
|
-
|
|
922
|
+
const axisWidth = normalizeCount(width, 0);
|
|
923
|
+
if (dates.length === 0 || axisWidth <= 0) return "";
|
|
905
924
|
|
|
906
925
|
const normalizedDates = dates.map((value) => (value instanceof Date ? value : new Date(value)));
|
|
907
926
|
const first = normalizedDates[0]!;
|
|
908
927
|
const last = normalizedDates[normalizedDates.length - 1]!;
|
|
909
928
|
const rawSpanMs = last.getTime() - first.getTime();
|
|
910
929
|
const spanMs = Math.max(rawSpanMs, 1);
|
|
911
|
-
const roughLabelCount = Math.max(Math.floor(
|
|
930
|
+
const roughLabelCount = Math.max(Math.floor(axisWidth / 10), 2);
|
|
912
931
|
const minGapMs = getMinPositiveGapMs(normalizedDates);
|
|
913
932
|
const effectiveStepMs = Math.max(spanMs / Math.max(roughLabelCount - 1, 1), minGapMs || 0);
|
|
914
933
|
const unit = resolveAxisLabelUnit(effectiveStepMs);
|
|
915
934
|
const allSameTimestamp = rawSpanMs === 0 && minGapMs === 0;
|
|
916
|
-
const axis = new Array(
|
|
935
|
+
const axis = new Array(axisWidth).fill(" ");
|
|
917
936
|
const minGap = unit === "year" || unit === "month" ? 2 : 1;
|
|
918
937
|
const idealLabelWidth = estimateAxisLabelWidth(unit, first, last);
|
|
919
938
|
const targetLabelCount = Math.min(
|
|
920
939
|
normalizedDates.length,
|
|
921
|
-
Math.max(Math.floor(
|
|
940
|
+
Math.max(Math.floor(axisWidth / (idealLabelWidth + minGap)), 2),
|
|
922
941
|
);
|
|
923
942
|
const candidateIndices = [...new Set(
|
|
924
943
|
Array.from({ length: targetLabelCount }, (_, index) => (
|
|
@@ -930,33 +949,33 @@ export function buildTimeAxis(dates: Array<Date | string | number>, width: numbe
|
|
|
930
949
|
|
|
931
950
|
const firstLabel = formatTimeAxisBoundaryLabel(first, unit, last);
|
|
932
951
|
if (allSameTimestamp) {
|
|
933
|
-
const centeredStart = resolveCenteredAxisLabelStart(firstLabel,
|
|
952
|
+
const centeredStart = resolveCenteredAxisLabelStart(firstLabel, axisWidth);
|
|
934
953
|
writeAxisLabel(axis, centeredStart, firstLabel);
|
|
935
954
|
return axis.join("");
|
|
936
955
|
}
|
|
937
956
|
|
|
938
|
-
const firstStart = resolveAxisLabelStart(0, firstLabel,
|
|
957
|
+
const firstStart = resolveAxisLabelStart(0, firstLabel, axisWidth);
|
|
939
958
|
writeAxisLabel(axis, firstStart, firstLabel);
|
|
940
959
|
const placedLabels = new Set<string>([firstLabel]);
|
|
941
960
|
|
|
942
961
|
let lastPlacedDate = first;
|
|
943
962
|
let lastEnd = firstStart + firstLabel.length - 1;
|
|
944
963
|
|
|
945
|
-
const lastPos =
|
|
964
|
+
const lastPos = axisWidth - 1;
|
|
946
965
|
const lastLabel = normalizedDates.length === 1
|
|
947
966
|
? firstLabel
|
|
948
967
|
: formatTimeAxisBoundaryLabel(last, unit, first);
|
|
949
|
-
const lastStart = resolveAxisLabelStart(lastPos, lastLabel,
|
|
968
|
+
const lastStart = resolveAxisLabelStart(lastPos, lastLabel, axisWidth);
|
|
950
969
|
const lastFits = normalizedDates.length === 1 || lastStart > lastEnd + minGap;
|
|
951
970
|
|
|
952
971
|
for (const index of candidateIndices.slice(1, -1)) {
|
|
953
972
|
const date = normalizedDates[index]!;
|
|
954
973
|
if (isNaN(date.getTime())) continue;
|
|
955
974
|
|
|
956
|
-
const pos = Math.round((index / (normalizedDates.length - 1)) * (
|
|
975
|
+
const pos = Math.round((index / (normalizedDates.length - 1)) * (axisWidth - 1));
|
|
957
976
|
const label = formatTimeAxisLabel(date, lastPlacedDate, unit);
|
|
958
977
|
if (placedLabels.has(label)) continue;
|
|
959
|
-
const start = resolveAxisLabelStart(pos, label,
|
|
978
|
+
const start = resolveAxisLabelStart(pos, label, axisWidth);
|
|
960
979
|
const end = start + label.length - 1;
|
|
961
980
|
|
|
962
981
|
if (label === axis.slice(start, end + 1).join("")) continue;
|
|
@@ -1120,6 +1139,7 @@ export function buildChartScene(
|
|
|
1120
1139
|
): ChartScene | null {
|
|
1121
1140
|
if (points.length === 0) return null;
|
|
1122
1141
|
|
|
1142
|
+
const dimensions = normalizeRenderDimensions(opts);
|
|
1123
1143
|
const dataMin = opts.mode === "candles" || opts.mode === "ohlc"
|
|
1124
1144
|
? Math.min(...points.map((point) => point.low))
|
|
1125
1145
|
: Math.min(...points.map((point) => point.close));
|
|
@@ -1129,43 +1149,42 @@ export function buildChartScene(
|
|
|
1129
1149
|
const indicatorValues = getIndicatorOverlayValues(opts.indicators);
|
|
1130
1150
|
const min = indicatorValues.length > 0 ? Math.min(dataMin, ...indicatorValues) : dataMin;
|
|
1131
1151
|
const max = indicatorValues.length > 0 ? Math.max(dataMax, ...indicatorValues) : dataMax;
|
|
1132
|
-
const activeIdx = getActivePointIndex(points.length,
|
|
1152
|
+
const activeIdx = getActivePointIndex(points.length, dimensions.width, opts.cursorX, opts.mode);
|
|
1133
1153
|
const activePoint = points[activeIdx]!;
|
|
1134
1154
|
const range = max - min || 1;
|
|
1135
|
-
const chartRows = opts.height - (opts.showVolume ? opts.volumeHeight : 0);
|
|
1136
1155
|
const cursorX = opts.cursorX === null
|
|
1137
1156
|
? null
|
|
1138
|
-
: Math.min(Math.max(opts.cursorX, 0), Math.max(
|
|
1157
|
+
: Math.min(Math.max(opts.cursorX, 0), Math.max(dimensions.width - 1, 0));
|
|
1139
1158
|
const cursorColumn = cursorX === null
|
|
1140
1159
|
? null
|
|
1141
1160
|
: Math.round(cursorX);
|
|
1142
1161
|
const cursorDotX = cursorX === null
|
|
1143
1162
|
? null
|
|
1144
|
-
: Math.round((cursorX / Math.max(
|
|
1163
|
+
: Math.round((cursorX / Math.max(dimensions.width - 1, 1)) * Math.max(dimensions.width * 2 - 1, 0));
|
|
1145
1164
|
const cursorY = cursorX === null
|
|
1146
1165
|
? null
|
|
1147
1166
|
: opts.cursorY !== null
|
|
1148
|
-
? Math.min(Math.max(opts.cursorY, 0), Math.max(chartRows - 1, 0))
|
|
1167
|
+
? Math.min(Math.max(opts.cursorY, 0), Math.max(dimensions.chartRows - 1, 0))
|
|
1149
1168
|
: Math.min(
|
|
1150
|
-
Math.max(Math.round((1 - (activePoint.close - min) / range) * Math.max(chartRows - 1, 0)), 0),
|
|
1151
|
-
Math.max(chartRows - 1, 0),
|
|
1169
|
+
Math.max(Math.round((1 - (activePoint.close - min) / range) * Math.max(dimensions.chartRows - 1, 0)), 0),
|
|
1170
|
+
Math.max(dimensions.chartRows - 1, 0),
|
|
1152
1171
|
);
|
|
1153
1172
|
const cursorRow = cursorY === null
|
|
1154
1173
|
? null
|
|
1155
1174
|
: Math.round(cursorY);
|
|
1156
1175
|
const crosshairPrice = cursorY === null
|
|
1157
1176
|
? null
|
|
1158
|
-
: max - (cursorY / Math.max(chartRows - 1, 1)) * range;
|
|
1177
|
+
: max - (cursorY / Math.max(dimensions.chartRows - 1, 1)) * range;
|
|
1159
1178
|
|
|
1160
1179
|
const timeAxisDates = opts.timeAxisDates ?? points.map((point) => point.date);
|
|
1161
1180
|
|
|
1162
1181
|
return {
|
|
1163
1182
|
points,
|
|
1164
|
-
width:
|
|
1165
|
-
height:
|
|
1166
|
-
showVolume:
|
|
1167
|
-
volumeHeight:
|
|
1168
|
-
chartRows,
|
|
1183
|
+
width: dimensions.width,
|
|
1184
|
+
height: dimensions.height,
|
|
1185
|
+
showVolume: dimensions.showVolume,
|
|
1186
|
+
volumeHeight: dimensions.volumeHeight,
|
|
1187
|
+
chartRows: dimensions.chartRows,
|
|
1169
1188
|
mode: opts.mode,
|
|
1170
1189
|
colors: opts.colors,
|
|
1171
1190
|
indicators: opts.indicators ?? null,
|
|
@@ -1178,7 +1197,7 @@ export function buildChartScene(
|
|
|
1178
1197
|
dateAtCursor: activePoint.date,
|
|
1179
1198
|
changeAtCursor: activePoint.close - points[0]!.close,
|
|
1180
1199
|
changePctAtCursor: points[0]!.close ? ((activePoint.close - points[0]!.close) / points[0]!.close) * 100 : 0,
|
|
1181
|
-
timeLabels: buildTimeAxis(timeAxisDates,
|
|
1200
|
+
timeLabels: buildTimeAxis(timeAxisDates, dimensions.width),
|
|
1182
1201
|
cursorX,
|
|
1183
1202
|
cursorY,
|
|
1184
1203
|
cursorColumn,
|
|
@@ -1246,16 +1265,16 @@ export function renderChart(
|
|
|
1246
1265
|
};
|
|
1247
1266
|
}
|
|
1248
1267
|
|
|
1249
|
-
const { width, colors: palette, mode } =
|
|
1268
|
+
const { width, height, showVolume, volumeHeight, colors: palette, mode } = scene;
|
|
1250
1269
|
const axisMode = opts.axisMode ?? "price";
|
|
1251
1270
|
const currency = opts.currency ?? "USD";
|
|
1252
1271
|
const assetCategory = opts.assetCategory;
|
|
1253
1272
|
|
|
1254
1273
|
// Braille resolution: 2 dot-columns per terminal column, 4 dot-rows per terminal row
|
|
1255
1274
|
const dotWidth = width * 2;
|
|
1256
|
-
const volTermRows =
|
|
1257
|
-
const chartTermRows =
|
|
1258
|
-
const totalDotH =
|
|
1275
|
+
const volTermRows = showVolume ? volumeHeight : 0;
|
|
1276
|
+
const chartTermRows = height - volTermRows;
|
|
1277
|
+
const totalDotH = height * 4;
|
|
1259
1278
|
const chartDotBottom = chartTermRows * 4 - 1;
|
|
1260
1279
|
const volDotTop = chartTermRows * 4;
|
|
1261
1280
|
const volDotBottom = totalDotH - 1;
|
|
@@ -1297,7 +1316,7 @@ export function renderChart(
|
|
|
1297
1316
|
break;
|
|
1298
1317
|
}
|
|
1299
1318
|
|
|
1300
|
-
if (
|
|
1319
|
+
if (showVolume && volTermRows > 0) {
|
|
1301
1320
|
drawVolumeBars(
|
|
1302
1321
|
buf,
|
|
1303
1322
|
points,
|
|
@@ -1318,7 +1337,7 @@ export function renderChart(
|
|
|
1318
1337
|
const { activePoint, priceAtCursor, crosshairPrice, dateAtCursor, changeAtCursor, changePctAtCursor } = scene;
|
|
1319
1338
|
|
|
1320
1339
|
if (scene.cursorDotX !== null) {
|
|
1321
|
-
drawCrosshair(buf, scene.cursorDotX, 0,
|
|
1340
|
+
drawCrosshair(buf, scene.cursorDotX, 0, showVolume ? volDotBottom : chartDotBottom, palette.crosshairColor);
|
|
1322
1341
|
}
|
|
1323
1342
|
|
|
1324
1343
|
const axisLabelsByRow = new Map<number, string>();
|
|
@@ -1332,7 +1351,7 @@ export function renderChart(
|
|
|
1332
1351
|
const timeAxisDates = opts.timeAxisDates ?? points.map((point) => point.date);
|
|
1333
1352
|
|
|
1334
1353
|
return {
|
|
1335
|
-
lines: bufferToBrailleLines(buf
|
|
1354
|
+
lines: bufferToBrailleLines(buf),
|
|
1336
1355
|
axisLabels: [...axisLabelsByRow.entries()].map(([row, label]) => ({ row, label })),
|
|
1337
1356
|
timeLabels: buildTimeAxis(timeAxisDates, width),
|
|
1338
1357
|
activePoint,
|
|
@@ -1348,47 +1367,3 @@ export function renderChart(
|
|
|
1348
1367
|
pixelBuffer: buf,
|
|
1349
1368
|
};
|
|
1350
1369
|
}
|
|
1351
|
-
|
|
1352
|
-
// ---------------------------------------------------------------------------
|
|
1353
|
-
// Pixel buffer → RGBA conversion for Kitty graphics protocol
|
|
1354
|
-
// ---------------------------------------------------------------------------
|
|
1355
|
-
|
|
1356
|
-
function parseHex(hex: string): [number, number, number] {
|
|
1357
|
-
const h = hex.replace("#", "");
|
|
1358
|
-
return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)];
|
|
1359
|
-
}
|
|
1360
|
-
|
|
1361
|
-
/**
|
|
1362
|
-
* Convert a PixelBuffer to RGBA pixel data for use with drawSuperSampleBuffer.
|
|
1363
|
-
* Each pixel in the buffer maps to one RGBA pixel in the output.
|
|
1364
|
-
*/
|
|
1365
|
-
export function pixelBufferToRGBA(buf: PixelBuffer, bgColor: string): Uint8Array {
|
|
1366
|
-
const [bgR, bgG, bgB] = parseHex(bgColor);
|
|
1367
|
-
const data = new Uint8Array(buf.width * buf.height * 4);
|
|
1368
|
-
const colorCache = new Map<string, [number, number, number]>();
|
|
1369
|
-
|
|
1370
|
-
for (let y = 0; y < buf.height; y++) {
|
|
1371
|
-
for (let x = 0; x < buf.width; x++) {
|
|
1372
|
-
const offset = (y * buf.width + x) * 4;
|
|
1373
|
-
const pixel = buf.pixels[y]?.[x] ?? null;
|
|
1374
|
-
if (pixel) {
|
|
1375
|
-
let rgb = colorCache.get(pixel.color);
|
|
1376
|
-
if (!rgb) {
|
|
1377
|
-
rgb = parseHex(pixel.color);
|
|
1378
|
-
colorCache.set(pixel.color, rgb);
|
|
1379
|
-
}
|
|
1380
|
-
data[offset] = rgb[0];
|
|
1381
|
-
data[offset + 1] = rgb[1];
|
|
1382
|
-
data[offset + 2] = rgb[2];
|
|
1383
|
-
data[offset + 3] = 255;
|
|
1384
|
-
} else {
|
|
1385
|
-
data[offset] = bgR;
|
|
1386
|
-
data[offset + 1] = bgG;
|
|
1387
|
-
data[offset + 2] = bgB;
|
|
1388
|
-
data[offset + 3] = 255;
|
|
1389
|
-
}
|
|
1390
|
-
}
|
|
1391
|
-
}
|
|
1392
|
-
|
|
1393
|
-
return data;
|
|
1394
|
-
}
|
|
@@ -9,7 +9,7 @@ export interface ChartResolutionSupport {
|
|
|
9
9
|
|
|
10
10
|
export const TIME_RANGE_ORDER: TimeRange[] = ["1D", "1W", "1M", "3M", "6M", "1Y", "5Y", "ALL"];
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
const CHART_RESOLUTION_ORDER: ChartResolution[] = [
|
|
13
13
|
"auto",
|
|
14
14
|
"1m",
|
|
15
15
|
"5m",
|
|
@@ -22,7 +22,7 @@ export const CHART_RESOLUTION_ORDER: ChartResolution[] = [
|
|
|
22
22
|
"1mo",
|
|
23
23
|
];
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
const CHART_RESOLUTION_LABELS: Record<ChartResolution, string> = {
|
|
26
26
|
auto: "AUTO",
|
|
27
27
|
"1m": "1M",
|
|
28
28
|
"5m": "5M",
|
|
@@ -35,7 +35,7 @@ export const CHART_RESOLUTION_LABELS: Record<ChartResolution, string> = {
|
|
|
35
35
|
"1mo": "1MO",
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
const RANGE_PRESET_RESOLUTION: Record<TimeRange, ManualChartResolution> = {
|
|
39
39
|
"1D": "1m",
|
|
40
40
|
"1W": "5m",
|
|
41
41
|
"1M": "15m",
|
|
@@ -46,7 +46,7 @@ export const RANGE_PRESET_RESOLUTION: Record<TimeRange, ManualChartResolution> =
|
|
|
46
46
|
"ALL": "1mo",
|
|
47
47
|
};
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
const RANGE_PRELOAD_BUFFER: Record<TimeRange, TimeRange> = {
|
|
50
50
|
"1D": "1W",
|
|
51
51
|
"1W": "1M",
|
|
52
52
|
"1M": "3M",
|
|
@@ -113,11 +113,11 @@ function compareTimeRange(left: TimeRange, right: TimeRange): number {
|
|
|
113
113
|
return getTimeRangeIndex(left) - getTimeRangeIndex(right);
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
|
|
116
|
+
function isChartResolution(value: unknown): value is ChartResolution {
|
|
117
117
|
return CHART_RESOLUTION_ORDER.includes(value as ChartResolution);
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
|
|
120
|
+
function isManualChartResolution(value: unknown): value is ManualChartResolution {
|
|
121
121
|
return value !== "auto" && CHART_RESOLUTION_ORDER.includes(value as ChartResolution);
|
|
122
122
|
}
|
|
123
123
|
|
|
@@ -137,7 +137,7 @@ function getManualResolutionOrderIndex(resolution: ManualChartResolution): numbe
|
|
|
137
137
|
return CHART_RESOLUTION_ORDER.indexOf(resolution);
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
|
|
140
|
+
function getPresetBufferRange(range: TimeRange): TimeRange {
|
|
141
141
|
return RANGE_PRELOAD_BUFFER[range];
|
|
142
142
|
}
|
|
143
143
|
|
|
@@ -151,7 +151,7 @@ export function sortChartResolutions<T extends ChartResolution>(resolutions: rea
|
|
|
151
151
|
));
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
|
|
154
|
+
function normalizeManualChartResolutions(resolutions: readonly string[]): ManualChartResolution[] {
|
|
155
155
|
const seen = new Set<ManualChartResolution>();
|
|
156
156
|
for (const resolution of resolutions) {
|
|
157
157
|
if (isManualChartResolution(resolution)) {
|
|
@@ -169,7 +169,7 @@ export function intersectChartResolutions(resolutionSets: Array<readonly string[
|
|
|
169
169
|
);
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
-
|
|
172
|
+
function sortChartResolutionSupport(support: readonly ChartResolutionSupport[]): ChartResolutionSupport[] {
|
|
173
173
|
return [...support].sort((left, right) => (
|
|
174
174
|
CHART_RESOLUTION_ORDER.indexOf(left.resolution) - CHART_RESOLUTION_ORDER.indexOf(right.resolution)
|
|
175
175
|
));
|
|
@@ -223,7 +223,7 @@ export function getSupportMaxRange(
|
|
|
223
223
|
return normalizeChartResolutionSupport(support).find((entry) => entry.resolution === resolution)?.maxRange ?? null;
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
-
|
|
226
|
+
function minTimeRange(left: TimeRange, right: TimeRange): TimeRange {
|
|
227
227
|
return compareTimeRange(left, right) <= 0 ? left : right;
|
|
228
228
|
}
|
|
229
229
|
|
|
@@ -32,12 +32,13 @@ describe("chart-scroll", () => {
|
|
|
32
32
|
expect(result.remainder).toBeCloseTo(-0.2);
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
-
test("
|
|
35
|
+
test("keeps keyboard pan small while matching drag to the visible window", () => {
|
|
36
36
|
expect(getKeyboardPanCellCount(100)).toBe(2);
|
|
37
|
-
expect(getDragPanPointDelta(10, 100, 200)).toBe(
|
|
38
|
-
expect(getDragPanWindowRatio(10, 100)).toBeCloseTo(0.
|
|
39
|
-
expect(resolveDragPanOffset(20, 10, 100, 200, 40)).toBe(
|
|
40
|
-
expect(resolveDragPanOffset(
|
|
37
|
+
expect(getDragPanPointDelta(10, 100, 200)).toBe(20);
|
|
38
|
+
expect(getDragPanWindowRatio(10, 100)).toBeCloseTo(0.1);
|
|
39
|
+
expect(resolveDragPanOffset(20, 10, 100, 200, 40)).toBe(40);
|
|
40
|
+
expect(resolveDragPanOffset(20, -10, 100, 200, 40)).toBe(0);
|
|
41
|
+
expect(resolveDragPanOffset(38, 10, 100, 200, 40)).toBe(40);
|
|
41
42
|
});
|
|
42
43
|
|
|
43
44
|
test("resolves wheel direction, cell delta, remainder, and ratio together", () => {
|
|
@@ -5,7 +5,7 @@ export type ChartScrollDirection = "up" | "down" | "left" | "right";
|
|
|
5
5
|
|
|
6
6
|
const KEYBOARD_PAN_WIDTH_RATIO = 0.02;
|
|
7
7
|
const SCROLL_PAN_WIDTH_RATIO = 0.005;
|
|
8
|
-
const DRAG_PAN_VISIBLE_RATIO =
|
|
8
|
+
const DRAG_PAN_VISIBLE_RATIO = 1;
|
|
9
9
|
|
|
10
10
|
interface BufferExpansionViewState {
|
|
11
11
|
activePreset: TimeRange | null;
|
|
@@ -45,7 +45,7 @@ export function resolveDragPanOffset(
|
|
|
45
45
|
maxPanOffset: number,
|
|
46
46
|
): number {
|
|
47
47
|
return clamp(
|
|
48
|
-
startPanOffset
|
|
48
|
+
startPanOffset + getDragPanPointDelta(deltaCells, chartWidth, visibleCount),
|
|
49
49
|
0,
|
|
50
50
|
Math.max(maxPanOffset, 0),
|
|
51
51
|
);
|
|
@@ -10,7 +10,6 @@ export type ComparisonChartRenderMode = "area" | "line";
|
|
|
10
10
|
|
|
11
11
|
export const TIME_RANGES: TimeRange[] = ["1D", "1W", "1M", "3M", "6M", "1Y", "5Y", "ALL"];
|
|
12
12
|
export const CHART_RENDER_MODES: ChartRenderMode[] = ["area", "line", "candles", "ohlc"];
|
|
13
|
-
export const CHART_AXIS_MODES: ChartAxisMode[] = ["price", "percent"];
|
|
14
13
|
export const CHART_RENDERER_PREFERENCES: ChartRendererPreference[] = ["auto", "kitty", "braille"];
|
|
15
14
|
export const COMPARISON_RENDER_MODES: ComparisonChartRenderMode[] = ["area", "line"];
|
|
16
15
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
|
|
1
2
|
const MIN_VISIBLE_POINTS = 2;
|
|
2
3
|
export const RIGHT_EDGE_ANCHOR_RATIO = 1;
|
|
3
4
|
|
|
@@ -5,7 +6,7 @@ function clamp(value: number, min: number, max: number): number {
|
|
|
5
6
|
return Math.max(min, Math.min(max, value));
|
|
6
7
|
}
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
function getMaxChartZoom(totalPoints: number): number {
|
|
9
10
|
if (totalPoints <= 0) return 1;
|
|
10
11
|
return Math.max(1, totalPoints / MIN_VISIBLE_POINTS);
|
|
11
12
|
}
|
|
@@ -349,6 +349,43 @@ describe("renderChart", () => {
|
|
|
349
349
|
expect(scene?.max).toBe(18);
|
|
350
350
|
});
|
|
351
351
|
|
|
352
|
+
test("normalizes fractional web dimensions before drawing volume bars", () => {
|
|
353
|
+
const projection = projectChartData(chartFixture, 12, "area", false);
|
|
354
|
+
const result = renderChart(projection.points, {
|
|
355
|
+
width: 12.5,
|
|
356
|
+
height: 7.3,
|
|
357
|
+
showVolume: true,
|
|
358
|
+
volumeHeight: 3,
|
|
359
|
+
cursorX: null,
|
|
360
|
+
cursorY: null,
|
|
361
|
+
mode: projection.effectiveMode,
|
|
362
|
+
colors: palette,
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
expect(result.lines).toHaveLength(7);
|
|
366
|
+
expect(result.pixelBuffer?.width).toBe(24);
|
|
367
|
+
expect(result.pixelBuffer?.height).toBe(28);
|
|
368
|
+
expect(result.axisLabels.every((entry) => Number.isInteger(entry.row))).toBe(true);
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
test("keeps terminal chart cells transparent", () => {
|
|
372
|
+
const projection = projectChartData(chartFixture, 12, "area", false);
|
|
373
|
+
const result = renderChart(projection.points, {
|
|
374
|
+
width: 12,
|
|
375
|
+
height: 5,
|
|
376
|
+
showVolume: false,
|
|
377
|
+
volumeHeight: 0,
|
|
378
|
+
cursorX: null,
|
|
379
|
+
cursorY: null,
|
|
380
|
+
mode: projection.effectiveMode,
|
|
381
|
+
colors: palette,
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
const chunks = result.lines.flatMap((line) => line.chunks);
|
|
385
|
+
expect(chunks.some((chunk) => chunk.text.trim().length > 0)).toBe(true);
|
|
386
|
+
expect(chunks.every((chunk) => chunk.bg === undefined)).toBe(true);
|
|
387
|
+
});
|
|
388
|
+
|
|
352
389
|
test("keeps one decimal on zoomed equity axes even when whole-dollar ticks are distinct", () => {
|
|
353
390
|
const mediumRangeFixture: PricePoint[] = [
|
|
354
391
|
{ date: new Date("2024-01-02T09:30:00Z"), close: 233.82, volume: 100 },
|
|
@@ -6,7 +6,6 @@ import type {
|
|
|
6
6
|
ComparisonChartViewState,
|
|
7
7
|
TimeRange,
|
|
8
8
|
} from "./chart-types";
|
|
9
|
-
import { RANGE_DAYS } from "./chart-types";
|
|
10
9
|
import { getVisiblePointCount, resolveAnchoredChartZoom } from "./chart-viewport";
|
|
11
10
|
|
|
12
11
|
export interface ComparisonProjectedPoint {
|
|
@@ -26,14 +25,14 @@ export interface ComparisonProjectedSeries {
|
|
|
26
25
|
points: ComparisonProjectedPoint[];
|
|
27
26
|
}
|
|
28
27
|
|
|
29
|
-
|
|
28
|
+
interface ComparisonVisibleWindow {
|
|
30
29
|
dates: Date[];
|
|
31
30
|
startIdx: number;
|
|
32
31
|
endIdx: number;
|
|
33
32
|
totalDates: number;
|
|
34
33
|
}
|
|
35
34
|
|
|
36
|
-
|
|
35
|
+
interface ComparisonAxisModeResolution {
|
|
37
36
|
requestedAxisMode: ChartAxisMode;
|
|
38
37
|
effectiveAxisMode: ChartAxisMode;
|
|
39
38
|
warning: string | null;
|
|
@@ -64,19 +63,6 @@ function normalizeSeriesPoints(points: PricePoint[]): PricePoint[] {
|
|
|
64
63
|
));
|
|
65
64
|
}
|
|
66
65
|
|
|
67
|
-
export function filterComparisonSeriesByTimeRange(
|
|
68
|
-
series: ComparisonChartSeries[],
|
|
69
|
-
range: TimeRange,
|
|
70
|
-
): ComparisonChartSeries[] {
|
|
71
|
-
return series.map((entry) => {
|
|
72
|
-
const points = normalizeSeriesPoints(entry.points);
|
|
73
|
-
if (range === "ALL" || points.length <= RANGE_DAYS[range]) {
|
|
74
|
-
return { ...entry, points };
|
|
75
|
-
}
|
|
76
|
-
return { ...entry, points: points.slice(-RANGE_DAYS[range]) };
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
|
|
80
66
|
function getUniqueSortedDates(series: ComparisonChartSeries[]): Date[] {
|
|
81
67
|
const byTimestamp = new Map<number, Date>();
|
|
82
68
|
|
|
@@ -185,7 +171,7 @@ function buildSeriesMap(points: PricePoint[]): Map<number, number> {
|
|
|
185
171
|
return map;
|
|
186
172
|
}
|
|
187
173
|
|
|
188
|
-
|
|
174
|
+
function resolveComparisonAxisMode(
|
|
189
175
|
requestedAxisMode: ChartAxisMode,
|
|
190
176
|
series: ComparisonChartSeries[],
|
|
191
177
|
visibleDates: Date[],
|