@gloom-sh/terminal 0.18.5 → 0.19.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/package.json +1 -1
- package/patches/electrobun@1.18.1.patch +140 -7
- package/src/api-client/cot.ts +6 -2
- package/src/api-client/credit-documents.ts +6 -0
- package/src/api-client/data.ts +17 -6
- package/src/api-client/equity-screener.ts +24 -0
- package/src/api-client/fund-facts.ts +45 -4
- package/src/api-client/index.ts +11 -0
- package/src/api-client/market-discovery.ts +10 -0
- package/src/api-client/market-movers.ts +7 -0
- package/src/api-client/members.ts +1 -1
- package/src/api-client/perps.ts +43 -0
- package/src/api-client/research-activity.ts +3 -2
- package/src/api-client/types.ts +70 -2
- package/src/app/pane-runtime/plugin-bindings.ts +9 -5
- package/src/app/runtime/broker-import.ts +0 -2
- package/src/app/runtime/plugin-bindings.ts +73 -56
- package/src/architecture/pane-tabs-allowlist.json +3 -0
- package/src/architecture/ttl-caches-allowlist.json +19 -0
- package/src/brokers/sync-broker-instance.ts +2 -8
- package/src/capabilities/chart-series.ts +1 -1
- package/src/cli/commands/crypto-hints.ts +40 -24
- package/src/cli/commands/market.ts +72 -26
- package/src/cli/commands/options-chain.ts +48 -7
- package/src/cli/commands/options-leaps.ts +9 -3
- package/src/cli/commands/overview.ts +61 -12
- package/src/cli/commands/search.ts +21 -1
- package/src/cli/commands/themes.ts +11 -2
- package/src/cli/commands/ticker.ts +55 -34
- package/src/cli/company-name-hint.ts +41 -0
- package/src/cli/earnings-calendar.ts +1 -1
- package/src/cli/errors.ts +8 -0
- package/src/cli/glossary.ts +25 -2
- package/src/cli/history-rows.ts +57 -9
- package/src/cli/index.ts +2 -0
- package/src/cli/listing-arg.ts +180 -52
- package/src/cli/output-file.ts +68 -0
- package/src/cli/pane-functions/capabilities.ts +3 -0
- package/src/cli/pane-functions/catalog.ts +80 -7
- package/src/cli/pane-functions/dom.ts +13 -3
- package/src/cli/pane-functions/freshness.ts +86 -3
- package/src/cli/pane-functions/function-help.ts +13 -10
- package/src/cli/pane-functions/headless.ts +17 -7
- package/src/cli/pane-functions/index.ts +49 -14
- package/src/cli/pane-functions/market-session.ts +66 -4
- package/src/cli/pane-functions/options.ts +19 -1
- package/src/cli/pane-functions/resolver.ts +19 -8
- package/src/cli/pane-functions/screenshot-assessment.ts +6 -4
- package/src/cli/pane-functions/screenshot-types.ts +5 -0
- package/src/cli/quote-answer.ts +22 -11
- package/src/cli/ticker-resolution.ts +3 -2
- package/src/components/chart/composite/composite-chart.tsx +4 -0
- package/src/components/chart/composite/interactions.ts +6 -0
- package/src/components/chart/composite/legend.tsx +45 -7
- package/src/components/chart/composite/panel-gestures.ts +33 -0
- package/src/components/chart/composite/panel-surface.tsx +52 -3
- package/src/components/chart/composite/rasterizer.ts +24 -13
- package/src/components/chart/composite/scene.ts +39 -4
- package/src/components/chart/composite/series-cues.ts +14 -0
- package/src/components/chart/composite/text-renderer.ts +9 -2
- package/src/components/chart/composite/tool-catalog.ts +36 -0
- package/src/components/chart/composite/tools.ts +203 -44
- package/src/components/chart/composite/use-composite-chart-keyboard.ts +11 -4
- package/src/components/command-bar/assist/inventory.ts +2 -1
- package/src/components/command-bar/commands/direct/index.ts +28 -13
- package/src/components/command-bar/pane-templates/items.ts +3 -2
- package/src/components/command-bar/workflow/collection-actions.ts +3 -3
- package/src/components/onboarding/onboarding-wizard.tsx +4 -1
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +12 -7
- package/src/components/onboarding/wizard-broker-sync.ts +0 -1
- package/src/components/onboarding/wizard-positions.ts +3 -3
- package/src/components/ticker/list-table-view.tsx +4 -1
- package/src/components/ui/choice-dialog.tsx +23 -18
- package/src/components/ui/choice-groups.ts +18 -0
- package/src/components/ui/data-table/opentui/index.tsx +9 -7
- package/src/components/ui/data-table/opentui/model.ts +26 -1
- package/src/components/ui/data-table/types.ts +2 -0
- package/src/components/ui/list-view.tsx +8 -4
- package/src/components/ui/query-bar-keys.tsx +2 -0
- package/src/components/ui/query-bar.tsx +1 -0
- package/src/components/ui/select-button.tsx +5 -1
- package/src/components/ui/select-field.tsx +1 -0
- package/src/components/ui/table-layout.ts +8 -5
- package/src/core/app-runtime.ts +2 -0
- package/src/core/app-services.ts +2 -2
- package/src/data/config/chart-settings.ts +1 -1
- package/src/data/json-ticker-repository.ts +17 -8
- package/src/data/ticker-repository.ts +16 -9
- package/src/data/ticker-writes.ts +81 -0
- package/src/data/ttl-cache.ts +165 -0
- package/src/docs/snapshot.json +71 -137
- package/src/i18n/es.ts +18 -2
- package/src/i18n/ja.ts +18 -2
- package/src/i18n/ko.ts +18 -2
- package/src/i18n/zh-cn.ts +18 -2
- package/src/i18n/zh-tw.ts +18 -2
- package/src/market-data/depositary-pair.ts +2 -2
- package/src/market-data/fund-facts.ts +50 -0
- package/src/market-data/market/change-basis.ts +16 -3
- package/src/market-data/market/format.ts +25 -0
- package/src/market-data/market/freshness.ts +2 -1
- package/src/market-data/options-alternatives.ts +81 -4
- package/src/news/aggregator.ts +113 -9
- package/src/news/cloud-session.ts +21 -0
- package/src/news/hooks.ts +1 -0
- package/src/news/news-model.ts +1 -0
- package/src/news/types.ts +11 -0
- package/src/plugins/builtin/alerts/alert-engine.ts +20 -1
- package/src/plugins/builtin/alerts/alert-log.ts +33 -0
- package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
- package/src/plugins/builtin/alerts/cli.ts +336 -77
- package/src/plugins/builtin/alerts/command.ts +25 -1
- package/src/plugins/builtin/alerts/events-pane.tsx +19 -4
- package/src/plugins/builtin/alerts/fired-log.ts +73 -9
- package/src/plugins/builtin/alerts/format.ts +75 -5
- package/src/plugins/builtin/alerts/headless.ts +24 -6
- package/src/plugins/builtin/alerts/history-rows.ts +3 -2
- package/src/plugins/builtin/alerts/history.ts +1 -1
- package/src/plugins/builtin/alerts/index.tsx +79 -16
- package/src/plugins/builtin/alerts/metric-command.ts +77 -4
- package/src/plugins/builtin/alerts/metric-data.ts +54 -13
- package/src/plugins/builtin/alerts/metric-model.ts +163 -14
- package/src/plugins/builtin/alerts/metric-parity-fixture.json +1 -1
- package/src/plugins/builtin/alerts/pane.tsx +61 -23
- package/src/plugins/builtin/alerts/quotes.ts +6 -0
- package/src/plugins/builtin/alerts/storage.ts +12 -8
- package/src/plugins/builtin/alerts/types.ts +4 -0
- package/src/plugins/builtin/analytics/index.tsx +2 -1
- package/src/plugins/builtin/analytics/risk-client.ts +16 -5
- package/src/plugins/builtin/analytics/risk-fx.ts +70 -15
- package/src/plugins/builtin/analytics/risk-headless.ts +27 -0
- package/src/plugins/builtin/analytics/risk-model.ts +55 -9
- package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
- package/src/plugins/builtin/attention/index.tsx +2 -1
- package/src/plugins/builtin/attention/listing.ts +14 -0
- package/src/plugins/builtin/broker-manager/index.tsx +7 -3
- package/src/plugins/builtin/buildout/cells.tsx +12 -4
- package/src/plugins/builtin/buildout/format.ts +38 -0
- package/src/plugins/builtin/buildout/model/normalizers.ts +36 -1
- package/src/plugins/builtin/buildout/pane/index.tsx +4 -1
- package/src/plugins/builtin/buildout/table-model.ts +23 -2
- package/src/plugins/builtin/builtin-plugin-meta.ts +6 -2
- package/src/plugins/builtin/changelog/index.tsx +9 -6
- package/src/plugins/builtin/chart-composer/catalog-inventory.ts +3 -0
- package/src/plugins/builtin/chart-composer/cli-options.ts +4 -0
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +7 -0
- package/src/plugins/builtin/chart-composer/editor/controller.ts +2 -2
- package/src/plugins/builtin/chart-composer/headless-schema.ts +7 -1
- package/src/plugins/builtin/chart-composer/headless.ts +6 -1
- package/src/plugins/builtin/chart-composer/index.tsx +1 -1
- package/src/plugins/builtin/chart-composer/pane.tsx +37 -8
- package/src/plugins/builtin/chart-composer/settings.ts +27 -0
- package/src/plugins/builtin/chat/content/command-suggestions.ts +1 -0
- package/src/plugins/builtin/chat/pane-links/commands.ts +7 -1
- package/src/plugins/builtin/clinical-trials/model.ts +0 -9
- package/src/plugins/builtin/clinical-trials/pane.tsx +15 -22
- package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +8 -6
- package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
- package/src/plugins/builtin/company-attention/building.tsx +5 -1
- package/src/plugins/builtin/company-attention/client.ts +8 -9
- package/src/plugins/builtin/company-attention/evidence.ts +4 -1
- package/src/plugins/builtin/company-attention/moves.tsx +5 -1
- package/src/plugins/builtin/company-attention/pane.tsx +9 -2
- package/src/plugins/builtin/company-attention/signals.tsx +5 -1
- package/src/plugins/builtin/correlation/headless.ts +13 -5
- package/src/plugins/builtin/correlation/history.ts +22 -0
- package/src/plugins/builtin/cot/client.ts +22 -18
- package/src/plugins/builtin/cot/headless.ts +15 -10
- package/src/plugins/builtin/cot/model.ts +14 -1
- package/src/plugins/builtin/cot/pane.tsx +22 -14
- package/src/plugins/builtin/credit-boards/client.ts +6 -10
- package/src/plugins/builtin/credit-conditions/headless.ts +8 -2
- package/src/plugins/builtin/credit-conditions/index.tsx +5 -1
- package/src/plugins/builtin/credit-conditions/model.ts +4 -0
- package/src/plugins/builtin/credit-conditions/series.ts +3 -1
- package/src/plugins/builtin/credit-documents/detail.tsx +2 -2
- package/src/plugins/builtin/credit-documents/export.ts +173 -0
- package/src/plugins/builtin/credit-documents/headless.ts +7 -7
- package/src/plugins/builtin/credit-documents/model.ts +104 -14
- package/src/plugins/builtin/credit-documents/pane.tsx +12 -5
- package/src/plugins/builtin/custom-view/sources.ts +2 -0
- package/src/plugins/builtin/dividend-yield/client.ts +131 -23
- package/src/plugins/builtin/dividend-yield/headless.ts +44 -16
- package/src/plugins/builtin/dividend-yield/model.ts +8 -5
- package/src/plugins/builtin/dividend-yield/pane.tsx +38 -13
- package/src/plugins/builtin/dividend-yield/ranking.ts +37 -14
- package/src/plugins/builtin/dividend-yield/types.ts +20 -2
- package/src/plugins/builtin/dividend-yield/view.ts +30 -2
- package/src/plugins/builtin/docs/targets.ts +5 -1
- package/src/plugins/builtin/earnings/board-model.ts +16 -15
- package/src/plugins/builtin/earnings/board.tsx +62 -71
- package/src/plugins/builtin/earnings/headless.ts +40 -4
- package/src/plugins/builtin/earnings/scope.ts +31 -43
- package/src/plugins/builtin/earnings-calls/calendar.tsx +2 -2
- package/src/plugins/builtin/earnings-calls/format.ts +31 -2
- package/src/plugins/builtin/earnings-calls/headless.ts +7 -2
- package/src/plugins/builtin/earnings-calls/model.ts +6 -1
- package/src/plugins/builtin/earnings-calls/pane.tsx +5 -4
- package/src/plugins/builtin/econ/types.ts +2 -0
- package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +6 -4
- package/src/plugins/builtin/econ-statistics/stats.ts +36 -9
- package/src/plugins/builtin/equity-screener/categories.ts +179 -0
- package/src/plugins/builtin/equity-screener/client.ts +4 -2
- package/src/plugins/builtin/equity-screener/headless.ts +144 -13
- package/src/plugins/builtin/equity-screener/ideas.ts +49 -0
- package/src/plugins/builtin/equity-screener/model.ts +99 -10
- package/src/plugins/builtin/equity-screener/pane.tsx +68 -13
- package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
- package/src/plugins/builtin/futures/use-board-terms.ts +42 -28
- package/src/plugins/builtin/futures/use-front-returns.ts +45 -28
- package/src/plugins/builtin/fx-matrix/client.ts +6 -3
- package/src/plugins/builtin/fx-matrix/headless.ts +1 -1
- package/src/plugins/builtin/fx-matrix/index.tsx +4 -2
- package/src/plugins/builtin/fx-matrix/live-legs.ts +42 -3
- package/src/plugins/builtin/gpu/headless.ts +8 -2
- package/src/plugins/builtin/help/function-index.ts +4 -1
- package/src/plugins/builtin/help/index.tsx +1 -0
- package/src/plugins/builtin/help/shortcut-model.ts +1 -1
- package/src/plugins/builtin/holders/fund-holders.ts +51 -0
- package/src/plugins/builtin/holders/headless.ts +18 -1
- package/src/plugins/builtin/iv-history/client.ts +52 -11
- package/src/plugins/builtin/iv-history/format.ts +36 -23
- package/src/plugins/builtin/iv-history/headless.ts +33 -18
- package/src/plugins/builtin/iv-history/index.tsx +2 -2
- package/src/plugins/builtin/iv-history/model.ts +163 -27
- package/src/plugins/builtin/iv-history/pane.tsx +10 -6
- package/src/plugins/builtin/iv-history/screen-pane.tsx +22 -15
- package/src/plugins/builtin/iv-history/universe.ts +5 -1
- package/src/plugins/builtin/jobs/client.ts +25 -29
- package/src/plugins/builtin/kelly-sizer/model.ts +12 -8
- package/src/plugins/builtin/kelly-sizer/pane.tsx +9 -9
- package/src/plugins/builtin/kelly-sizer/sections.tsx +19 -10
- package/src/plugins/builtin/kelly-sizer/types.ts +10 -4
- package/src/plugins/builtin/macro-day/release-table.ts +2 -2
- package/src/plugins/builtin/market-heatmap/data.ts +8 -33
- package/src/plugins/builtin/market-movers/client.ts +25 -7
- package/src/plugins/builtin/market-movers/headless.ts +32 -12
- package/src/plugins/builtin/market-movers/index.tsx +26 -14
- package/src/plugins/builtin/market-movers/min-float.ts +74 -0
- package/src/plugins/builtin/market-movers/model.ts +4 -1
- package/src/plugins/builtin/market-movers/screener.ts +14 -0
- package/src/plugins/builtin/market-movers/session-body.tsx +13 -8
- package/src/plugins/builtin/market-movers/session-table.ts +4 -3
- package/src/plugins/builtin/market-movers/table.ts +17 -5
- package/src/plugins/builtin/members/breadth-pane.tsx +2 -1
- package/src/plugins/builtin/news/index.tsx +1 -0
- package/src/plugins/builtin/news/wire/categories.ts +19 -3
- package/src/plugins/builtin/news/wire/industry-pane.tsx +1 -0
- package/src/plugins/builtin/news/wire/news/footer.ts +31 -5
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +6 -1
- package/src/plugins/builtin/news/wire/news/table.tsx +2 -2
- package/src/plugins/builtin/openfda/client.ts +14 -2
- package/src/plugins/builtin/openfda/index.tsx +3 -2
- package/src/plugins/builtin/openfda/model.ts +9 -0
- package/src/plugins/builtin/openfda/pane.tsx +42 -6
- package/src/plugins/builtin/options/analytics.ts +29 -5
- package/src/plugins/builtin/options/enrichment.ts +17 -5
- package/src/plugins/builtin/options/table.ts +5 -2
- package/src/plugins/builtin/options/types.ts +2 -0
- package/src/plugins/builtin/options/view.tsx +17 -4
- package/src/plugins/builtin/options-positioning/client.ts +11 -0
- package/src/plugins/builtin/options-positioning/headless.ts +21 -8
- package/src/plugins/builtin/options-positioning/index.tsx +1 -1
- package/src/plugins/builtin/options-positioning/model.ts +78 -9
- package/src/plugins/builtin/options-positioning/pane.tsx +21 -6
- package/src/plugins/builtin/options-scenario/client.ts +116 -47
- package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +142 -10
- package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +206 -55
- package/src/plugins/builtin/options-scenario/csp-screen.ts +63 -23
- package/src/plugins/builtin/options-scenario/csp.ts +8 -2
- package/src/plugins/builtin/options-scenario/index.tsx +9 -7
- package/src/plugins/builtin/options-scenario/strategy-screen.ts +532 -0
- package/src/plugins/builtin/pe-band/model.ts +12 -49
- package/src/plugins/builtin/perps/cascade-tabs.tsx +161 -0
- package/src/plugins/builtin/perps/cascade.ts +236 -0
- package/src/plugins/builtin/perps/client.ts +56 -2
- package/src/plugins/builtin/perps/headless.ts +65 -9
- package/src/plugins/builtin/perps/index.tsx +7 -5
- package/src/plugins/builtin/perps/model.ts +18 -4
- package/src/plugins/builtin/perps/pane.tsx +19 -22
- package/src/plugins/builtin/perps/status-line.ts +25 -0
- package/src/plugins/builtin/plugin-module.ts +5 -1
- package/src/plugins/builtin/portfolio-list/cached-account.ts +3 -1
- package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +2 -8
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +4 -4
- package/src/plugins/builtin/portfolio-list/cli/render.ts +7 -5
- package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +4 -1
- package/src/plugins/builtin/portfolio-list/pane/streaming.ts +5 -2
- package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +3 -2
- package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +3 -1
- package/src/plugins/builtin/portfolio-list/table.tsx +11 -3
- package/src/plugins/builtin/primary-dealers/client.ts +13 -38
- package/src/plugins/builtin/realized-vol/index.tsx +2 -2
- package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
- package/src/plugins/builtin/relative-rotation/client.ts +18 -4
- package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
- package/src/plugins/builtin/relative-rotation/index.tsx +2 -1
- package/src/plugins/builtin/relative-rotation/model.ts +16 -40
- package/src/plugins/builtin/relative-rotation/pane.tsx +3 -2
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -2
- package/src/plugins/builtin/research/event-model.ts +10 -8
- package/src/plugins/builtin/research/headless.ts +1 -1
- package/src/plugins/builtin/research/relative-valuation-headless.ts +5 -1
- package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +7 -5
- package/src/plugins/builtin/reverse-dcf/headless.ts +5 -3
- package/src/plugins/builtin/reverse-dcf/model.ts +61 -6
- package/src/plugins/builtin/reverse-dcf/pane.tsx +7 -4
- package/src/plugins/builtin/scanner/flow-model.ts +50 -4
- package/src/plugins/builtin/scanner/flow-pane.tsx +367 -65
- package/src/plugins/builtin/scanner/flow-session.ts +146 -0
- package/src/plugins/builtin/scanner/headless.ts +204 -35
- package/src/plugins/builtin/scanner/index.ts +16 -5
- package/src/plugins/builtin/scanner/live-headless.ts +27 -3
- package/src/plugins/builtin/scanner/live-model.ts +11 -0
- package/src/plugins/builtin/scanner/live-pane.tsx +3 -3
- package/src/plugins/builtin/scanner/lock-note.ts +3 -2
- package/src/plugins/builtin/seasonality/headless.ts +10 -1
- package/src/plugins/builtin/sec/fund-facts-view.tsx +5 -2
- package/src/plugins/builtin/sec/fund-facts.ts +116 -34
- package/src/plugins/builtin/sec/headless.ts +2 -1
- package/src/plugins/builtin/series-inventory.ts +2 -0
- package/src/plugins/builtin/shared/cloud-resource.ts +3 -2
- package/src/plugins/builtin/shared/cloud-upgrade.ts +4 -3
- package/src/plugins/builtin/shared/earnings-calendar.ts +6 -2
- package/src/plugins/builtin/shared/earnings-report.ts +2 -2
- package/src/plugins/builtin/shared/job-posting.ts +1 -0
- package/src/plugins/builtin/shared/members-menu.tsx +3 -2
- package/src/plugins/builtin/shared/pro-lock-note.ts +12 -12
- package/src/plugins/builtin/shared/report-freshness.ts +3 -2
- package/src/plugins/builtin/shared/ticker-choice.tsx +2 -1
- package/src/plugins/builtin/shared/ticker-company.ts +77 -0
- package/src/plugins/builtin/shared/use-quote-board.ts +2 -1
- package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
- package/src/plugins/builtin/shared/volatility/pricing.ts +19 -0
- package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
- package/src/plugins/builtin/themes/model.ts +16 -7
- package/src/plugins/builtin/themes/pane.tsx +5 -5
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +43 -18
- package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +3 -2
- package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +62 -0
- package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +83 -46
- package/src/plugins/builtin/ticker-detail/headless.ts +31 -16
- package/src/plugins/builtin/ticker-detail/overview/model.ts +1 -1
- package/src/plugins/builtin/ticker-detail/pane.tsx +3 -2
- package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -1
- package/src/plugins/builtin/ticker-detail/settings.ts +1 -0
- package/src/plugins/builtin/time-sales/pane.tsx +31 -19
- package/src/plugins/builtin/treasury-daily/client.ts +41 -5
- package/src/plugins/builtin/treasury-daily/headless.ts +2 -2
- package/src/plugins/builtin/treasury-daily/index.tsx +1 -1
- package/src/plugins/builtin/treasury-daily/model.ts +50 -3
- package/src/plugins/builtin/treasury-daily/pane.tsx +81 -2
- package/src/plugins/builtin/vol-surface/client.ts +3 -13
- package/src/plugins/builtin/vol-surface/headless.ts +3 -3
- package/src/plugins/builtin/vol-surface/index.tsx +1 -1
- package/src/plugins/builtin/vol-surface/pane.tsx +7 -5
- package/src/plugins/builtin/volatility/index.tsx +2 -2
- package/src/plugins/builtin/volatility/pane.tsx +2 -2
- package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
- package/src/plugins/catalog-manifest.ts +2 -1
- package/src/plugins/marketplace/model.ts +18 -5
- package/src/portfolio/futures-positions.ts +6 -3
- package/src/portfolio/position-quantity.ts +15 -9
- package/src/remote/resources.ts +2 -1
- package/src/renderers/dom/data-table/row.tsx +26 -0
- package/src/renderers/dom/desktop/list-view.tsx +13 -10
- package/src/renderers/dom/desktop/query-bar.tsx +4 -2
- package/src/renderers/dom/desktop/select-field.tsx +4 -2
- package/src/renderers/dom/host/chart-surface.tsx +21 -0
- package/src/renderers/electrobun/bun/desktop/app-name.ts +3 -3
- package/src/renderers/electrobun/bun/desktop/backend.ts +12 -0
- package/src/renderers/electrobun/bun/desktop/host-requests.ts +7 -0
- package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +115 -14
- package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
- package/src/renderers/electrobun/shared/app-name.ts +13 -0
- package/src/renderers/electrobun/shared/protocol.ts +10 -0
- package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
- package/src/renderers/electrobun/view/main.tsx +24 -1
- package/src/renderers/electrobun/view/news-source.ts +3 -1
- package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
- package/src/renderers/electrobun/view/view-ids.ts +85 -0
- package/src/renderers/opentui/start.tsx +5 -0
- package/src/series/registry.ts +1 -0
- package/src/series/types.ts +6 -0
- package/src/series/validate.ts +1 -0
- package/src/sources/gloomberb-cloud/index.ts +1 -0
- package/src/sources/gloomberb-cloud/news.ts +47 -7
- package/src/sources/provider-router/financials.ts +15 -3
- package/src/sources/provider-router/index.ts +8 -5
- package/src/state/ticker-cleanup.ts +50 -0
- package/src/sync/controller.ts +4 -3
- package/src/sync/core-contributors.ts +90 -31
- package/src/telemetry/stall-reports-dom.ts +38 -0
- package/src/telemetry/stall-reports-node.ts +75 -0
- package/src/telemetry/stall-reports.ts +214 -0
- package/src/test-support/preload.ts +14 -0
- package/src/theme/themes.ts +10 -0
- package/src/tickers/company-name.ts +44 -3
- package/src/tickers/listing-venues.ts +15 -3
- package/src/tickers/terminal-codes.ts +19 -0
- package/src/time-series/alignment.ts +1 -1
- package/src/time-series/economic.ts +18 -4
- package/src/time-series/field-catalog.ts +1 -1
- package/src/time-series/filed-trailing-eps.ts +197 -0
- package/src/time-series/fundamentals.ts +44 -1
- package/src/time-series/ice-credit.ts +3 -0
- package/src/time-series/price-comparison.ts +30 -2
- package/src/time-series/resolve-context.ts +3 -1
- package/src/time-series/resolve.ts +56 -4
- package/src/time-series/session-bar.ts +11 -0
- package/src/time-series/spec.ts +3 -2
- package/src/time-series/studies.ts +8 -2
- package/src/time-series/total-return.ts +160 -0
- package/src/time-series/transforms.ts +2 -3
- package/src/time-series/types.ts +22 -1
- package/src/types/financials.ts +32 -1
- package/src/types/headless.ts +21 -3
- package/src/types/news-source.ts +1 -0
- package/src/types/plugin.ts +9 -0
- package/src/ui/host.tsx +3 -0
- package/src/updater/failure.ts +4 -1
- package/src/utils/cli-output.ts +42 -1
- package/src/utils/crypto-pair.ts +44 -0
- package/src/utils/forward-dividend.ts +2 -0
- package/src/utils/fundamentals.ts +24 -3
- package/src/utils/fx-market-hours.ts +26 -3
- package/src/utils/main-thread-stall.ts +35 -2
- package/src/utils/market-capitalization.ts +24 -1
- package/src/utils/price-history.ts +7 -1
- package/src/utils/shell-word.ts +8 -0
- package/src/utils/trailing-dividend.ts +53 -4
- package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
|
@@ -55,7 +55,9 @@ import {
|
|
|
55
55
|
import { colors } from "../../../theme/colors";
|
|
56
56
|
import { isUsListingExchange, parsePublicTickerKey, publicTickerKey } from "../../../utils/exchanges";
|
|
57
57
|
import { getTimeSeriesField, isMarketFieldId } from "../../../time-series/field-catalog";
|
|
58
|
-
import { isDatasetChart } from "../../../time-series/spec";
|
|
58
|
+
import { isDatasetChart, isOhlcSeriesStyle } from "../../../time-series/spec";
|
|
59
|
+
import { useThemeColors } from "../../../theme/theme-context";
|
|
60
|
+
import { priceComparisonSeriesIds } from "../../../time-series/price-comparison";
|
|
59
61
|
import { seriesMessageName, studyMessageName } from "../../../time-series/series-name";
|
|
60
62
|
import { CHART_COMPOSER_PANE_ID } from "../../../types/config";
|
|
61
63
|
import { useRemoteUiNode } from "../../../remote/semantic-tree";
|
|
@@ -117,6 +119,8 @@ import {
|
|
|
117
119
|
CHART_BAR_SIZE_PRESETS,
|
|
118
120
|
CHART_RANGES as RANGES,
|
|
119
121
|
CHART_RESOLUTIONS,
|
|
122
|
+
CHART_CANDLE_COLOURS_SETTING_KEY,
|
|
123
|
+
chartCandleColours,
|
|
120
124
|
chartStudyOptionsFor,
|
|
121
125
|
chartStudyPeriodTitle,
|
|
122
126
|
chartStudySettingLabel,
|
|
@@ -342,6 +346,14 @@ function ChartComposerSurface({
|
|
|
342
346
|
// plausible time is dropped without pinning. This handle only sets the
|
|
343
347
|
// pin; the chart it opens on is what shows and keeps it.
|
|
344
348
|
const [pinnedMoment, setPinnedMoment] = usePaneSettingValue<unknown>(CHART_PINNED_MOMENT_SETTING_KEY, null);
|
|
349
|
+
const [candleColoursSetting] = usePaneSettingValue<unknown>(CHART_CANDLE_COLOURS_SETTING_KEY, null);
|
|
350
|
+
const gainColour = useThemeColors().positive;
|
|
351
|
+
// Rising candles in the gain colour; falling ones already take the loss colour.
|
|
352
|
+
const candleColour = useCallback((list: ResolvedSeries[]) => (
|
|
353
|
+
chartCandleColours(candleColoursSetting) === "change"
|
|
354
|
+
? list.map((entry) => isOhlcSeriesStyle(entry.style) ? { ...entry, color: gainColour } : entry)
|
|
355
|
+
: list
|
|
356
|
+
), [candleColoursSetting, gainColour]);
|
|
345
357
|
const momentLink = useTabCursorLink(paneId ? `${paneId}:moment` : null, { viewer: false });
|
|
346
358
|
useEffect(() => {
|
|
347
359
|
if (pinnedMoment == null || !momentLink?.pin) return;
|
|
@@ -475,9 +487,9 @@ function ChartComposerSurface({
|
|
|
475
487
|
});
|
|
476
488
|
const resolvedIds = new Set(resolved.map((entry) => entry.id));
|
|
477
489
|
const missing = spec.series.filter((entry) => !resolvedIds.has(entry.id));
|
|
478
|
-
if (missing.length === 0) return resolved;
|
|
490
|
+
if (missing.length === 0) return candleColour(resolved);
|
|
479
491
|
const bySpecOrder = new Map(spec.series.map((entry, index) => [entry.id, index] as const));
|
|
480
|
-
return [
|
|
492
|
+
return candleColour([
|
|
481
493
|
...resolved,
|
|
482
494
|
...missing.map((entry): ResolvedSeries => ({
|
|
483
495
|
id: entry.id,
|
|
@@ -495,15 +507,15 @@ function ChartComposerSurface({
|
|
|
495
507
|
hidden: true,
|
|
496
508
|
points: [],
|
|
497
509
|
})),
|
|
498
|
-
].sort((a, b) => (bySpecOrder.get(a.id) ?? Number.MAX_SAFE_INTEGER) - (bySpecOrder.get(b.id) ?? Number.MAX_SAFE_INTEGER));
|
|
499
|
-
}, [listings.names, nameSeries, resolution.legendSeries, spec.series]);
|
|
510
|
+
].sort((a, b) => (bySpecOrder.get(a.id) ?? Number.MAX_SAFE_INTEGER) - (bySpecOrder.get(b.id) ?? Number.MAX_SAFE_INTEGER)));
|
|
511
|
+
}, [candleColour, listings.names, nameSeries, resolution.legendSeries, spec.series]);
|
|
500
512
|
const plottedSeries = useMemo(
|
|
501
|
-
() => projectVisibleChartSeries(
|
|
513
|
+
() => candleColour(projectVisibleChartSeries(
|
|
502
514
|
spec,
|
|
503
515
|
resolution.bufferedSeries ?? resolution.series,
|
|
504
516
|
resolution.legendSeries,
|
|
505
|
-
),
|
|
506
|
-
[resolution.bufferedSeries, resolution.legendSeries, resolution.series, spec],
|
|
517
|
+
)),
|
|
518
|
+
[candleColour, resolution.bufferedSeries, resolution.legendSeries, resolution.series, spec],
|
|
507
519
|
);
|
|
508
520
|
const { notify } = usePluginAppActions();
|
|
509
521
|
const { runtime } = usePluginRenderContext();
|
|
@@ -1190,6 +1202,22 @@ function ChartComposerSurface({
|
|
|
1190
1202
|
}]
|
|
1191
1203
|
: [];
|
|
1192
1204
|
|
|
1205
|
+
// Returns of two or more prices compare as price returns; the toggle reinvests their cash distributions.
|
|
1206
|
+
const totalReturnOn = spec.viewport.totalReturn === true;
|
|
1207
|
+
const totalReturnFilters = totalReturnOn || priceComparisonSeriesIds(spec) !== null
|
|
1208
|
+
? [{
|
|
1209
|
+
id: "total-return",
|
|
1210
|
+
kind: "toggle" as const,
|
|
1211
|
+
label: "Total return",
|
|
1212
|
+
short: "Total ret",
|
|
1213
|
+
value: totalReturnOn,
|
|
1214
|
+
onChange: (value: boolean) => {
|
|
1215
|
+
const { totalReturn: _previous, ...viewport } = specRef.current.viewport;
|
|
1216
|
+
updateSpec({ ...specRef.current, viewport: value ? { ...viewport, totalReturn: true } : viewport });
|
|
1217
|
+
},
|
|
1218
|
+
}]
|
|
1219
|
+
: [];
|
|
1220
|
+
|
|
1193
1221
|
// A company that reports no quarterly figures says so, and where its annual ones are.
|
|
1194
1222
|
const unreported = resolution.legendSeries?.find((entry) => entry.unreportedPeriod && entry.points.length === 0);
|
|
1195
1223
|
const emptyMessage = spec.series.length === 0
|
|
@@ -1216,6 +1244,7 @@ function ChartComposerSurface({
|
|
|
1216
1244
|
onChange: (value: string) => setRange(value as TimeRange) },
|
|
1217
1245
|
...genericFilters,
|
|
1218
1246
|
...extendedHoursFilters,
|
|
1247
|
+
...totalReturnFilters,
|
|
1219
1248
|
]}
|
|
1220
1249
|
view={resolutionOptions.length > 1 ? {
|
|
1221
1250
|
value: spec.viewport.resolution,
|
|
@@ -129,6 +129,20 @@ export const CHART_SETTING_KEYS = {
|
|
|
129
129
|
realizedVolEstimator: "chartRealizedVolEstimator",
|
|
130
130
|
} as const;
|
|
131
131
|
|
|
132
|
+
/**
|
|
133
|
+
* Whether rising candles take their series' colour or the theme's gain colour.
|
|
134
|
+
* Kept beside the spec in the pane's settings, so a build without it opens the
|
|
135
|
+
* chart unchanged; it is not one of CHART_SETTING_KEYS, which a spec edit
|
|
136
|
+
* clears as derived from the spec.
|
|
137
|
+
*/
|
|
138
|
+
export const CHART_CANDLE_COLOURS_SETTING_KEY = "chartCandleColours";
|
|
139
|
+
|
|
140
|
+
export type ChartCandleColours = "series" | "change";
|
|
141
|
+
|
|
142
|
+
export function chartCandleColours(value: unknown): ChartCandleColours {
|
|
143
|
+
return value === "change" ? "change" : "series";
|
|
144
|
+
}
|
|
145
|
+
|
|
132
146
|
function fallbackSpec(symbol: string | null | undefined): ChartSpec {
|
|
133
147
|
return symbol ? buildPriceChartPreset(symbol) : buildEmptyChartPreset();
|
|
134
148
|
}
|
|
@@ -417,6 +431,7 @@ export function buildChartComposerPaneSettingsDef(
|
|
|
417
431
|
[CHART_SETTING_KEYS.range]: spec.viewport.range,
|
|
418
432
|
[CHART_SETTING_KEYS.resolution]: spec.viewport.resolution,
|
|
419
433
|
[CHART_SETTING_KEYS.mode]: inlineStyleTarget?.style ?? "",
|
|
434
|
+
[CHART_CANDLE_COLOURS_SETTING_KEY]: chartCandleColours(settings[CHART_CANDLE_COLOURS_SETTING_KEY]),
|
|
420
435
|
...(realizedVol ? {
|
|
421
436
|
[CHART_SETTING_KEYS.realizedVolWindow]: String(realizedVol.parameters.window ?? 30),
|
|
422
437
|
[CHART_SETTING_KEYS.realizedVolEstimator]: realizedVol.parameters.estimator ?? "close-to-close",
|
|
@@ -494,6 +509,18 @@ export function buildChartComposerPaneSettingsDef(
|
|
|
494
509
|
options: modes.map((mode) => ({ value: mode, label: mode.toUpperCase() })),
|
|
495
510
|
}]
|
|
496
511
|
: []),
|
|
512
|
+
...(spec.series.some((series) => isOhlcSeriesStyle(series.style))
|
|
513
|
+
? [{
|
|
514
|
+
key: CHART_CANDLE_COLOURS_SETTING_KEY,
|
|
515
|
+
label: "Candle Colours",
|
|
516
|
+
description: "Draw rising candles in their series' colour, or in the theme's gain colour against its loss colour.",
|
|
517
|
+
type: "select" as const,
|
|
518
|
+
options: [
|
|
519
|
+
{ value: "series", label: "Series colour" },
|
|
520
|
+
{ value: "change", label: "Gains & losses" },
|
|
521
|
+
],
|
|
522
|
+
}]
|
|
523
|
+
: []),
|
|
497
524
|
],
|
|
498
525
|
applyValue: applyChartComposerPaneSetting,
|
|
499
526
|
};
|
|
@@ -55,6 +55,7 @@ export function filterCommandSuggestions(
|
|
|
55
55
|
const byCode: ChatFunction[] = [];
|
|
56
56
|
const byName: ChatFunction[] = [];
|
|
57
57
|
for (const fn of new Set(functions.values())) {
|
|
58
|
+
if (fn.unlisted) continue;
|
|
58
59
|
if (fn.code.startsWith(upper) || fn.aliases.includes(upper)) byCode.push(fn);
|
|
59
60
|
else if (fn.name.toLowerCase().split(/[\s/&()-]+/).some((word) => word.startsWith(lower))) byName.push(fn);
|
|
60
61
|
}
|
|
@@ -36,6 +36,8 @@ export interface ChatFunction {
|
|
|
36
36
|
arg: ChatFunctionArg;
|
|
37
37
|
/** For a choice, the values it takes. */
|
|
38
38
|
choices?: () => readonly string[];
|
|
39
|
+
/** Written as a chip when someone types it, but never suggested (`listed: false`). */
|
|
40
|
+
unlisted?: true;
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
/** A function named in a message, and the text the command bar runs for it. */
|
|
@@ -106,7 +108,11 @@ export function chatFunctionsFrom(source: ChatFunctionSource, core: readonly Com
|
|
|
106
108
|
// A switched-off plugin's code opens nothing until it is turned on, and
|
|
107
109
|
// leaves the code to a function that does.
|
|
108
110
|
if (source.isTemplateOff?.(template.id)) continue;
|
|
109
|
-
add(upperCodes(paneTemplateShortcutPrefixes(template)), {
|
|
111
|
+
add(upperCodes(paneTemplateShortcutPrefixes(template)), {
|
|
112
|
+
name: getPaneTemplateDisplayLabel(template),
|
|
113
|
+
...templateArg(template),
|
|
114
|
+
...(template.listed === false ? { unlisted: true as const } : {}),
|
|
115
|
+
});
|
|
110
116
|
}
|
|
111
117
|
return byCode;
|
|
112
118
|
}
|
|
@@ -267,12 +267,3 @@ export function trialLayout(width: number): TrialLayout {
|
|
|
267
267
|
const statusAt = fixed.findIndex((column) => column.id === "status") + 1;
|
|
268
268
|
return { compact, showVenue: wide, columns: [...fixed.slice(0, statusAt), title, ...fixed.slice(statusAt)] };
|
|
269
269
|
}
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
* A search typed as a ticker (`TRIAL VKTX`): one to five letters, with a
|
|
273
|
-
* share class. The registry knows sponsors by name, so a symbol no study
|
|
274
|
-
* mentions is looked up as its company.
|
|
275
|
-
*/
|
|
276
|
-
export function looksLikeTicker(term: string): boolean {
|
|
277
|
-
return /^[A-Za-z]{1,5}(?:[.-][A-Za-z])?$/.test(term.trim());
|
|
278
|
-
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback, useEffect, useMemo, useRef
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef } from "react";
|
|
2
2
|
import {
|
|
3
3
|
DataTableStackView,
|
|
4
4
|
PaneLinkMenu,
|
|
@@ -41,7 +41,6 @@ import {
|
|
|
41
41
|
SPONSOR_FILTER_OPTIONS,
|
|
42
42
|
STATUS_FILTER_OPTIONS,
|
|
43
43
|
keepsToneWhenSelected,
|
|
44
|
-
looksLikeTicker,
|
|
45
44
|
normalizeTrialFilters,
|
|
46
45
|
sponsorClassLabel,
|
|
47
46
|
trialFiltersNarrow,
|
|
@@ -57,7 +56,7 @@ import {
|
|
|
57
56
|
type TrialFilters,
|
|
58
57
|
} from "./model";
|
|
59
58
|
import { useSponsorTickers, type SponsorTicker } from "./sponsor-tickers";
|
|
60
|
-
import {
|
|
59
|
+
import { looksLikeTicker, useTickerCompanySearch } from "../shared/ticker-company";
|
|
61
60
|
import { CLINICAL_TRIALS_PANE_ID } from "./types";
|
|
62
61
|
|
|
63
62
|
const SEARCH_DEBOUNCE_MS = 250;
|
|
@@ -187,7 +186,6 @@ export function TrialsPane({ width, height, focused }: PaneProps) {
|
|
|
187
186
|
}, [client, filtersKey, term]);
|
|
188
187
|
const studies = usePagedRows(loadPage, { getId: (trial) => trial.nctId, keepPreviousRows: true });
|
|
189
188
|
const trials = studies.rows;
|
|
190
|
-
const error = studies.error?.message ?? null;
|
|
191
189
|
const loadMore = useTableLoadMore(scrollRef, studies.hasMore && !openItemId, studies.loadMore);
|
|
192
190
|
// Scrolling asks for the next page, so a page that does not fill a tall
|
|
193
191
|
// pane asks for it once it is laid out.
|
|
@@ -230,28 +228,23 @@ export function TrialsPane({ width, height, focused }: PaneProps) {
|
|
|
230
228
|
setOpenItemId(null);
|
|
231
229
|
}, [filters, setOpenItemId, setSavedFilters, setSelectedId]);
|
|
232
230
|
|
|
233
|
-
const reload = useCallback(() => {
|
|
234
|
-
studies.reload();
|
|
235
|
-
sponsorTickers.retry();
|
|
236
|
-
}, [sponsorTickers, studies]);
|
|
237
231
|
// `TRIAL VKTX`: no study names a symbol, so one that finds nothing is
|
|
238
232
|
// searched as its company's name, quoted so the registry matches the whole
|
|
239
233
|
// name as a sponsor files it, and the search box then shows it. Each term
|
|
240
234
|
// is looked up once.
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
const loading = (studies.loading && trials.length === 0) || resolvingTicker;
|
|
235
|
+
const companySearch = useTickerCompanySearch(dataProvider, term, {
|
|
236
|
+
eligible: studies.status === "loaded" && trials.length === 0 && !studies.error && looksLikeTicker(term),
|
|
237
|
+
key: term.toUpperCase(),
|
|
238
|
+
onCompany: (name) => updateQuery(`"${name}"`),
|
|
239
|
+
});
|
|
240
|
+
const retryCompanySearch = companySearch.retry;
|
|
241
|
+
const error = studies.error?.message ?? companySearch.error;
|
|
242
|
+
const reload = useCallback(() => {
|
|
243
|
+
retryCompanySearch();
|
|
244
|
+
studies.reload();
|
|
245
|
+
sponsorTickers.retry();
|
|
246
|
+
}, [retryCompanySearch, sponsorTickers, studies]);
|
|
247
|
+
const loading = (studies.loading && trials.length === 0) || companySearch.resolving;
|
|
255
248
|
usePaneRefreshKey(reload, { focused, enabled: !searchFocused && !openItemId });
|
|
256
249
|
|
|
257
250
|
const detailTicker = detailTrial ? tickerOf(detailTrial) : undefined;
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
validateOrganizationTickers,
|
|
7
7
|
type OrganizationTicker,
|
|
8
8
|
} from "../../../api-client/organization-tickers";
|
|
9
|
+
import { createTtlCache, type TtlCache } from "../../../data/ttl-cache";
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* A lead sponsor's ticker, only ever as Gloom Cloud resolved it. `none` is an
|
|
@@ -46,17 +47,19 @@ const sponsorKey = (name: string) => name.trim().replace(/\s+/g, " ").toLowerCas
|
|
|
46
47
|
* never stored as having no ticker because the lookup did not happen.
|
|
47
48
|
*/
|
|
48
49
|
export class SponsorTickerStore {
|
|
49
|
-
private readonly answers
|
|
50
|
+
private readonly answers: TtlCache<SponsorTicker>;
|
|
50
51
|
private readonly failures = new Map<string, string>();
|
|
51
52
|
private readonly inFlight = new Set<string>();
|
|
52
53
|
private readonly listeners = new Set<() => void>();
|
|
53
54
|
|
|
54
|
-
constructor(private readonly lookup: Lookup,
|
|
55
|
+
constructor(private readonly lookup: Lookup, now: () => number = Date.now) {
|
|
56
|
+
this.answers = createTtlCache<SponsorTicker>({ ttlMs: CACHE_TTL_MS, now });
|
|
57
|
+
}
|
|
55
58
|
|
|
56
59
|
get(name: string): SponsorTicker {
|
|
57
60
|
const key = sponsorKey(name);
|
|
58
|
-
const answer = this.answers.
|
|
59
|
-
if (answer
|
|
61
|
+
const answer = this.answers.peek(key);
|
|
62
|
+
if (answer) return answer;
|
|
60
63
|
const reason = this.failures.get(key);
|
|
61
64
|
return reason ? { kind: "failed", reason } : { kind: "pending" };
|
|
62
65
|
}
|
|
@@ -86,8 +89,7 @@ export class SponsorTickerStore {
|
|
|
86
89
|
keys.forEach((key) => this.inFlight.add(key));
|
|
87
90
|
try {
|
|
88
91
|
const results = await this.lookup(batch, signal);
|
|
89
|
-
|
|
90
|
-
results.forEach((result, index) => this.answers.set(keys[index]!, { value: sponsorTickerFrom(result), expiresAt }));
|
|
92
|
+
results.forEach((result, index) => this.answers.set(keys[index]!, sponsorTickerFrom(result)));
|
|
91
93
|
} catch (error) {
|
|
92
94
|
// An abandoned lookup (its pane closed) is neither an answer nor a failure.
|
|
93
95
|
if (!signal?.aborted) {
|
|
@@ -142,6 +142,8 @@ function headlessCandidates(registry: PaneFunctionCatalog): ManifestCandidate[]
|
|
|
142
142
|
const pane = registry.panes.get(template.paneId);
|
|
143
143
|
if (!pane) continue;
|
|
144
144
|
templatedPaneIds.add(pane.id);
|
|
145
|
+
// An unlisted function (`listed: false`) is no tool, and its pane none in its place.
|
|
146
|
+
if (template.listed === false) continue;
|
|
145
147
|
const definition = definitionFor(template, pane);
|
|
146
148
|
if (!definition) continue;
|
|
147
149
|
const token = template.shortcut?.prefix ?? template.id;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { logFailure } from "../../../utils/log-failure";
|
|
1
2
|
import { useCallback, useMemo, useRef, type ReactNode } from "react";
|
|
2
3
|
import { isAccessDenied } from "../../../api-client/errors";
|
|
3
4
|
import type { HiringBuildingFilter, HiringBuildingShift } from "../../../api-client/hiring";
|
|
@@ -60,7 +61,10 @@ export function HiringBuildingView({ symbol, width, height, focused, accessKey,
|
|
|
60
61
|
try {
|
|
61
62
|
const payload = snapshotSetting ? validateBuilding(snapshotSetting) : null;
|
|
62
63
|
return payload && ("filter" in payload ? !symbol : payload.symbol.toUpperCase() === symbol?.toUpperCase()) ? payload : null;
|
|
63
|
-
} catch {
|
|
64
|
+
} catch (error) {
|
|
65
|
+
logFailure("company-attention.building", "Validate hiring building snapshot failed")(error);
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
64
68
|
}, [snapshotSetting, symbol]);
|
|
65
69
|
const loader = useCallback((force: boolean) => snapshot ? Promise.resolve({ payload: snapshot, stale: false, refreshError: null }) : loadBuilding(symbol, accessKey, filter, force), [snapshot, symbol, accessKey, filter]);
|
|
66
70
|
const resource = useAsyncResource(loader, { initialData: () => cachedBuilding(symbol, accessKey, filter), clearOnError: isAccessDenied, keepPreviousData: true });
|
|
@@ -3,6 +3,7 @@ import type { AppAttentionFilter, AppAttentionPayload, AppRankPayload } from "..
|
|
|
3
3
|
import type { HiringBoard, HiringBuildingBoard, HiringBuildingFilter, HiringBuildingShift, HiringCompanyBuilding, HiringCompanyMoves, HiringCompanySignals, HiringMixRow, HiringMove, HiringMovePosting, HiringMovesBoard, HiringMovesFilter, HiringMoveWeek, HiringPayload, HiringSignalGroup, HiringSignalsBoard, HiringSignalsFilter, HiringSummary, HiringWeekSignal } from "../../../api-client/hiring";
|
|
4
4
|
import type { CloudJobsPostingDetail } from "../../../api-client/types";
|
|
5
5
|
import { createPluginCache } from "../../../data/plugin-cache";
|
|
6
|
+
import { createTtlCache } from "../../../data/ttl-cache";
|
|
6
7
|
import { cachedCloudResource, loadCloudResource, unavailableOnServer } from "../shared/cloud-resource";
|
|
7
8
|
import type { AppFocus, AttentionKind } from "./model";
|
|
8
9
|
|
|
@@ -19,7 +20,10 @@ const finite = (value: unknown) => typeof value === "number" && Number.isFinite(
|
|
|
19
20
|
const positive = (value: unknown) => finite(value) && (value as number) >= 0;
|
|
20
21
|
const fraction = (value: unknown) => positive(value) && (value as number) <= 1;
|
|
21
22
|
const date = (value: unknown) => typeof value === "string" && Number.isFinite(Date.parse(value));
|
|
22
|
-
const url = (value: unknown) => { try { return typeof value === "string" && ["https:", "http:"].includes(new URL(value).protocol); } catch {
|
|
23
|
+
const url = (value: unknown) => { try { return typeof value === "string" && ["https:", "http:"].includes(new URL(value).protocol); } catch {
|
|
24
|
+
// Intentional ignore: malformed URLs fail this web link field validator.
|
|
25
|
+
return false;
|
|
26
|
+
} };
|
|
23
27
|
function numericTree(value: unknown): boolean {
|
|
24
28
|
if (typeof value === "number") return Number.isFinite(value);
|
|
25
29
|
if (Array.isArray(value)) return value.every(numericTree);
|
|
@@ -165,21 +169,16 @@ export async function fetchAppRank(focus: AppFocus, days: number, offset = 0, si
|
|
|
165
169
|
export const loadAppRank = (focus: AppFocus, access: string, days: number, force = false) => loadCloudResource(appRankCache, `${access}:${focus.store}:${focus.appId}:${focus.country}:${focus.chart}:${days}`, () => fetchAppRank(focus, days), { force });
|
|
166
170
|
|
|
167
171
|
const POSTING_TTL_MS = 5 * 60_000;
|
|
168
|
-
const
|
|
172
|
+
const postings = createTtlCache<CloudJobsPostingDetail>({ ttlMs: POSTING_TTL_MS });
|
|
169
173
|
/**
|
|
170
174
|
* The page HIRE's evidence row was read from: its title, place, date, pay,
|
|
171
175
|
* hours and description. Kept a few minutes, so stepping back and forth
|
|
172
176
|
* through the evidence does not read it again.
|
|
173
177
|
*/
|
|
174
178
|
export function fetchPostingDetail(ticker: string, externalId: string, client: Pick<typeof apiClient, "getCloudJobsPosting"> = apiClient): Promise<CloudJobsPostingDetail> {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
if (cached && Date.now() - cached.at < POSTING_TTL_MS) return cached.posting;
|
|
178
|
-
const posting = client.getCloudJobsPosting(ticker, { externalId }).then((detail) => {
|
|
179
|
+
return postings.load(`${ticker.toUpperCase()}:${externalId}`, async () => {
|
|
180
|
+
const detail = await client.getCloudJobsPosting(ticker, { externalId });
|
|
179
181
|
if (!detail || typeof detail.title !== "string" || detail.externalId !== externalId) throw new Error("The server returned an unreadable posting.");
|
|
180
182
|
return detail;
|
|
181
183
|
});
|
|
182
|
-
postingCache.set(key, { at: Date.now(), posting });
|
|
183
|
-
posting.catch(() => postingCache.delete(key));
|
|
184
|
-
return posting;
|
|
185
184
|
}
|
|
@@ -45,7 +45,10 @@ export function attentionScreenshotEvidence(kind: AttentionKind): PaneScreenshot
|
|
|
45
45
|
read(value) {
|
|
46
46
|
if (!isRecord(value) || value.kind !== kind || value.version !== 1 || value.complete !== true || !isRecord(value.payload)
|
|
47
47
|
|| !["table", "chart", "mix", "peers", "evidence", ...(kind === "hiring" ? ["moves", "expansion", "anomalies", "building"] : [])].includes(String(value.tab)) || !Array.isArray(value.rowIds) || !value.rowIds.every((id) => typeof id === "string") || value.plottedValueCount !== value.rowIds.length) return null;
|
|
48
|
-
try { if (!(kind === "apps" && Array.isArray(value.payload.rankHistory))) validateAttention(kind, value.payload as unknown as AttentionPayload); return value as unknown as AttentionEvidence; } catch {
|
|
48
|
+
try { if (!(kind === "apps" && Array.isArray(value.payload.rankHistory))) validateAttention(kind, value.payload as unknown as AttentionPayload); return value as unknown as AttentionEvidence; } catch {
|
|
49
|
+
// Intentional ignore: invalid captured observations are rejected as evidence by returning null.
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
49
52
|
},
|
|
50
53
|
mismatches(evidence, { resolved, payload }) {
|
|
51
54
|
const settings = payload.config.layout.instances.find((entry) => entry.instanceId === payload.paneId)?.settings;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { logFailure } from "../../../utils/log-failure";
|
|
1
2
|
import { useCallback, useMemo, useRef, type ReactNode } from "react";
|
|
2
3
|
import { isAccessDenied } from "../../../api-client/errors";
|
|
3
4
|
import type { HiringMovesFilter } from "../../../api-client/hiring";
|
|
@@ -63,7 +64,10 @@ export function HiringMovesView({ symbol, width, height, focused, accessKey, com
|
|
|
63
64
|
try {
|
|
64
65
|
const payload = snapshotSetting ? validateMoves(snapshotSetting) : null;
|
|
65
66
|
return payload && ("moves" in payload ? !symbol : payload.symbol.toUpperCase() === symbol?.toUpperCase()) ? payload : null;
|
|
66
|
-
} catch {
|
|
67
|
+
} catch (error) {
|
|
68
|
+
logFailure("company-attention.moves", "Validate hiring moves snapshot failed")(error);
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
67
71
|
}, [snapshotSetting, symbol]);
|
|
68
72
|
const loader = useCallback((force: boolean) => snapshot ? Promise.resolve({ payload: snapshot, stale: false, refreshError: null }) : loadMoves(symbol, accessKey, filter, force), [snapshot, symbol, accessKey, filter]);
|
|
69
73
|
const resource = useAsyncResource(loader, { initialData: () => cachedMoves(symbol, accessKey, filter), clearOnError: isAccessDenied, keepPreviousData: true });
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { logFailure } from "../../../utils/log-failure";
|
|
1
2
|
import { useCallback, useMemo, useRef } from "react";
|
|
2
3
|
import { isAccessDenied } from "../../../api-client/errors";
|
|
3
4
|
import { usePlanAccess } from "../../../api-client/plan-access";
|
|
@@ -57,7 +58,10 @@ function Evidence({ row, width, posting, source }: Size & { row: AttentionRow; p
|
|
|
57
58
|
}
|
|
58
59
|
/** A link's site, as a reader names it: "sec.gov". */
|
|
59
60
|
function linkName(url: string): string {
|
|
60
|
-
try { return new URL(url).hostname.replace(/^www\./, ""); } catch {
|
|
61
|
+
try { return new URL(url).hostname.replace(/^www\./, ""); } catch {
|
|
62
|
+
// Intentional ignore: a malformed link has no site to name, so it reads as Source.
|
|
63
|
+
return "Source";
|
|
64
|
+
}
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
export function HiringPane(props: Size) {
|
|
@@ -105,7 +109,10 @@ function AttentionView({ kind, symbol, width, height, focused, initialTab }: Siz
|
|
|
105
109
|
const [mix, setMix] = usePluginPaneState<string>(`${kind}:mix`, kind === "hiring" ? "functions" : "countries");
|
|
106
110
|
const filters = useMemo<AppAttentionFilter>(() => kind === "apps" ? { ...(country ? { country } : {}), ...(chart ? { chart: chart as AppChart } : {}), days: Number(days) || 90 } : {}, [kind, country, chart, days]);
|
|
107
111
|
const [snapshotSetting] = usePaneSettingValue<AttentionPayload | null>("attentionSnapshot", null);
|
|
108
|
-
const snapshot = useMemo(() => { try { return snapshotSetting ? validateAttention(kind, snapshotSetting) : null; } catch {
|
|
112
|
+
const snapshot = useMemo(() => { try { return snapshotSetting ? validateAttention(kind, snapshotSetting) : null; } catch (error) {
|
|
113
|
+
logFailure("company-attention.pane", "Validate attention snapshot failed")(error);
|
|
114
|
+
return null;
|
|
115
|
+
} }, [kind, snapshotSetting]);
|
|
109
116
|
const loader = useCallback((force: boolean) => snapshot ? Promise.resolve({ payload: snapshot, stale: false, refreshError: null }) : loadAttention(kind, symbol, accessKey, filters, force), [kind, symbol, accessKey, filters, snapshot]);
|
|
110
117
|
const resource = useAsyncResource(loader, { initialData: () => cachedAttention(kind, symbol, accessKey, filters), clearOnError: isAccessDenied });
|
|
111
118
|
const data = resource.data?.payload;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { logFailure } from "../../../utils/log-failure";
|
|
1
2
|
import { useCallback, useMemo, useRef, type ReactNode } from "react";
|
|
2
3
|
import { isAccessDenied } from "../../../api-client/errors";
|
|
3
4
|
import type { HiringSignalGroup, HiringSignalsFilter, HiringWeekSignal } from "../../../api-client/hiring";
|
|
@@ -60,7 +61,10 @@ export function HiringSignalsView({ group, symbol, width, height, focused, acces
|
|
|
60
61
|
try {
|
|
61
62
|
const payload = snapshotSetting ? validateSignals(snapshotSetting) : null;
|
|
62
63
|
return payload && ("group" in payload ? !symbol && payload.group === group : payload.symbol.toUpperCase() === symbol?.toUpperCase()) ? payload : null;
|
|
63
|
-
} catch {
|
|
64
|
+
} catch (error) {
|
|
65
|
+
logFailure("company-attention.signals", "Validate hiring signals snapshot failed")(error);
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
64
68
|
}, [snapshotSetting, symbol, group]);
|
|
65
69
|
const loader = useCallback((force: boolean) => snapshot ? Promise.resolve({ payload: snapshot, stale: false, refreshError: null }) : loadSignals(group, symbol, accessKey, filter, force), [snapshot, group, symbol, accessKey, filter]);
|
|
66
70
|
const resource = useAsyncResource(loader, { initialData: () => cachedSignals(group, symbol, accessKey, filter), clearOnError: isAccessDenied, keepPreviousData: true });
|
|
@@ -8,8 +8,9 @@ import { buildCorrelationMatrix, buildCorrelationSeries, buildGeoCorrelationSeri
|
|
|
8
8
|
import { geoSeriesToken, loadGeoCorrelationHistory } from "./geo";
|
|
9
9
|
import { buildRelationshipAnalysis, DEFAULT_RELATIONSHIP_SECOND_SYMBOL } from "./relationship/model";
|
|
10
10
|
import { paneSchemas } from "./headless-schema";
|
|
11
|
-
import { CORRELATION_RETURN_BASIS, GEO_CORRELATION_CHANGE_BASIS, loadCorrelationHistory } from "./history";
|
|
11
|
+
import { CORRELATION_RETURN_BASIS, GEO_CORRELATION_CHANGE_BASIS, loadCorrelationHistory, unfinishedBarFreshness } from "./history";
|
|
12
12
|
import type { HeadlessPaneContext } from "../../../types/headless";
|
|
13
|
+
import type { PricePoint } from "../../../types/financials";
|
|
13
14
|
import { newestReportTime } from "../../../utils/utc-time";
|
|
14
15
|
import { DAILY_CLOSES } from "../shared/report-freshness";
|
|
15
16
|
|
|
@@ -34,9 +35,13 @@ async function loadListedHistory(ctx: HeadlessPaneContext, key: string, range: T
|
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
/** A ticker's daily closes, or a map series' daily values read straight from the Cloud client. */
|
|
37
|
-
async function loadCorrelationInput(ctx: HeadlessPaneContext, key: string, range: TimeRange): Promise<CorrelationSeries> {
|
|
38
|
+
async function loadCorrelationInput(ctx: HeadlessPaneContext, key: string, range: TimeRange, onBars?: (bars: PricePoint[]) => void): Promise<CorrelationSeries> {
|
|
38
39
|
const geo = geoSeriesToken(key);
|
|
39
|
-
if (!geo)
|
|
40
|
+
if (!geo) {
|
|
41
|
+
const bars = await loadHistory(ctx, key, range);
|
|
42
|
+
onBars?.(bars);
|
|
43
|
+
return buildCorrelationSeries(key, bars);
|
|
44
|
+
}
|
|
40
45
|
const request = <T,>(path: string, init?: RequestInit) => ctx.apiClient.geo<T>(path, { ...init, signal: init?.signal ?? ctx.signal });
|
|
41
46
|
return buildGeoCorrelationSeries(key, await loadGeoCorrelationHistory(request, geo, range, ctx.signal));
|
|
42
47
|
}
|
|
@@ -53,7 +58,8 @@ export const correlationHeadless: HeadlessPaneDefinition<"rows"> = {
|
|
|
53
58
|
],
|
|
54
59
|
async load({ symbols, options }, ctx) {
|
|
55
60
|
const range = (options.rangePreset ?? "1Y") as TimeRange;
|
|
56
|
-
const
|
|
61
|
+
const bars = new Map<string, PricePoint[]>();
|
|
62
|
+
const loaded = await loadHeadlessSymbols(symbols, ctx, (symbol) => loadCorrelationInput(ctx, symbol, range, (points) => bars.set(symbol, points)));
|
|
57
63
|
const bySymbol = new Map(loaded.entries.map(({ symbol, data }) => [symbol, data]));
|
|
58
64
|
const matrix = buildCorrelationMatrix(symbols, bySymbol);
|
|
59
65
|
const rows = symbols.flatMap((left, index) => symbols.slice(index + 1).map((right) => {
|
|
@@ -68,8 +74,9 @@ export const correlationHeadless: HeadlessPaneDefinition<"rows"> = {
|
|
|
68
74
|
? "Inconsistent OHLC history"
|
|
69
75
|
: row.sampleSize < 5 ? "Insufficient shared return observations" : "Zero return variance",
|
|
70
76
|
}));
|
|
77
|
+
const asOf = newestReportTime(symbols.map((symbol) => bySymbol.get(symbol)?.prices.at(-1)?.dateKey));
|
|
71
78
|
return {
|
|
72
|
-
freshness: { asOf
|
|
79
|
+
freshness: { asOf, ...unfinishedBarFreshness([...bars.values()], asOf?.slice(0, 10)) },
|
|
73
80
|
rows, unavailableSymbols,
|
|
74
81
|
errors: [
|
|
75
82
|
...loaded.errors,
|
|
@@ -132,6 +139,7 @@ export const relationshipHeadless: HeadlessPaneDefinition<"series"> = {
|
|
|
132
139
|
})),
|
|
133
140
|
{ key: "returnCount", label: "Shared returns", value: analysis.stats?.sampleSize ?? analysis.returns.length },
|
|
134
141
|
],
|
|
142
|
+
freshness: unfinishedBarFreshness([...histories.values()], analysis.aligned.at(-1)?.dateKey) ?? {},
|
|
135
143
|
unavailableSymbols: [...new Set(unavailableSymbols)],
|
|
136
144
|
errors: [...loaded.errors, ...(analysis.unavailableReason ? [analysis.unavailableReason] : []),
|
|
137
145
|
...(analysis.correlationUnavailableReason ? [analysis.correlationUnavailableReason] : [])],
|
|
@@ -2,6 +2,9 @@ import type { AssetDataProvider, MarketDataRequestContext } from "../../../types
|
|
|
2
2
|
import type { TimeRange } from "../../../time-series/range";
|
|
3
3
|
import type { ManualChartResolution } from "../../../time-series/resolution";
|
|
4
4
|
import { clipPriceHistoryToRange } from "../../../time-series/history-window";
|
|
5
|
+
import { DAY_SO_FAR_TEXT } from "../../../time-series/session-bar";
|
|
6
|
+
import type { PricePoint } from "../../../types/financials";
|
|
7
|
+
import type { HeadlessPaneFreshness } from "../../../types/headless";
|
|
5
8
|
|
|
6
9
|
/** Risk views need daily closes even when a chart preset defaults to intraday or weekly bars. */
|
|
7
10
|
export const CORRELATION_HISTORY_RESOLUTION: ManualChartResolution = "1d";
|
|
@@ -16,6 +19,25 @@ export async function loadCorrelationHistory(
|
|
|
16
19
|
return clipPriceHistoryToRange(points, range);
|
|
17
20
|
}
|
|
18
21
|
|
|
22
|
+
/**
|
|
23
|
+
* What an analysis of daily closes says when its newest day is a bar the source
|
|
24
|
+
* has not finished: a coin's UTC day or a future's session under way
|
|
25
|
+
* (`partial`), or a future's last trade before its settlement (`unsettled`).
|
|
26
|
+
* That day is no close, so the line calls the bars daily bars and says so, as
|
|
27
|
+
* `history` does, instead of dating a close by it. Nothing when `day` (the
|
|
28
|
+
* newest date the analysis uses) is a finished bar in every history.
|
|
29
|
+
*/
|
|
30
|
+
export function unfinishedBarFreshness(
|
|
31
|
+
histories: ReadonlyArray<ReadonlyArray<Pick<PricePoint, "date" | "partial" | "unsettled">> | undefined>,
|
|
32
|
+
day: string | null | undefined,
|
|
33
|
+
): Pick<HeadlessPaneFreshness, "basis" | "note"> | null {
|
|
34
|
+
if (!day) return null;
|
|
35
|
+
const bars = histories.flatMap((history) => history?.filter((point) => new Date(point.date).toISOString().slice(0, 10) === day) ?? []);
|
|
36
|
+
if (bars.some((bar) => bar.partial)) return { basis: "daily bars", note: `the newest bar is ${DAY_SO_FAR_TEXT}` };
|
|
37
|
+
if (bars.some((bar) => bar.unsettled)) return { basis: "daily bars", note: "the newest bar is not yet settled" };
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
19
41
|
export const CORRELATION_RETURN_BASIS = "Local-price close-to-close returns between shared UTC dates; cash distributions and FX conversion are excluded, and exchange closing times may differ.";
|
|
20
42
|
|
|
21
43
|
export const GEO_CORRELATION_CHANGE_BASIS = "Map series (GEO:) use the change in daily value between the same shared UTC dates, because counts such as transits can be zero.";
|