@gloom-sh/terminal 0.18.5 → 0.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/package.json +1 -1
- package/patches/electrobun@1.18.1.patch +163 -8
- package/src/api-client/app-attention.ts +3 -0
- package/src/api-client/beneficial-owners.ts +3 -0
- package/src/api-client/company-kpis.ts +3 -0
- package/src/api-client/cot.ts +6 -2
- package/src/api-client/credit-documents.ts +6 -0
- package/src/api-client/data.ts +19 -7
- package/src/api-client/equity-screener.ts +24 -0
- package/src/api-client/fund-facts.ts +45 -4
- package/src/api-client/hiring.ts +3 -0
- package/src/api-client/index.ts +15 -0
- package/src/api-client/ipo.ts +24 -1
- 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/mna.ts +3 -0
- package/src/api-client/paths.ts +9 -1
- package/src/api-client/perps.ts +43 -0
- package/src/api-client/research-activity.ts +3 -2
- package/src/api-client/roadmap.ts +85 -0
- package/src/api-client/types.ts +82 -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/core-builtin-allowlist.json +2 -2
- 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 +107 -38
- package/src/cli/commands/news-search.ts +22 -0
- package/src/cli/commands/options-chain.ts +48 -7
- package/src/cli/commands/options-leaps.ts +9 -3
- package/src/cli/commands/overview.ts +147 -32
- package/src/cli/commands/search.ts +21 -1
- package/src/cli/commands/themes.ts +11 -2
- package/src/cli/commands/ticker.ts +87 -36
- package/src/cli/company-name-hint.ts +41 -0
- package/src/cli/earnings-calendar.ts +1 -1
- package/src/cli/errors.ts +25 -2
- package/src/cli/glossary.ts +31 -2
- package/src/cli/history-rows.ts +57 -9
- package/src/cli/index-roots.ts +0 -7
- package/src/cli/index.ts +2 -0
- package/src/cli/listing-arg.ts +235 -60
- package/src/cli/output-file.ts +68 -0
- package/src/cli/pane-functions/capabilities.ts +4 -0
- package/src/cli/pane-functions/catalog.ts +80 -7
- package/src/cli/pane-functions/discovery.ts +21 -1
- package/src/cli/pane-functions/dom.ts +13 -3
- package/src/cli/pane-functions/freshness.ts +93 -4
- package/src/cli/pane-functions/function-help.ts +20 -10
- package/src/cli/pane-functions/headless.ts +17 -7
- package/src/cli/pane-functions/index.ts +54 -16
- 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/saved-copy-persistence.ts +88 -0
- 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/list/model.ts +5 -0
- package/src/components/command-bar/pane-templates/items.ts +3 -2
- package/src/components/command-bar/routes/root/runtime.ts +6 -7
- package/src/components/command-bar/selection-runtime.ts +52 -3
- package/src/components/command-bar/surface/index.tsx +2 -0
- package/src/components/command-bar/workflow/collection-actions.ts +3 -3
- package/src/components/layout/pane/capture-stamp.ts +2 -2
- package/src/components/layout/pane/footer/index.tsx +22 -6
- package/src/components/layout/pane/footer/model.ts +83 -15
- package/src/components/layout/pane/status-footer.ts +35 -2
- package/src/components/layout/status-widget-space.ts +9 -0
- 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 +8 -21
- 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 +18 -3
- package/src/components/ui/icon.tsx +4 -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/data/config/chart-settings.ts +1 -1
- package/src/data/json-ticker-repository.ts +17 -8
- package/src/data/plugin-cache.ts +5 -3
- package/src/data/saved-copies.ts +8 -0
- 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 +103 -142
- package/src/i18n/es.ts +20 -6
- package/src/i18n/ja.ts +20 -6
- package/src/i18n/ko.ts +20 -6
- package/src/i18n/zh-cn.ts +20 -6
- package/src/i18n/zh-tw.ts +20 -6
- package/src/market-data/depositary-pair.ts +27 -3
- 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/market-data/quotes/metadata.ts +7 -0
- package/src/news/aggregator.ts +119 -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 +17 -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/calendar-events.ts +10 -4
- package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
- package/src/plugins/builtin/alerts/cli.ts +362 -80
- package/src/plugins/builtin/alerts/command.ts +25 -1
- package/src/plugins/builtin/alerts/event-rows.ts +24 -3
- package/src/plugins/builtin/alerts/events-pane.tsx +34 -10
- 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 +34 -12
- 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/cds/client.ts +142 -30
- package/src/plugins/builtin/cds/headless.ts +35 -8
- package/src/plugins/builtin/cds/model.ts +145 -18
- package/src/plugins/builtin/cds/pane.tsx +16 -5
- 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/follow-binding.ts +14 -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 +5 -6
- 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/controller/index.ts +5 -1
- package/src/plugins/builtin/chat/controller/notifications.ts +33 -3
- package/src/plugins/builtin/chat/controller/state.ts +25 -2
- package/src/plugins/builtin/chat/controller/unread-people.ts +89 -23
- package/src/plugins/builtin/chat/controller/utils.ts +7 -2
- package/src/plugins/builtin/chat/pane-links/commands.ts +7 -1
- package/src/plugins/builtin/chat/status-preview.ts +79 -0
- package/src/plugins/builtin/chat/status-widget.tsx +147 -10
- package/src/plugins/builtin/chat/unread-badge.ts +51 -10
- package/src/plugins/builtin/clinical-trials/client.ts +49 -4
- package/src/plugins/builtin/clinical-trials/model.ts +86 -31
- package/src/plugins/builtin/clinical-trials/pane.tsx +61 -49
- package/src/plugins/builtin/clinical-trials/sponsor-listing.ts +90 -0
- package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +95 -15
- package/src/plugins/builtin/clinical-trials/trial-pages.ts +50 -0
- 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/headless.ts +2 -1
- package/src/plugins/builtin/company-attention/moves.tsx +5 -1
- package/src/plugins/builtin/company-attention/pane.tsx +12 -2
- package/src/plugins/builtin/company-attention/signals.tsx +5 -1
- package/src/plugins/builtin/company-kpis/headless.ts +8 -3
- package/src/plugins/builtin/company-kpis/model.ts +73 -5
- package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
- package/src/plugins/builtin/composite-plugins.ts +2 -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 +10 -10
- 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/doe/headless.ts +3 -2
- package/src/plugins/builtin/doe/pane.tsx +13 -3
- 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/data.ts +18 -6
- package/src/plugins/builtin/earnings-calls/format.ts +31 -2
- package/src/plugins/builtin/earnings-calls/headless.ts +20 -5
- package/src/plugins/builtin/earnings-calls/model.ts +6 -1
- package/src/plugins/builtin/earnings-calls/pane.tsx +12 -4
- package/src/plugins/builtin/econ/calendar-model.ts +48 -1
- package/src/plugins/builtin/econ/calendar-source.ts +4 -2
- package/src/plugins/builtin/econ/detail-view.tsx +2 -2
- package/src/plugins/builtin/econ/figures.ts +48 -0
- package/src/plugins/builtin/econ/index.tsx +14 -5
- package/src/plugins/builtin/econ/print-watch.ts +3 -1
- package/src/plugins/builtin/econ/types.ts +2 -0
- package/src/plugins/builtin/econ-statistics/client.ts +11 -0
- package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +16 -6
- package/src/plugins/builtin/econ-statistics/pane.tsx +8 -4
- package/src/plugins/builtin/econ-statistics/stats.ts +39 -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 +62 -59
- package/src/plugins/builtin/help/shortcut-model.ts +1 -1
- package/src/plugins/builtin/help/shortcut-table.tsx +5 -6
- package/src/plugins/builtin/help/table-cells.tsx +13 -2
- package/src/plugins/builtin/holders/beneficial-client.ts +5 -1
- package/src/plugins/builtin/holders/beneficial-view.tsx +3 -0
- package/src/plugins/builtin/holders/fund-holders.ts +51 -0
- package/src/plugins/builtin/holders/headless.ts +22 -3
- package/src/plugins/builtin/holders/pane.tsx +3 -0
- package/src/plugins/builtin/holders/report-rows.ts +4 -1
- package/src/plugins/builtin/insider/headless.ts +2 -2
- package/src/plugins/builtin/ipo-calendar/client.ts +18 -2
- package/src/plugins/builtin/ipo-calendar/headless.ts +75 -11
- package/src/plugins/builtin/ipo-calendar/listing-ipo.ts +160 -0
- package/src/plugins/builtin/ipo-calendar/model.ts +98 -2
- 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/headless.ts +12 -5
- package/src/plugins/builtin/macro-day/index.tsx +1 -1
- package/src/plugins/builtin/macro-day/model.ts +39 -5
- package/src/plugins/builtin/macro-day/pane.tsx +27 -19
- 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 +27 -7
- package/src/plugins/builtin/market-movers/headless.ts +34 -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 +22 -4
- 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/mna/headless.ts +2 -0
- package/src/plugins/builtin/mna/pane.tsx +3 -1
- package/src/plugins/builtin/news/headless.ts +3 -1
- package/src/plugins/builtin/news/index.tsx +4 -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 +6 -7
- 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/index.tsx +0 -14
- package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +5 -2
- package/src/plugins/builtin/portfolio-list/table.tsx +15 -9
- package/src/plugins/builtin/primary-dealers/client.ts +13 -38
- package/src/plugins/builtin/realized-vol/client.ts +13 -5
- package/src/plugins/builtin/realized-vol/evidence.ts +29 -4
- package/src/plugins/builtin/realized-vol/headless.ts +8 -4
- package/src/plugins/builtin/realized-vol/index.tsx +2 -2
- package/src/plugins/builtin/realized-vol/model.ts +37 -6
- package/src/plugins/builtin/realized-vol/pane.tsx +11 -9
- 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 +40 -12
- package/src/plugins/builtin/research/headless.ts +1 -1
- package/src/plugins/builtin/research/index.tsx +7 -3
- package/src/plugins/builtin/research/relative-valuation-headless.ts +13 -26
- package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +59 -20
- package/src/plugins/builtin/research/relative-valuation-peers.ts +217 -20
- package/src/plugins/builtin/research/relative-valuation-settings.ts +44 -0
- 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/risk-factors/data.ts +4 -2
- package/src/plugins/builtin/risk-factors/headless.ts +5 -2
- package/src/plugins/builtin/roadmap/index.tsx +28 -0
- package/src/plugins/builtin/roadmap/model.ts +67 -0
- package/src/plugins/builtin/roadmap/pane.tsx +244 -0
- 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 +48 -5
- 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 +4 -2
- package/src/plugins/builtin/sec/index.tsx +3 -0
- package/src/plugins/builtin/series-inventory.ts +2 -0
- package/src/plugins/builtin/shared/cloud-resource.ts +16 -7
- 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/headless-market-data.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 +11 -2
- package/src/plugins/builtin/shared/research-cells.tsx +1 -1
- package/src/plugins/builtin/shared/series-cache.ts +4 -4
- 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/thirteenf/api.ts +12 -1
- package/src/plugins/builtin/thirteenf/data.ts +7 -7
- package/src/plugins/builtin/thirteenf/headless.ts +31 -4
- 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 +11 -3
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +2 -1
- package/src/plugins/builtin/ticker-detail/pane.tsx +14 -8
- 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-auctions/headless.ts +21 -12
- package/src/plugins/builtin/treasury-auctions/pane.tsx +9 -3
- 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 +7 -4
- package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
- package/src/plugins/builtin/yield-curve/client.ts +61 -1
- package/src/plugins/builtin/yield-curve/headless.ts +9 -6
- package/src/plugins/builtin/yield-curve/index.tsx +34 -10
- package/src/plugins/builtin/yield-curve/swaps.tsx +9 -5
- package/src/plugins/catalog-browser.ts +2 -1
- package/src/plugins/catalog-manifest.ts +2 -1
- package/src/plugins/host-link.ts +1 -1
- package/src/plugins/marketplace/model.ts +18 -5
- package/src/portfolio/column-values.ts +11 -7
- 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/icons.tsx +7 -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 +21 -10
- package/src/renderers/electrobun/bun/desktop/host-requests.ts +15 -0
- package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
- package/src/renderers/electrobun/bun/desktop/installer-folder.ts +731 -0
- package/src/renderers/electrobun/bun/desktop/relaunch.ts +102 -10
- 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/bun/desktop/windows-script.ts +92 -0
- package/src/renderers/electrobun/bun/index.ts +26 -2
- package/src/renderers/electrobun/bun/window/create.ts +43 -4
- package/src/renderers/electrobun/bun/window/events.ts +20 -0
- package/src/renderers/electrobun/bun/window/windows-custom-chrome.ts +9 -2
- package/src/renderers/electrobun/bun/window/windows-native.ts +78 -0
- package/src/renderers/electrobun/bun/window/windows-quit.ts +57 -0
- package/src/renderers/electrobun/shared/app-name.ts +13 -0
- package/src/renderers/electrobun/shared/protocol.ts +12 -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 +5 -1
- package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
- package/src/renderers/electrobun/view/ui-host.tsx +8 -0
- 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/fred-series.ts +3 -1
- package/src/sources/gloomberb-cloud/index.ts +43 -8
- 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-listing.ts +53 -13
- package/src/tickers/company-name.ts +50 -4
- package/src/tickers/index-roots.ts +7 -0
- package/src/tickers/listing-fate.ts +2 -2
- package/src/tickers/listing-venues.ts +15 -3
- package/src/tickers/search/index.ts +6 -2
- package/src/tickers/search/ranking.ts +5 -1
- package/src/tickers/search/upsert.ts +13 -0
- package/src/tickers/terminal-codes.ts +19 -0
- package/src/tickers/ticker-changes.ts +8 -4
- 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/company-from.ts +40 -0
- package/src/types/data-provider.ts +7 -0
- package/src/types/financials.ts +59 -1
- package/src/types/headless.ts +27 -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 +165 -7
- package/src/utils/declared-dividends.ts +1 -1
- package/src/utils/forward-dividend.ts +2 -0
- package/src/utils/fundamentals.ts +60 -8
- 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/version.ts +2 -2
- package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
|
@@ -2,6 +2,7 @@ import type { ScreenerQuote } from "../../../api-client/market-discovery";
|
|
|
2
2
|
export type { ScreenerQuote } from "../../../api-client/market-discovery";
|
|
3
3
|
import { resolveCurrencyUnit } from "../../../utils/currency-units";
|
|
4
4
|
import { logFailure } from "../../../utils/log-failure";
|
|
5
|
+
import { savedCopyMark, servedFromSavedCopy } from "../../../data/saved-copies";
|
|
5
6
|
import type { PluginPersistence } from "../../../types/plugin";
|
|
6
7
|
import { apiClient } from "../../../api-client";
|
|
7
8
|
import type {
|
|
@@ -68,7 +69,7 @@ function shouldUseCache(api: MarketScreenerApi, options?: FetchCacheOptions): bo
|
|
|
68
69
|
return options?.cache === true || (options?.cache !== false && api === screenerApi);
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
function readCache<T>(key: string, options?: { allowExpired?: boolean }): { data: T; stale: boolean } | null {
|
|
72
|
+
function readCache<T>(key: string, options?: { allowExpired?: boolean }): { data: T; stale: boolean; fetchedAt: number } | null {
|
|
72
73
|
const record = marketMoversPersistence?.getResource<CachedResult<T>>(CACHE_KIND, key, {
|
|
73
74
|
sourceKey: CACHE_SOURCE,
|
|
74
75
|
schemaVersion: CACHE_SCHEMA_VERSION,
|
|
@@ -79,6 +80,7 @@ function readCache<T>(key: string, options?: { allowExpired?: boolean }): { data
|
|
|
79
80
|
return {
|
|
80
81
|
data: record.value.data,
|
|
81
82
|
stale: record.value.stale || !!record.stale || failedFetches.has(key),
|
|
83
|
+
fetchedAt: record.fetchedAt,
|
|
82
84
|
};
|
|
83
85
|
}
|
|
84
86
|
|
|
@@ -117,10 +119,11 @@ async function loadCached<T>(
|
|
|
117
119
|
return result;
|
|
118
120
|
})
|
|
119
121
|
.catch((error) => {
|
|
120
|
-
// Serving the expired copy is right; hiding that it is expired is not.
|
|
122
|
+
// Serving the expired copy is right; hiding that it is expired is not. A command that shows it
|
|
123
|
+
// ends with when the copy was saved.
|
|
121
124
|
if (fallback) {
|
|
122
125
|
failedFetches.add(key);
|
|
123
|
-
return { data: fallback.data, stale: true };
|
|
126
|
+
return servedFromSavedCopy({ data: fallback.data, stale: true }, fallback.fetchedAt);
|
|
124
127
|
}
|
|
125
128
|
throw error;
|
|
126
129
|
})
|
|
@@ -220,6 +223,17 @@ function parseScreenerSessions(row: Record<string, unknown>): CloudMoverSessionF
|
|
|
220
223
|
return sessions;
|
|
221
224
|
}
|
|
222
225
|
|
|
226
|
+
/** A positive float with its state, or nothing: a row without one fails any minimum. */
|
|
227
|
+
function floatOf(source: { floatShares?: unknown; floatSharesState?: unknown; floatSharesReason?: unknown } | null | undefined): Pick<ScreenerQuote, "floatShares" | "floatSharesState" | "floatSharesReason"> {
|
|
228
|
+
const shares = screenerNumber(source?.floatShares);
|
|
229
|
+
if (shares == null || !(shares > 0)) return {};
|
|
230
|
+
return {
|
|
231
|
+
floatShares: shares,
|
|
232
|
+
...(source?.floatSharesState === "partial" ? { floatSharesState: "partial" as const } : {}),
|
|
233
|
+
...(source?.floatSharesState === "partial" && typeof source.floatSharesReason === "string" ? { floatSharesReason: source.floatSharesReason } : {}),
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
223
237
|
/** Validate numeric fields at the API boundary so absent values stay unavailable. */
|
|
224
238
|
export function parseScreenerResponse(data: unknown): ScreenerQuote[] {
|
|
225
239
|
if (!data || typeof data !== "object" || !("quotes" in data) || !Array.isArray(data.quotes)) {
|
|
@@ -239,6 +253,7 @@ export function parseScreenerResponse(data: unknown): ScreenerQuote[] {
|
|
|
239
253
|
previousClose: screenerNumber(q.previousClose) ?? undefined, exchange: typeof q.exchange === "string" ? q.exchange : "",
|
|
240
254
|
lastUpdated: typeof q.lastUpdated === "number" && Number.isFinite(q.lastUpdated) && q.lastUpdated > 0 ? q.lastUpdated : undefined,
|
|
241
255
|
sessions: parseScreenerSessions(q),
|
|
256
|
+
...floatOf(q),
|
|
242
257
|
}];
|
|
243
258
|
});
|
|
244
259
|
}
|
|
@@ -325,6 +340,8 @@ function mergeCloudScreenerItem(
|
|
|
325
340
|
lastUpdated: item.lastUpdated,
|
|
326
341
|
previousClose: screenerNumber(item.previousClose) ?? undefined,
|
|
327
342
|
sessions: parseScreenerSessions(item as unknown as Record<string, unknown>),
|
|
343
|
+
// The live list's own float, the delayed list's when it sends none, each with its own state.
|
|
344
|
+
...floatOf(item.floatShares != null ? item : metadata),
|
|
328
345
|
};
|
|
329
346
|
}
|
|
330
347
|
|
|
@@ -375,7 +392,7 @@ export async function fetchPreferredMarketMovers(
|
|
|
375
392
|
const eligibility = sources.isCloudEligible();
|
|
376
393
|
if (!(typeof eligibility === "boolean" ? eligibility : await eligibility)) {
|
|
377
394
|
const result = await sources.fetchMarket(category, count, options);
|
|
378
|
-
return { quotes: rankScreenerQuotes(category, result.data), source: "gloom", stale: result.stale };
|
|
395
|
+
return { ...savedCopyMark(result), quotes: rankScreenerQuotes(category, result.data), source: "gloom", stale: result.stale };
|
|
379
396
|
}
|
|
380
397
|
|
|
381
398
|
const marketMetadata = sources.fetchMarket(
|
|
@@ -411,6 +428,7 @@ export async function fetchPreferredMarketMovers(
|
|
|
411
428
|
|
|
412
429
|
const fallback = await marketMetadata;
|
|
413
430
|
return {
|
|
431
|
+
...savedCopyMark(fallback),
|
|
414
432
|
quotes: rankScreenerQuotes(category, fallback.data),
|
|
415
433
|
source: "gloom",
|
|
416
434
|
stale: fallback.stale,
|
|
@@ -20,6 +20,7 @@ import { nextHeaderSort } from "../../../utils/sort-values";
|
|
|
20
20
|
import { usePluginPaneState, usePluginTickerActions } from "../../runtime";
|
|
21
21
|
import { ProWall, SignInWall } from "../cloud/auth-actions";
|
|
22
22
|
import { loadSessionMovers, SessionMoversAccessError } from "./client";
|
|
23
|
+
import { ANY_FLOAT, minFloatEmptyTitle, minFloatFilter, minFloatShares, partialFloatFooter } from "./min-float";
|
|
23
24
|
import { summaryFooterSegments } from "./footer";
|
|
24
25
|
import type { MarketSummaryQuote } from "./screener";
|
|
25
26
|
import {
|
|
@@ -104,7 +105,9 @@ function SessionMoversTable({ view, session, focused, width, summaryQuotes, live
|
|
|
104
105
|
const { pinTicker } = usePluginTickerActions();
|
|
105
106
|
const [savedSide, setSavedSide] = usePluginPaneState<CloudSessionMoversSide>("sessionSide", "up");
|
|
106
107
|
const side = resolveSide(view, savedSide);
|
|
107
|
-
const
|
|
108
|
+
const [minFloatValue, setMinFloatValue] = usePluginPaneState<string>("minFloat", ANY_FLOAT);
|
|
109
|
+
const minFloat = minFloatShares(minFloatValue);
|
|
110
|
+
const listKey = `${view}:${side}:${minFloat ?? ""}`;
|
|
108
111
|
const [loaded, setLoaded] = useState<LoadedList | null>(null);
|
|
109
112
|
// The first frame would otherwise claim an empty list.
|
|
110
113
|
const [loading, setLoading] = useState(true);
|
|
@@ -132,9 +135,9 @@ function SessionMoversTable({ view, session, focused, width, summaryQuotes, live
|
|
|
132
135
|
setLoadError(null);
|
|
133
136
|
}
|
|
134
137
|
try {
|
|
135
|
-
const next = await loadSessionMovers(view, side, options);
|
|
138
|
+
const next = await loadSessionMovers(view, side, { ...options, minFloat });
|
|
136
139
|
if (fetchGenRef.current !== gen) return;
|
|
137
|
-
setLoaded({ key: `${view}:${side}`, payload: next });
|
|
140
|
+
setLoaded({ key: `${view}:${side}:${minFloat ?? ""}`, payload: next });
|
|
138
141
|
setLoadError(null);
|
|
139
142
|
setLastLoadedAt(Date.now());
|
|
140
143
|
} catch (error) {
|
|
@@ -147,7 +150,7 @@ function SessionMoversTable({ view, session, focused, width, summaryQuotes, live
|
|
|
147
150
|
} finally {
|
|
148
151
|
if (fetchGenRef.current === gen && !options?.background) setLoading(false);
|
|
149
152
|
}
|
|
150
|
-
}, [onDenied, side, view]);
|
|
153
|
+
}, [minFloat, onDenied, side, view]);
|
|
151
154
|
|
|
152
155
|
useEffect(() => {
|
|
153
156
|
setSelectedId(null);
|
|
@@ -167,7 +170,7 @@ function SessionMoversTable({ view, session, focused, width, summaryQuotes, live
|
|
|
167
170
|
freshnessScopeKey: `market-movers:${listKey}`,
|
|
168
171
|
liveStreaming,
|
|
169
172
|
});
|
|
170
|
-
const columns = useMemo(() => buildSessionMoverColumns(view, width), [view, width]);
|
|
173
|
+
const columns = useMemo(() => buildSessionMoverColumns(view, width, { float: minFloat != null }), [minFloat, view, width]);
|
|
171
174
|
// The sort survives a change of list, but not onto a column this list or width does not show.
|
|
172
175
|
const sort = columns.some((column) => column.id === sortPreference.columnId) ? sortPreference : DEFAULT_SORT;
|
|
173
176
|
const rows = useMemo(
|
|
@@ -199,8 +202,10 @@ function SessionMoversTable({ view, session, focused, width, summaryQuotes, live
|
|
|
199
202
|
parts: [{ text: feedStatus, tone: feedStatus === "live" ? "value" as const : "muted" as const }],
|
|
200
203
|
}] : []),
|
|
201
204
|
...(payload?.stale ? [{ id: "stale", parts: [{ text: "stale", tone: "muted" as const }] }] : []),
|
|
205
|
+
// A float the source does not date says so once, while the FLOAT column shows.
|
|
206
|
+
...partialFloatFooter(columns.some((column) => column.id === "floatShares"), rows),
|
|
202
207
|
],
|
|
203
|
-
}), [asOfLabel, feedStatus, loadError, loading, payload?.stale, sessionLabel, summaryQuotes]);
|
|
208
|
+
}), [asOfLabel, columns, feedStatus, loadError, loading, payload?.stale, rows, sessionLabel, summaryQuotes]);
|
|
204
209
|
|
|
205
210
|
const openRow = useCallback((row: SessionMoverRow) => {
|
|
206
211
|
pinTicker(rowKey(row), { floating: true, paneType: TICKER_RESEARCH_PANE_ID, instrument: null });
|
|
@@ -217,7 +222,7 @@ function SessionMoversTable({ view, session, focused, width, summaryQuotes, live
|
|
|
217
222
|
const sides = sessionSides(view);
|
|
218
223
|
// Until this list's first answer the table is loading, including the frame
|
|
219
224
|
// between a change of list and its request going out.
|
|
220
|
-
const emptyTitle = payload ? "No movers yet." : loadError ?? "Loading movers...";
|
|
225
|
+
const emptyTitle = payload ? (minFloat != null ? minFloatEmptyTitle(minFloatValue) : "No movers yet.") : loadError ?? "Loading movers...";
|
|
221
226
|
return (
|
|
222
227
|
<DataTableView<SessionMoverRow, SessionMoverColumn>
|
|
223
228
|
focused={focused}
|
|
@@ -231,7 +236,7 @@ function SessionMoversTable({ view, session, focused, width, summaryQuotes, live
|
|
|
231
236
|
value: side,
|
|
232
237
|
options: sides,
|
|
233
238
|
onChange: (value: CloudSessionMoversSide) => setSavedSide(value),
|
|
234
|
-
}]}
|
|
239
|
+
}, minFloatFilter(minFloatValue, setMinFloatValue)]}
|
|
235
240
|
/>
|
|
236
241
|
)}
|
|
237
242
|
selection={{ kind: "id", selectedId, getId: rowKey, onChange: setSelectedId }}
|
|
@@ -21,9 +21,10 @@ const VOLUME_LABEL: Record<CloudSessionMoversCategory, string> = {
|
|
|
21
21
|
* Pre-market and after hours: last, change from the reference close, session
|
|
22
22
|
* volume, relative volume, float and the catalyst. Gaps add the gap and the
|
|
23
23
|
* distance from VWAP. The name gives way first, then the columns a narrow pane
|
|
24
|
-
* can do without (the day's volume, then the float
|
|
24
|
+
* can do without (the day's volume, then the float, which stays while a
|
|
25
|
+
* minimum float is set: `float`).
|
|
25
26
|
*/
|
|
26
|
-
export function buildSessionMoverColumns(view: CloudSessionMoversCategory, width: number): SessionMoverColumn[] {
|
|
27
|
+
export function buildSessionMoverColumns(view: CloudSessionMoversCategory, width: number, options: { float?: boolean } = {}): SessionMoverColumn[] {
|
|
27
28
|
const columns: SessionMoverColumn[] = [
|
|
28
29
|
{ id: "rank", label: "#", width: 3, align: "left" },
|
|
29
30
|
{ id: "symbol", label: "TICKER", width: 8, align: "left" },
|
|
@@ -37,7 +38,7 @@ export function buildSessionMoverColumns(view: CloudSessionMoversCategory, width
|
|
|
37
38
|
{ id: "floatShares", label: "FLOAT", width: 7, align: "right" },
|
|
38
39
|
{ id: "catalyst", label: "EVENT", width: 6, align: "left" },
|
|
39
40
|
];
|
|
40
|
-
const optional: SessionMoverColumnId[] = view === "gaps" ? ["volume",
|
|
41
|
+
const optional: SessionMoverColumnId[] = [...(view === "gaps" ? ["volume" as const] : []), ...(options.float ? [] : ["floatShares" as const])];
|
|
41
42
|
// Measured the way the table draws it: a header widens its column, and a
|
|
42
43
|
// left-aligned column after a right-aligned one gets a lead gap. The name
|
|
43
44
|
// sits between left-aligned columns, so it adds only its width and a gutter.
|
|
@@ -26,15 +26,20 @@ function fiftyTwoWeekPosition(price: number | null, low: number | undefined, hig
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
const EXTENDED_COLUMN_WIDTH = 9;
|
|
29
|
-
/** With an extended column, a narrow pane drops MCAP, then 52W%, before the name gets narrower than this. */
|
|
29
|
+
/** With an extended or float column, a narrow pane drops MCAP, then 52W%, before the name gets narrower than this. */
|
|
30
30
|
const EXTENDED_MIN_NAME_WIDTH = 12;
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* The list's columns at `width`. A pre-market or after-hours column follows
|
|
34
|
-
* CHG% for each session in `extendedSessions
|
|
35
|
-
*
|
|
34
|
+
* CHG% for each session in `extendedSessions`, and FLOAT ends the row while a
|
|
35
|
+
* minimum float is set (`float`); to make room for them a narrow pane gives up
|
|
36
|
+
* MCAP, then 52W%, before it cuts the names short.
|
|
36
37
|
*/
|
|
37
|
-
export function buildMarketMoverColumns(
|
|
38
|
+
export function buildMarketMoverColumns(
|
|
39
|
+
width: number,
|
|
40
|
+
extendedSessions: readonly ExtendedSession[] = [],
|
|
41
|
+
options: { float?: boolean } = {},
|
|
42
|
+
): MarketMoverColumn[] {
|
|
38
43
|
const lead: MarketMoverColumn[] = [
|
|
39
44
|
{ id: "rank", label: "#", width: 3, align: "left" },
|
|
40
45
|
{ id: "symbol", label: "TICKER", width: 8, align: "left" },
|
|
@@ -55,12 +60,13 @@ export function buildMarketMoverColumns(width: number, extendedSessions: readonl
|
|
|
55
60
|
{ id: "volumeRatio", label: "V/AVG", width: 6, align: "right" },
|
|
56
61
|
{ id: "range", label: "52W%", width: 6, align: "right" },
|
|
57
62
|
{ id: "marketCap", label: "MCAP", width: 8, align: "right" },
|
|
63
|
+
...(options.float ? [{ id: "floatShares" as const, label: "FLOAT", width: 7, align: "right" as const }] : []),
|
|
58
64
|
];
|
|
59
65
|
// Measured the way the table draws them (a header and its sort mark widen a
|
|
60
66
|
// column, gaps between them); the name adds its own width and a gap.
|
|
61
67
|
const nameRoom = () => width - getTableWidth([...lead, ...quote, ...trailing]) - 1;
|
|
62
68
|
for (const id of ["marketCap", "range"] as const) {
|
|
63
|
-
if (extendedSessions.length === 0 || nameRoom() >= EXTENDED_MIN_NAME_WIDTH) break;
|
|
69
|
+
if ((extendedSessions.length === 0 && !options.float) || nameRoom() >= EXTENDED_MIN_NAME_WIDTH) break;
|
|
64
70
|
trailing.splice(trailing.findIndex((column) => column.id === id), 1);
|
|
65
71
|
}
|
|
66
72
|
return [...lead, { id: "name", label: "NAME", width: Math.max(6, nameRoom()), align: "left" }, ...quote, ...trailing];
|
|
@@ -121,5 +127,11 @@ export function renderMarketMoverCell(
|
|
|
121
127
|
value: row.marketCap,
|
|
122
128
|
color: colors.textDim,
|
|
123
129
|
};
|
|
130
|
+
case "floatShares":
|
|
131
|
+
return {
|
|
132
|
+
text: row.floatShares != null ? formatCompact(row.floatShares, { fixedDecimals: true }) : "—",
|
|
133
|
+
value: row.floatShares ?? null,
|
|
134
|
+
color: colors.textDim,
|
|
135
|
+
};
|
|
124
136
|
}
|
|
125
137
|
}
|
|
@@ -40,7 +40,8 @@ export function Breadth({ fund, width, height, focused, active }: Pick<PaneProps
|
|
|
40
40
|
color: colors.warning, calendarSpaced: true,
|
|
41
41
|
})], [history, metric, colors.warning]);
|
|
42
42
|
const link = useChartTableSelection({ rows, getId: keyOf, getDate: dateOf, selectedId: selected ?? latest?.date ?? null, onSelect: setSelected, focused });
|
|
43
|
-
|
|
43
|
+
// The resource keeps a failed load as its error (stale, and a warning below), so this refresh never rejects.
|
|
44
|
+
const sessionOpen = useLiveSessionRefresh(() => resource.load(true), data?.access === "realtime" ? 15_000 : 60_000, active && visible, resource.updatedAt).live;
|
|
44
45
|
useAutoRefresh(resource.updatedAt, () => { if (active && visible && !sessionOpen) void resource.load(data?.basis === "intraday"); });
|
|
45
46
|
usePaneRefreshKey(() => void resource.reload(), { focused, enabled: active });
|
|
46
47
|
usePaneStatusFooter({ registrationId: "members:breadth", loading: !!data && resource.loading,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { MnaDealsParams, MnaRegionFilter, MnaStatusFilter, MnaTargetFilter } from "../../../api-client/mna";
|
|
2
2
|
import type { HeadlessPaneColumn, HeadlessPaneDefinition } from "../../../types/plugin";
|
|
3
3
|
import { fetchMnaDeals } from "./client";
|
|
4
|
+
import { companyDataNotes } from "../../../types/company-from";
|
|
4
5
|
import { dealsSymbol, formatDealValue, formatExpectedClose, stageLabel, termsLabel } from "./model";
|
|
5
6
|
import { REPORT_ROWS } from "../shared/headless-options";
|
|
6
7
|
|
|
@@ -92,6 +93,7 @@ export const mnaHeadless: HeadlessPaneDefinition<"rows"> = {
|
|
|
92
93
|
headline: deal.headline,
|
|
93
94
|
})),
|
|
94
95
|
errors: payload.lockedDeals > 0 ? [`${payload.lockedDeals} deals from the last ${payload.delayDays} days need Gloom Pro`] : [],
|
|
96
|
+
...(payload.companyFrom ? { notes: companyDataNotes(payload.companyFrom) } : {}),
|
|
95
97
|
metadata: {
|
|
96
98
|
asOf: payload.asOf, access: payload.access, delayDays: payload.delayDays, hasMore: payload.hasMore,
|
|
97
99
|
},
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
type PaneHint,
|
|
13
13
|
type SelectControl,
|
|
14
14
|
} from "../../../components";
|
|
15
|
-
import { usePaneStatusFooter } from "../../../components/layout/pane/status-footer";
|
|
15
|
+
import { useCompanyDataFooter, usePaneStatusFooter } from "../../../components/layout/pane/status-footer";
|
|
16
16
|
import { usePaneRefreshKey } from "../../../components/data-table/table-pane";
|
|
17
17
|
import { isAccessDenied } from "../../../api-client/errors";
|
|
18
18
|
import type {
|
|
@@ -337,6 +337,8 @@ function MnaDealsView({ focused, width, height, symbol }: {
|
|
|
337
337
|
hints,
|
|
338
338
|
});
|
|
339
339
|
usePaneFooter("mna:access", () => (access.segment && !openId ? { order: 5, info: [access.segment] } : null), [access.segment, openId]);
|
|
340
|
+
// A receipt's or foreign line's deals include its company's, read from the home line.
|
|
341
|
+
useCompanyDataFooter(data?.companyFrom, !openId);
|
|
340
342
|
|
|
341
343
|
const placeholder = blendHex(colors.bg, colors.textMuted, 0.35);
|
|
342
344
|
const renderCell = useCallback((item: Item, column: DataTableColumn, _index: number, state: { selected: boolean }): DataTableCell => {
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
type CloudNewsRead,
|
|
13
13
|
} from "../../../sources/gloomberb-cloud/news";
|
|
14
14
|
import { parsePublicTickerKey } from "../../../utils/exchanges";
|
|
15
|
+
import { companyDataNotes, readCompanyFrom } from "../../../types/company-from";
|
|
15
16
|
import { formatUtcSeconds } from "../../../utils/utc-time";
|
|
16
17
|
import { cloudNewsFreshness, cloudRealtimeAccess } from "../shared/report-freshness";
|
|
17
18
|
import { REPORT_FILTER, REPORT_ROWS } from "../shared/headless-options";
|
|
@@ -95,7 +96,8 @@ function projectNewsHeadless(
|
|
|
95
96
|
// A coin pair no story is filed under shows the stories that mention the coin, and says so.
|
|
96
97
|
...(response.searchedFor && args.symbols[0]
|
|
97
98
|
? { notes: [coinNewsFallbackNotice(parsePublicTickerKey(args.symbols[0]).symbol, response.searchedFor)] }
|
|
98
|
-
|
|
99
|
+
// A receipt's or foreign line's stories are its company's, read from the home line.
|
|
100
|
+
: response.companyFrom ? { notes: companyDataNotes(readCompanyFrom(response.companyFrom)) } : {}),
|
|
99
101
|
metadata: {
|
|
100
102
|
nextCursor: response.nextCursor,
|
|
101
103
|
sentiment,
|
|
@@ -6,6 +6,7 @@ import { instrumentFromTicker } from "../../../market-data/request-types";
|
|
|
6
6
|
import { useCallback } from "react";
|
|
7
7
|
import { useDebouncedPluginPaneState, usePluginPaneState } from "../../runtime";
|
|
8
8
|
import { EmptyState, PaneStatusBody, QueryBar, usePaneNoticeFooter } from "../../../components";
|
|
9
|
+
import { useCompanyDataFooter } from "../../../components/layout/pane/status-footer";
|
|
9
10
|
import { coinNewsFallbackNotice } from "../../../sources/gloomberb-cloud/news";
|
|
10
11
|
import { newsMuteSettingsDef, newsMutesApplyToFeed, useNewsMuteFilter } from "./wire/mutes";
|
|
11
12
|
import { newsListEmptyCopy } from "./wire/filter-articles";
|
|
@@ -112,7 +113,10 @@ function TickerNewsView({ width, height, focused }: { width: number; height: num
|
|
|
112
113
|
? [{ id: "summary", parts: [{ text: "summary loading", tone: "muted" as const }] }]
|
|
113
114
|
: undefined,
|
|
114
115
|
onPopOut: readableArticle ? popOutReadable : undefined,
|
|
116
|
+
feedAccess: newsState.access,
|
|
115
117
|
});
|
|
118
|
+
// A receipt's or foreign line's stories are its company's, read from the home line.
|
|
119
|
+
useCompanyDataFooter(newsState.companyFrom, !detailOpen);
|
|
116
120
|
|
|
117
121
|
if (!ticker) {
|
|
118
122
|
return (
|
|
@@ -29,10 +29,13 @@ const CATEGORY_LABELS: Record<string, string> = {
|
|
|
29
29
|
us: "US",
|
|
30
30
|
};
|
|
31
31
|
|
|
32
|
+
function categoryKey(value: string | null | undefined): string {
|
|
33
|
+
return value?.trim().toLowerCase().replace(/[\s-]+/g, "_") ?? "";
|
|
34
|
+
}
|
|
35
|
+
|
|
32
36
|
export function formatNewsCategory(value: string | null | undefined): string {
|
|
33
|
-
const
|
|
34
|
-
if (!
|
|
35
|
-
const key = raw.toLowerCase().replace(/[\s-]+/g, "_");
|
|
37
|
+
const key = categoryKey(value);
|
|
38
|
+
if (!key) return "";
|
|
36
39
|
const mapped = CATEGORY_LABELS[key];
|
|
37
40
|
if (mapped) return mapped;
|
|
38
41
|
const words = key.split("_").filter(Boolean);
|
|
@@ -46,6 +49,19 @@ export function formatNewsCategory(value: string | null | undefined): string {
|
|
|
46
49
|
.join(" ");
|
|
47
50
|
}
|
|
48
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Labels for the news tables' category column, which is ten cells wide, for the
|
|
54
|
+
* ids whose label above would be cut in it. Anywhere with room (a story's
|
|
55
|
+
* detail) keeps the full label.
|
|
56
|
+
*/
|
|
57
|
+
const COLUMN_CATEGORY_LABELS: Record<string, string> = {
|
|
58
|
+
product_approval: "Approval",
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export function formatNewsCategoryForColumn(value: string | null | undefined): string {
|
|
62
|
+
return COLUMN_CATEGORY_LABELS[categoryKey(value)] ?? formatNewsCategory(value);
|
|
63
|
+
}
|
|
64
|
+
|
|
49
65
|
const KNOWN_TICKERS = new Set([
|
|
50
66
|
"AAPL", "MSFT", "GOOGL", "AMZN", "NVDA", "META", "TSLA", "JPM", "JNJ", "UNH",
|
|
51
67
|
"PG", "XOM", "CVX", "HD", "BAC", "V", "MA", "PFE", "KO", "PEP", "ABBV", "MRK",
|
|
@@ -125,6 +125,7 @@ function IndustryNewsBody({ focused, width, height, entry, code, codeFilter }: P
|
|
|
125
125
|
defaultSort: DEFAULT_SORT,
|
|
126
126
|
refreshing: loaded.loading && loaded.articles.length > 0,
|
|
127
127
|
error: loaded.error,
|
|
128
|
+
feedAccess: loaded.query ? loaded.newsState.access : null,
|
|
128
129
|
});
|
|
129
130
|
const { setSelectedArticleId } = stack;
|
|
130
131
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { useMemo } from "react";
|
|
2
2
|
import type { PaneFooterSegment } from "../../../../../components";
|
|
3
|
+
import { delayedFooterSegment } from "../../../../../components/layout/pane/footer/model";
|
|
4
|
+
import type { NewsAccess } from "../../../../../news/types";
|
|
3
5
|
import { t, tf } from "../../../../../i18n";
|
|
4
6
|
import { useAppLanguage } from "../../../../../i18n/react";
|
|
5
7
|
import { useCloudAccessFooter } from "../../../shared/cloud-upgrade";
|
|
@@ -22,6 +24,18 @@ interface UseNewsArticleFooterOptions {
|
|
|
22
24
|
loading?: boolean;
|
|
23
25
|
error?: string | null;
|
|
24
26
|
onPopOut?: () => void;
|
|
27
|
+
/** Which news the list on screen is, from the service's answer; null or absent when it did not say. */
|
|
28
|
+
feedAccess?: NewsAccess | null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Whether the stories are the live wire: what the service's answer said when
|
|
33
|
+
* it said; otherwise held back for an account without real-time news, and
|
|
34
|
+
* not said at all for one with it. The plan never claims the live wire.
|
|
35
|
+
*/
|
|
36
|
+
function newsFeedRealtime(feedAccess: NewsAccess | null | undefined, hasProAccess: boolean): boolean | null {
|
|
37
|
+
if (feedAccess) return feedAccess === "realtime";
|
|
38
|
+
return hasProAccess ? null : false;
|
|
25
39
|
}
|
|
26
40
|
|
|
27
41
|
export function useNewsArticleFooter({
|
|
@@ -32,32 +46,44 @@ export function useNewsArticleFooter({
|
|
|
32
46
|
loading = false,
|
|
33
47
|
error,
|
|
34
48
|
onPopOut,
|
|
49
|
+
feedAccess = null,
|
|
35
50
|
}: UseNewsArticleFooterOptions) {
|
|
36
51
|
const language = useAppLanguage();
|
|
52
|
+
const delayLabel = tf("{count}h", { count: CLOUD_NEWS_DELAY_HOURS });
|
|
37
53
|
const { access, hint: upgradeHint, segment } = useCloudAccessFooter({
|
|
38
|
-
delayLabel
|
|
54
|
+
delayLabel,
|
|
39
55
|
focused,
|
|
56
|
+
// A feed the service answered live has nothing to upgrade.
|
|
57
|
+
degraded: feedAccess !== "realtime",
|
|
40
58
|
segmentId: "news-access",
|
|
41
59
|
placement: "news-footer",
|
|
42
60
|
shortcutScope: `${registrationId}:news-upgrade`,
|
|
43
61
|
});
|
|
62
|
+
const realtime = newsFeedRealtime(feedAccess, access.hasProAccess);
|
|
44
63
|
|
|
45
64
|
// A report or capture of the pane says how far its stories are held back, as the footer does, and dates the
|
|
46
65
|
// list by its newest story, named so it is not read as the time now.
|
|
47
66
|
useRemoteUiNode({
|
|
48
67
|
role: "report-freshness",
|
|
49
68
|
label: "News freshness",
|
|
50
|
-
getMetadata: () => (
|
|
69
|
+
getMetadata: () => (realtime === true
|
|
51
70
|
? { status: "live", asOfLabel: NEWEST_STORY }
|
|
52
|
-
:
|
|
71
|
+
: realtime === false
|
|
72
|
+
? { status: "delayed", delayMinutes: CLOUD_NEWS_DELAY_HOURS * 60, asOfLabel: NEWEST_STORY }
|
|
73
|
+
: { status: "not-a-feed", basis: "published stories", asOfLabel: NEWEST_STORY }),
|
|
53
74
|
});
|
|
54
75
|
|
|
55
76
|
const accessInfo = useMemo<PaneFooterSegment[]>(() => {
|
|
56
|
-
|
|
77
|
+
// A trial keeps its days-left segment, which manages the plan.
|
|
78
|
+
if (realtime === true && !access.isTrialActive) {
|
|
57
79
|
return [{ id: "news-access", parts: [{ text: t("real-time news"), tone: "positive" }] }];
|
|
58
80
|
}
|
|
81
|
+
// Held back although the account has Pro (a read that went out before its session): say so, without a pitch.
|
|
82
|
+
if (feedAccess === "delayed" && access.hasProAccess) {
|
|
83
|
+
return [delayedFooterSegment({ id: "news-access", detail: tf("{delay} delayed", { delay: delayLabel }), delay: delayLabel })];
|
|
84
|
+
}
|
|
59
85
|
return segment ? [segment] : [];
|
|
60
|
-
}, [access.
|
|
86
|
+
}, [access.hasProAccess, access.isTrialActive, delayLabel, feedAccess, language, realtime, segment]);
|
|
61
87
|
const footerInfo = useMemo(() => [...accessInfo, ...(info ?? [])], [accessInfo, info]);
|
|
62
88
|
|
|
63
89
|
// The story's header names its source, so the footer keeps to the link.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useCallback, useMemo } from "react";
|
|
2
2
|
import { Box } from "../../../../../ui";
|
|
3
|
-
import type { NewsArticle, NewsQuery } from "../../../../../news/types";
|
|
3
|
+
import type { NewsAccess, NewsArticle, NewsQuery } from "../../../../../news/types";
|
|
4
4
|
import { useLoadNewsStory, useNewsArticles, useNewsTableLoadMore } from "../../../../../news/hooks";
|
|
5
5
|
import type { PaneProps } from "../../../../../types/plugin";
|
|
6
6
|
import { useDebouncedPluginPaneState, usePluginConfigState, usePluginPaneState } from "../../../../runtime";
|
|
@@ -31,6 +31,8 @@ interface NewsArticleStackOptions {
|
|
|
31
31
|
/** Rows stay on screen while a refresh runs, so the footer says it is loading. */
|
|
32
32
|
refreshing: boolean;
|
|
33
33
|
error: string | null;
|
|
34
|
+
/** Which news the rows are, from the service's answer, for the footer's real-time or delayed label. */
|
|
35
|
+
feedAccess?: NewsAccess | null;
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
/**
|
|
@@ -46,6 +48,7 @@ export function useNewsArticleStack({
|
|
|
46
48
|
defaultSort,
|
|
47
49
|
refreshing,
|
|
48
50
|
error,
|
|
51
|
+
feedAccess,
|
|
49
52
|
}: NewsArticleStackOptions) {
|
|
50
53
|
const [selectedArticleId, setSelectedArticleId] = useDebouncedPluginPaneState<string | null>(
|
|
51
54
|
`${paneKey}:selectedArticleId`,
|
|
@@ -95,6 +98,7 @@ export function useNewsArticleStack({
|
|
|
95
98
|
loading: detailLoading || refreshing,
|
|
96
99
|
error: [error, detailError].filter(Boolean).join(" ") || null,
|
|
97
100
|
onPopOut: readableArticle ? popOutReadable : undefined,
|
|
101
|
+
feedAccess,
|
|
98
102
|
});
|
|
99
103
|
|
|
100
104
|
return {
|
|
@@ -172,6 +176,7 @@ export function NewsPresetPane({
|
|
|
172
176
|
defaultSort,
|
|
173
177
|
refreshing: loading && persisted.length > 0,
|
|
174
178
|
error,
|
|
179
|
+
feedAccess: newsState.access,
|
|
175
180
|
});
|
|
176
181
|
const emptyCopy = newsListEmptyCopy({
|
|
177
182
|
query: searchQuery,
|
|
@@ -17,7 +17,7 @@ import { useLoadNewsStory } from "../../../../../news/hooks";
|
|
|
17
17
|
import { formatFeedTime } from "../../../../../utils/datetime-format";
|
|
18
18
|
import { logFailure } from "../../../../../utils/log-failure";
|
|
19
19
|
import { nextHeaderSort } from "../../../../../utils/sort-values";
|
|
20
|
-
import {
|
|
20
|
+
import { formatNewsCategoryForColumn } from "../categories";
|
|
21
21
|
import { useOpenTickerChoice } from "../../../shared/ticker-choice";
|
|
22
22
|
import { usePinnedMoment } from "../../../../../components/use-pinned-moment";
|
|
23
23
|
|
|
@@ -359,7 +359,7 @@ export function NewsArticleStackView({
|
|
|
359
359
|
}
|
|
360
360
|
case "categories":
|
|
361
361
|
return {
|
|
362
|
-
text:
|
|
362
|
+
text: formatNewsCategoryForColumn(item.categories[0]) || "-",
|
|
363
363
|
color: tone(colors.textDim),
|
|
364
364
|
};
|
|
365
365
|
case "sentiment": {
|
|
@@ -68,6 +68,17 @@ export function parseOpenFdaDate(value: unknown): Date | null {
|
|
|
68
68
|
|
|
69
69
|
const normalizeWords = (value: string): string => value.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
|
|
70
70
|
|
|
71
|
+
/** Each term once, in the order first coded, whatever its case: `Nausea` and `NAUSEA` are one. */
|
|
72
|
+
function uniqueTerms(terms: readonly string[]): string[] {
|
|
73
|
+
const seen = new Set<string>();
|
|
74
|
+
return terms.filter((term) => {
|
|
75
|
+
const key = normalizeWords(term);
|
|
76
|
+
if (seen.has(key)) return false;
|
|
77
|
+
seen.add(key);
|
|
78
|
+
return true;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
71
82
|
/** The search's words appear together in one of the fields, as openFDA matches a phrase. */
|
|
72
83
|
function fieldsMatch(fields: Array<string | undefined>, query: string): boolean {
|
|
73
84
|
const words = normalizeWords(query);
|
|
@@ -230,9 +241,10 @@ export function parseDrugReport(raw: unknown, query = "", role: DrugRole | null
|
|
|
230
241
|
product: shown?.name ?? "Unnamed drug",
|
|
231
242
|
role: shown?.role ?? null,
|
|
232
243
|
drugs: drugs.map(({ name, role }) => ({ name, role })),
|
|
233
|
-
|
|
244
|
+
// A report can code one reaction more than once (once per drug, or in another case): each is listed once.
|
|
245
|
+
reactions: uniqueTerms((Array.isArray(patient.reaction) ? patient.reaction : [])
|
|
234
246
|
.map((entry) => asString(asRecord(entry).reactionmeddrapt))
|
|
235
|
-
.filter((entry): entry is string => !!entry),
|
|
247
|
+
.filter((entry): entry is string => !!entry)),
|
|
236
248
|
outcome,
|
|
237
249
|
outcomes,
|
|
238
250
|
manufacturer: shown && shown.manufacturers.length === 1 ? shown.manufacturers[0]! : null,
|
|
@@ -3,8 +3,9 @@ import { OpenFdaPane } from "./pane";
|
|
|
3
3
|
import { OPENFDA_PANE_ID, OPENFDA_PLUGIN_ID } from "./types";
|
|
4
4
|
|
|
5
5
|
const createOpenFdaPaneInstance = (options?: PaneTemplateCreateOptions) => {
|
|
6
|
-
// A search of product and firm names
|
|
7
|
-
//
|
|
6
|
+
// A search of product and firm names: FDA records name the firm as it filed,
|
|
7
|
+
// not a symbol. A ticker that matches nothing is searched as its company's
|
|
8
|
+
// name, which the pane looks up (`FDA AZN` is AstraZeneca's reports).
|
|
8
9
|
const query = (options?.arg ?? options?.values?.query ?? "").trim();
|
|
9
10
|
const encoded = encodeURIComponent(query).replace(/%/g, "~");
|
|
10
11
|
return {
|
|
@@ -65,6 +65,15 @@ const TONES: Partial<Record<ColumnId, Record<string, StateTone>>> = {
|
|
|
65
65
|
status: { Ongoing: "warning", Completed: "text", Terminated: "muted" },
|
|
66
66
|
};
|
|
67
67
|
|
|
68
|
+
/** The most terms a row's list cell names before counting the rest; the record names them all. */
|
|
69
|
+
const MAX_CELL_TERMS = 3;
|
|
70
|
+
|
|
71
|
+
/** A report's coded terms as a row shows them: the first few, then how many more (`Nausea, Rash, Fatigue +9 more`). */
|
|
72
|
+
export function termsCellText(terms: readonly string[]): string {
|
|
73
|
+
const more = terms.length - MAX_CELL_TERMS;
|
|
74
|
+
return `${terms.slice(0, MAX_CELL_TERMS).join(", ")}${more > 0 ? ` +${more} more` : ""}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
68
77
|
export function recordTone(column: ColumnId, text: string): StateTone {
|
|
69
78
|
return TONES[column]?.[text] ?? "text";
|
|
70
79
|
}
|