gloomberb 0.15.8 → 0.16.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 +10 -2
- package/package.json +1 -1
- package/src/api-client/app-attention.ts +147 -0
- package/src/api-client/attention.ts +26 -0
- package/src/api-client/awards.ts +215 -0
- package/src/api-client/catalysts.ts +160 -0
- package/src/api-client/chat.ts +137 -14
- package/src/api-client/company-kpis.ts +153 -0
- package/src/api-client/cpi.ts +52 -0
- package/src/api-client/credit-documents.ts +107 -0
- package/src/api-client/data.ts +204 -5
- package/src/api-client/distress.ts +164 -0
- package/src/api-client/equity-screener.ts +11 -0
- package/src/api-client/exposure.ts +202 -0
- package/src/api-client/gpu.ts +93 -0
- package/src/api-client/hiring.ts +110 -0
- package/src/api-client/index.ts +80 -3
- package/src/api-client/members.ts +26 -0
- package/src/api-client/normalizers.ts +31 -1
- package/src/api-client/perps.ts +131 -0
- package/src/api-client/power.ts +148 -0
- package/src/api-client/request.ts +58 -1
- package/src/api-client/research-activity.ts +172 -13
- package/src/api-client/socket.ts +75 -27
- package/src/api-client/supply-chain-graph.ts +40 -0
- package/src/api-client/supply-chain.ts +98 -0
- package/src/api-client/telemetry.ts +29 -0
- package/src/api-client/themes.ts +28 -0
- package/src/api-client/types.ts +38 -2
- package/src/api-client/wall-experiments.ts +100 -0
- package/src/api-client/wall-summary.ts +49 -0
- package/src/api-client/web-experiments.ts +136 -0
- package/src/app/global-shortcuts.ts +14 -6
- package/src/app/keybindings/actions.ts +1 -1
- package/src/app/pane-runtime/plugin-bindings.ts +5 -0
- package/src/app/pane-runtime/ticker-open-runtime.ts +8 -2
- package/src/app/runtime/update.ts +51 -3
- package/src/app.tsx +10 -6
- package/src/cli/commands/system.ts +2 -0
- package/src/cli/desktop-pane-shot.ts +3 -4
- package/src/cli/pane-functions/data.ts +78 -0
- package/src/cli/pane-functions/financial-ratio-shot.ts +160 -0
- package/src/cli/pane-functions/function-help.ts +214 -10
- package/src/cli/pane-functions/headless.ts +4 -0
- package/src/cli/pane-functions/screenshot-evidence.ts +21 -0
- package/src/cli/pane-functions/screenshot.ts +49 -5
- package/src/components/chart-table/index.ts +1 -0
- package/src/components/chart-table/layout.ts +15 -0
- package/src/components/command-bar/action-runtime.ts +1 -0
- package/src/components/command-bar/commands/collection.ts +5 -1
- package/src/components/command-bar/commands/direct/index.ts +32 -0
- package/src/components/command-bar/commands/registry.ts +7 -0
- package/src/components/command-bar/pane-templates/items.ts +11 -0
- package/src/components/command-bar/pane-templates/workflow.ts +19 -4
- package/src/components/command-bar/routes/root/command-items.ts +15 -2
- package/src/components/command-bar/routes/root/plugin-install.ts +2 -2
- package/src/components/command-bar/routes/root/provider-search.ts +21 -5
- package/src/components/command-bar/routes/root/results.ts +64 -0
- package/src/components/command-bar/routes/root/runtime.ts +22 -5
- package/src/components/command-bar/routes/ticker-search/results.ts +116 -25
- package/src/components/command-bar/routes/ticker-search/route.ts +4 -2
- package/src/components/command-bar/routing/actions.ts +2 -0
- package/src/components/command-bar/selection-runtime.ts +38 -0
- package/src/components/command-bar/surface/index.tsx +22 -2
- package/src/components/command-bar/view-model.ts +3 -0
- package/src/components/command-bar/workflow/ops.ts +19 -0
- package/src/components/command-bar/workflow/tickers.ts +5 -0
- package/src/components/company-logo.tsx +15 -2
- package/src/components/data-table/table-pane.ts +2 -1
- package/src/components/index.ts +3 -2
- package/src/components/layout/detached-pane-shell.tsx +12 -1
- package/src/components/layout/floating-pane.tsx +6 -0
- package/src/components/layout/header.tsx +24 -7
- package/src/components/layout/missing-pane.tsx +87 -23
- package/src/components/layout/pane/footer/index.tsx +64 -18
- package/src/components/layout/pane/footer/model.ts +85 -0
- package/src/components/layout/pane/header.tsx +63 -30
- package/src/components/layout/pane/index.tsx +6 -0
- package/src/components/layout/pane/sidebar.tsx +19 -1
- package/src/components/layout/pane/title.ts +31 -7
- package/src/components/layout/shell/drag/index.ts +6 -5
- package/src/components/layout/shell/drag/runtime.ts +3 -0
- package/src/components/layout/shell/index.tsx +6 -0
- package/src/components/layout/shell/pane/layers.tsx +15 -7
- package/src/components/layout/shell/pane/management-shortcuts.ts +13 -1
- package/src/components/layout/shell/terminal-pointer-runtime.ts +24 -3
- package/src/components/layout/shell/ticker-link-menu.ts +23 -9
- package/src/components/layout/status-bar.tsx +93 -28
- package/src/components/layout/status-widget-space.ts +72 -0
- package/src/components/metric-treemap/heat-surface.tsx +718 -0
- package/src/components/metric-treemap/heat-tiles.ts +210 -0
- package/src/components/metric-treemap/index.tsx +2 -0
- package/src/components/metric-treemap/layout.ts +6 -0
- package/src/components/metric-treemap/scene.ts +432 -0
- package/src/components/onboarding/first-run-workspace.ts +8 -2
- package/src/components/onboarding/onboarding-wizard.tsx +35 -16
- package/src/components/pane-settings-dialog/field-dialogs.tsx +1 -0
- package/src/components/price-performance.tsx +31 -34
- package/src/components/toggle-list.tsx +38 -12
- package/src/components/ui/country-flag-art.tsx +449 -0
- package/src/components/ui/country-flag.tsx +41 -0
- package/src/components/ui/data-table/index.tsx +11 -8
- package/src/components/ui/data-table/types.ts +13 -0
- package/src/components/ui/display.tsx +9 -3
- package/src/components/ui/figure.tsx +94 -0
- package/src/components/ui/icon.tsx +5 -1
- package/src/components/ui/index.ts +4 -0
- package/src/components/ui/message-composer.tsx +8 -1
- package/src/components/ui/multi-select/dialog.tsx +108 -22
- package/src/components/ui/range-track.tsx +46 -0
- package/src/components/ui/ratio-bar.tsx +84 -0
- package/src/components/ui/tabs.tsx +4 -0
- package/src/components/ui/toggle.tsx +3 -1
- package/src/components/use-scrollbox-layout.ts +17 -0
- package/src/core/state/app/layout.ts +35 -1
- package/src/core/state/app/state.ts +15 -0
- package/src/core/state/app/types.ts +11 -1
- package/src/data/config/store/normalize.ts +24 -0
- package/src/i18n/es.ts +6 -3
- package/src/i18n/ja.ts +6 -3
- package/src/i18n/ko.ts +6 -3
- package/src/i18n/zh-cn.ts +6 -3
- package/src/i18n/zh-tw.ts +6 -3
- package/src/layout/ticker-navigation.ts +28 -2
- package/src/layout-marketplace/pane-imagery.ts +1 -0
- package/src/market-data/coordinator/entries.ts +13 -0
- package/src/market-data/coordinator/financials.ts +33 -8
- package/src/market-data/market/freshness.ts +16 -5
- package/src/market-data/published-cn-sessions.ts +25 -0
- package/src/market-data/published-nse-sessions.ts +20 -0
- package/src/market-data/quotes/contributions.ts +6 -0
- package/src/market-data/quotes/freshness.ts +36 -0
- package/src/market-data/quotes/resolution.ts +22 -8
- package/src/market-data/quotes/time.ts +16 -0
- package/src/news/aggregator.ts +4 -0
- package/src/plugins/builtin/account-management/model.ts +6 -1
- package/src/plugins/builtin/account-management/pane.tsx +36 -25
- package/src/plugins/builtin/alerts/catalysts.ts +77 -0
- package/src/plugins/builtin/alerts/events-pane.tsx +3 -0
- package/src/plugins/builtin/alerts/events.ts +10 -4
- package/src/plugins/builtin/alerts/history-pane.tsx +1 -1
- package/src/plugins/builtin/alerts/history.ts +1 -0
- package/src/plugins/builtin/alerts/index.tsx +6 -0
- package/src/plugins/builtin/alerts/research-builder.ts +10 -3
- package/src/plugins/builtin/alerts/research-rules.ts +15 -1
- package/src/plugins/builtin/analytics/risk-client.ts +197 -59
- package/src/plugins/builtin/analytics/risk-headless.ts +141 -30
- package/src/plugins/builtin/analytics/risk-math.ts +33 -13
- package/src/plugins/builtin/analytics/risk-model.ts +250 -60
- package/src/plugins/builtin/analytics/risk-pane.tsx +61 -30
- package/src/plugins/builtin/analytics/risk-test-data.ts +182 -0
- package/src/plugins/builtin/attention/client.ts +80 -0
- package/src/plugins/builtin/attention/evidence.ts +30 -0
- package/src/plugins/builtin/attention/headless.ts +33 -0
- package/src/plugins/builtin/attention/index.tsx +18 -0
- package/src/plugins/builtin/attention/model.ts +59 -0
- package/src/plugins/builtin/attention/pane.tsx +211 -0
- package/src/plugins/builtin/attention/trending.tsx +59 -0
- package/src/plugins/builtin/awards/alerts.ts +32 -0
- package/src/plugins/builtin/awards/client.ts +82 -0
- package/src/plugins/builtin/awards/detail.tsx +107 -0
- package/src/plugins/builtin/awards/evidence.ts +36 -0
- package/src/plugins/builtin/awards/headless.ts +55 -0
- package/src/plugins/builtin/awards/index.tsx +20 -0
- package/src/plugins/builtin/awards/model.ts +42 -0
- package/src/plugins/builtin/awards/pane.tsx +263 -0
- package/src/plugins/builtin/buildout/cells.tsx +15 -12
- package/src/plugins/builtin/buildout/detail/company.tsx +3 -3
- package/src/plugins/builtin/buildout/detail/index.tsx +0 -1
- package/src/plugins/builtin/buildout/detail/intel.tsx +6 -6
- package/src/plugins/builtin/buildout/detail/site.tsx +4 -7
- package/src/plugins/builtin/buildout/detail/ui.tsx +0 -19
- package/src/plugins/builtin/catalysts/client.ts +28 -0
- package/src/plugins/builtin/catalysts/detail.tsx +88 -0
- package/src/plugins/builtin/catalysts/evidence.ts +47 -0
- package/src/plugins/builtin/catalysts/headless.ts +42 -0
- package/src/plugins/builtin/catalysts/history.ts +26 -0
- package/src/plugins/builtin/catalysts/index.tsx +29 -0
- package/src/plugins/builtin/catalysts/model.ts +135 -0
- package/src/plugins/builtin/catalysts/pane.tsx +161 -0
- package/src/plugins/builtin/cds/index.tsx +1 -0
- package/src/plugins/builtin/chart-composer/index.tsx +8 -1
- package/src/plugins/builtin/chat/attachments/desktop.tsx +610 -0
- package/src/plugins/builtin/chat/attachments/files.ts +45 -0
- package/src/plugins/builtin/chat/attachments/model.ts +164 -0
- package/src/plugins/builtin/chat/attachments/terminal.tsx +72 -0
- package/src/plugins/builtin/chat/attachments/transfer.ts +165 -0
- package/src/plugins/builtin/chat/content/channel-navigation.ts +13 -12
- package/src/plugins/builtin/chat/content/composer-runtime.ts +25 -4
- package/src/plugins/builtin/chat/content/composer.tsx +46 -4
- package/src/plugins/builtin/chat/content/footer.tsx +46 -8
- package/src/plugins/builtin/chat/content/index.tsx +263 -62
- package/src/plugins/builtin/chat/content/layout-metrics.ts +10 -2
- package/src/plugins/builtin/chat/content/open-targets.ts +17 -3
- package/src/plugins/builtin/chat/content/scroll.ts +7 -1
- package/src/plugins/builtin/chat/content/shortcuts.ts +36 -11
- package/src/plugins/builtin/chat/content/snapshot.ts +3 -0
- package/src/plugins/builtin/chat/content/transcript.tsx +17 -1
- package/src/plugins/builtin/chat/content/types.ts +7 -0
- package/src/plugins/builtin/chat/controller/attachments.ts +213 -0
- package/src/plugins/builtin/chat/controller/connections.ts +3 -1
- package/src/plugins/builtin/chat/controller/index.ts +114 -2
- package/src/plugins/builtin/chat/controller/lifecycle.ts +9 -0
- package/src/plugins/builtin/chat/controller/messages.ts +35 -2
- package/src/plugins/builtin/chat/controller/send.ts +128 -25
- package/src/plugins/builtin/chat/controller/state.ts +12 -1
- package/src/plugins/builtin/chat/controller/utils.ts +7 -5
- package/src/plugins/builtin/chat/controller/view.ts +2 -0
- package/src/plugins/builtin/chat/index.tsx +21 -0
- package/src/plugins/builtin/chat/layout.ts +9 -4
- package/src/plugins/builtin/chat/message/desktop.tsx +55 -20
- package/src/plugins/builtin/chat/message/header.tsx +30 -5
- package/src/plugins/builtin/chat/message/inline-tokens.tsx +9 -0
- package/src/plugins/builtin/chat/message/pending-send.ts +35 -0
- package/src/plugins/builtin/chat/message/profile-popover.tsx +5 -1
- package/src/plugins/builtin/chat/message/render-state.ts +12 -2
- package/src/plugins/builtin/chat/message/terminal.tsx +51 -4
- package/src/plugins/builtin/chat/message/types.ts +6 -1
- package/src/plugins/builtin/chat/pane-state.ts +59 -1
- package/src/plugins/builtin/chat/profile-popover.ts +50 -1
- package/src/plugins/builtin/chat/sidebar.tsx +8 -3
- package/src/plugins/builtin/chat/status-widget.tsx +29 -12
- package/src/plugins/builtin/chat/unread-inbox-pane.tsx +143 -0
- package/src/plugins/builtin/chat/unread-inbox.ts +91 -0
- package/src/plugins/builtin/clinical-trials/client.ts +237 -0
- package/src/plugins/builtin/clinical-trials/index.tsx +74 -0
- package/src/plugins/builtin/clinical-trials/pane.tsx +225 -0
- package/src/plugins/builtin/clinical-trials/types.ts +2 -0
- package/src/plugins/builtin/cloud/askg/controller.ts +37 -6
- package/src/plugins/builtin/cloud/askg/executor.ts +59 -44
- package/src/plugins/builtin/cloud/askg/host.ts +8 -1
- package/src/plugins/builtin/cloud/askg/manifest.ts +29 -2
- package/src/plugins/builtin/cloud/askg/model.ts +47 -13
- package/src/plugins/builtin/cloud/askg/notes.ts +100 -0
- package/src/plugins/builtin/cloud/askg/pane.tsx +50 -14
- package/src/plugins/builtin/cloud/askg/protocol.ts +39 -1
- package/src/plugins/builtin/cloud/askg/result-budget.ts +156 -0
- package/src/plugins/builtin/cloud/askg/user-data.ts +89 -0
- package/src/plugins/builtin/cloud/auth-actions.tsx +55 -31
- package/src/plugins/builtin/cloud/plugin.tsx +4 -1
- package/src/plugins/builtin/cloud/team/pane-sections.tsx +10 -1
- package/src/plugins/builtin/cloud/team/pane.tsx +3 -2
- package/src/plugins/builtin/cloud/thesis/board-pane.tsx +2 -2
- package/src/plugins/builtin/cloud/thesis/detail.tsx +1 -1
- package/src/plugins/builtin/cloud/thesis/ticker-tab.tsx +2 -2
- package/src/plugins/builtin/cloud/trial-offer-status-widget.tsx +202 -0
- package/src/plugins/builtin/cloud/upgrade-command.ts +1 -1
- package/src/plugins/builtin/cloud/upgrade-dialog.tsx +29 -3
- package/src/plugins/builtin/cloud/upgrade-status-widget.tsx +1 -1
- package/src/plugins/builtin/cloud-tweets/table.tsx +1 -1
- package/src/plugins/builtin/comment-letters/client.ts +342 -0
- package/src/plugins/builtin/comment-letters/index.tsx +74 -0
- package/src/plugins/builtin/comment-letters/pane.tsx +295 -0
- package/src/plugins/builtin/comment-letters/types.ts +43 -0
- package/src/plugins/builtin/company-attention/app-rank.tsx +79 -0
- package/src/plugins/builtin/company-attention/client.ts +69 -0
- package/src/plugins/builtin/company-attention/evidence.ts +45 -0
- package/src/plugins/builtin/company-attention/headless.ts +47 -0
- package/src/plugins/builtin/company-attention/index.tsx +30 -0
- package/src/plugins/builtin/company-attention/model.ts +129 -0
- package/src/plugins/builtin/company-attention/pane.tsx +183 -0
- package/src/plugins/builtin/company-kpis/client.ts +92 -0
- package/src/plugins/builtin/company-kpis/headless.ts +32 -0
- package/src/plugins/builtin/company-kpis/index.tsx +26 -0
- package/src/plugins/builtin/company-kpis/model.ts +124 -0
- package/src/plugins/builtin/company-kpis/pane.tsx +226 -0
- package/src/plugins/builtin/company-kpis/related.ts +11 -0
- package/src/plugins/builtin/company-kpis/screenshot-evidence.ts +44 -0
- package/src/plugins/builtin/composite-plugins.ts +32 -0
- package/src/plugins/builtin/correlation/history.ts +4 -1
- package/src/plugins/builtin/correlation/index.tsx +139 -80
- package/src/plugins/builtin/correlation/matrix/pair-chart.tsx +48 -0
- package/src/plugins/builtin/correlation/matrix/selection.ts +77 -0
- package/src/plugins/builtin/correlation/relationship/model.ts +2 -2
- package/src/plugins/builtin/cpi/client.ts +66 -0
- package/src/plugins/builtin/cpi/headless.ts +78 -0
- package/src/plugins/builtin/cpi/index.tsx +31 -0
- package/src/plugins/builtin/cpi/model.ts +198 -0
- package/src/plugins/builtin/cpi/pane.tsx +157 -0
- package/src/plugins/builtin/credit-documents/client.ts +99 -0
- package/src/plugins/builtin/credit-documents/detail.tsx +109 -0
- package/src/plugins/builtin/credit-documents/evidence.ts +64 -0
- package/src/plugins/builtin/credit-documents/gauge.tsx +55 -0
- package/src/plugins/builtin/credit-documents/headless.ts +40 -0
- package/src/plugins/builtin/credit-documents/index.tsx +19 -0
- package/src/plugins/builtin/credit-documents/model.ts +336 -0
- package/src/plugins/builtin/credit-documents/pane.tsx +185 -0
- package/src/plugins/builtin/custom-view/view-spec.ts +14 -2
- package/src/plugins/builtin/debt-maturities/pane.tsx +1 -0
- package/src/plugins/builtin/debt-maturities/wall-bar.tsx +14 -41
- package/src/plugins/builtin/dividend-yield/client.ts +4 -2
- package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
- package/src/plugins/builtin/earnings/board.tsx +3 -0
- package/src/plugins/builtin/earnings/client.ts +2 -2
- package/src/plugins/builtin/earnings/index.tsx +2 -0
- package/src/plugins/builtin/earnings-calls/index.tsx +1 -0
- package/src/plugins/builtin/earnings-calls/pane.tsx +7 -1
- package/src/plugins/builtin/earnings-ripple/client.ts +60 -0
- package/src/plugins/builtin/earnings-ripple/headless.ts +35 -0
- package/src/plugins/builtin/earnings-ripple/index.tsx +35 -0
- package/src/plugins/builtin/earnings-ripple/model.ts +98 -0
- package/src/plugins/builtin/earnings-ripple/pane.tsx +103 -0
- package/src/plugins/builtin/equity-screener/client.ts +1 -1
- package/src/plugins/builtin/equity-screener/criterion-editor.tsx +1 -1
- package/src/plugins/builtin/equity-screener/model.ts +74 -9
- package/src/plugins/builtin/equity-screener/pane.tsx +2 -0
- package/src/plugins/builtin/estimate-revisions/pane.tsx +3 -0
- package/src/plugins/builtin/executives/pane.tsx +3 -1
- package/src/plugins/builtin/exposure/charts.tsx +66 -0
- package/src/plugins/builtin/exposure/client.ts +53 -0
- package/src/plugins/builtin/exposure/drivers.ts +29 -0
- package/src/plugins/builtin/exposure/editor.tsx +61 -0
- package/src/plugins/builtin/exposure/evidence.ts +39 -0
- package/src/plugins/builtin/exposure/headless.ts +51 -0
- package/src/plugins/builtin/exposure/holdings.ts +56 -0
- package/src/plugins/builtin/exposure/index.tsx +13 -0
- package/src/plugins/builtin/exposure/model.ts +104 -0
- package/src/plugins/builtin/exposure/pane.tsx +247 -0
- package/src/plugins/builtin/fear-greed/pane.tsx +2 -9
- package/src/plugins/builtin/filing-events/pane.tsx +2 -1
- package/src/plugins/builtin/fx-matrix/direction.ts +81 -0
- package/src/plugins/builtin/fx-matrix/index.tsx +71 -33
- package/src/plugins/builtin/fx-matrix/live-legs.ts +17 -0
- package/src/plugins/builtin/fx-matrix/pairs.ts +68 -14
- package/src/plugins/builtin/fx-matrix/settings.ts +62 -0
- package/src/plugins/builtin/gpu/board.tsx +163 -0
- package/src/plugins/builtin/gpu/changes.tsx +94 -0
- package/src/plugins/builtin/gpu/client.ts +130 -0
- package/src/plugins/builtin/gpu/equities.tsx +112 -0
- package/src/plugins/builtin/gpu/headless.ts +67 -0
- package/src/plugins/builtin/gpu/history.tsx +140 -0
- package/src/plugins/builtin/gpu/index.tsx +18 -0
- package/src/plugins/builtin/gpu/ladder.tsx +127 -0
- package/src/plugins/builtin/gpu/model.ts +317 -0
- package/src/plugins/builtin/gpu/pane.tsx +63 -0
- package/src/plugins/builtin/help/index.tsx +19 -2
- package/src/plugins/builtin/holders/pane.tsx +4 -8
- package/src/plugins/builtin/insider/detail.tsx +119 -0
- package/src/plugins/builtin/insider/display.ts +221 -0
- package/src/plugins/builtin/insider/headless.ts +24 -3
- package/src/plugins/builtin/insider/index.tsx +206 -108
- package/src/plugins/builtin/insider/insider-data.ts +31 -3
- package/src/plugins/builtin/insider/model.ts +53 -4
- package/src/plugins/builtin/insider/table-model.ts +226 -0
- package/src/plugins/builtin/insider/table.tsx +79 -0
- package/src/plugins/builtin/iv-history/screen-pane.tsx +1 -1
- package/src/plugins/builtin/jobs/index.tsx +1 -0
- package/src/plugins/builtin/jobs/pane.tsx +14 -7
- package/src/plugins/builtin/jobs/share-bars.tsx +5 -28
- package/src/plugins/builtin/macro-day/client.ts +50 -0
- package/src/plugins/builtin/macro-day/headless.ts +51 -0
- package/src/plugins/builtin/macro-day/index.tsx +21 -0
- package/src/plugins/builtin/macro-day/model.ts +137 -0
- package/src/plugins/builtin/macro-day/pane.tsx +118 -0
- package/src/plugins/builtin/macro-day/releases.ts +58 -0
- package/src/plugins/builtin/market-halts/model.ts +30 -17
- package/src/plugins/builtin/market-halts/pane.tsx +15 -21
- package/src/plugins/builtin/market-heatmap/data.ts +29 -3
- package/src/plugins/builtin/market-heatmap/index.tsx +438 -119
- package/src/plugins/builtin/market-heatmap/live.ts +88 -0
- package/src/plugins/builtin/market-heatmap/portfolio.ts +240 -0
- package/src/plugins/builtin/market-movers/client.ts +3 -2
- package/src/plugins/builtin/market-movers/headless.ts +8 -1
- package/src/plugins/builtin/market-movers/index.tsx +1 -0
- package/src/plugins/builtin/market-movers/screener.ts +18 -5
- package/src/plugins/builtin/market-movers/session-body.tsx +5 -2
- package/src/plugins/builtin/members/client.ts +36 -0
- package/src/plugins/builtin/members/headless.ts +33 -0
- package/src/plugins/builtin/members/index.tsx +20 -0
- package/src/plugins/builtin/members/model.ts +60 -0
- package/src/plugins/builtin/members/pane.tsx +168 -0
- package/src/plugins/builtin/mna/distress/client.ts +326 -0
- package/src/plugins/builtin/mna/distress/detail.tsx +247 -0
- package/src/plugins/builtin/mna/distress/headless.ts +257 -0
- package/src/plugins/builtin/mna/distress/model.ts +427 -0
- package/src/plugins/builtin/mna/distress/pane.tsx +606 -0
- package/src/plugins/builtin/mna/index.tsx +17 -1
- package/src/plugins/builtin/mna/pane.tsx +47 -3
- package/src/plugins/builtin/news/index.tsx +62 -16
- package/src/plugins/builtin/news/wire/filter-articles.ts +54 -0
- package/src/plugins/builtin/news/wire/index.tsx +37 -3
- package/src/plugins/builtin/news/wire/industry-pane.tsx +29 -13
- package/src/plugins/builtin/news/wire/mutes.ts +253 -0
- package/src/plugins/builtin/news/wire/news/command-bar-search.ts +117 -0
- package/src/plugins/builtin/news/wire/news/footer.ts +4 -0
- package/src/plugins/builtin/news/wire/news/list-search.ts +34 -0
- package/src/plugins/builtin/news/wire/news/pop-out.tsx +158 -0
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +64 -19
- package/src/plugins/builtin/openfda/client.ts +300 -0
- package/src/plugins/builtin/openfda/index.tsx +71 -0
- package/src/plugins/builtin/openfda/pane.tsx +382 -0
- package/src/plugins/builtin/openfda/types.ts +76 -0
- package/src/plugins/builtin/options/footer.ts +1 -0
- package/src/plugins/builtin/options/index.tsx +2 -1
- package/src/plugins/builtin/options-calculator/pane.tsx +0 -1
- package/src/plugins/builtin/options-positioning/index.tsx +4 -2
- package/src/plugins/builtin/options-positioning/pane.tsx +1 -1
- package/src/plugins/builtin/pe-band/client.ts +88 -0
- package/src/plugins/builtin/pe-band/headless.ts +51 -0
- package/src/plugins/builtin/pe-band/index.tsx +20 -0
- package/src/plugins/builtin/pe-band/model.ts +271 -0
- package/src/plugins/builtin/pe-band/pane.tsx +160 -0
- package/src/plugins/builtin/pe-band/report-dates.ts +78 -0
- package/src/plugins/builtin/perps/client.ts +80 -0
- package/src/plugins/builtin/perps/equity-identity.ts +16 -0
- package/src/plugins/builtin/perps/equity-row.tsx +30 -0
- package/src/plugins/builtin/perps/headless.ts +34 -0
- package/src/plugins/builtin/perps/index.tsx +14 -0
- package/src/plugins/builtin/perps/model.ts +58 -0
- package/src/plugins/builtin/perps/pane.tsx +185 -0
- package/src/plugins/builtin/plugin-marketplace/activation.ts +19 -0
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +2 -3
- package/src/plugins/builtin/plugin-marketplace/store.ts +2 -0
- package/src/plugins/builtin/portfolio-list/cli/render.ts +177 -87
- package/src/plugins/builtin/portfolio-list/headless.ts +237 -0
- package/src/plugins/builtin/portfolio-list/index.tsx +2 -0
- package/src/plugins/builtin/portfolio-list/mutations.ts +17 -0
- package/src/plugins/builtin/portfolio-list/pane/data.ts +17 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +88 -5
- package/src/plugins/builtin/portfolio-list/pane/remove-ticker.ts +85 -0
- package/src/plugins/builtin/portfolio-list/pane/streaming.ts +18 -12
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +49 -2
- package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +48 -5
- package/src/plugins/builtin/power/client.ts +100 -0
- package/src/plugins/builtin/power/detail.tsx +82 -0
- package/src/plugins/builtin/power/evidence.ts +56 -0
- package/src/plugins/builtin/power/generation.tsx +37 -0
- package/src/plugins/builtin/power/headless.ts +43 -0
- package/src/plugins/builtin/power/index.tsx +16 -0
- package/src/plugins/builtin/power/model.ts +89 -0
- package/src/plugins/builtin/power/pane.tsx +209 -0
- package/src/plugins/builtin/power/query.ts +25 -0
- package/src/plugins/builtin/relative-rotation/pane.tsx +3 -3
- package/src/plugins/builtin/research/analyst-pane.tsx +1 -1
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +13 -4
- package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +5 -3
- package/src/plugins/builtin/research-search/pane.tsx +21 -7
- package/src/plugins/builtin/revenue-breakdown/pane.tsx +1 -1
- package/src/plugins/builtin/reverse-dcf/client.ts +35 -0
- package/src/plugins/builtin/reverse-dcf/headless.ts +44 -0
- package/src/plugins/builtin/reverse-dcf/index.tsx +20 -0
- package/src/plugins/builtin/reverse-dcf/model.ts +87 -0
- package/src/plugins/builtin/reverse-dcf/pane.tsx +107 -0
- package/src/plugins/builtin/risk-factors/pane.tsx +2 -1
- package/src/plugins/builtin/scanner/denied.tsx +4 -2
- package/src/plugins/builtin/scanner/feed.ts +1 -0
- package/src/plugins/builtin/scanner/flow-pane.tsx +3 -2
- package/src/plugins/builtin/scanner/hilo-bars.tsx +12 -70
- package/src/plugins/builtin/scanner/hilo-model.ts +0 -18
- package/src/plugins/builtin/scanner/hilo-pane.tsx +1 -1
- package/src/plugins/builtin/seasonality/client.ts +4 -1
- package/src/plugins/builtin/sec/command-bar-search.ts +217 -0
- package/src/plugins/builtin/sec/forms.ts +54 -0
- package/src/plugins/builtin/sec/headless.ts +31 -5
- package/src/plugins/builtin/sec/index.tsx +67 -11
- package/src/plugins/builtin/sec/model.ts +35 -5
- package/src/plugins/builtin/sectors/headless.ts +1 -0
- package/src/plugins/builtin/sectors/index.tsx +72 -26
- package/src/plugins/builtin/sectors/move-bar.tsx +10 -32
- package/src/plugins/builtin/shared/cloud-upgrade.ts +143 -39
- package/src/plugins/builtin/shared/pro-feature-wall.tsx +8 -4
- package/src/plugins/builtin/shared/research-cells.tsx +107 -0
- package/src/plugins/builtin/shared/series-list-detail.tsx +5 -1
- package/src/plugins/builtin/shared/wall-teaser-catalog.ts +80 -0
- package/src/plugins/builtin/shared/wall-teaser.tsx +192 -0
- package/src/plugins/builtin/shared/wall-view-lifecycle.ts +43 -0
- package/src/plugins/builtin/short-interest/index.tsx +31 -0
- package/src/plugins/builtin/short-interest/watch-client.ts +70 -0
- package/src/plugins/builtin/short-interest/watch-headless.ts +49 -0
- package/src/plugins/builtin/short-interest/watch-model.ts +185 -0
- package/src/plugins/builtin/short-interest/watch-pane.tsx +169 -0
- package/src/plugins/builtin/short-volume/pane.tsx +1 -1
- package/src/plugins/builtin/social-mentions/index.tsx +1 -1
- package/src/plugins/builtin/social-mentions/pane.tsx +1 -1
- package/src/plugins/builtin/supply-chain/client.ts +104 -0
- package/src/plugins/builtin/supply-chain/disclosure-quote.tsx +17 -0
- package/src/plugins/builtin/supply-chain/evidence.ts +88 -0
- package/src/plugins/builtin/supply-chain/flow-layout.ts +133 -0
- package/src/plugins/builtin/supply-chain/flow.tsx +323 -0
- package/src/plugins/builtin/supply-chain/graph-client.ts +83 -0
- package/src/plugins/builtin/supply-chain/graph-evidence.ts +25 -0
- package/src/plugins/builtin/supply-chain/graph-model.ts +45 -0
- package/src/plugins/builtin/supply-chain/graph-pane.tsx +218 -0
- package/src/plugins/builtin/supply-chain/graph.tsx +163 -0
- package/src/plugins/builtin/supply-chain/headless.ts +105 -0
- package/src/plugins/builtin/supply-chain/index.tsx +17 -0
- package/src/plugins/builtin/supply-chain/model.ts +145 -0
- package/src/plugins/builtin/supply-chain/pane.tsx +276 -0
- package/src/plugins/builtin/supply-chain/row-evidence.tsx +96 -0
- package/src/plugins/builtin/supply-chain/trust.ts +34 -0
- package/src/plugins/builtin/themes/client.ts +39 -0
- package/src/plugins/builtin/themes/headless.ts +38 -0
- package/src/plugins/builtin/themes/model.ts +92 -0
- package/src/plugins/builtin/themes/pane.tsx +125 -0
- package/src/plugins/builtin/thirteenf/index.tsx +2 -1
- package/src/plugins/builtin/thirteenf/pane.tsx +6 -15
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +5 -6
- package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +201 -0
- package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +327 -0
- package/src/plugins/builtin/ticker-detail/financials/period-end-history.ts +5 -2
- package/src/plugins/builtin/ticker-detail/financials/ratios.ts +18 -2
- package/src/plugins/builtin/ticker-detail/financials/tab.tsx +2 -1
- package/src/plugins/builtin/ticker-detail/headless.ts +7 -2
- package/src/plugins/builtin/ticker-detail/index.tsx +22 -0
- package/src/plugins/builtin/ticker-detail/overview/components.tsx +49 -81
- package/src/plugins/builtin/ticker-detail/overview/pane.tsx +2 -0
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +111 -46
- package/src/plugins/builtin/ticker-detail/pane.tsx +51 -7
- package/src/plugins/builtin/ticker-detail/quote-footer.ts +7 -1
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +40 -45
- package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +3 -0
- package/src/plugins/builtin/time-sales/pane.tsx +1 -1
- package/src/plugins/builtin/vol-surface/index.tsx +1 -1
- package/src/plugins/builtin/world-indices/indices.ts +1 -0
- package/src/plugins/catalog-browser.ts +40 -0
- package/src/plugins/catalog-manifest.ts +4 -0
- package/src/plugins/catalog-ui.ts +9 -1
- package/src/plugins/catalog.ts +1 -1
- package/src/plugins/current-target.ts +0 -9
- package/src/plugins/host-link.ts +33 -1
- package/src/plugins/installer.ts +48 -12
- package/src/plugins/loader.ts +18 -3
- package/src/plugins/manager-node.ts +15 -5
- package/src/plugins/registry/index.ts +1 -1
- package/src/plugins/registry/pane-settings.ts +25 -7
- package/src/public/__snapshots__/public-api.test.ts.snap +3 -0
- package/src/renderers/browser/cloud-transport.ts +66 -1
- package/src/renderers/browser/main.tsx +2 -0
- package/src/renderers/dom/data-table/row.tsx +52 -16
- package/src/renderers/dom/desktop/controls.tsx +38 -25
- package/src/renderers/dom/desktop/icons.tsx +9 -0
- package/src/renderers/dom/host/box.tsx +69 -26
- package/src/renderers/dom/host/chart-surface.tsx +36 -2
- package/src/renderers/dom/host/focus-scope.ts +11 -1
- package/src/renderers/dom/host/tabs.tsx +37 -1
- package/src/renderers/dom/styles.css +68 -5
- package/src/renderers/electrobun/bun/desktop/backend-requests.ts +11 -6
- package/src/renderers/electrobun/bun/desktop/backend.ts +1 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +181 -87
- package/src/renderers/electrobun/bun/index.ts +73 -6
- package/src/renderers/electrobun/bun/window/remembered-frame.ts +122 -0
- package/src/renderers/electrobun/shared/protocol.ts +3 -0
- package/src/renderers/electrobun/view/backend-rpc.ts +3 -2
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +2 -1
- package/src/renderers/electrobun/view/http-fetch.ts +2 -0
- package/src/renderers/electrobun/view/main.tsx +10 -2
- package/src/renderers/electrobun/view/renderer-window-error.ts +18 -0
- package/src/renderers/electrobun/view/rpc-timeout.ts +59 -0
- package/src/renderers/electrobun/view/update-host.ts +45 -21
- package/src/renderers/opentui/start.tsx +3 -0
- package/src/shares/api.ts +3 -1
- package/src/sources/gloomberb-cloud/normalizers.ts +9 -2
- package/src/sources/provider-router/financials.ts +3 -1
- package/src/sources/sec-edgar/identity.ts +36 -0
- package/src/sources/sec-edgar.ts +1 -34
- package/src/state/app/context/index.tsx +8 -0
- package/src/sync/core-contributors.ts +2 -0
- package/src/telemetry/attention-counts.ts +164 -0
- package/src/telemetry/benign-window-error.ts +7 -0
- package/src/telemetry/crash-reports-dom.ts +2 -0
- package/src/telemetry/live-config.ts +7 -0
- package/src/test-support/daily-nav.ts +25 -0
- package/src/test-support/rpc-loopback.ts +7 -3
- package/src/test-support/shell-pane-keys.tsx +34 -0
- package/src/theme/heat-colors.ts +174 -1
- package/src/tickers/instrument-kind.ts +46 -13
- package/src/tickers/search/asset-classes.ts +106 -0
- package/src/tickers/search/index.ts +62 -15
- package/src/tickers/search/ranking.ts +42 -3
- package/src/types/config.ts +23 -1
- package/src/types/financials.ts +2 -0
- package/src/types/headless.ts +17 -0
- package/src/types/plugin.ts +18 -5
- package/src/ui/context-menu.tsx +1 -0
- package/src/ui/host.tsx +8 -0
- package/src/updater/index.ts +29 -2
- package/src/utils/double-escape-close.ts +20 -0
- package/src/utils/html-entities.ts +15 -0
- package/src/utils/http-proxy-response.ts +44 -7
- package/src/utils/plugin-proxy-hosts.json +3 -0
- package/src/utils/sec.ts +19 -1
- package/src/utils/text-wrap.ts +13 -8
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type { DataTableColumn, StatItem } from "../../../components";
|
|
2
|
+
import { scalarPoint, staticSeries } from "../../../components/chart/static/series";
|
|
3
|
+
import type { HiringBoard, HiringPayload, HiringSummary } from "../../../api-client/hiring";
|
|
4
|
+
import type { AppAttentionPayload, AppCompany } from "../../../api-client/app-attention";
|
|
5
|
+
import { humanLabel } from "../shared/research-cells";
|
|
6
|
+
|
|
7
|
+
export type AttentionKind = "hiring" | "apps";
|
|
8
|
+
export type AttentionTab = "table" | "chart" | "mix" | "peers" | "evidence";
|
|
9
|
+
export interface AttentionRow {
|
|
10
|
+
id: string;
|
|
11
|
+
values: Record<string, string | number | null>;
|
|
12
|
+
symbol?: string;
|
|
13
|
+
date?: string;
|
|
14
|
+
url?: string | null;
|
|
15
|
+
details?: Array<{ label: string; value: string }>;
|
|
16
|
+
app?: AppFocus;
|
|
17
|
+
}
|
|
18
|
+
export interface AppFocus { store: "app-store" | "google-play"; appId: string; name: string; country: string; chart: "free" | "paid" | "grossing" | "unranked" }
|
|
19
|
+
interface AttentionSection {
|
|
20
|
+
columns: DataTableColumn[];
|
|
21
|
+
rows: AttentionRow[];
|
|
22
|
+
empty: string;
|
|
23
|
+
}
|
|
24
|
+
export interface AttentionModel {
|
|
25
|
+
asOf: string | null;
|
|
26
|
+
preview: boolean;
|
|
27
|
+
locked: number;
|
|
28
|
+
notices: string[];
|
|
29
|
+
figures: StatItem[];
|
|
30
|
+
sections: Record<AttentionTab, AttentionSection>;
|
|
31
|
+
chart: { label: string; unit: string; points: Array<{ date: string; value: number | null }> };
|
|
32
|
+
}
|
|
33
|
+
export const count = (value: number | null | undefined) => value == null ? "--" : value.toLocaleString("en-US", { maximumFractionDigits: 0 });
|
|
34
|
+
const signed = (value: number | null | undefined, decimals = 0) => value == null ? "--" : `${value > 0 ? "+" : ""}${value.toLocaleString("en-US", { maximumFractionDigits: decimals })}`;
|
|
35
|
+
const percent = (value: number | null | undefined) => value == null ? "--" : `${(value * 100).toFixed(1)}%`;
|
|
36
|
+
const column = (id: string, label: string, width: number, numeric = false, flex = false): DataTableColumn => ({ id, label, width, align: numeric ? "right" : "left", ...(flex ? { flexGrow: 1 } : {}) });
|
|
37
|
+
const roleFamily = (value: string | null) => value === "data_ai" || value === "data ai" ? "Data / AI" : value ? humanLabel(value) : null;
|
|
38
|
+
const COMPANY_COLUMNS = [column("symbol", "Ticker", 12), column("name", "Company", 24, false, true), column("open", "Open roles", 12, true), column("net", "Net / week", 12, true), column("remote", "Remote %", 10, true), column("z", "Z-score", 10, true), column("signal", "Signal", 12), column("date", "As of", 12)];
|
|
39
|
+
const HISTORY_COLUMNS = [column("date", "Week", 12), column("open", "Open roles", 12, true), column("added", "Added", 9, true), column("removed", "Removed", 9, true), column("net", "Net", 9, true), column("change", "Change %", 11, true), column("remote", "Remote %", 10, true), column("coverage", "Capture", 13, false, true)];
|
|
40
|
+
const companyRow = (row: HiringSummary): AttentionRow => ({ id: row.symbol, symbol: row.symbol, values: { symbol: row.symbol, name: row.name, open: row.latest?.openCount ?? null, net: row.latest?.netChange ?? null, remote: row.latest?.remoteShare == null ? null : row.latest.remoteShare * 100, z: row.zScore, signal: humanLabel(row.signal ?? row.status), date: row.latest?.observedAt.slice(0, 10) ?? null } });
|
|
41
|
+
|
|
42
|
+
export function hiringModel(data: HiringBoard | HiringPayload, mix = "functions"): AttentionModel {
|
|
43
|
+
const detail = "series" in data ? data : null;
|
|
44
|
+
const companies = "companies" in data ? data.companies : [data];
|
|
45
|
+
const history = detail?.series ?? [];
|
|
46
|
+
const latest = detail?.latest;
|
|
47
|
+
const mixKey = ["functions", "seniority", "countries", "locations", "signals"].includes(mix) ? mix as "functions" | "seniority" | "countries" | "locations" | "signals" : "functions";
|
|
48
|
+
const rows = mixKey === "signals" ? (detail?.signals ?? []).map((row, i): AttentionRow => ({ id: `${row.type}:${i}`, url: row.evidenceUrls[0], values: { label: row.label, count: row.value, share: row.confidence * 100, date: row.asOf.slice(0, 10) }, details: [{ label: "Observed", value: row.asOf }, { label: "Confidence", value: percent(row.confidence) }, { label: "Location", value: row.location ?? "--" }] }))
|
|
49
|
+
: (detail?.[mixKey] ?? []).map((row): AttentionRow => ({ id: row.id, values: { label: mixKey === "functions" ? roleFamily(row.label) : mixKey === "seniority" ? humanLabel(row.label) : row.label, count: row.count, share: row.share * 100 } }));
|
|
50
|
+
const model: AttentionModel = {
|
|
51
|
+
asOf: latest?.observedAt ?? data.generatedAt,
|
|
52
|
+
preview: data.preview,
|
|
53
|
+
locked: typeof data.locked === "number" ? data.locked : Object.values(data.locked).reduce((a, b) => a + b, 0),
|
|
54
|
+
// How counts compare is methodology (docs/hiring-app-attention.md); the footer keeps only what is wrong with this capture.
|
|
55
|
+
notices: detail ? [...(latest?.completeness !== "complete" ? ["The latest capture has incomplete or unknown coverage."] : []), ...(detail.status === "stale" ? ["The latest hiring capture is stale."] : [])] : [],
|
|
56
|
+
// A figure the first capture cannot give yet (a weekly change, a z-score) is left out, not shown as a dash.
|
|
57
|
+
figures: latest ? [
|
|
58
|
+
{ label: "Open roles", value: count(latest.openCount) },
|
|
59
|
+
...(latest.netChange != null ? [{ label: "Net / week", value: signed(latest.netChange), tone: latest.netChange === 0 ? "muted" as const : latest.netChange > 0 ? "positive" as const : "negative" as const }] : []),
|
|
60
|
+
...(latest.remoteShare != null ? [{ label: "Remote", value: percent(latest.remoteShare), detail: "known work modes" }] : []),
|
|
61
|
+
...(detail?.zScore != null ? [{ label: "Z-score", value: detail.zScore.toFixed(2), detail: detail.signal ? `hiring ${detail.signal}` : undefined,
|
|
62
|
+
tone: detail.signal === "surge" ? "positive" as const : detail.signal === "freeze" ? "negative" as const : undefined }] : []),
|
|
63
|
+
] : [],
|
|
64
|
+
sections: {
|
|
65
|
+
table: { columns: COMPANY_COLUMNS, rows: companies.map(companyRow), empty: "Hiring observations are not available yet." },
|
|
66
|
+
chart: { columns: HISTORY_COLUMNS, rows: [...history].reverse().map((row): AttentionRow => ({ id: row.week, date: row.week, values: { date: row.week, open: row.openCount, added: row.added, removed: row.removed, net: row.netChange, change: row.changePct, remote: row.remoteShare == null ? null : row.remoteShare * 100, coverage: row.completeness }, details: [{ label: "Observed", value: row.observedAt }, { label: "Confidence", value: percent(row.confidence) }, { label: "Capture", value: row.kind }, { label: "Evergreen roles", value: count(row.evergreenCount) }] })), empty: data.preview ? "Full hiring history requires Pro." : "History is accumulating from the first capture." },
|
|
67
|
+
mix: { columns: [column("label", mixKey === "signals" ? "Signal" : "Group", 28, false, true), column("count", mixKey === "signals" ? "Value" : "Roles", 12, true), column("share", mixKey === "signals" ? "Confidence %" : "Share %", mixKey === "signals" ? 14 : 30, true), ...(mixKey === "signals" ? [column("date", "Observed", 12)] : [])], rows, empty: "No classified observations for this group yet." },
|
|
68
|
+
peers: { columns: COMPANY_COLUMNS, rows: (detail?.peers ?? []).map(companyRow), empty: "No comparable mapped peers yet." },
|
|
69
|
+
evidence: { columns: [column("title", "Role", 32, false, true), column("location", "Location", 24), column("function", "Function", 18), column("seniority", "Seniority", 13), column("date", "Observed", 12), column("confidence", "Confidence %", 13, true)], rows: (detail?.evidence ?? []).map((row): AttentionRow => ({ id: `${row.snapshotId}:${row.id}`, url: row.url ?? row.sourceUrl, values: { title: row.title, location: row.location, function: roleFamily(row.jobFunction), seniority: row.seniority ? humanLabel(row.seniority) : null, date: row.observedAt.slice(0, 10), confidence: row.confidence * 100 }, details: [{ label: "Posting title", value: row.title }, { label: "Location", value: row.location ?? "--" }, { label: "Country", value: row.country ?? "Unknown" }, { label: "Function", value: row.jobFunction ?? "Unclassified" }, { label: "Seniority", value: row.seniority ?? "Unclassified" }, { label: "Remote", value: row.remote === null ? "Unknown" : row.remote ? "Yes" : "No" }, { label: "Observed", value: row.observedAt }, { label: "Revision", value: String(row.revision) }, { label: "Confidence", value: percent(row.confidence) }, { label: "Reporting ticker", value: row.sourceTicker }] })), empty: "No primary posting evidence is available yet." },
|
|
70
|
+
},
|
|
71
|
+
chart: { label: "Open roles", unit: "roles", points: history.map((point) => ({ date: point.week, value: point.openCount })) },
|
|
72
|
+
};
|
|
73
|
+
if (detail) {
|
|
74
|
+
model.sections.table = { ...model.sections.evidence, columns: model.sections.evidence.columns.filter((entry) => entry.id !== "confidence"), empty: "No current role evidence has been captured yet." };
|
|
75
|
+
}
|
|
76
|
+
return model;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function attentionSeries(model: Pick<AttentionModel, "chart">, color: string) {
|
|
80
|
+
// One or two captures are observations, not an established trend.
|
|
81
|
+
if (model.chart.points.filter((point) => point.value !== null).length < 3) return [];
|
|
82
|
+
const cadence = model.chart.unit === "roles" ? 7 * 86_400_000 : 86_400_000;
|
|
83
|
+
const points = [...model.chart.points].sort((a, b) => a.date.localeCompare(b.date)).flatMap((point, i, all) => {
|
|
84
|
+
const time = Date.parse(point.date), previous = i ? Date.parse(all[i - 1]!.date) : null;
|
|
85
|
+
const gap = previous !== null && time - previous > cadence * 1.5;
|
|
86
|
+
return [...(gap ? [scalarPoint(new Date(previous + cadence), null)] : []), scalarPoint(new Date(time), point.value)];
|
|
87
|
+
});
|
|
88
|
+
return [{ ...staticSeries(points, { id: "company-attention", label: model.chart.label, color, calendarSpaced: true }), unit: model.chart.unit, unitGroup: model.chart.unit }];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function sortAttentionRows(rows: AttentionRow[], columnId: string, direction: "asc" | "desc") {
|
|
92
|
+
return [...rows].sort((a, b) => {
|
|
93
|
+
const left = a.values[columnId], right = b.values[columnId];
|
|
94
|
+
if (left == null) return right == null ? a.id.localeCompare(b.id) : 1;
|
|
95
|
+
if (right == null) return -1;
|
|
96
|
+
const result = typeof left === "number" && typeof right === "number" ? left - right : String(left).localeCompare(String(right));
|
|
97
|
+
return (direction === "asc" ? result : -result) || a.id.localeCompare(b.id);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const APP_COLUMNS = [column("name", "App", 27, false, true), column("symbol", "Ticker", 12), column("country", "Country", 9), column("chart", "Chart", 10), column("rank", "Rank", 7, true), column("change", "7D places", 12, true), column("rating", "Rating / 5", 12, true), column("ratings", "Ratings", 12, true), column("drift", "7D rating", 12, true), column("growth", "7D ratings", 12, true)];
|
|
102
|
+
const APP_COMPANIES = [column("symbol", "Ticker", 12), column("name", "Company", 24, false, true), column("score", "Score / 100", 13, true), column("velocity", "Places / day", 14, true), column("apps", "Apps", 8, true), column("countries", "Countries", 11, true), column("drift", "7D rating", 12, true), column("growth", "7D ratings", 12, true)];
|
|
103
|
+
const appCompanyRow = (row: AppCompany): AttentionRow => ({ id: row.symbol, symbol: row.symbol, values: { symbol: row.symbol, name: row.name, score: row.attentionScore, velocity: row.rankVelocity7d, apps: row.appCount, countries: row.countryCount, drift: row.ratingChange7d, growth: row.ratingCountGrowth7d } });
|
|
104
|
+
export function appsModel(data: AppAttentionPayload, mix = "countries"): AttentionModel {
|
|
105
|
+
const evidence = data.evidence.map((row): AttentionRow => ({ id: `${row.revisionId}:${row.appId}`, url: row.sourceUrl, values: { name: row.name, country: row.country, chart: row.chart, rank: row.rank, observed: row.observedAt.slice(0, 10), confidence: row.confidence * 100 }, details: [{ label: "App", value: row.name }, { label: "App ID", value: row.appId }, { label: "Country / chart", value: `${row.country} / ${row.chart}` }, { label: "Rank", value: count(row.rank) }, { label: "Rating", value: row.rating?.toFixed(2) ?? "--" }, { label: "Ratings", value: count(row.ratingCount) }, { label: "Written reviews", value: count(row.reviewCount) }, { label: "Observed", value: row.observedAt }, { label: "Published", value: row.sourceUpdatedAt ?? "Unavailable" }, { label: "Revision", value: row.revisionId }, { label: "Supersedes", value: row.supersedesRevisionId ?? "Initial capture" }, { label: "Mapping confidence", value: percent(row.confidence) }, ...(row.mappingSourceUrl ? [{ label: "Ownership evidence", value: row.mappingSourceUrl }] : []), ...(row.ratingSourceUrl ? [{ label: "Rating evidence", value: row.ratingSourceUrl }] : [])] }));
|
|
106
|
+
return {
|
|
107
|
+
asOf: data.summary.lastObservedAt,
|
|
108
|
+
preview: data.access === "preview",
|
|
109
|
+
locked: Object.values(data.locked).reduce((a, b) => a + b, 0),
|
|
110
|
+
notices: [...data.coverage.limitations, ...(data.apps.some((row) => row.stale) ? ["Some app observations are stale."] : [])],
|
|
111
|
+
// Seven-day figures appear once a week of captures can give them.
|
|
112
|
+
figures: [
|
|
113
|
+
{ label: "Attention score", value: data.summary.attentionScore?.toFixed(1) ?? "--", detail: "/ 100" },
|
|
114
|
+
...(data.summary.rankVelocity7d != null ? [{ label: "Rank velocity", value: signed(data.summary.rankVelocity7d, 2), detail: "places / day" }]
|
|
115
|
+
: [{ label: "Apps", value: count(data.summary.appCount), detail: `${count(data.summary.countryCount)} countries` }]),
|
|
116
|
+
...(data.summary.ratingChange7d != null ? [{ label: "Rating drift", value: signed(data.summary.ratingChange7d, 2), detail: "7D / 5" }] : []),
|
|
117
|
+
...(data.summary.ratingCountGrowth7d != null ? [{ label: "Ratings added", value: signed(data.summary.ratingCountGrowth7d), detail: "7D" }] : []),
|
|
118
|
+
],
|
|
119
|
+
sections: {
|
|
120
|
+
table: { columns: APP_COLUMNS, rows: data.apps.map((row): AttentionRow => ({ id: `${row.sourceId}:${row.appId}`, app: { store: row.store, appId: row.appId, name: row.name, country: row.country, chart: row.chart }, symbol: row.symbol ?? undefined, url: row.appUrl, values: { name: row.name, symbol: row.symbol, country: row.country, chart: row.chart, rank: row.rank, change: row.rankChange7d, rating: row.rating, ratings: row.ratingCount, drift: row.ratingChange7d, growth: row.ratingCountGrowth7d }, details: [{ label: "App", value: row.name }, { label: "Developer", value: row.developer }, { label: "Parent ticker", value: row.symbol ?? "Unmapped" }, { label: "Genres", value: row.genres.join(", ") || "--" }, { label: "Observed", value: row.observedAt }, { label: "Ratings observed", value: row.ratingObservedAt ?? "Unavailable" }, { label: "Mapping confidence", value: percent(row.confidence) }, { label: "Mapping revision", value: row.mappingRevisionId ?? "Unmapped" }, { label: "Capture revision", value: row.revisionId }] })), empty: "No app observations match these filters yet." },
|
|
121
|
+
chart: { columns: [column("date", "Date", 12), column("score", "Score / 100", 14, true), column("change", "Change", 10, true), column("apps", "Apps", 9, true), column("countries", "Countries", 12, true), column("rating", "Rating / 5", 12, true), column("ratings", "Ratings", 13, true)], rows: data.history.map((point, i, all): AttentionRow => ({ id: point.date, date: point.date, values: { date: point.date, score: point.attentionScore, change: point.attentionScore != null && all[i - 1]?.attentionScore != null ? point.attentionScore - all[i - 1]!.attentionScore! : null, apps: point.appCount, countries: point.countryCount, rating: point.rating, ratings: point.ratingCount } })).reverse(), empty: data.access === "preview" ? "Full app history requires Pro." : "History is accumulating from the first capture." },
|
|
122
|
+
mix: mix === "spreads" ? { columns: [column("name", "App", 28, false, true), column("chart", "Chart", 10), column("best", "Best country", 15), column("bestRank", "Best rank", 11, true), column("worst", "Worst country", 15), column("worstRank", "Worst rank", 12, true), column("spread", "Rank spread", 13, true)], rows: data.spreads.map((row): AttentionRow => ({ id: `${row.appId}:${row.chart}:${row.category}`, values: { name: row.name, chart: row.chart, best: row.bestCountry, bestRank: row.bestRank, worst: row.worstCountry, worstRank: row.worstRank, spread: row.spread } })), empty: "Comparable country observations are accumulating." }
|
|
123
|
+
: { columns: [column("country", "Country", 16, false, true), column("apps", "Apps", 9, true), column("ranked", "Ranked apps", 13, true), column("score", "Score / 100", 14, true), column("velocity", "Places / day", 15, true), column("date", "Observed", 12)], rows: data.countries.map((row): AttentionRow => ({ id: row.country, values: { country: row.country, apps: row.appCount, ranked: row.rankedApps, score: row.attentionScore, velocity: row.rankVelocity7d, date: row.observedAt.slice(0, 10) } })), empty: "Country observations are not available yet." },
|
|
124
|
+
peers: { columns: APP_COMPANIES, rows: (data.symbol ? data.peers : data.companies).map(appCompanyRow), empty: "No comparable mapped companies yet." },
|
|
125
|
+
evidence: { columns: [column("name", "App", 27, false, true), column("country", "Country", 10), column("chart", "Chart", 10), column("rank", "Rank", 8, true), column("observed", "Observed", 12), column("confidence", "Confidence %", 14, true)], rows: evidence, empty: "No primary chart evidence is available yet." },
|
|
126
|
+
},
|
|
127
|
+
chart: { label: "Attention score", unit: "/100", points: data.history.map((point) => ({ date: point.date, value: point.attentionScore })) },
|
|
128
|
+
};
|
|
129
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { useCallback, useMemo, useRef } from "react";
|
|
2
|
+
import { isAccessDenied } from "../../../api-client/errors";
|
|
3
|
+
import { usePlanAccess } from "../../../api-client/plan-access";
|
|
4
|
+
import type { AppAttentionFilter, AppAttentionPayload, AppChart } from "../../../api-client/app-attention";
|
|
5
|
+
import type { HiringBoard, HiringPayload } from "../../../api-client/hiring";
|
|
6
|
+
import { ChartTableHeader, DataTableView, EmptyState, KeyValueRow, PageStackView, PaneFooterScope, PaneStatusBody, QueryBar, useChartTableSelection, usePagedRows, useTableLoadMore, usePaneNoticeFooter, usePaneStatusFooter, usePaneTabs, type PaneHint } from "../../../components";
|
|
7
|
+
import { usePaneRefreshKey } from "../../../components/data-table/table-pane";
|
|
8
|
+
import { useAsyncResource, useAutoRefresh, usePaneSettingValue, usePluginAppActions, usePluginPaneState } from "../../../public/react";
|
|
9
|
+
import { usePaneTickerIdentity } from "../../../state/hooks/pane-ticker";
|
|
10
|
+
import { useThemeColors } from "../../../theme/theme-context";
|
|
11
|
+
import type { PaneProps } from "../../../types/plugin";
|
|
12
|
+
import { Box, ScrollBox, Text, useRendererHost, useUiCapabilities, type ScrollBoxRenderable } from "../../../ui";
|
|
13
|
+
import { SignInWall } from "../cloud/auth-actions";
|
|
14
|
+
import { CLOUD_PLAN_KEY, useCloudUpgradeAction } from "../shared/cloud-upgrade";
|
|
15
|
+
import { Blurred, LockedOverlay, UpgradeLabel } from "../shared/locked-rows";
|
|
16
|
+
import { isCloudSessionRequired, useResearchCloudSession } from "../shared/research-cloud-session";
|
|
17
|
+
import { humanLabel, listingCell, missingCell, shareCell, signedColor, toneColor, withoutQuietColumns, type StateTone } from "../shared/research-cells";
|
|
18
|
+
import { AppRankView } from "./app-rank";
|
|
19
|
+
import { useAttentionEvidence } from "./evidence";
|
|
20
|
+
import { cachedAttention, fetchAttention, loadAttention, validateAttention, type AttentionPayload } from "./client";
|
|
21
|
+
import { appsModel, attentionSeries, count, hiringModel, sortAttentionRows, type AttentionKind, type AttentionRow, type AttentionTab } from "./model";
|
|
22
|
+
|
|
23
|
+
type Size = Pick<PaneProps, "width" | "height" | "focused">;
|
|
24
|
+
const rowId = (row: AttentionRow) => row.id;
|
|
25
|
+
const rowDate = (row: AttentionRow) => row.date ? new Date(row.date) : null;
|
|
26
|
+
/** Decimals per column: percentages and scores to one place, ratings and z-scores to two, counts whole. */
|
|
27
|
+
const DECIMALS: Record<string, number> = { score: 1, remote: 1, share: 1, confidence: 0, velocity: 2, rating: 2, drift: 2, z: 2, change: 1 };
|
|
28
|
+
const SIGNED_COLUMNS = new Set(["net", "change", "velocity", "drift", "growth"]);
|
|
29
|
+
const SIGNAL_TONES: Record<string, StateTone> = { Surge: "positive", Freeze: "negative", Stale: "warning", Collecting: "muted", Uncovered: "muted", Ok: "text" };
|
|
30
|
+
/** Columns left out while every row reads the same default: a first capture has no weekly change yet. */
|
|
31
|
+
const QUIET_COLUMNS: Record<string, readonly string[]> = { net: [""], z: [""], change: [""], velocity: [""], drift: [""], growth: [""], added: [""], removed: [""], coverage: ["complete"], spread: [""] };
|
|
32
|
+
const LABEL_COLUMNS = new Set(["chart"]);
|
|
33
|
+
function Evidence({ row, width, height }: Size & { row: AttentionRow }) {
|
|
34
|
+
const colors = useThemeColors();
|
|
35
|
+
const desktop = !!useUiCapabilities().nativePaneChrome;
|
|
36
|
+
return <ScrollBox width={width} height={desktop ? undefined : height} flexGrow={1} flexBasis={0} contentOptions={{ paddingX: 1 }}>
|
|
37
|
+
{(row.details ?? Object.entries(row.values).map(([label, value]) => ({ label, value: String(value ?? "--") }))).map((entry) => <KeyValueRow key={entry.label} label={entry.label} value={entry.value} labelWidth={22} />)}
|
|
38
|
+
{row.url ? <Box paddingY={1}><Text fg={colors.textMuted}>{row.url}</Text></Box> : null}
|
|
39
|
+
</ScrollBox>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function HiringPane(props: Size) {
|
|
43
|
+
const { symbol } = usePaneTickerIdentity();
|
|
44
|
+
return <AttentionView key={symbol ?? "board"} {...props} kind="hiring" symbol={symbol ?? undefined} />;
|
|
45
|
+
}
|
|
46
|
+
export function AppsPane(props: Size) {
|
|
47
|
+
const { symbol } = usePaneTickerIdentity();
|
|
48
|
+
const [appId] = usePaneSettingValue<string>("appId", "");
|
|
49
|
+
const [store] = usePaneSettingValue<string>("store", "app-store");
|
|
50
|
+
const [country] = usePaneSettingValue<string>("country", "");
|
|
51
|
+
const [chart] = usePaneSettingValue<string>("chart", "");
|
|
52
|
+
const [name] = usePaneSettingValue<string>("appRankName", appId);
|
|
53
|
+
const session = useResearchCloudSession();
|
|
54
|
+
const access = usePlanAccess();
|
|
55
|
+
const focus = useMemo(() => ({ appId, name, country: (country || (store === "google-play" ? "GLOBAL" : "US")).toUpperCase(), chart: (chart || (store === "google-play" ? "unranked" : "free")) as AppChart, store: store === "google-play" ? "google-play" as const : "app-store" as const }), [appId, name, country, chart, store]);
|
|
56
|
+
if (appId) return <AppRankView {...props} focus={focus} accessKey={`${session.requestKey}:${access.hasProAccess ? "full" : "preview"}`} />;
|
|
57
|
+
return <AttentionView key={symbol ?? "board"} {...props} kind="apps" symbol={symbol ?? undefined} />;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function AttentionView({ kind, symbol, width, height, focused }: Size & { kind: AttentionKind; symbol?: string }) {
|
|
61
|
+
const colors = useThemeColors();
|
|
62
|
+
const desktop = !!useUiCapabilities().nativePaneChrome;
|
|
63
|
+
const host = useRendererHost();
|
|
64
|
+
const { createPaneFromTemplate } = usePluginAppActions();
|
|
65
|
+
const session = useResearchCloudSession();
|
|
66
|
+
const access = usePlanAccess();
|
|
67
|
+
const accessKey = `${session.requestKey}:${access.hasProAccess ? "full" : "preview"}`;
|
|
68
|
+
const upgrade = useCloudUpgradeAction(kind === "hiring" ? "hire" : "apps");
|
|
69
|
+
const [openingTab] = usePaneSettingValue<string>("tab", "table");
|
|
70
|
+
const [savedTab, setTab] = usePluginPaneState<string>(`${kind}:${symbol ?? "board"}:tab`, openingTab);
|
|
71
|
+
const tab: AttentionTab = ["chart", "mix", "peers", "evidence"].includes(savedTab) && (kind === "apps" || symbol) ? savedTab as AttentionTab : "table";
|
|
72
|
+
const [openingCountry] = usePaneSettingValue<string>("country", "");
|
|
73
|
+
const [country, setCountry] = usePluginPaneState<string>(`${kind}:country`, openingCountry);
|
|
74
|
+
const [openingChart] = usePaneSettingValue<string>("chart", "");
|
|
75
|
+
const [chart, setChart] = usePluginPaneState<string>(`${kind}:chart`, openingChart);
|
|
76
|
+
const [openingDays] = usePaneSettingValue<string>("days", "90");
|
|
77
|
+
const [days, setDays] = usePluginPaneState<string>(`${kind}:days`, String(openingDays));
|
|
78
|
+
const [mix, setMix] = usePluginPaneState<string>(`${kind}:mix`, kind === "hiring" ? "functions" : "countries");
|
|
79
|
+
const filters = useMemo<AppAttentionFilter>(() => kind === "apps" ? { ...(country ? { country } : {}), ...(chart ? { chart: chart as AppChart } : {}), days: Number(days) || 90 } : {}, [kind, country, chart, days]);
|
|
80
|
+
const [snapshotSetting] = usePaneSettingValue<AttentionPayload | null>("attentionSnapshot", null);
|
|
81
|
+
const snapshot = useMemo(() => { try { return snapshotSetting ? validateAttention(kind, snapshotSetting) : null; } catch { return null; } }, [kind, snapshotSetting]);
|
|
82
|
+
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]);
|
|
83
|
+
const resource = useAsyncResource(loader, { initialData: () => cachedAttention(kind, symbol, accessKey, filters), clearOnError: isAccessDenied });
|
|
84
|
+
const data = resource.data?.payload;
|
|
85
|
+
const model = useMemo(() => data ? kind === "hiring" ? hiringModel(data as HiringPayload | HiringBoard, mix) : appsModel(data as AppAttentionPayload, mix) : null, [data, kind, mix]);
|
|
86
|
+
const [selectedId, setSelected] = usePluginPaneState<string | null>(`${kind}:${symbol ?? "board"}:${tab}:selected`, null);
|
|
87
|
+
const [openCompany, setOpenCompany] = usePluginPaneState<string | null>(`${kind}:${symbol ?? "board"}:company`, null);
|
|
88
|
+
const [openAppId, setOpenApp] = usePluginPaneState<string | null>(`${kind}:${symbol ?? "board"}:app`, null);
|
|
89
|
+
const [openEvidence, setOpenEvidence] = usePluginPaneState<string | null>(`${kind}:${symbol ?? "board"}:evidence`, null);
|
|
90
|
+
const [sort, setSort] = usePluginPaneState<{ column: string; direction: "asc" | "desc" }>(`${kind}:${symbol ?? "board"}:${tab}:sort`, { column: tab === "chart" ? "date" : tab === "mix" ? "count" : kind === "hiring" ? symbol ? "title" : "open" : "rank", direction: tab === "chart" || tab === "mix" || (kind === "hiring" && !symbol) ? "desc" : "asc" });
|
|
91
|
+
const section = model?.sections[tab];
|
|
92
|
+
const paginated = kind === "apps" && ["table", "evidence", "peers"].includes(tab) || kind === "hiring" && !symbol;
|
|
93
|
+
const pageLoader = useCallback(async ({ offset, signal }: { offset: number; signal: AbortSignal; force: boolean }) => {
|
|
94
|
+
const payload = offset === 0 && data ? data : await fetchAttention(kind, symbol, { ...filters, offset, limit: 100 }, undefined, signal);
|
|
95
|
+
const projected = kind === "hiring" ? hiringModel(payload as HiringPayload | HiringBoard, mix) : appsModel(payload as AppAttentionPayload, mix);
|
|
96
|
+
const nextOffset = "companies" in payload && kind === "hiring" && "total" in payload
|
|
97
|
+
? !payload.preview && offset + payload.companies.length < payload.total ? offset + payload.companies.length : null
|
|
98
|
+
: (payload as AppAttentionPayload).page?.nextOffset ?? null;
|
|
99
|
+
return { rows: projected.sections[tab].rows, hasMore: !snapshot && nextOffset !== null, nextOffset };
|
|
100
|
+
}, [data, kind, symbol, filters, mix, tab, snapshot]);
|
|
101
|
+
const paged = usePagedRows(data && paginated ? pageLoader : null, { getId: rowId });
|
|
102
|
+
const sectionRows = paginated && paged.status !== "idle" ? paged.rows : section?.rows ?? [];
|
|
103
|
+
const scrollRef = useRef<ScrollBoxRenderable | null>(null);
|
|
104
|
+
const scrollMore = useTableLoadMore(scrollRef, paged.hasMore && !openCompany && !openEvidence, paged.loadMore);
|
|
105
|
+
const lockedCount = !data || !model?.preview ? 0 : typeof data.locked === "number" ? data.locked
|
|
106
|
+
: kind === "hiring" ? (data as HiringPayload).locked[tab === "table" || tab === "evidence" ? "evidence" : tab === "chart" ? "history" : tab === "peers" ? "peers" : mix === "signals" ? "signals" : "mix"]
|
|
107
|
+
: (data as AppAttentionPayload).locked[tab === "table" ? "apps" : tab === "chart" ? "history" : tab === "mix" ? mix === "spreads" ? "spreads" : "countries" : tab === "peers" ? symbol ? "peers" : "companies" : "evidence"];
|
|
108
|
+
const lockedRows = Math.min(3, lockedCount);
|
|
109
|
+
const rows = useMemo(() => [...sortAttentionRows(sectionRows, sort.column, sort.direction), ...Array.from({ length: lockedRows }, (_, index): AttentionRow => ({ id: `locked:${index}`, values: {} }))], [sectionRows, sort, lockedRows]);
|
|
110
|
+
useAttentionEvidence(kind, data ?? null, tab, rows.filter((row) => !row.id.startsWith("locked:")).map(rowId));
|
|
111
|
+
const dataRows = useMemo(() => rows.filter((row) => !row.id.startsWith("locked:")), [rows]);
|
|
112
|
+
// A company's own apps all carry its ticker; the column only earns its room on the board.
|
|
113
|
+
const columns = useMemo(() => !section ? [] : withoutQuietColumns(section.columns.filter((column) => !(column.id === "symbol" && kind === "apps" && symbol && tab !== "peers")),
|
|
114
|
+
dataRows, (row, id) => row.values[id] ?? "", QUIET_COLUMNS), [section, dataRows, kind, symbol, tab]);
|
|
115
|
+
const maxShare = Math.max(0, ...dataRows.map((row) => typeof row.values.share === "number" ? row.values.share : 0));
|
|
116
|
+
const selected = rows.find((row) => row.id === selectedId) ?? rows[0] ?? null;
|
|
117
|
+
const evidence = rows.find((row) => row.id === openEvidence) ?? null;
|
|
118
|
+
const app = rows.find((row) => row.id === openAppId)?.app ?? null;
|
|
119
|
+
const detailOpen = !!openCompany || !!evidence || !!app;
|
|
120
|
+
const tabs = [{ value: "table", label: "Table" }, ...(kind === "apps" || symbol ? [{ value: "chart", label: "Chart" }, { value: "mix", label: kind === "hiring" ? "Mix" : "Markets" }, { value: "peers", label: kind === "apps" && !symbol ? "Companies" : "Peers" }, { value: "evidence", label: "Evidence" }] : [])];
|
|
121
|
+
const { strip, rows: tabRows } = usePaneTabs(model && !openCompany && !app ? { tabs, activeValue: tab, onSelect: (value) => { setTab(value); setOpenEvidence(null); }, focused, dense: true } : null);
|
|
122
|
+
const bodyHeight = Math.max(3, height - tabRows);
|
|
123
|
+
const series = useMemo(() => model ? attentionSeries(model, colors.textBright) : [], [model, colors.textBright]);
|
|
124
|
+
const chartSelection = useChartTableSelection({ rows, getId: rowId, getDate: rowDate, selectedId: selected?.id ?? null, onSelect: setSelected, focused: focused && !detailOpen, enabled: tab === "chart" });
|
|
125
|
+
useAutoRefresh(resource.updatedAt, resource.load);
|
|
126
|
+
usePaneRefreshKey(() => { void resource.reload(); paged.reload(); }, { focused: focused && !detailOpen });
|
|
127
|
+
const selectedSymbol = selected?.symbol ?? symbol;
|
|
128
|
+
const source = evidence?.url ?? selected?.url;
|
|
129
|
+
const hints: PaneHint[] = [
|
|
130
|
+
...(source ? [{ id: "source", key: "o", label: "pen source", onPress: () => void host.openExternal(source) }] : []),
|
|
131
|
+
...(selected && !evidence && selected.details ? [{ id: "evidence", key: "e", label: "vidence", onPress: () => setOpenEvidence(selected.id) }] : []),
|
|
132
|
+
...(selected?.app && selectedSymbol && !symbol ? [{ id: "company", key: "c", label: "ompany", onPress: () => setOpenCompany(selectedSymbol) }] : []),
|
|
133
|
+
...(selectedSymbol ? [
|
|
134
|
+
{ id: "description", key: "d", label: "es", onPress: () => createPaneFromTemplate("new-ticker-detail-pane", { symbol: selectedSymbol }) },
|
|
135
|
+
{ id: "financials", key: "f", label: "a", onPress: () => createPaneFromTemplate("financial-analysis-pane", { symbol: selectedSymbol }) },
|
|
136
|
+
{ id: "chart", key: "g", label: "raph", onPress: () => createPaneFromTemplate("chart-composer-pane", { arg: selectedSymbol }) },
|
|
137
|
+
{ id: "related", key: "a", label: kind === "hiring" ? "pps" : " hiring", title: kind === "hiring" ? "App attention" : "Hiring momentum", onPress: () => createPaneFromTemplate(kind === "hiring" ? "apps-pane" : "hiring-pane", { symbol: selectedSymbol }) },
|
|
138
|
+
] : []),
|
|
139
|
+
...(model?.preview ? [{ id: "upgrade", key: CLOUD_PLAN_KEY, label: "upgrade", title: "Upgrade to Pro", onPress: upgrade }] : []),
|
|
140
|
+
];
|
|
141
|
+
usePaneStatusFooter({ registrationId: `${kind}:status`, enabled: !openCompany && !app, loading: resource.loading || paged.loadingMore, error: data ? resource.error ?? paged.moreError?.message ?? paged.error?.message : null, stale: resource.data?.stale,
|
|
142
|
+
info: [...(model?.asOf ? [{ id: "observed", parts: [{ text: `as of ${model.asOf.slice(0, 10)}`, tone: "muted" as const }] }] : []), ...(model?.preview ? [{ id: "preview", parts: [{ text: "Pro preview", tone: "warning" as const }] }] : [])], hints });
|
|
143
|
+
usePaneNoticeFooter({ registrationId: `${kind}:notices`, enabled: !openCompany && !app, focused, notices: [...(model?.notices ?? []), ...(resource.data?.refreshError ? [resource.data.refreshError] : [])] });
|
|
144
|
+
if (!data && isCloudSessionRequired(resource.error)) return <SignInWall placement={kind === "hiring" ? "hiring-momentum-signin" : "app-attention-signin"} action={kind === "hiring" ? "view hiring momentum" : "view app attention"} needsVerification={session.needsVerification} />;
|
|
145
|
+
const countryOptions = data && kind === "apps" ? (data as AppAttentionPayload).coverage.configuredCountries : [];
|
|
146
|
+
const query = kind === "hiring" && tab !== "mix" ? undefined : <QueryBar width={width} filters={kind === "apps" ? [
|
|
147
|
+
{ id: "country", label: "Country", value: country, defaultValue: "", options: [{ value: "", label: "All countries" }, ...countryOptions.map((value) => ({ value, label: value }))], onChange: setCountry },
|
|
148
|
+
{ id: "chart", label: "Chart", value: chart, defaultValue: "", options: [{ value: "", label: "All charts" }, { value: "free", label: "Free" }, { value: "paid", label: "Paid" }, { value: "unranked", label: "Unranked" }], onChange: setChart },
|
|
149
|
+
...(tab === "chart" ? [{ id: "days", label: "Window", value: days, options: ["30", "90", "365", "730"].map((value) => ({ value, label: `${value}D` })), onChange: setDays }] : []),
|
|
150
|
+
...(tab === "mix" ? [{ id: "mix", label: "View", value: mix, options: [{ value: "countries", label: "Countries" }, { value: "spreads", label: "Rank spreads" }], onChange: setMix }] : []),
|
|
151
|
+
] : tab === "mix" ? [{ id: "mix", label: "Group", value: mix, options: [{ value: "functions", label: "Role family" }, { value: "seniority", label: "Seniority" }, { value: "countries", label: "Countries" }, { value: "locations", label: "Locations" }, { value: "signals", label: "Signals" }], onChange: setMix }] : []} />;
|
|
152
|
+
return <Box width={width} height={height} flexDirection="column">
|
|
153
|
+
{strip}
|
|
154
|
+
<PaneStatusBody loading={!data && resource.loading} error={!data ? resource.error : null} subject={kind === "hiring" ? "hiring momentum" : "app attention"}>
|
|
155
|
+
{model && section ? <PageStackView focused={focused} detailOpen={detailOpen} onBack={() => { setOpenCompany(null); setOpenEvidence(null); setOpenApp(null); }} detailTitle={openCompany ?? app?.name ?? (evidence ? String(evidence.values.title ?? evidence.values.name ?? evidence.id) : undefined)}
|
|
156
|
+
detailContent={openCompany ? <PaneFooterScope active><AttentionView key={openCompany} kind={kind} symbol={openCompany} width={width} height={bodyHeight - 1} focused={focused} /></PaneFooterScope> : app ? <AppRankView focus={app} accessKey={accessKey} width={width} height={bodyHeight - 1} focused={focused} /> : evidence ? <Evidence row={evidence} width={width} height={bodyHeight - 1} focused={focused} /> : null}
|
|
157
|
+
rootContent={<DataTableView columns={columns} items={rows} rootWidth={width} rootHeight={bodyHeight} focused={focused && !detailOpen} scrollRef={scrollRef} onBodyScrollActivity={scrollMore}
|
|
158
|
+
rootBefore={<ChartTableHeader width={width} height={bodyHeight} tableRows={rows.length} tableColumns={columns} query={query} figures={model.figures}
|
|
159
|
+
chart={tab === "chart" ? { series, ...chartSelection, formatValue: (value) => kind === "hiring" ? count(value) : value.toFixed(1), empty: series.length ? undefined : model.preview ? "Full history requires Pro." : "Collecting history", remoteKind: `${kind}-history` } : null} />}
|
|
160
|
+
selection={{ kind: "id", selectedId: selected?.id ?? null, getId: rowId, onChange: setSelected }} getItemKey={rowId}
|
|
161
|
+
sortColumnId={sort.column} sortDirection={sort.direction} onHeaderClick={(column) => setSort((old) => ({ column, direction: old.column === column && old.direction === "desc" ? "asc" : "desc" }))}
|
|
162
|
+
onActivate={(row) => row.id.startsWith("locked:") ? void upgrade() : row.app ? setOpenApp(row.id) : row.symbol && row.symbol !== symbol && (tab === "peers" || !symbol) ? setOpenCompany(row.symbol) : row.details ? setOpenEvidence(row.id) : row.url ? void host.openExternal(row.url) : undefined}
|
|
163
|
+
renderCell={(row, column, _index, state) => {
|
|
164
|
+
if (row.id.startsWith("locked:")) {
|
|
165
|
+
if (!desktop && column.id === columns[0]?.id && row.id === "locked:0") return { text: "Upgrade for all observations", content: <UpgradeLabel text="Upgrade for all observations" onPress={upgrade} role="attention-upgrade" /> };
|
|
166
|
+
return desktop ? { text: "", content: <Blurred><Text fg={colors.textDim}>Hidden</Text></Blurred> } : { text: "░░░░", color: colors.textDim };
|
|
167
|
+
}
|
|
168
|
+
const value = row.values[column.id];
|
|
169
|
+
if (value == null || value === "") return missingCell(colors);
|
|
170
|
+
if (column.id === "symbol" && typeof value === "string") return listingCell(value, colors, state.selected);
|
|
171
|
+
if (column.id === "signal" && typeof value === "string") return { text: value, color: toneColor(SIGNAL_TONES[value] ?? "text", colors), keepColorWhenSelected: value === "Surge" || value === "Freeze" };
|
|
172
|
+
const numeric = typeof value === "number";
|
|
173
|
+
const decimals = DECIMALS[column.id] ?? 0;
|
|
174
|
+
const text = numeric ? `${SIGNED_COLUMNS.has(column.id) && value > 0 ? "+" : ""}${value.toLocaleString("en-US", { minimumFractionDigits: decimals, maximumFractionDigits: decimals })}` : LABEL_COLUMNS.has(column.id) ? humanLabel(value) : column.id === "title" ? value.trim() : value;
|
|
175
|
+
if (column.id === "share" && numeric && tab === "mix") return { ...shareCell(text, maxShare ? value / maxShare : null, column.width, colors, state.selected, desktop), value };
|
|
176
|
+
return { text, value, color: numeric && SIGNED_COLUMNS.has(column.id) ? signedColor(value, colors)
|
|
177
|
+
: column.id === "name" || column.id === "title" ? colors.textBright : column.id === "date" || column.id === "observed" || column.id === "confidence" ? colors.textDim : colors.text };
|
|
178
|
+
}} selectedTextOverridesCellColor showHorizontalScrollbar resetScrollKey={`${kind}:${symbol}:${tab}:${mix}:${country}:${chart}`}
|
|
179
|
+
emptyStateTitle={section.empty} bodyAfter={lockedRows && desktop ? <LockedOverlay rows={lockedRows} text="Upgrade for all observations" onPress={upgrade} role="attention-upgrade" /> : undefined} />}
|
|
180
|
+
/> : <EmptyState title="Observations are not available yet." />}
|
|
181
|
+
</PaneStatusBody>
|
|
182
|
+
</Box>;
|
|
183
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { apiClient } from "../../../api-client";
|
|
2
|
+
import type { GuidancePayload, KpiEvidence, KpiGuidance, KpiObservation, KpiPeriod, KpiQueryOptions, KpisPayload } from "../../../api-client/company-kpis";
|
|
3
|
+
import { createPluginCache } from "../../../data/plugin-cache";
|
|
4
|
+
import { cachedCloudResource, loadCloudResource, unavailableOnServer } from "../shared/cloud-resource";
|
|
5
|
+
|
|
6
|
+
export type CompanyDataset = KpisPayload | GuidancePayload;
|
|
7
|
+
export type CompanyMode = "kpis" | "guidance";
|
|
8
|
+
export const KPI_UNAVAILABLE = "Company disclosures are not available yet.";
|
|
9
|
+
export const companyKpisCache = createPluginCache<CompanyDataset>({ kind: "company-kpis", source: "gloom-cloud", schemaVersion: 1,
|
|
10
|
+
policy: { staleMs: 60 * 60_000, expireMs: 30 * 86_400_000 } });
|
|
11
|
+
const finite = (value: unknown): value is number => typeof value === "number" && Number.isFinite(value);
|
|
12
|
+
const text = (value: unknown): value is string => typeof value === "string";
|
|
13
|
+
const nullableText = (value: unknown) => value === null || text(value);
|
|
14
|
+
const date = (value: unknown) => text(value) && /^\d{4}-\d{2}-\d{2}$/.test(value) && Number.isFinite(Date.parse(value)) && new Date(value).toISOString().slice(0, 10) === value;
|
|
15
|
+
const instant = (value: unknown) => text(value) && Number.isFinite(Date.parse(value));
|
|
16
|
+
const count = (value: unknown) => Number.isInteger(value) && Number(value) >= 0;
|
|
17
|
+
const confidence = (value: unknown) => finite(value) && value >= 0 && value <= 1;
|
|
18
|
+
const nullableNumber = (value: unknown) => value === null || finite(value);
|
|
19
|
+
const units = ["currency", "currency_per_share", "currency_per_unit", "percent", "basis_points", "count", "ratio", "volume"];
|
|
20
|
+
const bases = ["reported", "adjusted", "constant_currency", "organic"];
|
|
21
|
+
const sourceKinds = ["press_release", "filing", "transcript", "opendart", "ir", "esef", "edinet"];
|
|
22
|
+
const record = (value: unknown): value is Record<string, unknown> => !!value && typeof value === "object" && !Array.isArray(value);
|
|
23
|
+
function validPeriod(period: KpiPeriod): boolean {
|
|
24
|
+
return !!period && (period.end === null || date(period.end)) && (period.start === null || date(period.start) && (period.end === null || period.start <= period.end))
|
|
25
|
+
&& ["quarter", "half", "year", "ytd", "instant", "other"].includes(period.kind) && text(period.label)
|
|
26
|
+
&& (period.fiscalYear === null || Number.isInteger(period.fiscalYear))
|
|
27
|
+
&& (period.fiscalQuarter === null || [1, 2, 3, 4].includes(period.fiscalQuarter));
|
|
28
|
+
}
|
|
29
|
+
function validEvidence(evidence: KpiEvidence): boolean {
|
|
30
|
+
let safeUrl = false;
|
|
31
|
+
try { safeUrl = ["https:", "http:"].includes(new URL(evidence.url).protocol); } catch { /* Invalid evidence cannot be opened. */ }
|
|
32
|
+
return !!evidence && text(evidence.id) && text(evidence.documentId) && text(evidence.sourceId)
|
|
33
|
+
&& sourceKinds.includes(evidence.sourceKind) && safeUrl && text(evidence.title) && instant(evidence.publishedAt)
|
|
34
|
+
&& text(evidence.country) && text(evidence.language) && text(evidence.quote) && evidence.quote.trim().length > 0
|
|
35
|
+
&& count(evidence.quoteOffset) && Number.isInteger(evidence.quoteSourceLength) && evidence.quoteSourceLength > 0
|
|
36
|
+
&& ["exact", "whitespace", "nfkc_whitespace"].includes(evidence.quoteMatchMode) && confidence(evidence.confidence);
|
|
37
|
+
}
|
|
38
|
+
function validRow(row: KpiObservation | KpiGuidance): boolean {
|
|
39
|
+
return !!row && text(row.id) && text(row.symbol) && text(row.metricId) && !!row.metric && row.metric.id === row.metricId
|
|
40
|
+
&& text(row.metric.name) && text(row.metric.definition) && units.includes(row.unit) && bases.includes(row.basis)
|
|
41
|
+
&& nullableText(row.currency) && (!row.unit.startsWith("currency") || !!row.currency)
|
|
42
|
+
&& record(row.dimensions) && Object.values(row.dimensions).every(text) && validPeriod(row.period)
|
|
43
|
+
&& instant(row.asOf) && text(row.seriesKey) && confidence(row.confidence) && count(row.revision)
|
|
44
|
+
&& nullableText(row.supersedesId) && typeof row.current === "boolean" && Array.isArray(row.evidence)
|
|
45
|
+
&& row.evidence.length > 0 && row.evidence.every(validEvidence);
|
|
46
|
+
}
|
|
47
|
+
function validObservation(row: KpiObservation) { return validRow(row) && finite(row.value) && typeof row.conflict === "boolean" && (row.contested === undefined || typeof row.contested === "boolean")
|
|
48
|
+
&& (row.valueQualifier === undefined || ["exact", "approximately", "at_least", "at_most", "greater_than", "less_than"].includes(row.valueQualifier)); }
|
|
49
|
+
function validGuidance(row: KpiGuidance) {
|
|
50
|
+
return validRow(row) && date(row.issuedDate) && [row.low, row.high, row.point, row.midpointChangePct].every(nullableNumber)
|
|
51
|
+
&& (row.low === null || row.high === null || row.low <= row.high)
|
|
52
|
+
&& ["exact", "approximately", "at_least", "at_most", "greater_than", "less_than", "qualitative", "withdrawn"].includes(row.hedge)
|
|
53
|
+
&& ["active", "withdrawn"].includes(row.status) && text(row.rangeText) && nullableText(row.previousId)
|
|
54
|
+
&& (row.conditions === undefined || nullableText(row.conditions))
|
|
55
|
+
&& ["raised", "cut", "reiterated", "mixed", "initiated", "withdrawn", "not_comparable"].includes(row.direction)
|
|
56
|
+
&& (row.actual === null || text(row.actual.observationId) && finite(row.actual.value)
|
|
57
|
+
&& ["above", "below", "within"].includes(row.actual.outcome) && ["beat", "miss", "in_line", "neutral"].includes(row.actual.favorable)
|
|
58
|
+
&& finite(row.actual.difference) && nullableNumber(row.actual.differencePct));
|
|
59
|
+
}
|
|
60
|
+
export function validateCompanyData<T extends CompanyDataset>(data: T): T {
|
|
61
|
+
const coverage = data?.coverage;
|
|
62
|
+
const common = !!data && text(data.symbol) && ["available", "unavailable"].includes(data.status)
|
|
63
|
+
&& (data.asOf === null || instant(data.asOf)) && ["full", "preview"].includes(data.access)
|
|
64
|
+
&& count(data.lockedRows) && count(data.totalRows) && typeof data.truncated === "boolean"
|
|
65
|
+
&& (data.previewRows === null || count(data.previewRows)) && text(data.methodology)
|
|
66
|
+
&& !!coverage && [coverage.documents, coverage.observations, coverage.guidance, coverage.conflicts].every(count)
|
|
67
|
+
&& Array.isArray(coverage.languages) && coverage.languages.every(text) && Array.isArray(coverage.sourceKinds)
|
|
68
|
+
&& coverage.sourceKinds.every((kind) => sourceKinds.includes(kind)) && Array.isArray(data.dictionary);
|
|
69
|
+
const valid = common && ("series" in data
|
|
70
|
+
? Array.isArray(data.series) && data.series.every((series) => !!series && text(series.key) && validObservation(series.latest) && series.latest.seriesKey === series.key
|
|
71
|
+
&& Array.isArray(series.observations) && series.observations.every((row) => validObservation(row) && row.seriesKey === series.key))
|
|
72
|
+
&& Array.isArray(data.revisions) && data.revisions.every(validObservation)
|
|
73
|
+
: Array.isArray(data.guidance) && data.guidance.every(validGuidance) && Array.isArray(data.history) && data.history.every(validGuidance));
|
|
74
|
+
if (!valid) throw new Error("The server returned unreadable company disclosures");
|
|
75
|
+
return data;
|
|
76
|
+
}
|
|
77
|
+
type CompanyClient = Pick<typeof apiClient, "getCloudCompanyKpis" | "getCloudCompanyGuidance">;
|
|
78
|
+
export async function fetchCompanyData(mode: CompanyMode, symbol: string, options: KpiQueryOptions = {}, client: CompanyClient = apiClient) {
|
|
79
|
+
try {
|
|
80
|
+
const data = validateCompanyData(await (mode === "kpis" ? client.getCloudCompanyKpis(symbol, options) : client.getCloudCompanyGuidance(symbol, options)));
|
|
81
|
+
if (("series" in data) !== (mode === "kpis") || data.symbol.toUpperCase() !== symbol.trim().toUpperCase()) throw new Error("Company disclosures do not match the requested company");
|
|
82
|
+
return data;
|
|
83
|
+
}
|
|
84
|
+
catch (error) { throw unavailableOnServer(error, KPI_UNAVAILABLE, [404, 503]); }
|
|
85
|
+
}
|
|
86
|
+
export const companyQuery = (options: Record<string, unknown>): KpiQueryOptions => Object.fromEntries(["metric", "basis", "from", "to", "asOf"]
|
|
87
|
+
.flatMap((key) => typeof options[key] === "string" && options[key] !== "all" ? [[key, options[key]]] : [])) as KpiQueryOptions;
|
|
88
|
+
// Partition paid history by both identity and entitlement, including screenshot snapshots.
|
|
89
|
+
const cacheKey = (mode: CompanyMode, symbol: string, access: string) => `${mode}:${symbol.toUpperCase()}:${access}`;
|
|
90
|
+
export const cachedCompanyData = (mode: CompanyMode, symbol: string, access: string) => cachedCloudResource(companyKpisCache, cacheKey(mode, symbol, access), validateCompanyData);
|
|
91
|
+
export const loadCompanyData = (mode: CompanyMode, symbol: string, access: string, force = false) => loadCloudResource(companyKpisCache,
|
|
92
|
+
cacheKey(mode, symbol, access), () => fetchCompanyData(mode, symbol), { force, validate: validateCompanyData });
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { HeadlessPaneDefinition } from "../../../types/plugin";
|
|
2
|
+
import { companyQuery, fetchCompanyData, type CompanyMode } from "./client";
|
|
3
|
+
|
|
4
|
+
export function companyHeadless(mode: CompanyMode): HeadlessPaneDefinition<"bundle"> {
|
|
5
|
+
return {
|
|
6
|
+
shape: "bundle",
|
|
7
|
+
argument: { kind: "ticker", description: "Listed company ticker, including exchange-qualified global symbols.", placeholder: "ticker" },
|
|
8
|
+
discovery: { aliases: [mode === "kpis" ? "KPIS" : "GUIDE"], dataRequirements: ["Gloom Cloud company disclosures"],
|
|
9
|
+
limitations: ["Pro dataset with a fixed latest preview on Free", "Coverage follows available company disclosures; missing data is not zero", "Compare matching definition, period, currency, basis and scope", "Fiscal labels without explicit calendar dates are retained without inferred dates"] },
|
|
10
|
+
options: [
|
|
11
|
+
{ key: "tab", type: "enum", settingKey: "tab", defaultValue: "table", values: [{ value: "table" }, { value: "chart" }, ...(mode === "guidance" ? [{ value: "history" }] : []), { value: "evidence" }], description: "The pane section to render." },
|
|
12
|
+
{ key: "metric", type: "string", settingKey: "metric", description: "Canonical metric ID, such as arr or subscribers." },
|
|
13
|
+
{ key: "basis", type: "enum", values: [{ value: "reported" }, { value: "adjusted" }, { value: "constant_currency" }, { value: "organic" }], description: "Accounting basis filter (Pro)." },
|
|
14
|
+
{ key: "from", type: "string", description: "First calendar period end, YYYY-MM-DD (Pro)." },
|
|
15
|
+
{ key: "to", type: "string", description: "Last calendar period end, YYYY-MM-DD (Pro)." },
|
|
16
|
+
{ key: "asOf", type: "string", description: "Point-in-time publication cutoff, YYYY-MM-DD (Pro)." },
|
|
17
|
+
],
|
|
18
|
+
describe: (args) => `${mode === "kpis" ? "Company KPIs" : "Company guidance"} | ${args.symbols[0]}`,
|
|
19
|
+
async load(args, ctx) {
|
|
20
|
+
const options = companyQuery(args.options);
|
|
21
|
+
const data = await fetchCompanyData(mode, args.symbols[0]!, options, ctx.apiClient);
|
|
22
|
+
return { complete: !data.truncated, errors: data.truncated ? [data.access === "preview" ? "Full disclosures and history require Gloom Pro" : "Response limit reached; narrow the metric or date range"] : [],
|
|
23
|
+
sections: "series" in data ? [
|
|
24
|
+
{ title: "Latest KPIs", rows: data.series.map((series) => ({ ...series.latest })) },
|
|
25
|
+
{ title: "KPI history", rows: data.series.flatMap((series) => series.observations.map((row) => ({ ...row }))) },
|
|
26
|
+
{ title: "Revisions and conflicts", rows: data.revisions.map((row) => ({ ...row })) },
|
|
27
|
+
] : [{ title: "Current guidance", rows: data.guidance.map((row) => ({ ...row })) }, { title: "Guidance history", rows: data.history.map((row) => ({ ...row })) }],
|
|
28
|
+
metadata: { symbol: data.symbol, asOf: data.asOf, access: data.access, coverage: data.coverage, dictionary: data.dictionary,
|
|
29
|
+
lockedRows: data.lockedRows, totalRows: data.totalRows, methodology: data.methodology } };
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { PluginModule } from "../plugin-module";
|
|
2
|
+
import { createTickerSurfacePaneTemplate } from "../shared/ticker-surface";
|
|
3
|
+
import { companyKpisCache } from "./client";
|
|
4
|
+
import { companyHeadless } from "./headless";
|
|
5
|
+
import { CompanyGuidancePane, CompanyKpisPane } from "./pane";
|
|
6
|
+
|
|
7
|
+
const kpisHeadless = companyHeadless("kpis");
|
|
8
|
+
const guidanceHeadless = companyHeadless("guidance");
|
|
9
|
+
export const companyKpisModule: PluginModule = {
|
|
10
|
+
setup(ctx) { companyKpisCache.attach(ctx.persistence); },
|
|
11
|
+
dispose() { companyKpisCache.reset(); },
|
|
12
|
+
panes: [
|
|
13
|
+
{ id: "company-kpis", name: "Company KPIs", icon: "K", component: CompanyKpisPane, defaultPosition: "right", defaultMode: "floating",
|
|
14
|
+
defaultFloatingSize: { width: 130, height: 30 }, tickerFollower: true, tableExport: true, headless: kpisHeadless },
|
|
15
|
+
{ id: "company-guidance", name: "Company Guidance", icon: "G", component: CompanyGuidancePane, defaultPosition: "right", defaultMode: "floating",
|
|
16
|
+
defaultFloatingSize: { width: 130, height: 30 }, tickerFollower: true, tableExport: true, headless: guidanceHeadless },
|
|
17
|
+
],
|
|
18
|
+
paneTemplates: [
|
|
19
|
+
{ ...createTickerSurfacePaneTemplate({ id: "company-kpis-pane", paneId: "company-kpis", label: "Company KPIs", shortcut: "KPIS",
|
|
20
|
+
description: "Pro company operating KPIs, comparable history, revisions and cited evidence. Free preview.",
|
|
21
|
+
keywords: ["kpis", "operating metrics", "arr", "subscribers", "backlog", "occupancy"] }), headless: kpisHeadless },
|
|
22
|
+
{ ...createTickerSurfacePaneTemplate({ id: "company-guidance-pane", paneId: "company-guidance", label: "Company Guidance", shortcut: "GUIDE",
|
|
23
|
+
description: "Pro management guidance ranges, raises and cuts, actual versus guide and cited evidence. Free preview.",
|
|
24
|
+
keywords: ["guidance", "outlook", "raise", "cut", "beat", "miss"] }), headless: guidanceHeadless },
|
|
25
|
+
],
|
|
26
|
+
};
|