gloomberb 0.11.2 → 0.12.0
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 +7 -4
- package/README.zh-CN.md +4 -2
- package/package.json +28 -1
- package/src/api-client/auth.ts +10 -0
- package/src/api-client/data.ts +108 -1
- package/src/api-client/index.test.ts +127 -0
- package/src/api-client/index.ts +134 -1
- package/src/api-client/normalizers.ts +39 -0
- package/src/api-client/paths.ts +85 -1
- package/src/api-client/types.ts +220 -0
- package/src/app/pane-runtime/index.tsx +15 -12
- package/src/app/pane-runtime/layout-placement.test.ts +27 -0
- package/src/app/pane-runtime/layout-placement.ts +13 -0
- package/src/app/pane-runtime/plugin-bindings.ts +62 -0
- package/src/app.tsx +60 -18
- package/src/architecture/browser-entry-graph.test.ts +96 -0
- package/src/brokers/profile-form.test.ts +25 -27
- package/src/brokers/test-broker.ts +68 -0
- package/src/capabilities/chart-series.ts +3 -2
- package/src/cli/commands/market.ts +2 -2
- package/src/cli/commands/plugins.ts +39 -11
- package/src/cli/desktop-pane-shot.ts +285 -16
- package/src/cli/index.ts +1 -1
- package/src/cli/pane-functions/capabilities.ts +61 -31
- package/src/cli/pane-functions/catalog.ts +16 -4
- package/src/cli/pane-functions/discovery.ts +1 -0
- package/src/cli/pane-functions/headless.test.ts +247 -0
- package/src/cli/pane-functions/headless.ts +423 -0
- package/src/cli/pane-functions/index.test.ts +9 -0
- package/src/cli/pane-functions/index.ts +6 -2
- package/src/cli/pane-functions/options.ts +27 -2
- package/src/cli/pane-functions/report.ts +4 -0
- package/src/cli/pane-functions/resolver.ts +26 -8
- package/src/cli/pane-functions/screenshot.test.ts +76 -0
- package/src/cli/pane-functions/screenshot.ts +236 -20
- package/src/cli/registry.ts +0 -22
- package/src/cli/restore-plugins.ts +34 -0
- package/src/cli/result.ts +7 -10
- package/src/components/chart/{static/chart → composite}/axis-overlays.test.tsx +3 -3
- package/src/components/chart/{static/chart → composite}/axis-overlays.tsx +6 -11
- package/src/components/chart/{static/chart → composite}/bitmap.ts +17 -3
- package/src/components/chart/composite/composite-chart.test.tsx +68 -20
- package/src/components/chart/composite/composite-chart.tsx +301 -204
- package/src/components/chart/composite/format.ts +8 -2
- package/src/components/chart/composite/interactions.test.ts +68 -102
- package/src/components/chart/composite/interactions.ts +214 -264
- package/src/components/chart/composite/price-axis-labels.test.ts +21 -0
- package/src/components/chart/{price-axis-labels.tsx → composite/price-axis-labels.tsx} +9 -4
- package/src/components/chart/composite/scene.ts +57 -3
- package/src/components/chart/composite/text-renderer.ts +3 -2
- package/src/components/chart/composite/time-scale.ts +138 -57
- package/src/components/chart/composite/types.ts +25 -1
- package/src/components/chart/core/data.ts +9 -13
- package/src/components/chart/core/date-window.ts +7 -1
- package/src/components/chart/core/pointer.ts +3 -0
- package/src/components/chart/core/types.ts +0 -45
- package/src/components/chart/native/chart-rasterizer.ts +0 -1
- package/src/components/chart/native/kitty/index.test.ts +0 -188
- package/src/components/chart/native/raster/crosshair.ts +6 -5
- package/src/components/chart/native/raster/primitives.ts +0 -23
- package/src/components/chart/native/renderer-selection.ts +81 -31
- package/src/components/chart/static/{chart/surface.test.tsx → chart-surface.test.tsx} +28 -80
- package/src/components/chart/static/chart-surface.tsx +150 -0
- package/src/components/chart/static/index.ts +8 -0
- package/src/components/chart/static/multi-line-chart-surface.tsx +51 -166
- package/src/components/chart/static/scatter-chart-surface.test.ts +30 -0
- package/src/components/chart/static/scatter-chart-surface.tsx +135 -34
- package/src/components/chart/static/series.ts +49 -0
- package/src/components/command-bar/action-runtime.ts +1 -3
- package/src/components/command-bar/assist/model.test.ts +17 -2
- package/src/components/command-bar/assist/model.ts +40 -9
- package/src/components/command-bar/commands/direct/index.ts +1 -1
- package/src/components/command-bar/commands/direct/runtime.ts +1 -1
- package/src/components/command-bar/commands/registry.ts +0 -11
- package/src/components/command-bar/helpers.test.ts +2 -2
- package/src/components/command-bar/helpers.ts +2 -6
- package/src/components/command-bar/keyboard-shortcuts.ts +0 -11
- package/src/components/command-bar/list/badge.test.ts +54 -0
- package/src/components/command-bar/list/badge.ts +62 -0
- package/src/components/command-bar/list/model.test.ts +103 -0
- package/src/components/command-bar/list/model.ts +61 -4
- package/src/components/command-bar/list/view.test.tsx +113 -0
- package/src/components/command-bar/list/view.tsx +147 -131
- package/src/components/command-bar/pane-templates/workflow.ts +1 -1
- package/src/components/command-bar/panel/body.tsx +51 -65
- package/src/components/command-bar/panel/index.tsx +48 -13
- package/src/components/command-bar/panel/layout.test.ts +139 -0
- package/src/components/command-bar/panel/layout.ts +65 -28
- package/src/components/command-bar/panel/native-surface.ts +24 -0
- package/src/components/command-bar/panel/prompt-binding.ts +54 -0
- package/src/components/command-bar/panel/runtime.ts +6 -3
- package/src/components/command-bar/panel/state.ts +41 -7
- package/src/components/command-bar/panel/types.ts +7 -1
- package/src/components/command-bar/routes/root/provider-search.ts +34 -15
- package/src/components/command-bar/routes/root/results.test.ts +161 -0
- package/src/components/command-bar/routes/root/results.ts +30 -28
- package/src/components/command-bar/routes/root/runtime.ts +37 -20
- package/src/components/command-bar/routes/root/search-providers.test.tsx +224 -0
- package/src/components/command-bar/routes/root/search-providers.ts +205 -0
- package/src/components/command-bar/routes/root/selection.ts +1 -12
- package/src/components/command-bar/routes/root/shortcuts.test.ts +8 -8
- package/src/components/command-bar/routes/ticker-search/actions.ts +10 -9
- package/src/components/command-bar/routes/ticker-search/results.test.ts +54 -1
- package/src/components/command-bar/routes/ticker-search/results.ts +60 -19
- package/src/components/command-bar/routes/ticker-search/route.ts +1 -0
- package/src/components/command-bar/routing/actions.ts +1 -81
- package/src/components/command-bar/routing/launch-request.ts +1 -1
- package/src/components/command-bar/routing/list-state.ts +6 -21
- package/src/components/command-bar/selection-runtime.ts +1 -1
- package/src/components/command-bar/surface/assist.test.tsx +14 -10
- package/src/components/command-bar/surface/environment.ts +8 -1
- package/src/components/command-bar/surface/index.test.tsx +84 -68
- package/src/components/command-bar/surface/index.tsx +33 -36
- package/src/components/command-bar/surface/test-harness.tsx +15 -3
- package/src/components/command-bar/theme-picker.tsx +43 -15
- package/src/components/command-bar/view-model.test.ts +53 -8
- package/src/components/command-bar/view-model.ts +43 -14
- package/src/components/command-bar/workflow/tickers.ts +1 -0
- package/src/components/command-bar/workflow/types.ts +1 -1
- package/src/components/data-table/export.test.ts +21 -0
- package/src/components/data-table/export.ts +21 -0
- package/src/components/data-table/view.test.tsx +45 -6
- package/src/components/data-table/view.tsx +55 -9
- package/src/components/index.ts +10 -2
- package/src/components/input-search-bar.test.tsx +36 -2
- package/src/components/input-search-bar.tsx +18 -1
- package/src/components/layout/header.test.tsx +57 -10
- package/src/components/layout/header.tsx +292 -149
- package/src/components/layout/market-summary.test.ts +81 -0
- package/src/components/layout/market-summary.ts +109 -0
- package/src/components/layout/shell/chrome.ts +96 -1
- package/src/components/layout/shell/index.tsx +11 -3
- package/src/components/layout/shell/menu.ts +9 -0
- package/src/components/layout/shell/pane/actions.ts +29 -0
- package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
- package/src/components/layout/shell/shortcuts.ts +3 -0
- package/src/components/layout/status-bar.test.tsx +9 -8
- package/src/components/layout/status-bar.tsx +85 -54
- package/src/components/onboarding/onboarding-frame.tsx +2 -26
- package/src/components/price-sparkline/index.ts +61 -34
- package/src/components/price-sparkline/view.tsx +1 -1
- package/src/components/sign-in-gate.test.tsx +128 -0
- package/src/components/sign-in-gate.tsx +149 -0
- package/src/components/speedometer-gauge/desktop.tsx +2 -12
- package/src/components/ticker/badge/list.tsx +17 -3
- package/src/components/ticker/list-table-view.tsx +6 -3
- package/src/components/ui/data-table/index.tsx +22 -4
- package/src/components/ui/data-table/opentui/index.tsx +165 -120
- package/src/components/ui/frame.tsx +28 -0
- package/src/components/ui/index.ts +3 -0
- package/src/components/ui/native-select.tsx +20 -10
- package/src/components/ui/select-button.tsx +138 -0
- package/src/components/ui/status.tsx +2 -2
- package/src/components/ui/table-layout.ts +1 -1
- package/src/components/use-double-click-activation.ts +7 -3
- package/src/core/app-services.ts +3 -3
- package/src/data/config/chart-settings.ts +4 -4
- package/src/data/config/layout.ts +12 -6
- package/src/data/config/store/index.test.ts +97 -0
- package/src/data/config/store/migrations.ts +41 -0
- package/src/data/config/store/normalize.ts +5 -3
- package/src/i18n/es.ts +6 -2
- package/src/i18n/ja.ts +6 -2
- package/src/i18n/ko.ts +6 -2
- package/src/i18n/zh-cn.ts +6 -2
- package/src/i18n/zh-tw.ts +6 -2
- package/src/layout-marketplace/gallery-terminal.tsx +126 -96
- package/src/layout-marketplace/gallery.test.tsx +65 -1
- package/src/layout-marketplace/pane-imagery.ts +1 -0
- package/src/layout-marketplace/payload.test.ts +17 -0
- package/src/layout-marketplace/payload.ts +40 -5
- package/src/market-data/coordinator/index.ts +3 -2
- package/src/market-data/coordinator/quotes.ts +173 -66
- package/src/market-data/coordinator/subscriptions.test.ts +215 -37
- package/src/pane-settings.ts +1 -1
- package/src/plugins/builtin/analytics/pane-model.ts +1 -1
- package/src/plugins/builtin/analytics/view.tsx +1 -1
- package/src/plugins/builtin/broker-manager/index.test.tsx +3 -2
- package/src/plugins/builtin/broker-manager/model.test.ts +2 -2
- package/src/plugins/builtin/chart-composer/catalog-inventory.ts +16 -2
- package/src/plugins/builtin/chart-composer/cli-options.ts +12 -9
- package/src/plugins/builtin/chart-composer/pane.tsx +22 -8
- package/src/plugins/builtin/chart-composer/settings.ts +2 -13
- package/src/plugins/builtin/chat/content/composer.tsx +8 -4
- package/src/plugins/builtin/cloud/auth-dialog.tsx +14 -228
- package/src/plugins/builtin/cloud/auth-form.tsx +277 -0
- package/src/plugins/builtin/cloud/plugin.tsx +1 -0
- package/src/plugins/builtin/composite-plugins.ts +16 -1
- package/src/plugins/builtin/correlation/relationship/model.ts +1 -1
- package/src/plugins/builtin/correlation/relationship/pane.tsx +2 -6
- package/src/plugins/builtin/correlation/relationship/view-model.ts +2 -2
- package/src/plugins/builtin/credit-conditions/index.test.tsx +12 -6
- package/src/plugins/builtin/credit-conditions/index.tsx +2 -21
- package/src/plugins/builtin/dividend-yield/index.tsx +1 -0
- package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
- package/src/plugins/builtin/earnings/index.tsx +1 -0
- package/src/plugins/builtin/earnings-calls/data.ts +223 -0
- package/src/plugins/builtin/earnings-calls/format.ts +49 -0
- package/src/plugins/builtin/earnings-calls/index.tsx +69 -0
- package/src/plugins/builtin/earnings-calls/pane.tsx +756 -0
- package/src/plugins/builtin/earnings-calls/prose.test.ts +36 -0
- package/src/plugins/builtin/earnings-calls/prose.ts +59 -0
- package/src/plugins/builtin/earnings-calls/transcript-view.tsx +334 -0
- package/src/plugins/builtin/econ/detail-view.tsx +1 -2
- package/src/plugins/builtin/econ/fred-series-map.ts +16 -1
- package/src/plugins/builtin/econ/index.tsx +3 -1
- package/src/plugins/builtin/econ-statistics/cache.ts +4 -0
- package/src/plugins/builtin/econ-statistics/client.ts +92 -0
- package/src/plugins/builtin/econ-statistics/defs.ts +66 -0
- package/src/plugins/builtin/econ-statistics/detail.tsx +154 -0
- package/src/plugins/builtin/econ-statistics/headless.test.ts +54 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +171 -0
- package/src/plugins/builtin/econ-statistics/index.tsx +54 -0
- package/src/plugins/builtin/econ-statistics/pane.tsx +356 -0
- package/src/plugins/builtin/econ-statistics/stats.ts +522 -0
- package/src/plugins/builtin/econ-statistics/transform.test.ts +86 -0
- package/src/plugins/builtin/econ-statistics/transform.ts +85 -0
- package/src/plugins/builtin/econ-statistics/trend.ts +57 -0
- package/src/plugins/builtin/econ-statistics/view.ts +90 -0
- package/src/plugins/builtin/fear-greed/charts.tsx +7 -18
- package/src/plugins/builtin/fear-greed/data.ts +6 -1
- package/src/plugins/builtin/futures/index.tsx +1 -0
- package/src/plugins/builtin/fx-matrix/index.tsx +1 -0
- package/src/plugins/builtin/help/index.tsx +4 -0
- package/src/plugins/builtin/holders/index.tsx +1 -0
- package/src/plugins/builtin/insider/index.tsx +1 -0
- package/src/plugins/builtin/ipo-calendar/index.tsx +1 -0
- package/src/plugins/builtin/kelly-sizer/pane.tsx +1 -1
- package/src/plugins/builtin/kelly-sizer/sections.tsx +2 -3
- package/src/plugins/builtin/market-halts/index.tsx +1 -0
- package/src/plugins/builtin/market-movers/index.tsx +1 -0
- package/src/plugins/builtin/market-valuation/align.ts +122 -0
- package/src/plugins/builtin/market-valuation/cache.ts +32 -0
- package/src/plugins/builtin/market-valuation/chart-projection.ts +217 -0
- package/src/plugins/builtin/market-valuation/chart-series.ts +89 -0
- package/src/plugins/builtin/market-valuation/client.test.ts +141 -0
- package/src/plugins/builtin/market-valuation/client.ts +116 -0
- package/src/plugins/builtin/market-valuation/defs.ts +251 -0
- package/src/plugins/builtin/market-valuation/detail.tsx +159 -0
- package/src/plugins/builtin/market-valuation/headless.test.ts +58 -0
- package/src/plugins/builtin/market-valuation/headless.ts +170 -0
- package/src/plugins/builtin/market-valuation/index.tsx +64 -0
- package/src/plugins/builtin/market-valuation/indicators.ts +422 -0
- package/src/plugins/builtin/market-valuation/model.test.ts +350 -0
- package/src/plugins/builtin/market-valuation/model.ts +8 -0
- package/src/plugins/builtin/market-valuation/pane.test.tsx +247 -0
- package/src/plugins/builtin/market-valuation/pane.tsx +344 -0
- package/src/plugins/builtin/market-valuation/series.ts +12 -0
- package/src/plugins/builtin/market-valuation/settings.ts +38 -0
- package/src/plugins/builtin/market-valuation/sources.ts +130 -0
- package/src/plugins/builtin/market-valuation/trend.ts +89 -0
- package/src/plugins/builtin/market-valuation/view.ts +137 -0
- package/src/plugins/builtin/market-valuation/zone-scale/desktop.tsx +123 -0
- package/src/plugins/builtin/market-valuation/zone-scale/index.tsx +12 -0
- package/src/plugins/builtin/market-valuation/zone-scale/model.ts +60 -0
- package/src/plugins/builtin/market-valuation/zone-scale/terminal.tsx +94 -0
- package/src/plugins/builtin/news/wire/breaking/filters.test.ts +99 -0
- package/src/plugins/builtin/news/wire/breaking/filters.ts +110 -0
- package/src/plugins/builtin/news/wire/breaking/notifications.test.ts +157 -53
- package/src/plugins/builtin/news/wire/breaking/notifications.ts +91 -25
- package/src/plugins/builtin/news/wire/index.tsx +31 -44
- package/src/plugins/builtin/options/analytics.test.ts +83 -0
- package/src/plugins/builtin/options/analytics.ts +110 -0
- package/src/plugins/builtin/options/index.tsx +18 -2
- package/src/plugins/builtin/options/table.ts +121 -51
- package/src/plugins/builtin/options/types.ts +22 -15
- package/src/plugins/builtin/options/view.test.tsx +53 -1
- package/src/plugins/builtin/options/view.tsx +92 -41
- package/src/plugins/builtin/plugin-marketplace/feed.ts +80 -0
- package/src/plugins/builtin/plugin-marketplace/index.tsx +31 -0
- package/src/plugins/builtin/plugin-marketplace/model.test.ts +200 -0
- package/src/plugins/builtin/plugin-marketplace/model.ts +204 -0
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +385 -0
- package/src/plugins/builtin/plugin-marketplace/store.ts +46 -0
- package/src/plugins/builtin/plugin-module.test.ts +2 -4
- package/src/plugins/builtin/plugin-module.ts +9 -8
- package/src/plugins/builtin/portfolio-list/index.tsx +1 -0
- package/src/plugins/builtin/research/index.tsx +4 -0
- package/src/plugins/builtin/research-search/command-bar-search.test.ts +81 -0
- package/src/plugins/builtin/research-search/command-bar-search.ts +175 -0
- package/src/plugins/builtin/research-search/data.ts +109 -0
- package/src/plugins/builtin/research-search/document-view.test.tsx +75 -0
- package/src/plugins/builtin/research-search/document-view.tsx +254 -0
- package/src/plugins/builtin/research-search/filter-bar.tsx +62 -0
- package/src/plugins/builtin/research-search/focus-handoff.ts +47 -0
- package/src/plugins/builtin/research-search/index.tsx +85 -0
- package/src/plugins/builtin/research-search/model.test.ts +40 -0
- package/src/plugins/builtin/research-search/model.ts +291 -0
- package/src/plugins/builtin/research-search/pane.test.tsx +241 -0
- package/src/plugins/builtin/research-search/pane.tsx +678 -0
- package/src/plugins/builtin/research-search/saved-view.tsx +171 -0
- package/src/plugins/builtin/research-search/snippet-text.tsx +35 -0
- package/src/plugins/builtin/research-search/snippet.test.ts +78 -0
- package/src/plugins/builtin/research-search/snippet.ts +134 -0
- package/src/plugins/builtin/scanner/flow-model.test.ts +11 -1
- package/src/plugins/builtin/scanner/flow-model.ts +29 -1
- package/src/plugins/builtin/scanner/flow-pane.tsx +78 -22
- package/src/plugins/builtin/scanner/index.ts +2 -0
- package/src/plugins/builtin/sec/index.tsx +1 -0
- package/src/plugins/builtin/sectors/index.tsx +1 -0
- package/src/plugins/builtin/shared/series-cache.ts +93 -0
- package/src/plugins/builtin/short-interest/index.tsx +1 -0
- package/src/plugins/builtin/short-interest/pane.tsx +1 -1
- package/src/plugins/builtin/thirteenf/index.tsx +1 -0
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +3 -5
- package/src/plugins/builtin/ticker-detail/index.tsx +2 -14
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +1 -1
- package/src/plugins/builtin/treasury-auctions/index.tsx +1 -0
- package/src/plugins/builtin/volatility/index.tsx +1 -1
- package/src/plugins/builtin/world-indices/index.tsx +1 -0
- package/src/plugins/builtin/world-venue-map/index.ts +27 -0
- package/src/plugins/builtin/world-venue-map/map.desktop.test.tsx +106 -0
- package/src/plugins/builtin/world-venue-map/map.tsx +481 -0
- package/src/plugins/builtin/world-venue-map/model.test.ts +85 -0
- package/src/plugins/builtin/world-venue-map/model.ts +268 -0
- package/src/plugins/builtin/world-venue-map/pane.tsx +300 -0
- package/src/plugins/builtin/world-venue-map/world-outlines.ts +2 -0
- package/src/plugins/builtin/yield-curve/index.tsx +1 -1
- package/src/plugins/bundle.test.ts +109 -0
- package/src/plugins/bundle.ts +139 -0
- package/src/plugins/catalog-backend.ts +6 -44
- package/src/plugins/catalog-browser.test.ts +1 -0
- package/src/plugins/catalog-browser.ts +12 -0
- package/src/plugins/catalog-manifest.test.ts +43 -0
- package/src/plugins/catalog-manifest.ts +92 -0
- package/src/plugins/catalog-ui.ts +20 -4
- package/src/plugins/catalog.ts +9 -2
- package/src/plugins/current-target.ts +28 -0
- package/src/plugins/host-contract.ts +37 -0
- package/src/plugins/host-link.ts +158 -0
- package/src/plugins/host-modules.ts +61 -0
- package/src/plugins/loader.ts +41 -22
- package/src/plugins/ownership.ts +1 -0
- package/src/plugins/prediction-markets/index.tsx +1 -0
- package/src/plugins/registry/context.ts +3 -0
- package/src/plugins/registry/contributions.ts +27 -0
- package/src/plugins/registry/index.test.ts +25 -0
- package/src/plugins/registry/index.ts +8 -0
- package/src/plugins/registry/pane-settings.test.ts +53 -0
- package/src/plugins/registry/pane-settings.ts +29 -3
- package/src/plugins/seed.test.ts +83 -0
- package/src/plugins/seed.ts +80 -0
- package/src/public/broker.ts +31 -0
- package/src/public/market-data.ts +9 -0
- package/src/public/pane-hooks.ts +57 -0
- package/src/public/public-api.test.ts +285 -0
- package/src/public/react.ts +62 -0
- package/src/public/test-support.ts +23 -0
- package/src/public/time-series.ts +8 -0
- package/src/public/utils.ts +77 -0
- package/src/remote/layout-helpers.ts +6 -8
- package/src/remote/resources.ts +2 -0
- package/src/remote/schema.ts +1 -0
- package/src/remote/semantic-tree.test.tsx +105 -0
- package/src/remote/semantic-tree.tsx +53 -35
- package/src/renderers/browser/main.tsx +6 -0
- package/src/renderers/browser/ui-host.tsx +11 -0
- package/src/renderers/cloudflare/worker.test.ts +19 -0
- package/src/renderers/cloudflare/worker.ts +1 -1
- package/src/renderers/electrobun/bun/desktop/host-requests.ts +9 -2
- package/src/renderers/electrobun/bun/external-plugins.ts +140 -0
- package/src/renderers/electrobun/bun/index.ts +6 -2
- package/src/renderers/electrobun/shared/protocol.ts +25 -0
- package/src/renderers/electrobun/view/build-assets.ts +0 -1
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +51 -1
- package/src/renderers/electrobun/view/external-plugins.test.ts +45 -0
- package/src/renderers/electrobun/view/external-plugins.ts +63 -0
- package/src/renderers/electrobun/view/host/ascii-text.test.ts +48 -0
- package/src/renderers/electrobun/view/host/ascii-text.ts +11 -1
- package/src/renderers/electrobun/view/host/chart-surface.tsx +38 -32
- package/src/renderers/electrobun/view/host/mouse.ts +16 -2
- package/src/renderers/electrobun/view/main.tsx +27 -2
- package/src/renderers/electrobun/view/styles.css +5 -0
- package/src/renderers/electrobun/view/toast-host.tsx +18 -0
- package/src/renderers/electrobun/view/ui-host.tsx +3 -0
- package/src/renderers/opentui/chart-surface.tsx +5 -1
- package/src/renderers/opentui/host.tsx +20 -2
- package/src/renderers/opentui/interaction-performance.test.ts +79 -0
- package/src/renderers/opentui/interaction-performance.ts +188 -0
- package/src/renderers/opentui/start.tsx +26 -1
- package/src/renderers/opentui/test-utils.tsx +13 -0
- package/src/renderers/opentui/toast-host.tsx +26 -2
- package/src/renderers/opentui/ui-host.tsx +1 -0
- package/src/sources/provider-router/search.test.ts +191 -0
- package/src/sources/provider-router/search.ts +92 -39
- package/src/state/app/context/index.tsx +2 -0
- package/src/state/app/context/selector.test.tsx +33 -1
- package/src/state/hooks/quote-streaming.test.tsx +93 -0
- package/src/state/hooks/quote-streaming.ts +106 -30
- package/src/state/pane-table-export-registry.ts +50 -0
- package/src/theme/color-utils.ts +1 -1
- package/src/theme/colors.ts +50 -0
- package/src/theme/themes.test.ts +15 -2
- package/src/theme/themes.ts +19 -1
- package/src/tickers/search/index.test.ts +26 -0
- package/src/tickers/search/index.ts +42 -17
- package/src/tickers/search/ranking.ts +14 -0
- package/src/time-series/chart-data.test.ts +216 -0
- package/src/time-series/range.ts +12 -0
- package/src/time-series/resolution.test.ts +25 -26
- package/src/time-series/resolution.ts +44 -25
- package/src/time-series/resolve.ts +7 -0
- package/src/time-series/spec.ts +5 -5
- package/src/time-series/types.ts +3 -1
- package/src/time-series/use-chart-resolution.ts +5 -0
- package/src/types/config.ts +123 -3
- package/src/types/data-provider.ts +11 -0
- package/src/types/headless.ts +161 -0
- package/src/types/plugin.ts +104 -20
- package/src/ui/host.tsx +19 -1
- package/src/ui/index.tsx +1 -0
- package/src/ui/toast.tsx +8 -4
- package/src/utils/csv.test.ts +13 -0
- package/src/utils/csv.ts +37 -0
- package/src/utils/format.ts +35 -0
- package/src/utils/fuzzy-search.test.ts +94 -0
- package/src/utils/fuzzy-search.ts +81 -22
- package/src/utils/save-text-file.test.ts +24 -0
- package/src/utils/save-text-file.ts +36 -0
- package/src/utils/shortcut-labels.ts +14 -0
- package/src/components/chart/core/axis-format.ts +0 -157
- package/src/components/chart/core/index.test.ts +0 -1075
- package/src/components/chart/core/projection.ts +0 -357
- package/src/components/chart/core/render-utils.ts +0 -4
- package/src/components/chart/core/renderer.ts +0 -244
- package/src/components/chart/core/scene.ts +0 -180
- package/src/components/chart/core/time-axis.ts +0 -330
- package/src/components/chart/cursor-motion.ts +0 -47
- package/src/components/chart/indicators/types.ts +0 -26
- package/src/components/chart/market-session.ts +0 -176
- package/src/components/chart/multi-line/renderer.test.ts +0 -83
- package/src/components/chart/multi-line/renderer.ts +0 -161
- package/src/components/chart/multi-line/scene.ts +0 -188
- package/src/components/chart/native/raster/layout.ts +0 -129
- package/src/components/chart/native/raster/price-chart.ts +0 -244
- package/src/components/chart/rendering/chart-draw.ts +0 -270
- package/src/components/chart/rendering/geometry.ts +0 -95
- package/src/components/chart/rendering/pixel-buffer.ts +0 -267
- package/src/components/chart/scatter-chart-renderer.test.ts +0 -31
- package/src/components/chart/scatter-chart-renderer.ts +0 -223
- package/src/components/chart/static/chart/surface.tsx +0 -358
- package/src/components/command-bar/routes/root/article-results.test.ts +0 -83
- package/src/components/command-bar/routes/root/article-results.ts +0 -55
- package/src/plugins/builtin/news/wire/article-search.test.ts +0 -72
- package/src/plugins/builtin/news/wire/article-search.ts +0 -140
- package/src/plugins/builtin/substack/api/auth.ts +0 -185
- package/src/plugins/builtin/substack/api/cache.ts +0 -82
- package/src/plugins/builtin/substack/api/loaders.ts +0 -170
- package/src/plugins/builtin/substack/api/store.ts +0 -186
- package/src/plugins/builtin/substack/api/types.ts +0 -42
- package/src/plugins/builtin/substack/api.test.ts +0 -298
- package/src/plugins/builtin/substack/article-detail.tsx +0 -397
- package/src/plugins/builtin/substack/article-stack.tsx +0 -133
- package/src/plugins/builtin/substack/content.ts +0 -478
- package/src/plugins/builtin/substack/feed-tabs.tsx +0 -44
- package/src/plugins/builtin/substack/index.tsx +0 -49
- package/src/plugins/builtin/substack/login-view.tsx +0 -126
- package/src/plugins/builtin/substack/model.test.ts +0 -155
- package/src/plugins/builtin/substack/normalize.ts +0 -343
- package/src/plugins/builtin/substack/pane-footer.ts +0 -52
- package/src/plugins/builtin/substack/pane-state.ts +0 -170
- package/src/plugins/builtin/substack/pane.tsx +0 -547
- package/src/plugins/builtin/substack/read-state.ts +0 -30
- package/src/plugins/builtin/substack/table.ts +0 -111
- package/src/plugins/builtin/substack/types.ts +0 -71
- package/src/plugins/builtin/substack/utils.ts +0 -131
- package/src/plugins/builtin/ticker-detail/data-panes/provider-search.tsx +0 -229
- package/src/plugins/ibkr/account-cache.test.ts +0 -102
- package/src/plugins/ibkr/account-cache.ts +0 -24
- package/src/plugins/ibkr/broker-adapter.ts +0 -286
- package/src/plugins/ibkr/config.test.ts +0 -79
- package/src/plugins/ibkr/config.ts +0 -250
- package/src/plugins/ibkr/dialogs.tsx +0 -40
- package/src/plugins/ibkr/flex/index.test.ts +0 -264
- package/src/plugins/ibkr/flex/index.ts +0 -367
- package/src/plugins/ibkr/flex/statement-client.ts +0 -235
- package/src/plugins/ibkr/fundamental-parser.ts +0 -149
- package/src/plugins/ibkr/gateway/account-loaders.ts +0 -266
- package/src/plugins/ibkr/gateway/account-summary.ts +0 -164
- package/src/plugins/ibkr/gateway/client-lock.ts +0 -154
- package/src/plugins/ibkr/gateway/connection-events.ts +0 -114
- package/src/plugins/ibkr/gateway/connection.ts +0 -152
- package/src/plugins/ibkr/gateway/contracts.ts +0 -132
- package/src/plugins/ibkr/gateway/fundamentals.ts +0 -83
- package/src/plugins/ibkr/gateway/helpers.ts +0 -33
- package/src/plugins/ibkr/gateway/history-requests.ts +0 -158
- package/src/plugins/ibkr/gateway/history.ts +0 -120
- package/src/plugins/ibkr/gateway/instrument-lookup.ts +0 -117
- package/src/plugins/ibkr/gateway/market-data.ts +0 -345
- package/src/plugins/ibkr/gateway/order-execution.ts +0 -218
- package/src/plugins/ibkr/gateway/orders.ts +0 -63
- package/src/plugins/ibkr/gateway/price-normalization.test.ts +0 -44
- package/src/plugins/ibkr/gateway/price-normalization.ts +0 -50
- package/src/plugins/ibkr/gateway/quote-stream-controller.ts +0 -203
- package/src/plugins/ibkr/gateway/quote-stream.ts +0 -171
- package/src/plugins/ibkr/gateway/selection.ts +0 -51
- package/src/plugins/ibkr/gateway/service/index.test.ts +0 -675
- package/src/plugins/ibkr/gateway/service/index.ts +0 -429
- package/src/plugins/ibkr/gateway/service/lifecycle.ts +0 -191
- package/src/plugins/ibkr/gateway/service/manager.ts +0 -52
- package/src/plugins/ibkr/gateway/service/market-data.ts +0 -48
- package/src/plugins/ibkr/gateway/service/native-loader.ts +0 -12
- package/src/plugins/ibkr/gateway/service/native.ts +0 -543
- package/src/plugins/ibkr/gateway/timeouts.ts +0 -13
- package/src/plugins/ibkr/gateway/types.ts +0 -27
- package/src/plugins/ibkr/index.test.tsx +0 -162
- package/src/plugins/ibkr/index.tsx +0 -159
- package/src/plugins/ibkr/instance-selection.test.ts +0 -120
- package/src/plugins/ibkr/instance-selection.ts +0 -36
- package/src/plugins/ibkr/portfolio-performance.test.ts +0 -32
- package/src/plugins/ibkr/portfolio-performance.ts +0 -129
- package/src/plugins/ibkr/trade/badge.tsx +0 -28
- package/src/plugins/ibkr/trade/dialogs.tsx +0 -44
- package/src/plugins/ibkr/trade/preview-panel.tsx +0 -130
- package/src/plugins/ibkr/trade/tab/actions.tsx +0 -443
- package/src/plugins/ibkr/trade/tab/field-editors.tsx +0 -79
- package/src/plugins/ibkr/trade/tab/footer.ts +0 -53
- package/src/plugins/ibkr/trade/tab/header.tsx +0 -167
- package/src/plugins/ibkr/trade/tab/index.test.tsx +0 -178
- package/src/plugins/ibkr/trade/tab/index.tsx +0 -298
- package/src/plugins/ibkr/trade/tab/model.ts +0 -159
- package/src/plugins/ibkr/trade/tab/order-request.ts +0 -64
- package/src/plugins/ibkr/trade/tab/shortcuts.ts +0 -89
- package/src/plugins/ibkr/trade/tab/state-sync.ts +0 -86
- package/src/plugins/ibkr/trade/ticket-panel.tsx +0 -301
- package/src/plugins/ibkr/trade/utils.test.ts +0 -175
- package/src/plugins/ibkr/trade/utils.ts +0 -144
- package/src/plugins/ibkr/trading/pane-view.tsx +0 -237
- package/src/plugins/ibkr/trading/pane.tsx +0 -332
- package/src/plugins/ibkr/trading/state.test.ts +0 -109
- package/src/plugins/ibkr/trading/state.ts +0 -335
|
@@ -2,17 +2,23 @@ import { Fragment, createElement, type ReactNode } from "react";
|
|
|
2
2
|
import type {
|
|
3
3
|
GloomPlugin,
|
|
4
4
|
GloomPluginContext,
|
|
5
|
-
PluginCliDescriptor,
|
|
6
5
|
} from "../../types/plugin";
|
|
7
6
|
|
|
8
|
-
type PluginMetadataKey =
|
|
7
|
+
type PluginMetadataKey =
|
|
8
|
+
| "id"
|
|
9
|
+
| "name"
|
|
10
|
+
| "version"
|
|
11
|
+
| "description"
|
|
12
|
+
| "toggleable"
|
|
13
|
+
| "order"
|
|
14
|
+
| "targets"
|
|
15
|
+
| "homepage";
|
|
9
16
|
type PluginMetadata = Pick<GloomPlugin, PluginMetadataKey>;
|
|
10
17
|
|
|
11
18
|
export type PluginModule = Omit<GloomPlugin, PluginMetadataKey>;
|
|
12
19
|
|
|
13
20
|
const HANDLED_MODULE_KEYS = [
|
|
14
21
|
"cliCommands",
|
|
15
|
-
"cli",
|
|
16
22
|
"setup",
|
|
17
23
|
"dispose",
|
|
18
24
|
"panes",
|
|
@@ -23,11 +29,8 @@ const HANDLED_MODULE_KEYS = [
|
|
|
23
29
|
] as const satisfies readonly (keyof PluginModule)[];
|
|
24
30
|
|
|
25
31
|
type MissingModuleKey = Exclude<keyof PluginModule, typeof HANDLED_MODULE_KEYS[number]>;
|
|
26
|
-
type MissingCliDescriptorKey = Exclude<keyof PluginCliDescriptor, "commands">;
|
|
27
32
|
const ALL_MODULE_KEYS_HANDLED: MissingModuleKey extends never ? true : never = true;
|
|
28
|
-
const ALL_CLI_DESCRIPTOR_KEYS_HANDLED: MissingCliDescriptorKey extends never ? true : never = true;
|
|
29
33
|
void ALL_MODULE_KEYS_HANDLED;
|
|
30
|
-
void ALL_CLI_DESCRIPTOR_KEYS_HANDLED;
|
|
31
34
|
|
|
32
35
|
interface CompositePluginOptions extends PluginMetadata {
|
|
33
36
|
modules: readonly PluginModule[];
|
|
@@ -72,7 +75,6 @@ function composeSlots(modules: readonly PluginModule[]): GloomPlugin["slots"] {
|
|
|
72
75
|
export function composeBuiltinPlugin(options: CompositePluginOptions): GloomPlugin {
|
|
73
76
|
const { modules, ...metadata } = options;
|
|
74
77
|
const cliCommands = modules.flatMap((module) => module.cliCommands ?? []);
|
|
75
|
-
const cliDescriptors = modules.flatMap((module) => module.cli?.commands ?? []);
|
|
76
78
|
const panes = modules.flatMap((module) => module.panes ?? []);
|
|
77
79
|
const paneTemplates = modules.flatMap((module) => module.paneTemplates ?? []);
|
|
78
80
|
const capabilities = modules.flatMap((module) => module.capabilities ?? []);
|
|
@@ -83,7 +85,6 @@ export function composeBuiltinPlugin(options: CompositePluginOptions): GloomPlug
|
|
|
83
85
|
return {
|
|
84
86
|
...metadata,
|
|
85
87
|
...(cliCommands.length > 0 ? { cliCommands } : {}),
|
|
86
|
-
...(cliDescriptors.length > 0 ? { cli: { commands: cliDescriptors } } : {}),
|
|
87
88
|
...(panes.length > 0 ? { panes } : {}),
|
|
88
89
|
...(paneTemplates.length > 0 ? { paneTemplates } : {}),
|
|
89
90
|
...(capabilities.length > 0 ? { capabilities } : {}),
|
|
@@ -50,6 +50,7 @@ export const researchModule: PluginModule = {
|
|
|
50
50
|
defaultPosition: "right",
|
|
51
51
|
defaultMode: "floating",
|
|
52
52
|
defaultFloatingSize: { width: 90, height: 28 },
|
|
53
|
+
tableExport: true,
|
|
53
54
|
},
|
|
54
55
|
{
|
|
55
56
|
id: "equity-diagnostic",
|
|
@@ -68,6 +69,7 @@ export const researchModule: PluginModule = {
|
|
|
68
69
|
defaultPosition: "right",
|
|
69
70
|
defaultMode: "floating",
|
|
70
71
|
defaultFloatingSize: { width: 104, height: 24 },
|
|
72
|
+
tableExport: true,
|
|
71
73
|
},
|
|
72
74
|
{
|
|
73
75
|
id: "relative-valuation",
|
|
@@ -77,6 +79,7 @@ export const researchModule: PluginModule = {
|
|
|
77
79
|
defaultPosition: "right",
|
|
78
80
|
defaultMode: "floating",
|
|
79
81
|
defaultFloatingSize: { width: 104, height: 24 },
|
|
82
|
+
tableExport: true,
|
|
80
83
|
},
|
|
81
84
|
{
|
|
82
85
|
id: "earnings-estimates",
|
|
@@ -86,6 +89,7 @@ export const researchModule: PluginModule = {
|
|
|
86
89
|
defaultPosition: "right",
|
|
87
90
|
defaultMode: "floating",
|
|
88
91
|
defaultFloatingSize: { width: 104, height: 22 },
|
|
92
|
+
tableExport: true,
|
|
89
93
|
},
|
|
90
94
|
],
|
|
91
95
|
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import type { CloudSearchHit } from "../../../api-client";
|
|
3
|
+
import { hitResultDef } from "./command-bar-search";
|
|
4
|
+
import { formatHitDateShort } from "./model";
|
|
5
|
+
|
|
6
|
+
const NOW = new Date("2026-09-01T12:00:00").getTime();
|
|
7
|
+
|
|
8
|
+
function hit(overrides: Partial<CloudSearchHit>): CloudSearchHit {
|
|
9
|
+
return {
|
|
10
|
+
id: "hit-1",
|
|
11
|
+
docType: "news",
|
|
12
|
+
sourceId: "doc-1",
|
|
13
|
+
chunkIndex: 0,
|
|
14
|
+
ticker: "NVDA",
|
|
15
|
+
publishedAt: "2026-08-30T09:00:00",
|
|
16
|
+
title: "Nvidia guides above estimates",
|
|
17
|
+
url: "https://example.com",
|
|
18
|
+
snippet: "record <mark>margin</mark> again",
|
|
19
|
+
score: 1,
|
|
20
|
+
metadata: { source: "Reuters" },
|
|
21
|
+
...overrides,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe("formatHitDateShort", () => {
|
|
26
|
+
/**
|
|
27
|
+
* Three formats hinge on two boundaries, a day and a year, and the column
|
|
28
|
+
* is too narrow to tell a mistake from a design choice.
|
|
29
|
+
*/
|
|
30
|
+
test("switches from age to day to month at the day and year boundaries", () => {
|
|
31
|
+
const at = (iso: string) => formatHitDateShort(iso, NOW);
|
|
32
|
+
expect(at("2026-09-01T11:59:30")).toBe("1m");
|
|
33
|
+
expect(at("2026-09-01T08:45:00")).toBe("3h");
|
|
34
|
+
expect(at("2026-08-31T12:00:01")).toBe("23h");
|
|
35
|
+
expect(at("2026-08-31T12:00:00")).toBe("Aug 31");
|
|
36
|
+
expect(at("2026-01-01T00:00:00")).toBe("Jan 1");
|
|
37
|
+
expect(at("2025-12-31T23:59:59")).toBe("Dec 2025");
|
|
38
|
+
// A clock ahead of ours is not "in -2h".
|
|
39
|
+
expect(at("2026-09-01T14:00:00")).toBe("Sep 1");
|
|
40
|
+
expect(at("not a date")).toBe("");
|
|
41
|
+
expect(at(undefined)).toBe("");
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe("document hit rows", () => {
|
|
46
|
+
test("put the date on the right and the source and ticker ahead of the snippet", () => {
|
|
47
|
+
const row = hitResultDef(hit({}), () => {}, NOW);
|
|
48
|
+
expect(row.badge).toBe("NEWS");
|
|
49
|
+
expect(row.right).toBe("Aug 30");
|
|
50
|
+
expect(row.lines).toEqual([{
|
|
51
|
+
segments: [
|
|
52
|
+
{ text: "Reuters · NVDA · ", emphasis: "muted" },
|
|
53
|
+
{ text: "record ", emphasis: undefined },
|
|
54
|
+
{ text: "margin", emphasis: "match" },
|
|
55
|
+
{ text: " again", emphasis: undefined },
|
|
56
|
+
],
|
|
57
|
+
}]);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("lead with the speaker for a call and the section for a filing, whose form is the badge", () => {
|
|
61
|
+
const call = hitResultDef(hit({
|
|
62
|
+
docType: "transcript",
|
|
63
|
+
metadata: { speaker: "Colette Kress", role: "CFO" },
|
|
64
|
+
}), () => {}, NOW);
|
|
65
|
+
expect(call.badge).toBe("CALL");
|
|
66
|
+
expect(call.lines?.[0]?.segments[0]?.text).toBe("Colette Kress, CFO · NVDA · ");
|
|
67
|
+
|
|
68
|
+
const filing = hitResultDef(hit({
|
|
69
|
+
docType: "filing",
|
|
70
|
+
metadata: { form: "10-K", section: "Item 1A" },
|
|
71
|
+
}), () => {}, NOW);
|
|
72
|
+
expect(filing.badge).toBe("10-K");
|
|
73
|
+
expect(filing.lines?.[0]?.segments[0]?.text).toBe("Item 1A · NVDA · ");
|
|
74
|
+
|
|
75
|
+
const bare = hitResultDef(hit({ metadata: {} }), () => {}, NOW);
|
|
76
|
+
expect(bare.lines?.[0]?.segments[0]?.text).toBe("NVDA · ");
|
|
77
|
+
|
|
78
|
+
// The News section is all one kind, so a badge there would only repeat the heading.
|
|
79
|
+
expect(hitResultDef(hit({}), () => {}, NOW, { badge: false }).badge).toBeUndefined();
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import type { CloudSearchDocType, CloudSearchHit } from "../../../api-client";
|
|
2
|
+
import type {
|
|
3
|
+
CommandBarResultDef,
|
|
4
|
+
CommandBarSearchProvider,
|
|
5
|
+
GloomPluginContext,
|
|
6
|
+
} from "../../../types/plugin";
|
|
7
|
+
import { apiClient } from "../../../api-client";
|
|
8
|
+
import { runDocumentSearch } from "./data";
|
|
9
|
+
import { requestDocumentFocus } from "./focus-handoff";
|
|
10
|
+
import {
|
|
11
|
+
formatHitDate,
|
|
12
|
+
formatHitDateShort,
|
|
13
|
+
hitLeadIn,
|
|
14
|
+
hitTypeLabel,
|
|
15
|
+
researchSearchInstanceId,
|
|
16
|
+
RESEARCH_SEARCH_TEMPLATE_ID,
|
|
17
|
+
} from "./model";
|
|
18
|
+
import { parseMarkedSnippet } from "./snippet";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Each hit costs two rows (title, then source and snippet), and the sheet now
|
|
22
|
+
* takes about half the terminal rather than a fixed sixteen rows. Four apiece
|
|
23
|
+
* fits both sections without pushing what follows off screen.
|
|
24
|
+
*/
|
|
25
|
+
const COMMAND_BAR_HIT_LIMIT = 4;
|
|
26
|
+
|
|
27
|
+
const NEWS_DOC_TYPES: CloudSearchDocType[] = ["news"];
|
|
28
|
+
const FILING_DOC_TYPES: CloudSearchDocType[] = ["transcript", "filing"];
|
|
29
|
+
|
|
30
|
+
export function hitResultDef(
|
|
31
|
+
hit: CloudSearchHit,
|
|
32
|
+
openHit: (hit: CloudSearchHit) => void,
|
|
33
|
+
now = Date.now(),
|
|
34
|
+
options?: { badge?: boolean },
|
|
35
|
+
): CommandBarResultDef {
|
|
36
|
+
const leadIn = [hitLeadIn(hit), hit.ticker].filter(Boolean).join(" · ");
|
|
37
|
+
const snippet = parseMarkedSnippet(hit.snippet).map((segment) => ({
|
|
38
|
+
text: segment.text,
|
|
39
|
+
emphasis: segment.marked ? "match" as const : undefined,
|
|
40
|
+
}));
|
|
41
|
+
// Source and ticker open the snippet line, so the right column is free for
|
|
42
|
+
// the date, which is what decides whether a hit is still worth reading.
|
|
43
|
+
const segments = leadIn
|
|
44
|
+
? [{ text: `${leadIn} · `, emphasis: "muted" as const }, ...snippet]
|
|
45
|
+
: snippet;
|
|
46
|
+
return {
|
|
47
|
+
id: hit.id,
|
|
48
|
+
label: hit.title,
|
|
49
|
+
detail: [leadIn, formatHitDate(hit.publishedAt)].filter(Boolean).join(" · "),
|
|
50
|
+
// CALL or the filing form. Dropped in the News section, where every row is
|
|
51
|
+
// the same kind and a badge would only repeat the heading.
|
|
52
|
+
badge: options?.badge === false ? undefined : hitTypeLabel(hit),
|
|
53
|
+
right: formatHitDateShort(hit.publishedAt, now),
|
|
54
|
+
lines: segments.length > 0 ? [{ segments }] : undefined,
|
|
55
|
+
keywords: [hit.ticker, hitTypeLabel(hit)],
|
|
56
|
+
execute: () => openHit(hit),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface CorpusSectionOptions {
|
|
61
|
+
id: string;
|
|
62
|
+
category: string;
|
|
63
|
+
priority: number;
|
|
64
|
+
docTypes: CloudSearchDocType[];
|
|
65
|
+
badge: boolean;
|
|
66
|
+
/** Appended below the hits by the section that offers the whole corpus. */
|
|
67
|
+
searchAllRow?: boolean;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Full-text corpus hits for free text the command bar could not resolve. Every
|
|
72
|
+
* failure — including the 402 a server-side refusal returns — degrades to no
|
|
73
|
+
* rows at all: the command bar is not the place to learn that a background
|
|
74
|
+
* search you never asked for went wrong.
|
|
75
|
+
*
|
|
76
|
+
* News is asked for in its own request rather than split out of a shared one: a
|
|
77
|
+
* single response has to spend its row budget on whichever kind scored better,
|
|
78
|
+
* so a query matching a hundred wire stories would leave no room for a filing.
|
|
79
|
+
*/
|
|
80
|
+
function createCorpusSearchProvider(
|
|
81
|
+
ctx: GloomPluginContext,
|
|
82
|
+
section: CorpusSectionOptions,
|
|
83
|
+
): CommandBarSearchProvider {
|
|
84
|
+
const openSearchPane = (query: string) => {
|
|
85
|
+
ctx.createPaneFromTemplate(RESEARCH_SEARCH_TEMPLATE_ID, { arg: query });
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
id: section.id,
|
|
90
|
+
category: section.category,
|
|
91
|
+
/**
|
|
92
|
+
* Async sections sit below every synchronous one, ordered by how long they
|
|
93
|
+
* take to arrive, so a late arrival only ever pushes rows below itself and
|
|
94
|
+
* never the rows the eye is already on. Instruments answer in ~200ms, the
|
|
95
|
+
* corpus in ~400ms, Ask AI later still; the view model pins those bands at
|
|
96
|
+
* 100, 190/200 and 300.
|
|
97
|
+
*/
|
|
98
|
+
priority: section.priority,
|
|
99
|
+
minQueryLength: 3,
|
|
100
|
+
debounceMs: 350,
|
|
101
|
+
|
|
102
|
+
async provide(query, _context, signal) {
|
|
103
|
+
// Only skip when there is no session at all, so a signed-out bar does not
|
|
104
|
+
// spend a request per query to be told so. Deliberately not gated on the
|
|
105
|
+
// cached user's plan: that cache is cleared whenever a session refresh
|
|
106
|
+
// comes back empty, which leaves the app authenticated for every other
|
|
107
|
+
// surface while reporting itself signed out here. Entitlement is the
|
|
108
|
+
// server's answer anyway, and it already returns 401 or 402 on its own.
|
|
109
|
+
if (!apiClient.isSignedIn()) return [];
|
|
110
|
+
|
|
111
|
+
let hits: CloudSearchHit[] = [];
|
|
112
|
+
try {
|
|
113
|
+
const response = await runDocumentSearch(
|
|
114
|
+
// One row per story: three matching paragraphs of one article is one
|
|
115
|
+
// result, and the teaser has three rows to spend.
|
|
116
|
+
{
|
|
117
|
+
query,
|
|
118
|
+
docTypes: section.docTypes,
|
|
119
|
+
limit: COMMAND_BAR_HIT_LIMIT,
|
|
120
|
+
count: false,
|
|
121
|
+
distinct: true,
|
|
122
|
+
},
|
|
123
|
+
signal,
|
|
124
|
+
);
|
|
125
|
+
hits = response.hits ?? [];
|
|
126
|
+
} catch {
|
|
127
|
+
return [];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const now = Date.now();
|
|
131
|
+
const rows = hits.map((hit) => hitResultDef(
|
|
132
|
+
hit,
|
|
133
|
+
(target) => {
|
|
134
|
+
openSearchPane(query);
|
|
135
|
+
requestDocumentFocus(researchSearchInstanceId(query), target);
|
|
136
|
+
},
|
|
137
|
+
now,
|
|
138
|
+
{ badge: section.badge },
|
|
139
|
+
));
|
|
140
|
+
if (!section.searchAllRow || rows.length === 0) return rows;
|
|
141
|
+
return [
|
|
142
|
+
...rows,
|
|
143
|
+
{
|
|
144
|
+
id: "search-all",
|
|
145
|
+
label: "Search all documents →",
|
|
146
|
+
detail: "Earnings calls, news, and SEC filings",
|
|
147
|
+
badge: "DOCS",
|
|
148
|
+
right: "SRCH",
|
|
149
|
+
execute: () => openSearchPane(query),
|
|
150
|
+
},
|
|
151
|
+
];
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function createNewsSearchProvider(ctx: GloomPluginContext): CommandBarSearchProvider {
|
|
157
|
+
return createCorpusSearchProvider(ctx, {
|
|
158
|
+
id: "research-search:news",
|
|
159
|
+
category: "News",
|
|
160
|
+
priority: 190,
|
|
161
|
+
docTypes: NEWS_DOC_TYPES,
|
|
162
|
+
badge: false,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function createDocumentSearchProvider(ctx: GloomPluginContext): CommandBarSearchProvider {
|
|
167
|
+
return createCorpusSearchProvider(ctx, {
|
|
168
|
+
id: "research-search:documents",
|
|
169
|
+
category: "Documents",
|
|
170
|
+
priority: 200,
|
|
171
|
+
docTypes: FILING_DOC_TYPES,
|
|
172
|
+
badge: true,
|
|
173
|
+
searchAllRow: true,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import {
|
|
2
|
+
apiClient,
|
|
3
|
+
type CloudSavedSearch,
|
|
4
|
+
type CloudSavedSearchInput,
|
|
5
|
+
type CloudSearchDocType,
|
|
6
|
+
type CloudSearchDocument,
|
|
7
|
+
type CloudSearchResponse,
|
|
8
|
+
} from "../../../api-client";
|
|
9
|
+
import { ApiRequestError } from "../../../api-client/errors";
|
|
10
|
+
import type { CloudSearchParams } from "../../../api-client/paths";
|
|
11
|
+
import type { PluginPersistence } from "../../../types/plugin";
|
|
12
|
+
|
|
13
|
+
const DOCUMENT_KIND = "document";
|
|
14
|
+
const CACHE_SOURCE = "research-search";
|
|
15
|
+
const CACHE_SCHEMA_VERSION = 1;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A published transcript, wire story, or filing does not change once indexed,
|
|
19
|
+
* so re-opening a hit should not hit the network again.
|
|
20
|
+
*/
|
|
21
|
+
const DOCUMENT_CACHE_POLICY = {
|
|
22
|
+
staleMs: 7 * 24 * 60 * 60 * 1000,
|
|
23
|
+
expireMs: 90 * 24 * 60 * 60 * 1000,
|
|
24
|
+
} as const;
|
|
25
|
+
|
|
26
|
+
let persistence: PluginPersistence | null = null;
|
|
27
|
+
|
|
28
|
+
export function attachResearchSearchPersistence(value: PluginPersistence): void {
|
|
29
|
+
persistence = value;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function resetResearchSearchPersistence(): void {
|
|
33
|
+
persistence = null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function statusOf(error: unknown): number | undefined {
|
|
37
|
+
return error instanceof ApiRequestError ? error.status : undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function errorMessage(error: unknown): string {
|
|
41
|
+
return error instanceof Error ? error.message : String(error);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function isAbortError(error: unknown): boolean {
|
|
45
|
+
return error instanceof Error && error.name === "AbortError";
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function runDocumentSearch(
|
|
49
|
+
params: CloudSearchParams,
|
|
50
|
+
signal?: AbortSignal,
|
|
51
|
+
): Promise<CloudSearchResponse> {
|
|
52
|
+
return apiClient.searchCloudDocuments(params, { signal });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function documentKey(docType: CloudSearchDocType, sourceId: string): string {
|
|
56
|
+
return `${docType}:${sourceId}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function loadSearchDocument(
|
|
60
|
+
docType: CloudSearchDocType,
|
|
61
|
+
sourceId: string,
|
|
62
|
+
signal?: AbortSignal,
|
|
63
|
+
): Promise<CloudSearchDocument> {
|
|
64
|
+
const key = documentKey(docType, sourceId);
|
|
65
|
+
const cached = persistence?.getResource<CloudSearchDocument>(DOCUMENT_KIND, key, {
|
|
66
|
+
sourceKey: CACHE_SOURCE,
|
|
67
|
+
schemaVersion: CACHE_SCHEMA_VERSION,
|
|
68
|
+
});
|
|
69
|
+
if (cached && !cached.stale) return cached.value;
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const document = await apiClient.getCloudSearchDocument(docType, sourceId, { signal });
|
|
73
|
+
persistence?.setResource(DOCUMENT_KIND, key, document, {
|
|
74
|
+
sourceKey: CACHE_SOURCE,
|
|
75
|
+
schemaVersion: CACHE_SCHEMA_VERSION,
|
|
76
|
+
cachePolicy: DOCUMENT_CACHE_POLICY,
|
|
77
|
+
});
|
|
78
|
+
return document;
|
|
79
|
+
} catch (error) {
|
|
80
|
+
const status = statusOf(error);
|
|
81
|
+
const expired = persistence?.getResource<CloudSearchDocument>(DOCUMENT_KIND, key, {
|
|
82
|
+
sourceKey: CACHE_SOURCE,
|
|
83
|
+
schemaVersion: CACHE_SCHEMA_VERSION,
|
|
84
|
+
allowExpired: true,
|
|
85
|
+
});
|
|
86
|
+
// Losing entitlement has to re-gate the document, not serve it from cache.
|
|
87
|
+
if (expired && status !== 401 && status !== 402 && status !== 403) return expired.value;
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function loadSavedSearches(signal?: AbortSignal): Promise<CloudSavedSearch[]> {
|
|
93
|
+
return apiClient.getCloudSavedSearches({ signal });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function createSavedSearch(input: CloudSavedSearchInput): Promise<CloudSavedSearch> {
|
|
97
|
+
return apiClient.createCloudSavedSearch(input);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function updateSavedSearch(
|
|
101
|
+
id: string,
|
|
102
|
+
update: Partial<CloudSavedSearchInput>,
|
|
103
|
+
): Promise<CloudSavedSearch> {
|
|
104
|
+
return apiClient.updateCloudSavedSearch(id, update);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function deleteSavedSearch(id: string): Promise<void> {
|
|
108
|
+
return apiClient.deleteCloudSavedSearch(id);
|
|
109
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { afterEach, expect, test } from "bun:test";
|
|
2
|
+
import { act } from "react";
|
|
3
|
+
import { testRender } from "../../../renderers/opentui/test-utils";
|
|
4
|
+
import type { CloudSearchDocument, CloudSearchHit } from "../../../api-client";
|
|
5
|
+
import { SearchDocumentView } from "./document-view";
|
|
6
|
+
|
|
7
|
+
// One unbroken run, so every wrapped line is exactly as wide as the wrap width
|
|
8
|
+
// and the rightmost printed column is the wrap width rather than wherever the
|
|
9
|
+
// last word happened to end.
|
|
10
|
+
const PARAGRAPH = "forward-looking-statement.".repeat(40);
|
|
11
|
+
|
|
12
|
+
const HIT = {
|
|
13
|
+
id: "hit-1",
|
|
14
|
+
docType: "news",
|
|
15
|
+
sourceId: "doc-1",
|
|
16
|
+
chunkIndex: 0,
|
|
17
|
+
ticker: "NVDA",
|
|
18
|
+
publishedAt: "2026-08-31T12:00:00.000Z",
|
|
19
|
+
title: "Press Release",
|
|
20
|
+
url: "https://example.com/doc-1",
|
|
21
|
+
snippet: "lorem <mark>ipsum</mark> dolor",
|
|
22
|
+
score: 1,
|
|
23
|
+
metadata: { source: "Business Wire" },
|
|
24
|
+
} as unknown as CloudSearchHit;
|
|
25
|
+
|
|
26
|
+
const DOCUMENT = {
|
|
27
|
+
docType: "news",
|
|
28
|
+
sourceId: "doc-1",
|
|
29
|
+
ticker: "NVDA",
|
|
30
|
+
title: "Press Release",
|
|
31
|
+
url: "https://example.com/doc-1",
|
|
32
|
+
publishedAt: "2026-08-31T12:00:00.000Z",
|
|
33
|
+
chunks: Array.from({ length: 4 }, (_, index) => ({
|
|
34
|
+
id: `chunk-${index}`,
|
|
35
|
+
chunkIndex: index,
|
|
36
|
+
body: PARAGRAPH,
|
|
37
|
+
metadata: { source: "Business Wire" },
|
|
38
|
+
})),
|
|
39
|
+
} as unknown as CloudSearchDocument;
|
|
40
|
+
|
|
41
|
+
let testSetup: Awaited<ReturnType<typeof testRender>> | undefined;
|
|
42
|
+
|
|
43
|
+
afterEach(async () => {
|
|
44
|
+
if (testSetup) {
|
|
45
|
+
await act(async () => {
|
|
46
|
+
testSetup!.renderer.destroy();
|
|
47
|
+
});
|
|
48
|
+
testSetup = undefined;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The wrap width, the scroll box's inset and its scrollbar are three separate
|
|
54
|
+
* subtractions from the same pane, and counting one of them twice is invisible
|
|
55
|
+
* in review: the text still renders, just short of the edge.
|
|
56
|
+
*/
|
|
57
|
+
for (const width of [60, 88, 120]) {
|
|
58
|
+
test(`paragraphs reach the column before the scrollbar at width ${width}`, async () => {
|
|
59
|
+
testSetup = await testRender(
|
|
60
|
+
<SearchDocumentView hit={HIT} document={DOCUMENT} loading={false} error={null} width={width} />,
|
|
61
|
+
{ width, height: 20 },
|
|
62
|
+
);
|
|
63
|
+
for (let frame = 0; frame < 3; frame += 1) {
|
|
64
|
+
await act(async () => {
|
|
65
|
+
await testSetup!.renderOnce();
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const rows = testSetup.captureCharFrame().split("\n");
|
|
70
|
+
// The scrollbar owns the last column on every row it covers, so measure the
|
|
71
|
+
// text against the column before it.
|
|
72
|
+
const textRight = Math.max(...rows.map((row) => row.slice(0, width - 1).trimEnd().length));
|
|
73
|
+
expect(textRight).toBe(width - 1);
|
|
74
|
+
});
|
|
75
|
+
}
|