gloomberb 0.18.4 → 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/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/package.json +1 -1
- package/patches/electrobun@1.18.1.patch +169 -5
- package/src/account/auth-model.ts +7 -3
- package/src/api-client/auth.ts +9 -4
- package/src/api-client/catalysts.ts +2 -1
- package/src/api-client/cot.ts +6 -2
- package/src/api-client/credit-documents.ts +6 -0
- package/src/api-client/data.ts +31 -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 +171 -11
- package/src/api-client/live-scan.ts +91 -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 +13 -1
- package/src/api-client/perps.ts +43 -0
- package/src/api-client/request.ts +222 -41
- package/src/api-client/research-activity.ts +6 -12
- package/src/api-client/types.ts +74 -0
- package/src/app/keybindings/index.ts +3 -0
- package/src/app/keybindings/labels.ts +18 -1
- package/src/app/keybindings/resolve.ts +14 -0
- package/src/app/pane-runtime/plugin-bindings.ts +9 -5
- package/src/app/runtime/broker-import.ts +42 -27
- package/src/app/runtime/plugin-bindings.ts +73 -56
- package/src/architecture/core-builtin-allowlist.json +1 -1
- 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/fred-series.ts +5 -0
- package/src/cli/commands/keybindings.ts +23 -0
- package/src/cli/commands/market.ts +138 -46
- package/src/cli/commands/options-chain.ts +107 -10
- package/src/cli/commands/options-leaps.ts +9 -3
- package/src/cli/commands/overview.ts +176 -39
- package/src/cli/commands/search.ts +39 -3
- package/src/cli/commands/themes.ts +11 -2
- package/src/cli/commands/ticker.ts +57 -35
- package/src/cli/company-name-hint.ts +41 -0
- package/src/cli/earnings-calendar.ts +1 -1
- package/src/cli/errors.ts +10 -2
- package/src/cli/export-source.ts +29 -1
- package/src/cli/glossary.ts +73 -1
- package/src/cli/history-rows.ts +87 -16
- package/src/cli/index.ts +2 -0
- package/src/cli/known-gaps.ts +5 -2
- package/src/cli/listing-arg.ts +195 -56
- 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 +111 -16
- package/src/cli/pane-functions/function-help.ts +23 -13
- package/src/cli/pane-functions/headless.ts +56 -12
- package/src/cli/pane-functions/index.ts +58 -16
- package/src/cli/pane-functions/market-session.ts +81 -9
- package/src/cli/pane-functions/options.ts +19 -1
- package/src/cli/pane-functions/report.ts +5 -1
- package/src/cli/pane-functions/resolver.ts +35 -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 +30 -13
- package/src/cli/report-tables.ts +18 -7
- package/src/cli/restore-plugins.ts +13 -1
- package/src/cli/result.ts +57 -5
- package/src/cli/ticker-resolution.ts +3 -2
- package/src/components/chart/composite/composite-chart.tsx +26 -4
- package/src/components/chart/composite/format.ts +27 -0
- package/src/components/chart/composite/interactions.ts +6 -0
- package/src/components/chart/composite/legend.tsx +45 -7
- package/src/components/chart/composite/levels.ts +2 -0
- package/src/components/chart/composite/panel-gestures.ts +33 -0
- package/src/components/chart/composite/panel-series.ts +1 -0
- package/src/components/chart/composite/panel-surface.tsx +61 -5
- package/src/components/chart/composite/range-statistics.ts +62 -0
- package/src/components/chart/composite/rasterizer.ts +24 -13
- package/src/components/chart/composite/scene.ts +46 -7
- package/src/components/chart/composite/series-axis-tags.tsx +16 -9
- 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/types.ts +4 -0
- 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/panel/prompt-binding.ts +0 -8
- package/src/components/command-bar/workflow/alert.ts +1 -1
- package/src/components/command-bar/workflow/collection-actions.ts +3 -3
- package/src/components/layout/header.tsx +26 -40
- package/src/components/layout/pane/footer/index.tsx +1 -0
- package/src/components/layout/pane/footer/model.ts +27 -0
- package/src/components/onboarding/onboarding-frame.tsx +1 -1
- package/src/components/onboarding/onboarding-wizard.tsx +4 -1
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +14 -9
- 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/display.tsx +5 -1
- 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/core/state/app/layout.ts +2 -0
- package/src/core/state/app/state.ts +9 -0
- package/src/core/state/app/types.ts +3 -0
- package/src/data/config/chart-settings.ts +1 -1
- package/src/data/json-ticker-repository.ts +17 -8
- package/src/data/saved-copies.ts +55 -16
- 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 +427 -216
- package/src/i18n/es.ts +19 -3
- package/src/i18n/ja.ts +19 -3
- package/src/i18n/ko.ts +19 -3
- package/src/i18n/zh-cn.ts +19 -3
- package/src/i18n/zh-tw.ts +19 -3
- package/src/market-data/coordinator/financials.ts +10 -2
- package/src/market-data/coordinator/index.ts +3 -1
- package/src/market-data/coordinator/quotes.ts +4 -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 +48 -7
- package/src/market-data/market/dated-previous-close.ts +203 -0
- package/src/market-data/market/format.ts +25 -0
- package/src/market-data/market/freshness.ts +2 -1
- package/src/market-data/market/futures-week.ts +68 -0
- package/src/market-data/market/status.ts +16 -4
- package/src/market-data/options-alternatives.ts +141 -4
- package/src/market-data/quotes/contributions.ts +20 -2
- package/src/market-data/quotes/resolution.ts +3 -0
- package/src/market-data/selectors.ts +3 -0
- 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/notifications/notification-log.ts +6 -4
- package/src/plugins/builtin/alerts/alert-engine.ts +25 -2
- package/src/plugins/builtin/alerts/alert-log.ts +33 -0
- package/src/plugins/builtin/alerts/calendar-events.ts +57 -6
- package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
- package/src/plugins/builtin/alerts/cli.ts +380 -82
- package/src/plugins/builtin/alerts/command.ts +31 -0
- package/src/plugins/builtin/alerts/constants.ts +1 -1
- package/src/plugins/builtin/alerts/events-pane.tsx +19 -4
- package/src/plugins/builtin/alerts/fired-log.ts +113 -8
- package/src/plugins/builtin/alerts/format.ts +80 -4
- package/src/plugins/builtin/alerts/headless.ts +30 -7
- package/src/plugins/builtin/alerts/history-rows.ts +7 -3
- package/src/plugins/builtin/alerts/history.ts +2 -0
- package/src/plugins/builtin/alerts/index.tsx +135 -22
- package/src/plugins/builtin/alerts/live.ts +3 -3
- package/src/plugins/builtin/alerts/metric-command.ts +134 -0
- package/src/plugins/builtin/alerts/metric-data.ts +111 -0
- package/src/plugins/builtin/alerts/metric-model.ts +324 -0
- package/src/plugins/builtin/alerts/metric-parity-fixture.json +725 -0
- package/src/plugins/builtin/alerts/pane.tsx +90 -39
- package/src/plugins/builtin/alerts/quotes.ts +6 -0
- package/src/plugins/builtin/alerts/storage.ts +37 -10
- package/src/plugins/builtin/alerts/types.ts +7 -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/backtest/headless.ts +10 -5
- package/src/plugins/builtin/backtest/pane.tsx +9 -4
- package/src/plugins/builtin/backtest/presets.ts +29 -3
- package/src/plugins/builtin/broker-manager/index.tsx +10 -5
- package/src/plugins/builtin/broker-manager/model.ts +6 -1
- package/src/plugins/builtin/broker-trading/controller.ts +22 -12
- package/src/plugins/builtin/broker-trading/index.tsx +13 -1
- package/src/plugins/builtin/broker-trading/order-feed.ts +9 -4
- package/src/plugins/builtin/broker-trading/pane.tsx +1 -1
- 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 +7 -3
- package/src/plugins/builtin/catalysts/model.ts +2 -1
- package/src/plugins/builtin/changelog/index.tsx +9 -6
- package/src/plugins/builtin/chart-composer/catalog-inventory.ts +6 -1
- package/src/plugins/builtin/chart-composer/chart-spec-edit.ts +1 -1
- package/src/plugins/builtin/chart-composer/cli-options.ts +9 -5
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +12 -2
- package/src/plugins/builtin/chart-composer/editor/controller.ts +4 -2
- package/src/plugins/builtin/chart-composer/editor/model.ts +2 -0
- package/src/plugins/builtin/chart-composer/editor.tsx +7 -5
- package/src/plugins/builtin/chart-composer/formula.ts +71 -18
- package/src/plugins/builtin/chart-composer/headless-schema.ts +21 -4
- package/src/plugins/builtin/chart-composer/headless.ts +19 -3
- package/src/plugins/builtin/chart-composer/index.tsx +1 -1
- package/src/plugins/builtin/chart-composer/pane.tsx +53 -9
- package/src/plugins/builtin/chart-composer/presets.ts +50 -15
- package/src/plugins/builtin/chart-composer/quick-add.tsx +40 -25
- package/src/plugins/builtin/chart-composer/session-history.ts +18 -9
- package/src/plugins/builtin/chart-composer/settings.ts +27 -0
- package/src/plugins/builtin/chart-composer/use-series-catalog.ts +40 -26
- package/src/plugins/builtin/chat/content/command-suggestions.ts +93 -0
- package/src/plugins/builtin/chat/content/composer.tsx +13 -6
- package/src/plugins/builtin/chat/content/footer.tsx +6 -3
- package/src/plugins/builtin/chat/content/index.tsx +50 -21
- package/src/plugins/builtin/chat/content/open-targets.ts +14 -3
- package/src/plugins/builtin/chat/controller/session-runtime.ts +4 -2
- package/src/plugins/builtin/chat/index.tsx +23 -0
- package/src/plugins/builtin/chat/message/desktop.tsx +1 -0
- package/src/plugins/builtin/chat/message/inline-tokens.tsx +23 -0
- package/src/plugins/builtin/chat/message/pane-chip.tsx +50 -20
- package/src/plugins/builtin/chat/pane-links/commands.ts +271 -0
- package/src/plugins/builtin/chat/pane-links/model.ts +12 -3
- package/src/plugins/builtin/chat/pane-links/open.ts +8 -1
- package/src/plugins/builtin/chat/unread-inbox-pane.tsx +10 -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/cloud/plugin.tsx +1 -1
- 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/calendar-model.ts +14 -9
- package/src/plugins/builtin/econ/figures.ts +72 -0
- package/src/plugins/builtin/econ/index.tsx +33 -3
- package/src/plugins/builtin/econ/types.ts +4 -0
- package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +85 -35
- package/src/plugins/builtin/econ-statistics/stats.ts +37 -9
- package/src/plugins/builtin/econ-statistics/view.ts +3 -0
- 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 +70 -15
- package/src/plugins/builtin/exposure/charts.tsx +2 -2
- package/src/plugins/builtin/exposure/model.ts +13 -1
- package/src/plugins/builtin/exposure/pane.tsx +4 -4
- package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
- package/src/plugins/builtin/futures/index.tsx +1 -0
- package/src/plugins/builtin/futures/model.ts +2 -1
- package/src/plugins/builtin/futures/table.tsx +2 -2
- 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 +11 -3
- package/src/plugins/builtin/fx-matrix/headless.ts +6 -3
- package/src/plugins/builtin/fx-matrix/index.tsx +16 -4
- package/src/plugins/builtin/fx-matrix/live-legs.ts +54 -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/keybindings-editor.tsx +18 -14
- 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 +36 -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/pane.tsx +7 -5
- 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/footer.ts +2 -2
- 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 +82 -0
- package/src/plugins/builtin/members/client.ts +19 -4
- package/src/plugins/builtin/members/headless.ts +20 -7
- package/src/plugins/builtin/members/index.tsx +9 -9
- package/src/plugins/builtin/members/model.ts +23 -4
- package/src/plugins/builtin/members/pane.tsx +25 -15
- 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 +54 -6
- package/src/plugins/builtin/options/enrichment.ts +17 -5
- package/src/plugins/builtin/options/model-spot.ts +5 -2
- package/src/plugins/builtin/options/strike-window.ts +2 -1
- package/src/plugins/builtin/options/table.ts +6 -9
- package/src/plugins/builtin/options/types.ts +2 -0
- package/src/plugins/builtin/options/view.tsx +42 -12
- package/src/plugins/builtin/options-positioning/client.ts +30 -0
- package/src/plugins/builtin/options-positioning/headless.ts +74 -21
- package/src/plugins/builtin/options-positioning/index.tsx +4 -4
- package/src/plugins/builtin/options-positioning/model.ts +122 -14
- package/src/plugins/builtin/options-positioning/pane.tsx +87 -42
- 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/freshness.ts +12 -10
- package/src/plugins/builtin/perps/headless.ts +65 -9
- package/src/plugins/builtin/perps/index.tsx +7 -5
- package/src/plugins/builtin/perps/live-rows.ts +10 -3
- package/src/plugins/builtin/perps/model.ts +18 -4
- package/src/plugins/builtin/perps/pane.tsx +32 -31
- 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 +12 -7
- package/src/plugins/builtin/portfolio-list/headless.ts +1 -1
- 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/summary/index.tsx +11 -8
- package/src/plugins/builtin/portfolio-list/table.tsx +11 -3
- package/src/plugins/builtin/primary-dealers/client.ts +13 -38
- package/src/plugins/builtin/rate-path/pane.tsx +2 -2
- 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 +11 -9
- 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/feed.ts +2 -2
- 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 +40 -6
- package/src/plugins/builtin/scanner/live-condition-editor.tsx +222 -0
- package/src/plugins/builtin/scanner/live-feed.ts +168 -0
- package/src/plugins/builtin/scanner/live-headless.ts +138 -0
- package/src/plugins/builtin/scanner/live-model.ts +362 -0
- package/src/plugins/builtin/scanner/live-pane.tsx +677 -0
- package/src/plugins/builtin/scanner/live-track.ts +186 -0
- 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 +3 -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/macro-releases.ts +65 -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/scanner-capture.ts +3 -0
- 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 +10 -3
- package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
- package/src/plugins/builtin/shared/volatility/pricing.ts +26 -1
- package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
- package/src/plugins/builtin/themes/headless.ts +6 -4
- package/src/plugins/builtin/themes/model.ts +17 -8
- package/src/plugins/builtin/themes/pane.tsx +7 -7
- 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-footer.ts +6 -1
- 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/charts.tsx +9 -4
- package/src/plugins/builtin/vol-surface/client.ts +9 -17
- package/src/plugins/builtin/vol-surface/headless.ts +63 -14
- package/src/plugins/builtin/vol-surface/index.tsx +2 -2
- package/src/plugins/builtin/vol-surface/model.ts +22 -7
- package/src/plugins/builtin/vol-surface/pane.tsx +48 -9
- package/src/plugins/builtin/vol-surface/tables.ts +31 -3
- package/src/plugins/builtin/volatility/dvol.ts +46 -0
- package/src/plugins/builtin/volatility/index.tsx +2 -2
- package/src/plugins/builtin/volatility/pane.tsx +4 -4
- package/src/plugins/builtin/volatility/series.ts +26 -0
- package/src/plugins/builtin/world-indices/footer.ts +1 -1
- package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
- package/src/plugins/catalog-browser.ts +13 -8
- package/src/plugins/catalog-manifest.ts +2 -1
- package/src/plugins/marketplace/model.ts +18 -5
- package/src/portfolio/column-values.ts +5 -5
- package/src/portfolio/futures-positions.ts +6 -3
- package/src/portfolio/position-quantity.ts +15 -9
- package/src/public/__snapshots__/public-api.test.ts.snap +1 -0
- package/src/remote/resources.ts +2 -1
- package/src/renderers/browser/app-services.ts +1 -7
- package/src/renderers/browser/config-host.ts +2 -55
- package/src/renderers/browser/main.tsx +7 -57
- 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 +222 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +29 -7
- 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/relaunch.ts +74 -25
- package/src/renderers/electrobun/bun/desktop/state-broadcaster.ts +1 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +173 -5
- package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
- package/src/renderers/electrobun/bun/desktop/workspace/index.ts +2 -0
- package/src/renderers/electrobun/bun/index.ts +25 -1
- package/src/renderers/electrobun/shared/app-name.ts +30 -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/cli-pane-shot-frame.tsx +3 -3
- package/src/renderers/electrobun/view/desktop/window/snapshot.ts +1 -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/renderers/opentui/toast-host.tsx +71 -43
- 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/gloomberb-cloud/normalizers.ts +2 -0
- package/src/sources/provider-router/batches.ts +10 -2
- package/src/sources/provider-router/financial-routes.ts +3 -3
- package/src/sources/provider-router/financials.ts +15 -3
- package/src/sources/provider-router/history.ts +28 -7
- package/src/sources/provider-router/index.ts +149 -10
- package/src/sources/provider-router/search.ts +13 -2
- package/src/state/app/context/index.tsx +3 -0
- package/src/state/ticker-cleanup.ts +50 -0
- package/src/sync/auth-transition.ts +19 -9
- package/src/sync/baseline.ts +10 -4
- package/src/sync/controller.ts +70 -19
- package/src/sync/core-contributors.ts +90 -31
- package/src/sync/react.ts +4 -1
- package/src/sync/types.ts +3 -0
- 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/telemetry/usage-counts.ts +0 -18
- package/src/test-support/preload.ts +14 -0
- package/src/theme/colors.ts +8 -0
- package/src/theme/themes.ts +10 -0
- package/src/tickers/company-name.ts +103 -3
- package/src/tickers/listing-venues.ts +38 -3
- package/src/tickers/search/bare-symbol.ts +36 -0
- package/src/tickers/search/index.ts +36 -7
- package/src/tickers/search/ranking.ts +10 -1
- package/src/tickers/search/types.ts +5 -0
- package/src/tickers/terminal-codes.ts +19 -0
- package/src/time-series/alignment.ts +1 -1
- package/src/time-series/base-series.ts +13 -5
- package/src/time-series/daily-valuation.ts +62 -0
- package/src/time-series/economic.ts +18 -4
- package/src/time-series/field-catalog.ts +2 -4
- package/src/time-series/filed-trailing-eps.ts +197 -0
- package/src/time-series/forward-valuation.ts +32 -0
- package/src/time-series/fundamentals.ts +198 -7
- package/src/time-series/hooks.ts +1 -0
- package/src/time-series/ice-credit.ts +3 -0
- package/src/time-series/price-comparison.ts +30 -2
- package/src/time-series/price-units.ts +72 -2
- package/src/time-series/resolve-context.ts +7 -1
- package/src/time-series/resolve.ts +116 -15
- package/src/time-series/series-presentation.ts +40 -6
- package/src/time-series/session-bar.ts +11 -0
- package/src/time-series/spec.ts +5 -3
- package/src/time-series/studies.ts +278 -74
- package/src/time-series/total-return.ts +160 -0
- package/src/time-series/transforms.ts +3 -3
- package/src/time-series/types.ts +27 -1
- package/src/time-series/valuation-releases.ts +39 -0
- package/src/types/data-provider.ts +9 -1
- package/src/types/desktop-window.ts +2 -0
- package/src/types/financials.ts +73 -2
- package/src/types/headless.ts +61 -5
- package/src/types/instrument.ts +3 -0
- package/src/types/news-source.ts +1 -0
- package/src/types/plugin.ts +10 -1
- package/src/ui/host.tsx +3 -0
- package/src/updater/command-names.ts +12 -7
- 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/financial-statements.ts +6 -1
- 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/network-failure.ts +9 -6
- package/src/utils/option-expiry.ts +48 -1
- package/src/utils/options.ts +10 -0
- package/src/utils/plugin-proxy-hosts.json +1 -0
- package/src/utils/price-history.ts +12 -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
- package/src/renderers/browser/first-screen.ts +0 -193
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { useSyncExternalStore } from "react";
|
|
2
|
+
import { commands as coreCommands, getCommandPrefixes, type Command } from "../../../../components/command-bar/commands/registry";
|
|
3
|
+
import { getPaneTemplateDisplayLabel, paneTemplateShortcutPrefixes } from "../../../../components/command-bar/pane-templates/items";
|
|
4
|
+
import { getPaneShortcutArgKind } from "../../../../components/command-bar/routes/root/shortcuts";
|
|
5
|
+
import type { CommandDef, PaneTemplateDef } from "../../../../types/plugin";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* What a function takes after its code in a message. Only an argument that
|
|
9
|
+
* names something to look at: free text is what some functions act on as they
|
|
10
|
+
* open (ASKG asks it, THESIS starts one), which one click on somebody else's
|
|
11
|
+
* message must never do, so those take none here.
|
|
12
|
+
*/
|
|
13
|
+
type ChatFunctionArg =
|
|
14
|
+
/** Nothing: the code alone. */
|
|
15
|
+
| "none"
|
|
16
|
+
/** One ticker: `DES AAPL`. */
|
|
17
|
+
| "ticker"
|
|
18
|
+
/** One or more tickers: `CMP AAPL MSFT`. */
|
|
19
|
+
| "tickers"
|
|
20
|
+
/** A function's code: `HELP OMON`. */
|
|
21
|
+
| "code"
|
|
22
|
+
/** One of the values the function names: `NI CRYPTO`. */
|
|
23
|
+
| "choice";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A function a message can name in backticks (`NI CRYPTO`, `DES AAPL`,
|
|
27
|
+
* `CRYP`): a code the command bar runs, and what it opens.
|
|
28
|
+
*/
|
|
29
|
+
export interface ChatFunction {
|
|
30
|
+
/** The code to write for it, upper case. */
|
|
31
|
+
code: string;
|
|
32
|
+
/** Other codes that run the same function (T for DES). */
|
|
33
|
+
aliases: readonly string[];
|
|
34
|
+
/** What the function is called: the command bar's name for it. */
|
|
35
|
+
name: string;
|
|
36
|
+
arg: ChatFunctionArg;
|
|
37
|
+
/** For a choice, the values it takes. */
|
|
38
|
+
choices?: () => readonly string[];
|
|
39
|
+
/** Written as a chip when someone types it, but never suggested (`listed: false`). */
|
|
40
|
+
unlisted?: true;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** A function named in a message, and the text the command bar runs for it. */
|
|
44
|
+
export interface ChatCommand {
|
|
45
|
+
query: string;
|
|
46
|
+
fn: ChatFunction;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The commands a chip may run besides the functions that open panes: a
|
|
51
|
+
* company's description and the help window. Every other command changes
|
|
52
|
+
* settings or data (adds to a portfolio, switches the theme), which one click
|
|
53
|
+
* on somebody else's message must never do, so its code stays text.
|
|
54
|
+
*/
|
|
55
|
+
const OPENING_COMMAND_ARGS = new Map<string, ChatFunctionArg>([["security-description", "ticker"], ["help", "code"]]);
|
|
56
|
+
|
|
57
|
+
export interface ChatFunctionSource {
|
|
58
|
+
paneTemplates: ReadonlyMap<string, PaneTemplateDef>;
|
|
59
|
+
commands: ReadonlyMap<string, CommandDef>;
|
|
60
|
+
/** Plugins switched off: the bar opens none of their functions, so they draw no chip. */
|
|
61
|
+
disabledPlugins?: readonly string[];
|
|
62
|
+
/** Whether a template's plugin (or its pane's) is one of those switched off. */
|
|
63
|
+
isTemplateOff?: (templateId: string) => boolean;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function upperCodes(codes: readonly string[]): string[] {
|
|
67
|
+
return codes.map((code) => code.trim().toUpperCase()).filter(Boolean);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function templateArg(template: PaneTemplateDef): Pick<ChatFunction, "arg" | "choices"> {
|
|
71
|
+
const kind = getPaneShortcutArgKind(template);
|
|
72
|
+
if (kind === "ticker") return { arg: "ticker" };
|
|
73
|
+
if (kind === "ticker-list") return { arg: "tickers" };
|
|
74
|
+
const options = template.shortcut?.argOptions;
|
|
75
|
+
if (kind === "text" && options) return { arg: "choice", choices: () => options().map((option) => option.value) };
|
|
76
|
+
return { arg: "none" };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The functions of the app's own panes and plugins, by every code that runs
|
|
81
|
+
* them, as the command bar matches its first word. A code the bar runs as a
|
|
82
|
+
* command that is not one of those above is left out, even where a pane
|
|
83
|
+
* shares it, since that command is what a click would run.
|
|
84
|
+
*/
|
|
85
|
+
export function chatFunctionsFrom(source: ChatFunctionSource, core: readonly Command[] = coreCommands): ReadonlyMap<string, ChatFunction> {
|
|
86
|
+
const byCode = new Map<string, ChatFunction>();
|
|
87
|
+
const taken = new Set<string>();
|
|
88
|
+
const add = (codes: readonly string[], fn: Omit<ChatFunction, "code" | "aliases"> | null) => {
|
|
89
|
+
const free = codes.filter((code) => !taken.has(code));
|
|
90
|
+
for (const code of codes) taken.add(code);
|
|
91
|
+
// A code of one letter only counts with its argument (`T AAPL`), so a
|
|
92
|
+
// function that takes none is written with a longer one (FH for N).
|
|
93
|
+
const usable = fn ? free.filter((code) => code.length > 1 || fn.arg !== "none") : [];
|
|
94
|
+
const [code, ...aliases] = usable;
|
|
95
|
+
if (!fn || !code) return;
|
|
96
|
+
const entry: ChatFunction = { ...fn, code, aliases };
|
|
97
|
+
for (const each of usable) byCode.set(each, entry);
|
|
98
|
+
};
|
|
99
|
+
for (const command of core) {
|
|
100
|
+
const arg = OPENING_COMMAND_ARGS.get(command.id);
|
|
101
|
+
add(upperCodes(getCommandPrefixes(command)), arg ? { name: command.label, arg } : null);
|
|
102
|
+
}
|
|
103
|
+
for (const command of source.commands.values()) {
|
|
104
|
+
const code = command.shortcut?.trim().toUpperCase();
|
|
105
|
+
if (code) taken.add(code);
|
|
106
|
+
}
|
|
107
|
+
for (const template of source.paneTemplates.values()) {
|
|
108
|
+
// A switched-off plugin's code opens nothing until it is turned on, and
|
|
109
|
+
// leaves the code to a function that does.
|
|
110
|
+
if (source.isTemplateOff?.(template.id)) continue;
|
|
111
|
+
add(upperCodes(paneTemplateShortcutPrefixes(template)), {
|
|
112
|
+
name: getPaneTemplateDisplayLabel(template),
|
|
113
|
+
...templateArg(template),
|
|
114
|
+
...(template.listed === false ? { unlisted: true as const } : {}),
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
return byCode;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Everything the functions are built from, in one string: a plugin swapped,
|
|
122
|
+
* a code moved to a command or a plugin switched off changes it, where the
|
|
123
|
+
* number of templates and commands may stay the same.
|
|
124
|
+
*/
|
|
125
|
+
function sourceSignature(source: ChatFunctionSource): string {
|
|
126
|
+
let signature = source.disabledPlugins?.join(",") ?? "";
|
|
127
|
+
for (const [id, template] of source.paneTemplates) {
|
|
128
|
+
const shortcut = template.shortcut;
|
|
129
|
+
if (!shortcut?.prefix) continue;
|
|
130
|
+
signature += `\n${id} ${shortcut.prefix} ${shortcut.aliases?.join(",") ?? ""} ${shortcut.argKind ?? shortcut.argPlaceholder ?? ""}${shortcut.argOptions ? " ?" : ""}`;
|
|
131
|
+
}
|
|
132
|
+
for (const command of source.commands.values()) {
|
|
133
|
+
if (command.shortcut) signature += `\n:${command.shortcut}`;
|
|
134
|
+
}
|
|
135
|
+
return signature;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const NO_FUNCTIONS: ReadonlyMap<string, ChatFunction> = new Map();
|
|
139
|
+
const built = new WeakMap<object, { signature: string; functions: ReadonlyMap<string, ChatFunction> }>();
|
|
140
|
+
|
|
141
|
+
/** The same, built again only when what they are built from changes. */
|
|
142
|
+
export function cachedChatFunctions(source: ChatFunctionSource | null | undefined): ReadonlyMap<string, ChatFunction> {
|
|
143
|
+
if (!source) return NO_FUNCTIONS;
|
|
144
|
+
const signature = sourceSignature(source);
|
|
145
|
+
const cached = built.get(source.paneTemplates);
|
|
146
|
+
if (cached?.signature === signature) return cached.functions;
|
|
147
|
+
const functions = chatFunctionsFrom(source);
|
|
148
|
+
built.set(source.paneTemplates, { signature, functions });
|
|
149
|
+
return functions;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
interface ChatCommandHost {
|
|
153
|
+
functions(): ReadonlyMap<string, ChatFunction>;
|
|
154
|
+
/** Runs the text in the command bar, as if typed there and entered. */
|
|
155
|
+
run(query: string): void;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
let host: ChatCommandHost | null = null;
|
|
159
|
+
const listeners = new Set<() => void>();
|
|
160
|
+
/** The functions messages are drawn with, read again when the app's plugins or settings change. */
|
|
161
|
+
let drawn: ReadonlyMap<string, ChatFunction> = NO_FUNCTIONS;
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Tells the messages already drawn that the functions may have changed (a
|
|
165
|
+
* plugin came in late, or was switched off); only those that did redraw.
|
|
166
|
+
*/
|
|
167
|
+
export function chatFunctionsChanged(): void {
|
|
168
|
+
const next = chatFunctions();
|
|
169
|
+
if (next === drawn) return;
|
|
170
|
+
drawn = next;
|
|
171
|
+
for (const listener of [...listeners]) listener();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** The chat plugin hands over the app's functions and its command bar. */
|
|
175
|
+
export function attachChatCommands(next: ChatCommandHost): () => void {
|
|
176
|
+
host = next;
|
|
177
|
+
chatFunctionsChanged();
|
|
178
|
+
return () => {
|
|
179
|
+
if (host !== next) return;
|
|
180
|
+
host = null;
|
|
181
|
+
chatFunctionsChanged();
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function chatFunctions(): ReadonlyMap<string, ChatFunction> {
|
|
186
|
+
return host?.functions() ?? NO_FUNCTIONS;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function subscribeChatFunctions(listener: () => void): () => void {
|
|
190
|
+
listeners.add(listener);
|
|
191
|
+
return () => {
|
|
192
|
+
listeners.delete(listener);
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const drawnFunctions = () => drawn;
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* The functions, for a view that draws them: it redraws when they change.
|
|
200
|
+
* Every line of every message reads them, so they are not built again here;
|
|
201
|
+
* a click checks its function against the app as it is then.
|
|
202
|
+
*/
|
|
203
|
+
export function useChatFunctions(): ReadonlyMap<string, ChatFunction> {
|
|
204
|
+
return useSyncExternalStore(subscribeChatFunctions, drawnFunctions, drawnFunctions);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Runs a function a chip names, if it is still one: what the bar runs for
|
|
209
|
+
* the code may have changed since the message was drawn.
|
|
210
|
+
*/
|
|
211
|
+
export function runChatCommand(query: string): void {
|
|
212
|
+
const command = resolveChatCommand(query);
|
|
213
|
+
if (command) host?.run(command.query);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** Past this a span in backticks is a sentence or code, not a function and its argument. */
|
|
217
|
+
const MAX_COMMAND_LENGTH = 48;
|
|
218
|
+
const MAX_TICKERS = 12;
|
|
219
|
+
/** Marks that change how text reads without being seen: direction overrides, zero-width joiners, controls. */
|
|
220
|
+
const INVISIBLE_RE = /[\u0000-\u0008\u000b-\u001f\u007f-\u009f\u00ad\u061c\u200b-\u200f\u202a-\u202e\u2060-\u2069\ufeff]/g;
|
|
221
|
+
/** A symbol as written: AAPL, BRK.B, BTC-USD, ES=F, SMSN:LSE, ^GSPC. With a letter, so `GP 100` is not one. */
|
|
222
|
+
const TICKER_RE = /^\^?[A-Z0-9][A-Z0-9.:=/!-]{0,23}$/;
|
|
223
|
+
const CODE_RE = /^[A-Z][A-Z0-9]{0,11}$/;
|
|
224
|
+
|
|
225
|
+
const isTicker = (word: string) => TICKER_RE.test(word) && /[A-Z]/.test(word);
|
|
226
|
+
|
|
227
|
+
function acceptsArg(fn: ChatFunction, arg: string): boolean {
|
|
228
|
+
switch (fn.arg) {
|
|
229
|
+
case "ticker":
|
|
230
|
+
return isTicker(arg);
|
|
231
|
+
case "tickers": {
|
|
232
|
+
const words = arg.split(/[\s,]+/).filter(Boolean);
|
|
233
|
+
return words.length > 0 && words.length <= MAX_TICKERS && words.every(isTicker);
|
|
234
|
+
}
|
|
235
|
+
case "code":
|
|
236
|
+
return CODE_RE.test(arg);
|
|
237
|
+
case "choice": {
|
|
238
|
+
const wanted = arg.toUpperCase();
|
|
239
|
+
try {
|
|
240
|
+
return fn.choices?.().some((value) => value.toUpperCase() === wanted) ?? false;
|
|
241
|
+
} catch {
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
default:
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* The function a span of inline code names, or null. The code is the first
|
|
252
|
+
* word, written in capitals as the app writes codes, so a key written as `n`
|
|
253
|
+
* or `esc` stays code; a code of one letter needs its argument (`T AAPL`), as
|
|
254
|
+
* `N` alone is more often the key than News. Words after the code are its
|
|
255
|
+
* argument, which must be what the function takes: a ticker in capitals
|
|
256
|
+
* (`CAT food` and `G is great` stay code), a list of them, a function's code
|
|
257
|
+
* for HELP, or one of the values the function names (`NI CRYPTO`). Marks that
|
|
258
|
+
* cannot be seen are dropped, so the chip shows all of the text it runs.
|
|
259
|
+
*/
|
|
260
|
+
export function resolveChatCommand(text: string, functions: ReadonlyMap<string, ChatFunction> = chatFunctions()): ChatCommand | null {
|
|
261
|
+
if (/[\n`]/.test(text)) return null;
|
|
262
|
+
const query = text.replace(INVISIBLE_RE, "").trim().replace(/\s+/g, " ");
|
|
263
|
+
if (!query || query.length > MAX_COMMAND_LENGTH) return null;
|
|
264
|
+
const space = query.indexOf(" ");
|
|
265
|
+
const code = space < 0 ? query : query.slice(0, space);
|
|
266
|
+
if (code !== code.toUpperCase()) return null;
|
|
267
|
+
const fn = functions.get(code);
|
|
268
|
+
if (!fn) return null;
|
|
269
|
+
if (space < 0) return code.length > 1 ? { query, fn } : null;
|
|
270
|
+
return acceptsArg(fn, query.slice(space + 1)) ? { query, fn } : null;
|
|
271
|
+
}
|
|
@@ -8,12 +8,21 @@ import { formatShortDate } from "../../../../utils/datetime-format";
|
|
|
8
8
|
* a Gloom share link someone posted, or what a Gloom Wire post names in its
|
|
9
9
|
* `paneLink` (never read out of the post's text).
|
|
10
10
|
*/
|
|
11
|
-
|
|
11
|
+
type ChatMessageChipLink =
|
|
12
12
|
| { kind: "share"; id: string; url: string }
|
|
13
13
|
| { kind: "wire"; link: ChatPaneLink };
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* What a chip opens: one of the above, or a function written in backticks in
|
|
17
|
+
* the text (`NI CRYPTO`), which draws its chip where it is written and runs
|
|
18
|
+
* in the command bar.
|
|
19
|
+
*/
|
|
20
|
+
export type ChatMessagePaneLink =
|
|
21
|
+
| ChatMessageChipLink
|
|
22
|
+
| { kind: "command"; query: string; name: string };
|
|
23
|
+
|
|
15
24
|
export interface ChatMessagePaneLinks {
|
|
16
|
-
links:
|
|
25
|
+
links: ChatMessageChipLink[];
|
|
17
26
|
/**
|
|
18
27
|
* The text to draw: the message's own, less the share links its chips stand
|
|
19
28
|
* for and the publisher address an older news post still ends in.
|
|
@@ -79,7 +88,7 @@ function withoutLegacyArticleLink(content: string): string {
|
|
|
79
88
|
|
|
80
89
|
function computePaneLinks(message: Pick<ChatMessage, "content" | "paneLink">): ChatMessagePaneLinks {
|
|
81
90
|
const content = message.paneLink?.kind === "news-story" ? withoutLegacyArticleLink(message.content) : message.content;
|
|
82
|
-
const links:
|
|
91
|
+
const links: ChatMessageChipLink[] = [];
|
|
83
92
|
const drop = new Set<number>();
|
|
84
93
|
// Cheap guard: the tokenizer only runs on messages that could hold a share link.
|
|
85
94
|
if (content.includes("gloom.sh")) {
|
|
@@ -8,14 +8,21 @@ interface PaneLinkActions {
|
|
|
8
8
|
createPaneFromTemplate(templateId: string, options?: PaneTemplateCreateOptions): void;
|
|
9
9
|
notify(notification: AppNotificationRequest): unknown;
|
|
10
10
|
openExternal(url: string): void;
|
|
11
|
+
/** Runs text in the command bar as if typed there and entered. */
|
|
12
|
+
runCommand(query: string): void;
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
/**
|
|
14
16
|
* A share opens the way every share link does in this app; one it cannot open
|
|
15
17
|
* here (offline, a snapshot) opens its web page instead. A Wire post opens its
|
|
16
|
-
* story or calendar from the app's own pane templates.
|
|
18
|
+
* story or calendar from the app's own pane templates. A function written in
|
|
19
|
+
* a message runs in the command bar, so it opens where and as typing it would.
|
|
17
20
|
*/
|
|
18
21
|
export function openChatPaneLink(link: ChatMessagePaneLink, actions: PaneLinkActions): void {
|
|
22
|
+
if (link.kind === "command") {
|
|
23
|
+
actions.runCommand(link.query);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
19
26
|
if (link.kind === "share") {
|
|
20
27
|
openShareLink(link.id, () => actions.openExternal(new URL(`/s/${link.id}`, PUBLIC_SHARE_ORIGIN).toString()));
|
|
21
28
|
return;
|
|
@@ -23,11 +23,13 @@ import {
|
|
|
23
23
|
import { scheduleConfigSave } from "../../../state/config-save-scheduler";
|
|
24
24
|
import { colors } from "../../../theme/colors";
|
|
25
25
|
import type { PaneProps } from "../../../types/plugin";
|
|
26
|
+
import { findPaneInstance } from "../../../types/config";
|
|
26
27
|
import { Box, Text, TextAttributes, useUiCapabilities } from "../../../ui";
|
|
27
28
|
import { formatFeedTime } from "../../../utils/datetime-format";
|
|
28
29
|
import { displayWidth, truncateToDisplayWidth } from "../../../utils/format";
|
|
29
30
|
import { usePluginAppActions, usePluginPaneState } from "../../runtime";
|
|
30
31
|
import { requestPriceAlertFocus } from "../alerts/focus";
|
|
32
|
+
import { LIVE_SCAN_TEMPLATE_ID } from "../shared/scanner-capture";
|
|
31
33
|
import { SignInWall } from "../cloud/auth-actions";
|
|
32
34
|
import { openTeamPane } from "../../../team/pane-request";
|
|
33
35
|
import { teamStore } from "../../../team/store";
|
|
@@ -57,6 +59,7 @@ const NOTICE_LABELS: Record<MissedNoticeKind, string> = {
|
|
|
57
59
|
catalyst: "Catalyst",
|
|
58
60
|
econ: "Economic release",
|
|
59
61
|
team: "Team",
|
|
62
|
+
scan: "Live scan",
|
|
60
63
|
};
|
|
61
64
|
|
|
62
65
|
function chatCell(
|
|
@@ -212,11 +215,17 @@ export function UnreadInboxPane({
|
|
|
212
215
|
showPane("econ-calendar");
|
|
213
216
|
return;
|
|
214
217
|
}
|
|
218
|
+
if (ref.kind === "scan") {
|
|
219
|
+
// The scanner that alerted, when it is still in the layout; otherwise the one SCAN opens.
|
|
220
|
+
if (findPaneInstance(stateRef.current.config.layout, ref.id)) dispatch({ type: "FOCUS_PANE", paneId: ref.id });
|
|
221
|
+
else createPaneFromTemplate(LIVE_SCAN_TEMPLATE_ID);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
215
224
|
const card = teamStore.getSnapshot().notifications.find((entry) => entry.id === ref.id) ?? null;
|
|
216
225
|
const action = card ? teamStore.notificationAction(card) : null;
|
|
217
226
|
if (action) action.onClick();
|
|
218
227
|
else openTeamPane(createPaneFromTemplate, card ? { teamId: card.data.team.id } : {});
|
|
219
|
-
}, [createPaneFromTemplate, showPane]);
|
|
228
|
+
}, [createPaneFromTemplate, dispatch, showPane, stateRef]);
|
|
220
229
|
|
|
221
230
|
const openRow = useCallback((row: UnreadListRow) => {
|
|
222
231
|
if (row.kind === "chat") openChatItem(row.item);
|
|
@@ -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;
|
|
@@ -35,7 +35,7 @@ function createCloudDataModule(): PluginModule {
|
|
|
35
35
|
setup(ctx) {
|
|
36
36
|
ctx.registerSyncTransport({
|
|
37
37
|
id: "gloomberb-cloud",
|
|
38
|
-
isAvailable: () => apiClient.
|
|
38
|
+
isAvailable: () => apiClient.getVerifiedSessionIdentity() !== null,
|
|
39
39
|
pullSnapshot: () => apiClient.getSyncSnapshot(),
|
|
40
40
|
pushSnapshot: (snapshot, options) => apiClient.putSyncSnapshot(snapshot, options),
|
|
41
41
|
});
|
|
@@ -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;
|