gloomberb 0.9.7 → 0.9.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/api-client/auth.ts +20 -0
- package/src/api-client/index.test.ts +3 -3
- package/src/api-client/index.ts +48 -6
- package/src/api-client/request.test.ts +63 -0
- package/src/api-client/request.ts +53 -5
- package/src/api-client/types.ts +45 -2
- package/src/app/pane-runtime/layout-placement.ts +0 -1
- package/src/app/runtime/desktop-deeplink.test.ts +87 -0
- package/src/app/runtime/desktop-deeplink.ts +496 -0
- package/src/app.tsx +34 -0
- package/src/brokers/account-cache.ts +2 -10
- package/src/brokers/broker-portfolio-sync.ts +0 -1
- package/src/brokers/broker-ticker-sync.ts +20 -16
- package/src/brokers/remote-broker-adapter.ts +3 -0
- package/src/capabilities/registry.test.ts +25 -0
- package/src/capabilities/registry.ts +22 -5
- package/src/cli/commands/automation.ts +1 -3
- package/src/cli/commands/command-utils.ts +0 -7
- package/src/cli/commands/overview.ts +2 -2
- package/src/cli/commands/plugins.ts +34 -17
- package/src/cli/index.ts +2 -2
- package/src/cli.test.ts +0 -157
- package/src/components/chart/bar-chart-renderer.test.ts +87 -12
- package/src/components/chart/bar-chart-renderer.ts +111 -55
- package/src/components/chart/comparison/renderer.ts +0 -1
- package/src/components/chart/comparison-stock-chart/render-output.ts +3 -8
- package/src/components/chart/comparison-stock-chart/view.tsx +0 -2
- package/src/components/chart/core/index.test.ts +0 -24
- package/src/components/chart/core/renderer.ts +0 -4
- package/src/components/chart/core/resolution.test.ts +0 -17
- package/src/components/chart/core/resolution.ts +0 -26
- package/src/components/chart/core/scroll.test.ts +0 -6
- package/src/components/chart/core/scroll.ts +0 -22
- package/src/components/chart/core/viewport.test.ts +2 -23
- package/src/components/chart/core/viewport.ts +0 -27
- package/src/components/chart/market-session.ts +1 -1
- package/src/components/chart/native/surface/hooks.ts +10 -3
- package/src/components/chart/static/bar-chart-surface.tsx +104 -20
- package/src/components/chart/stock-chart/data-runtime.ts +0 -1
- package/src/components/chart/stock-chart/footer.test.ts +65 -0
- package/src/components/chart/stock-chart/footer.ts +68 -13
- package/src/components/chart/stock-chart/resolved.tsx +18 -4
- package/src/components/chart/stock-chart/viewport/effects.ts +0 -3
- package/src/components/command-bar/helpers.test.ts +1 -80
- package/src/components/command-bar/helpers.ts +1 -36
- package/src/components/command-bar/pane-settings.ts +1 -1
- package/src/components/command-bar/pane-templates/items.ts +11 -3
- package/src/components/command-bar/panel/runtime.ts +0 -2
- package/src/components/command-bar/routes/root/results.ts +1 -1
- package/src/components/command-bar/routes/ticker-search/results.ts +0 -11
- package/src/components/command-bar/surface/index.test.tsx +52 -16
- package/src/components/command-bar/surface/index.tsx +0 -1
- package/src/components/command-bar/workflow/ops.ts +81 -20
- package/src/components/feed-data-table/stack-view.test.tsx +36 -130
- package/src/components/feed-data-table/stack-view.tsx +0 -1
- package/src/components/index.ts +1 -0
- package/src/components/layout/shell/index.test.tsx +2 -113
- package/src/components/layout/shell/index.tsx +0 -1
- package/src/components/layout/shell/menu.ts +0 -1
- package/src/components/layout/shell/terminal-pointer-runtime.ts +0 -1
- package/src/components/onboarding/onboarding-wizard.tsx +3 -3
- package/src/components/onboarding/wizard-keyboard.ts +2 -23
- package/src/components/price-selector-dialog.tsx +3 -3
- package/src/components/ticker/badge/text.tsx +0 -1
- package/src/components/toggle-list.tsx +29 -73
- package/src/components/ui/button.tsx +4 -1
- package/src/components/ui/checkbox.tsx +76 -0
- package/src/components/ui/choice-dialog.tsx +15 -3
- package/src/components/ui/frame.tsx +1 -1
- package/src/components/ui/index.ts +2 -0
- package/src/components/ui/message-composer.tsx +4 -0
- package/src/components/ui/native-select.tsx +4 -2
- package/src/components/ui/page-stack-view.tsx +1 -1
- package/src/components/ui/ui.test.tsx +0 -78
- package/src/core/state/app/layout-reducer.ts +0 -1
- package/src/data/config/layout.ts +3 -3
- package/src/data/resource-store.ts +6 -0
- package/src/data/sqlite/cache.test.ts +5 -0
- package/src/data/ticker-store.ts +0 -7
- package/src/market-data/coordinator/auxiliary.ts +1 -44
- package/src/market-data/coordinator/entries.ts +0 -1
- package/src/market-data/coordinator/events.test.ts +49 -0
- package/src/market-data/coordinator/events.ts +25 -4
- package/src/market-data/coordinator/index.test.ts +223 -10
- package/src/market-data/coordinator/index.ts +41 -21
- package/src/market-data/coordinator/quotes.ts +13 -11
- package/src/market-data/coordinator/subscriptions.test.ts +1 -0
- package/src/market-data/hooks.tsx +17 -13
- package/src/market-data/market/freshness.ts +58 -1
- package/src/market-data/market/status.ts +19 -1
- package/src/market-data/query-store.ts +0 -4
- package/src/market-data/quotes/freshness.test.ts +49 -1
- package/src/market-data/quotes/freshness.ts +8 -0
- package/src/market-data/quotes/resolution.test.ts +185 -0
- package/src/market-data/quotes/resolution.ts +96 -10
- package/src/market-data/request-types.test.ts +61 -1
- package/src/market-data/request-types.ts +42 -8
- package/src/market-data/result-types.ts +0 -11
- package/src/market-data/selectors.ts +1 -5
- package/src/news/aggregator.ts +5 -1
- package/src/pane-settings.test.ts +0 -18
- package/src/plugins/builtin/account-management/footer.ts +2 -5
- package/src/plugins/builtin/account-management/form-components.tsx +175 -59
- package/src/plugins/builtin/account-management/keyboard.ts +32 -2
- package/src/plugins/builtin/account-management/model.test.ts +99 -4
- package/src/plugins/builtin/account-management/model.ts +259 -23
- package/src/plugins/builtin/account-management/pane.tsx +784 -155
- package/src/plugins/builtin/broker-manager/index.test.tsx +0 -32
- package/src/plugins/builtin/buildout/cells.tsx +1 -1
- package/src/plugins/builtin/buildout/detail/satellite-images.tsx +1 -1
- package/src/plugins/builtin/buildout/detail/ui.tsx +0 -1
- package/src/plugins/builtin/buildout/format.ts +2 -11
- package/src/plugins/builtin/chat/chat.test.tsx +127 -1
- package/src/plugins/builtin/chat/content/channel-navigation.ts +1 -21
- package/src/plugins/builtin/chat/content/composer-cursor.ts +29 -0
- package/src/plugins/builtin/chat/content/composer-runtime.ts +22 -24
- package/src/plugins/builtin/chat/content/composer.tsx +84 -1
- package/src/plugins/builtin/chat/content/index.tsx +153 -16
- package/src/plugins/builtin/chat/content/layout-metrics.ts +10 -1
- package/src/plugins/builtin/chat/content/mentions.test.ts +74 -0
- package/src/plugins/builtin/chat/content/mentions.ts +88 -0
- package/src/plugins/builtin/chat/content/new-dm-dialog.tsx +264 -0
- package/src/plugins/builtin/chat/content/shortcuts.ts +47 -1
- package/src/plugins/builtin/chat/content/snapshot.ts +7 -0
- package/src/plugins/builtin/chat/content/transcript.tsx +0 -5
- package/src/plugins/builtin/chat/controller/index.ts +0 -10
- package/src/plugins/builtin/chat/controller.test.ts +5 -5
- package/src/plugins/builtin/chat/message/profile-popover.test.tsx +36 -0
- package/src/plugins/builtin/chat/message/profile-popover.tsx +124 -20
- package/src/plugins/builtin/chat/sidebar.test.tsx +83 -6
- package/src/plugins/builtin/chat/sidebar.tsx +26 -2
- package/src/plugins/builtin/chat/test-harness.tsx +22 -12
- package/src/plugins/builtin/cloud/plugin.tsx +8 -1
- package/src/plugins/builtin/cloud-tweets/pane.tsx +0 -1
- package/src/plugins/builtin/congress-trades/model.ts +2 -7
- package/src/plugins/builtin/congress-trades/pane.tsx +0 -1
- package/src/plugins/builtin/correlation/index.tsx +4 -1
- package/src/plugins/builtin/correlation/relationship/controls.tsx +8 -2
- package/src/plugins/builtin/correlation/relationship/model.ts +22 -2
- package/src/plugins/builtin/correlation/relationship/pane.tsx +9 -3
- package/src/plugins/builtin/earnings/index.tsx +0 -9
- package/src/plugins/builtin/econ/index.tsx +1 -5
- package/src/plugins/builtin/help/index.tsx +2 -2
- package/src/plugins/builtin/holders/pane.tsx +0 -5
- package/src/plugins/builtin/insider/index.tsx +1 -8
- package/src/plugins/builtin/kelly-sizer/index.test.tsx +0 -26
- package/src/plugins/builtin/kelly-sizer/index.tsx +0 -2
- package/src/plugins/builtin/market-movers/index.tsx +0 -2
- package/src/plugins/builtin/news/index.tsx +0 -1
- package/src/plugins/builtin/news/wire/news/table.tsx +0 -11
- package/src/plugins/builtin/options/view.test.tsx +1 -38
- package/src/plugins/builtin/plugin-groups.ts +17 -3
- package/src/plugins/builtin/portfolio-list/column-values.ts +15 -8
- package/src/plugins/builtin/portfolio-list/index.test.ts +52 -25
- package/src/plugins/builtin/portfolio-list/metrics.test.ts +44 -7
- package/src/plugins/builtin/portfolio-list/pane/data.test.ts +54 -0
- package/src/plugins/builtin/portfolio-list/pane/data.ts +50 -3
- package/src/plugins/builtin/portfolio-list/pane/index.test.tsx +314 -16
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +11 -4
- package/src/plugins/builtin/portfolio-list/pane/streaming.ts +83 -24
- package/src/plugins/builtin/sec/filing-documents.ts +0 -11
- package/src/plugins/builtin/sec/index.tsx +0 -2
- package/src/plugins/builtin/substack/article-detail.tsx +1 -1
- package/src/plugins/builtin/thirteenf/api.ts +0 -15
- package/src/plugins/builtin/thirteenf/pane.tsx +1 -3
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/content.tsx +42 -6
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/model.ts +415 -64
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/settings.ts +23 -2
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/types.ts +8 -1
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph.tsx +3 -1
- package/src/plugins/builtin/ticker-detail/data-panes.test.ts +95 -1
- package/src/plugins/builtin/ticker-detail/financials/tab.tsx +0 -7
- package/src/plugins/builtin/ticker-detail/index.tsx +2 -0
- package/src/plugins/builtin/ticker-detail/quote-monitor/index.test.tsx +5 -34
- package/src/plugins/builtin/world-indices/index.tsx +0 -4
- package/src/plugins/builtin/world-indices/model.ts +0 -7
- package/src/plugins/ibkr/account-cache.ts +1 -1
- package/src/plugins/ibkr/gateway/market-data.ts +12 -5
- package/src/plugins/ibkr/gateway/service/index.test.ts +31 -0
- package/src/plugins/ibkr/portfolio-performance.ts +1 -1
- package/src/plugins/ibkr/trade/utils.ts +1 -7
- package/src/plugins/loader.ts +2 -1
- package/src/plugins/prediction-markets/detail.test.tsx +0 -34
- package/src/plugins/registry/context.ts +15 -0
- package/src/plugins/registry/contributions.ts +22 -11
- package/src/plugins/registry/index.test.ts +44 -0
- package/src/plugins/registry/index.ts +75 -26
- package/src/remote/controller.ts +1 -7
- package/src/remote/json-patch.ts +15 -1
- package/src/remote/server.ts +16 -6
- package/src/renderers/electrobun/bun/application-menu/index.ts +1 -1
- package/src/renderers/electrobun/bun/core-capabilities.ts +2 -0
- package/src/renderers/electrobun/bun/desktop/http-fetch.test.ts +14 -0
- package/src/renderers/electrobun/bun/desktop/http-fetch.ts +8 -0
- package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -1
- package/src/renderers/electrobun/bun/index.ts +42 -0
- package/src/renderers/electrobun/shared/protocol.ts +5 -0
- package/src/renderers/electrobun/view/backend-rpc.ts +29 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -0
- package/src/renderers/electrobun/view/data-table/dom.ts +0 -1
- package/src/renderers/electrobun/view/desktop/controls.tsx +135 -4
- package/src/renderers/electrobun/view/desktop/list-view.tsx +38 -25
- package/src/renderers/electrobun/view/desktop-deeplink-bridge.ts +10 -0
- package/src/renderers/electrobun/view/host/box.tsx +0 -2
- package/src/renderers/electrobun/view/host/mouse.ts +0 -11
- package/src/renderers/electrobun/view/host/scroll-box.tsx +1 -2
- package/src/renderers/electrobun/view/host/text.tsx +1 -2
- package/src/renderers/electrobun/view/http-fetch.ts +15 -2
- package/src/renderers/electrobun/view/main.tsx +3 -0
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +9 -18
- package/src/renderers/electrobun/view/remote/capability-invoker.test.ts +50 -0
- package/src/renderers/electrobun/view/remote/capability-invoker.ts +36 -0
- package/src/renderers/electrobun/view/ui-host.tsx +2 -0
- package/src/renderers/opentui/host.tsx +11 -3
- package/src/renderers/opentui/input-host.tsx +5 -1
- package/src/sources/gloomberb-cloud/index.test.ts +0 -72
- package/src/sources/gloomberb-cloud/index.ts +1 -30
- package/src/sources/gloomberb-cloud/news.ts +0 -12
- package/src/sources/provider-router/batches.ts +11 -3
- package/src/sources/provider-router/cached-financials.test.ts +123 -0
- package/src/sources/provider-router/financial-routes.ts +77 -5
- package/src/sources/provider-router/financials.test.ts +49 -0
- package/src/sources/provider-router/financials.ts +40 -3
- package/src/sources/provider-router/index.test.ts +314 -0
- package/src/sources/provider-router/primary.ts +17 -3
- package/src/sources/provider-router/search.ts +5 -0
- package/src/sources/provider-router/streaming.ts +6 -1
- package/src/sources/provider-router/supplemental.ts +4 -3
- package/src/sources/sec-edgar.test.ts +42 -0
- package/src/sources/sec-edgar.ts +1 -2
- package/src/sources/yahoo-finance/history.ts +5 -1
- package/src/sources/yahoo-finance/mappers.test.ts +29 -0
- package/src/sources/yahoo-finance/mappers.ts +2 -1
- package/src/sources/yahoo-finance/options.ts +59 -14
- package/src/sources/yahoo-finance/quote-summary.ts +4 -1
- package/src/sources/yahoo-finance/requests.ts +2 -1
- package/src/sources/yahoo-finance/snapshots.ts +13 -2
- package/src/sources/yahoo-finance.test.ts +40 -21
- package/src/sources/yahoo-finance.ts +19 -10
- package/src/state/app/context/index.test.tsx +0 -81
- package/src/state/app/context/selector.test.tsx +6 -2
- package/src/state/hooks/quote-streaming.test.ts +14 -1
- package/src/state/hooks/quote-streaming.ts +5 -4
- package/src/sync/controller.test.ts +31 -0
- package/src/sync/controller.ts +300 -0
- package/src/sync/core-contributors.test.ts +251 -0
- package/src/sync/core-contributors.ts +400 -0
- package/src/sync/profile-analytics.ts +39 -0
- package/src/sync/react.ts +53 -0
- package/src/sync/types.ts +77 -0
- package/src/test-support/data-provider.ts +0 -1
- package/src/types/desktop-deeplink.ts +7 -0
- package/src/types/plugin.ts +3 -0
- package/src/ui/host.tsx +15 -1
- package/src/ui/index.tsx +2 -0
- package/src/updater.test.ts +0 -21
- package/src/updater.ts +20 -16
- package/src/utils/async-deadline.ts +26 -0
- package/src/utils/exchanges.test.ts +11 -1
- package/src/utils/exchanges.ts +5 -1
- package/src/utils/selection-clipboard.ts +2 -2
- package/src/version.ts +1 -1
- package/src/components/layout/pane/header.test.tsx +0 -39
- package/src/plugins/builtin/chat/channels.test.ts +0 -28
- package/src/plugins/builtin/chat/index.tsx +0 -4
- package/src/plugins/builtin/cloud-tweets/index.ts +0 -1
- package/src/plugins/builtin/earnings/index.test.ts +0 -12
- package/src/plugins/builtin/help/index.test.tsx +0 -242
- package/src/plugins/builtin/substack/article-detail.test.tsx +0 -10
- package/src/renderers/electrobun/bun/application-menu/index.test.ts +0 -21
- package/src/renderers/electrobun/bun/window/focus.test.ts +0 -35
- package/src/renderers/electrobun/view/host/mouse.test.ts +0 -37
- package/src/renderers/electrobun/view/host/text.test.ts +0 -22
- package/src/renderers/opentui/dialog-host.test.tsx +0 -44
- /package/src/{plugins/ibkr → utils}/hash.ts +0 -0
|
@@ -1,36 +1,140 @@
|
|
|
1
1
|
import { Box, Text } from "../../../../ui";
|
|
2
2
|
import { TextAttributes } from "../../../../ui";
|
|
3
3
|
import { colors } from "../../../../theme/colors";
|
|
4
|
-
import type { ChatUserSummary } from "../../../../api-client";
|
|
4
|
+
import type { ChatUserSummary, PublicPortfolioAnalytics } from "../../../../api-client";
|
|
5
|
+
import { formatNumber } from "../../../../utils/format";
|
|
5
6
|
import { truncateChannelLabel } from "../channels";
|
|
6
|
-
import { ChatActionChip } from "./action-chip";
|
|
7
7
|
|
|
8
8
|
export const PROFILE_POPOVER_CLOSE_DELAY_MS = 40;
|
|
9
9
|
|
|
10
|
+
function hasPortfolioAnalytics(analytics: PublicPortfolioAnalytics | null | undefined): boolean {
|
|
11
|
+
return Boolean(
|
|
12
|
+
analytics
|
|
13
|
+
&& (
|
|
14
|
+
analytics.oneYearReturn != null
|
|
15
|
+
|| analytics.spyBeta != null
|
|
16
|
+
),
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
10
20
|
export function hasPublicChatProfileInfo(user: ChatUserSummary): boolean {
|
|
11
21
|
if (user.profilePublic === false) return false;
|
|
12
|
-
return Boolean(user.bio?.trim() || user.title?.trim() || user.company?.trim());
|
|
22
|
+
return Boolean(user.bio?.trim() || user.title?.trim() || user.company?.trim() || hasPortfolioAnalytics(user.portfolioAnalytics));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function formatSignedPercent(value: number): string {
|
|
26
|
+
const percent = value * 100;
|
|
27
|
+
return `${percent >= 0 ? "+" : ""}${formatNumber(percent, 2)}%`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
type AnalyticsMetric = {
|
|
31
|
+
id: "one-year" | "beta";
|
|
32
|
+
label: string;
|
|
33
|
+
value: string;
|
|
34
|
+
rawValue: number;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
function analyticsValueColor(id: string, value: number): string {
|
|
38
|
+
if (id === "one-year") {
|
|
39
|
+
if (value > 0) return colors.positive;
|
|
40
|
+
if (value < 0) return colors.negative;
|
|
41
|
+
}
|
|
42
|
+
return colors.warning;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function analyticsMetrics(analytics: PublicPortfolioAnalytics): AnalyticsMetric[] {
|
|
46
|
+
return [
|
|
47
|
+
analytics.oneYearReturn != null
|
|
48
|
+
? {
|
|
49
|
+
id: "one-year",
|
|
50
|
+
label: "1Y",
|
|
51
|
+
value: formatSignedPercent(analytics.oneYearReturn),
|
|
52
|
+
rawValue: analytics.oneYearReturn,
|
|
53
|
+
}
|
|
54
|
+
: null,
|
|
55
|
+
analytics.spyBeta != null
|
|
56
|
+
? {
|
|
57
|
+
id: "beta",
|
|
58
|
+
label: "Beta",
|
|
59
|
+
value: formatNumber(analytics.spyBeta, 2),
|
|
60
|
+
rawValue: analytics.spyBeta,
|
|
61
|
+
}
|
|
62
|
+
: null,
|
|
63
|
+
].filter((metric): metric is AnalyticsMetric => !!metric);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function headerMetricLabel(metric: AnalyticsMetric): string {
|
|
67
|
+
return metric.id === "beta" ? "Beta" : metric.label;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function headerMetricsNaturalWidth(metrics: AnalyticsMetric[]): number {
|
|
71
|
+
const metricWidth = metrics.reduce((sum, metric) => (
|
|
72
|
+
sum + headerMetricLabel(metric).length + 1 + metric.value.length
|
|
73
|
+
), 0);
|
|
74
|
+
return metricWidth + Math.max(0, metrics.length - 1);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function HeaderAnalyticsStats({
|
|
78
|
+
metrics,
|
|
79
|
+
width,
|
|
80
|
+
}: {
|
|
81
|
+
metrics: AnalyticsMetric[];
|
|
82
|
+
width: number;
|
|
83
|
+
}) {
|
|
84
|
+
if (metrics.length === 0 || width < 8) return null;
|
|
85
|
+
const gapWidth = Math.max(0, metrics.length - 1);
|
|
86
|
+
const naturalWidths = metrics.map((metric) => headerMetricLabel(metric).length + 1 + metric.value.length);
|
|
87
|
+
const availableMetricWidth = Math.max(metrics.length * 4, width - gapWidth);
|
|
88
|
+
let overflow = Math.max(0, naturalWidths.reduce((sum, value) => sum + value, 0) - availableMetricWidth);
|
|
89
|
+
const metricWidths = naturalWidths.map((naturalWidth, index) => {
|
|
90
|
+
const shrinkable = Math.max(0, naturalWidth - 4);
|
|
91
|
+
const shrink = Math.min(shrinkable, Math.ceil(overflow / (naturalWidths.length - index)));
|
|
92
|
+
overflow -= shrink;
|
|
93
|
+
return naturalWidth - shrink;
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<Box flexDirection="row" gap={1} width={width}>
|
|
98
|
+
{metrics.map((metric, index) => {
|
|
99
|
+
const label = headerMetricLabel(metric);
|
|
100
|
+
const metricWidth = metricWidths[index] ?? 4;
|
|
101
|
+
const labelWidth = Math.max(1, Math.min(label.length, metricWidth - 2));
|
|
102
|
+
const valueWidth = Math.max(1, metricWidth - labelWidth - 1);
|
|
103
|
+
return (
|
|
104
|
+
<Box key={metric.id} width={metricWidth} height={1} flexDirection="row" gap={1}>
|
|
105
|
+
<Text fg={colors.textMuted}>{truncateChannelLabel(label, labelWidth)}</Text>
|
|
106
|
+
<Text fg={analyticsValueColor(metric.id, metric.rawValue)} attributes={TextAttributes.BOLD}>
|
|
107
|
+
{truncateChannelLabel(metric.value, valueWidth)}
|
|
108
|
+
</Text>
|
|
109
|
+
</Box>
|
|
110
|
+
);
|
|
111
|
+
})}
|
|
112
|
+
</Box>
|
|
113
|
+
);
|
|
13
114
|
}
|
|
14
115
|
|
|
15
116
|
export function UserProfilePopover({
|
|
16
117
|
user,
|
|
17
118
|
width,
|
|
18
|
-
currentUserId,
|
|
19
|
-
onDirectMessage,
|
|
20
119
|
onClose,
|
|
21
120
|
onKeepOpen,
|
|
22
121
|
}: {
|
|
23
122
|
user: ChatUserSummary;
|
|
24
123
|
width: number;
|
|
25
|
-
currentUserId?: string | null;
|
|
26
|
-
onDirectMessage: (user: ChatUserSummary) => void;
|
|
27
124
|
onClose: () => void;
|
|
28
125
|
onKeepOpen: () => void;
|
|
29
126
|
}) {
|
|
30
127
|
const popoverWidth = Math.max(24, Math.min(38, width - 4));
|
|
31
128
|
const meta = [user.title, user.company].filter(Boolean).join(" · ");
|
|
32
129
|
const bio = user.bio?.trim();
|
|
33
|
-
const
|
|
130
|
+
const analytics = user.portfolioAnalytics;
|
|
131
|
+
const metrics = analytics ? analyticsMetrics(analytics) : [];
|
|
132
|
+
const headerWidth = Math.max(1, popoverWidth - 2);
|
|
133
|
+
const maxStatsWidth = Math.max(0, headerWidth - 8);
|
|
134
|
+
const statsWidth = metrics.length > 0 && maxStatsWidth >= 8
|
|
135
|
+
? Math.min(headerMetricsNaturalWidth(metrics), maxStatsWidth)
|
|
136
|
+
: 0;
|
|
137
|
+
const usernameWidth = Math.max(1, headerWidth - (statsWidth > 0 ? statsWidth + 1 : 0));
|
|
34
138
|
|
|
35
139
|
return (
|
|
36
140
|
<Box
|
|
@@ -47,19 +151,19 @@ export function UserProfilePopover({
|
|
|
47
151
|
onMouseOut={onClose}
|
|
48
152
|
style={{ zIndex: 4 }}
|
|
49
153
|
>
|
|
50
|
-
<Box height={1} flexDirection="row">
|
|
51
|
-
<
|
|
52
|
-
|
|
53
|
-
|
|
154
|
+
<Box height={1} width={headerWidth} flexDirection="row">
|
|
155
|
+
<Box width={usernameWidth}>
|
|
156
|
+
<Text fg={colors.positive} attributes={TextAttributes.BOLD}>
|
|
157
|
+
{truncateChannelLabel(user.username ? `@${user.username}` : user.displayName, usernameWidth)}
|
|
158
|
+
</Text>
|
|
159
|
+
</Box>
|
|
160
|
+
{statsWidth > 0 ? (
|
|
161
|
+
<>
|
|
162
|
+
<Box width={1} />
|
|
163
|
+
<HeaderAnalyticsStats metrics={metrics} width={statsWidth} />
|
|
164
|
+
</>
|
|
165
|
+
) : null}
|
|
54
166
|
<Box flexGrow={1} />
|
|
55
|
-
<ChatActionChip
|
|
56
|
-
label={canDm ? "DM" : "Closed"}
|
|
57
|
-
width={canDm ? 5 : 9}
|
|
58
|
-
emphasized
|
|
59
|
-
onPress={() => {
|
|
60
|
-
if (canDm) onDirectMessage(user);
|
|
61
|
-
}}
|
|
62
|
-
/>
|
|
63
167
|
</Box>
|
|
64
168
|
{meta ? <Text fg={colors.textDim}>{truncateChannelLabel(meta, popoverWidth - 2)}</Text> : null}
|
|
65
169
|
{bio ? (
|
|
@@ -5,9 +5,10 @@ import { AppContext, appReducer, createInitialState, PaneInstanceProvider } from
|
|
|
5
5
|
import { createConfigBackedTestPluginRuntime, createTestPluginRuntime } from "../../../test-support/plugin-runtime";
|
|
6
6
|
import { createDefaultConfig, findPaneInstance, type PaneInstanceConfig } from "../../../types/config";
|
|
7
7
|
import { TextAttributes } from "../../../ui";
|
|
8
|
+
import { apiClient } from "../../../api-client";
|
|
8
9
|
import { PluginRenderProvider } from "../../runtime";
|
|
9
10
|
import { gloomberbCloudPlugin } from "../cloud";
|
|
10
|
-
import { ChatContent } from "
|
|
11
|
+
import { ChatContent } from "./content";
|
|
11
12
|
import { chatController } from "./controller";
|
|
12
13
|
import { useChatChannelNavigation } from "./content/channel-navigation";
|
|
13
14
|
import {
|
|
@@ -18,6 +19,7 @@ import {
|
|
|
18
19
|
installChatApiTestDefaults,
|
|
19
20
|
installServerChannels,
|
|
20
21
|
lineText,
|
|
22
|
+
makeMessage,
|
|
21
23
|
MemoryPersistence,
|
|
22
24
|
type ChatTestSetup,
|
|
23
25
|
} from "./test-harness";
|
|
@@ -38,7 +40,11 @@ afterEach(async () => {
|
|
|
38
40
|
testSetup = undefined;
|
|
39
41
|
});
|
|
40
42
|
|
|
41
|
-
function createChannelPane(
|
|
43
|
+
function createChannelPane(
|
|
44
|
+
controller: ReturnType<typeof createController>,
|
|
45
|
+
initialChannelId = "equities",
|
|
46
|
+
onChannelChange?: (channelId: string) => void,
|
|
47
|
+
) {
|
|
42
48
|
const state = createInitialState(createDefaultConfig("/tmp/gloomberb-chat"));
|
|
43
49
|
|
|
44
50
|
return function ChannelPane() {
|
|
@@ -52,7 +58,10 @@ function createChannelPane(controller: ReturnType<typeof createController>, init
|
|
|
52
58
|
height={12}
|
|
53
59
|
focused
|
|
54
60
|
channelId={channelId}
|
|
55
|
-
onChannelChange={
|
|
61
|
+
onChannelChange={(nextChannelId) => {
|
|
62
|
+
onChannelChange?.(nextChannelId);
|
|
63
|
+
setChannelId(nextChannelId);
|
|
64
|
+
}}
|
|
56
65
|
/>
|
|
57
66
|
</PluginRenderProvider>
|
|
58
67
|
</AppContext>
|
|
@@ -214,6 +223,76 @@ describe("ChatContent channel sidebar", () => {
|
|
|
214
223
|
expect(setup().captureCharFrame()).not.toContain("#options");
|
|
215
224
|
});
|
|
216
225
|
|
|
226
|
+
test("opens a new direct-message dialog from the DMs header", async () => {
|
|
227
|
+
const controller = createController({
|
|
228
|
+
messages: [{
|
|
229
|
+
...makeMessage(1),
|
|
230
|
+
user: { id: "u2", username: "bob", displayName: "Bob" },
|
|
231
|
+
}],
|
|
232
|
+
sessionToken: "token-123",
|
|
233
|
+
user: { id: "u1", username: "vince", emailVerified: true },
|
|
234
|
+
});
|
|
235
|
+
installServerChannels(controller);
|
|
236
|
+
controller.refreshChannels = async () => {};
|
|
237
|
+
controller.refreshChannelMessages = async () => {};
|
|
238
|
+
const openedTargets: Array<{ username?: string }> = [];
|
|
239
|
+
const originalOpenDirectChannel = apiClient.openDirectChannel.bind(apiClient);
|
|
240
|
+
apiClient.openDirectChannel = async (target) => {
|
|
241
|
+
openedTargets.push(target);
|
|
242
|
+
return {
|
|
243
|
+
id: "dm:bob",
|
|
244
|
+
name: "@bob",
|
|
245
|
+
kind: "direct",
|
|
246
|
+
created_at: "2026-07-03T09:30:00.000Z",
|
|
247
|
+
dmUser: { id: "u2", username: "bob", displayName: "Bob" },
|
|
248
|
+
};
|
|
249
|
+
};
|
|
250
|
+
const selectedChannels: string[] = [];
|
|
251
|
+
const ChannelPane = createChannelPane(controller, "everyone", (channelId) => {
|
|
252
|
+
selectedChannels.push(channelId);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
try {
|
|
256
|
+
await act(async () => {
|
|
257
|
+
testSetup = await testRender(<ChannelPane />, {
|
|
258
|
+
width: 90,
|
|
259
|
+
height: 14,
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
await flushFrame();
|
|
264
|
+
const lines = setup().captureCharFrame().split("\n");
|
|
265
|
+
const row = lines.findIndex((line) => line.includes("DMs"));
|
|
266
|
+
const col = lines[row]?.lastIndexOf("+") ?? -1;
|
|
267
|
+
expect(row).toBeGreaterThanOrEqual(0);
|
|
268
|
+
expect(col).toBeGreaterThanOrEqual(0);
|
|
269
|
+
|
|
270
|
+
await act(async () => {
|
|
271
|
+
await setup().mockMouse.click(col, row);
|
|
272
|
+
await setup().renderOnce();
|
|
273
|
+
await setup().renderOnce();
|
|
274
|
+
});
|
|
275
|
+
await flushFrame();
|
|
276
|
+
|
|
277
|
+
expect(setup().captureCharFrame()).toContain("New DM");
|
|
278
|
+
|
|
279
|
+
await act(async () => {
|
|
280
|
+
await setup().mockInput.typeText("@bob");
|
|
281
|
+
setup().mockInput.pressEnter();
|
|
282
|
+
await setup().renderOnce();
|
|
283
|
+
await setup().renderOnce();
|
|
284
|
+
});
|
|
285
|
+
await flushFrame();
|
|
286
|
+
|
|
287
|
+
expect(openedTargets).toEqual([{ username: "bob" }]);
|
|
288
|
+
expect(selectedChannels).toEqual(["dm:bob"]);
|
|
289
|
+
expect(setup().captureCharFrame()).toContain("@bob");
|
|
290
|
+
expect(setup().captureCharFrame()).not.toContain("New DM");
|
|
291
|
+
} finally {
|
|
292
|
+
apiClient.openDirectChannel = originalOpenDirectChannel;
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
|
|
217
296
|
test("shows the sidebar online count footer", async () => {
|
|
218
297
|
const controller = createController({ sessionToken: "token-123" });
|
|
219
298
|
installServerChannels(controller);
|
|
@@ -287,7 +366,6 @@ describe("ChatContent channel sidebar", () => {
|
|
|
287
366
|
});
|
|
288
367
|
|
|
289
368
|
test("coalesces rapid sidebar navigation to the final channel", async () => {
|
|
290
|
-
const controller = createController({ sessionToken: "token-123" });
|
|
291
369
|
const channels = [
|
|
292
370
|
{ id: "everyone", name: "everyone", created_at: "2026-03-26T12:10:05.684Z" },
|
|
293
371
|
{ id: "equities", name: "equities", created_at: "2026-05-09T00:00:00.000Z" },
|
|
@@ -314,8 +392,6 @@ describe("ChatContent channel sidebar", () => {
|
|
|
314
392
|
channelIdRef,
|
|
315
393
|
channels,
|
|
316
394
|
channelsLoading: false,
|
|
317
|
-
closeProfilePopover: () => {},
|
|
318
|
-
controller,
|
|
319
395
|
focused: true,
|
|
320
396
|
inputFocused: false,
|
|
321
397
|
onChannelChange: handleChannelChange,
|
|
@@ -412,6 +488,7 @@ describe("ChatContent channel sidebar", () => {
|
|
|
412
488
|
},
|
|
413
489
|
registerPaneTemplate: () => {},
|
|
414
490
|
registerTickerResearchTab: () => {},
|
|
491
|
+
registerSyncTransport: () => {},
|
|
415
492
|
registerShortcut: () => {},
|
|
416
493
|
registerCommand: () => {},
|
|
417
494
|
showPane: () => {},
|
|
@@ -109,9 +109,11 @@ export function ChannelSidebar({
|
|
|
109
109
|
keyboardFocused,
|
|
110
110
|
loading,
|
|
111
111
|
canManageNotifications,
|
|
112
|
+
canCreateConversation,
|
|
112
113
|
directExpanded,
|
|
113
114
|
onSelect,
|
|
114
115
|
onFocusRequest,
|
|
116
|
+
onCreateConversation,
|
|
115
117
|
onToggleNotifications,
|
|
116
118
|
onToggleDirectExpanded,
|
|
117
119
|
}: {
|
|
@@ -125,9 +127,11 @@ export function ChannelSidebar({
|
|
|
125
127
|
keyboardFocused: boolean;
|
|
126
128
|
loading: boolean;
|
|
127
129
|
canManageNotifications: boolean;
|
|
130
|
+
canCreateConversation: boolean;
|
|
128
131
|
directExpanded: boolean;
|
|
129
132
|
onSelect?: (channelId: string) => void;
|
|
130
133
|
onFocusRequest?: () => void;
|
|
134
|
+
onCreateConversation?: () => void;
|
|
131
135
|
onToggleNotifications?: (channelId: string, enabled: boolean) => void;
|
|
132
136
|
onToggleDirectExpanded?: () => void;
|
|
133
137
|
}) {
|
|
@@ -149,9 +153,9 @@ export function ChannelSidebar({
|
|
|
149
153
|
const conversationUnread = conversationChannels.some((channel) => (channelStateById.get(channel.id)?.unreadCount ?? 0) > 0);
|
|
150
154
|
const sidebarRows = useMemo(() => [
|
|
151
155
|
...publicChannels.map((channel) => ({ kind: "channel" as const, channel })),
|
|
152
|
-
...(conversationChannels.length > 0 ? [{ kind: "direct-header" as const }] : []),
|
|
156
|
+
...(conversationChannels.length > 0 || canCreateConversation ? [{ kind: "direct-header" as const }] : []),
|
|
153
157
|
...(directExpanded ? conversationChannels.map((channel) => ({ kind: "channel" as const, channel })) : []),
|
|
154
|
-
], [conversationChannels, directExpanded, publicChannels]);
|
|
158
|
+
], [canCreateConversation, conversationChannels, directExpanded, publicChannels]);
|
|
155
159
|
const sidebarLayoutHeight = nativePaneChrome ? "100%" : height;
|
|
156
160
|
const nativeFillStyle = nativePaneChrome ? { minHeight: 0 } : undefined;
|
|
157
161
|
const sidebarBorder = borderWidth > 0
|
|
@@ -201,6 +205,26 @@ export function ChannelSidebar({
|
|
|
201
205
|
>
|
|
202
206
|
{` ${directExpanded ? "▾" : "▸"} DMs`}
|
|
203
207
|
</Text>
|
|
208
|
+
<Box flexGrow={1} />
|
|
209
|
+
{canCreateConversation ? (
|
|
210
|
+
<Box
|
|
211
|
+
width={3}
|
|
212
|
+
height={1}
|
|
213
|
+
alignItems="center"
|
|
214
|
+
justifyContent="center"
|
|
215
|
+
backgroundColor={hoveredChannelId === "direct-header:create" ? hoverBg() : sidebarBg}
|
|
216
|
+
onMouseOver={() => setHoveredChannelId("direct-header:create")}
|
|
217
|
+
onMouseOut={() => setHoveredChannelId((current) => (current === "direct-header:create" ? null : current))}
|
|
218
|
+
onMouseDown={(event: any) => {
|
|
219
|
+
event?.preventDefault?.();
|
|
220
|
+
event?.stopPropagation?.();
|
|
221
|
+
onCreateConversation?.();
|
|
222
|
+
}}
|
|
223
|
+
style={{ cursor: "pointer" }}
|
|
224
|
+
>
|
|
225
|
+
<Text fg={hoveredChannelId === "direct-header:create" ? colors.textMuted : colors.textDim}>+</Text>
|
|
226
|
+
</Box>
|
|
227
|
+
) : null}
|
|
204
228
|
</Box>
|
|
205
229
|
);
|
|
206
230
|
}
|
|
@@ -9,7 +9,7 @@ import { Box } from "../../../ui";
|
|
|
9
9
|
import { apiClient, type ChatChannel, type ChatMessage } from "../../../api-client";
|
|
10
10
|
import { PluginRenderProvider, type PluginRuntimeAccess } from "../../runtime";
|
|
11
11
|
import { setSharedMarketDataForTests, setSharedRegistryForTests } from "../../registry";
|
|
12
|
-
import { ChatContent } from "
|
|
12
|
+
import { ChatContent } from "./content";
|
|
13
13
|
import { ChatController } from "./controller";
|
|
14
14
|
|
|
15
15
|
const TRANSCRIPT_KIND = "channel-transcript";
|
|
@@ -178,23 +178,33 @@ export function createChatTestControls(getSetup: () => ChatTestSetup) {
|
|
|
178
178
|
sequence?: string;
|
|
179
179
|
ctrl?: boolean;
|
|
180
180
|
meta?: boolean;
|
|
181
|
+
super?: boolean;
|
|
182
|
+
alt?: boolean;
|
|
181
183
|
shift?: boolean;
|
|
182
184
|
option?: boolean;
|
|
183
185
|
}) {
|
|
186
|
+
const keyEvent = {
|
|
187
|
+
ctrl: false,
|
|
188
|
+
meta: false,
|
|
189
|
+
option: false,
|
|
190
|
+
shift: false,
|
|
191
|
+
eventType: "press",
|
|
192
|
+
repeated: false,
|
|
193
|
+
defaultPrevented: false,
|
|
194
|
+
propagationStopped: false,
|
|
195
|
+
preventDefault() {
|
|
196
|
+
keyEvent.defaultPrevented = true;
|
|
197
|
+
},
|
|
198
|
+
stopPropagation() {
|
|
199
|
+
keyEvent.propagationStopped = true;
|
|
200
|
+
},
|
|
201
|
+
...event,
|
|
202
|
+
};
|
|
184
203
|
await act(async () => {
|
|
185
|
-
getSetup().renderer.keyInput.emit("keypress",
|
|
186
|
-
ctrl: false,
|
|
187
|
-
meta: false,
|
|
188
|
-
option: false,
|
|
189
|
-
shift: false,
|
|
190
|
-
eventType: "press",
|
|
191
|
-
repeated: false,
|
|
192
|
-
stopPropagation: () => {},
|
|
193
|
-
preventDefault: () => {},
|
|
194
|
-
...event,
|
|
195
|
-
} as any);
|
|
204
|
+
(getSetup().renderer.keyInput as any).emit("keypress", keyEvent);
|
|
196
205
|
await getSetup().renderOnce();
|
|
197
206
|
});
|
|
207
|
+
return keyEvent;
|
|
198
208
|
},
|
|
199
209
|
};
|
|
200
210
|
}
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
CONGRESS_TRADES_PANE_ID,
|
|
11
11
|
CongressTradesPane,
|
|
12
12
|
} from "../congress-trades/pane";
|
|
13
|
-
import { registerTwitterFeedFeature } from "../cloud-tweets";
|
|
13
|
+
import { registerTwitterFeedFeature } from "../cloud-tweets/registration";
|
|
14
14
|
import {
|
|
15
15
|
buildDmCommandResults,
|
|
16
16
|
formatChatPaneTitle,
|
|
@@ -103,6 +103,13 @@ export function createGloomberbCloudPlugin({
|
|
|
103
103
|
chatController.attachPersistence(ctx.persistence, ctx.resume);
|
|
104
104
|
chatController.setNotifier(ctx.notify);
|
|
105
105
|
|
|
106
|
+
ctx.registerSyncTransport({
|
|
107
|
+
id: "gloomberb-cloud",
|
|
108
|
+
isAvailable: () => apiClient.isVerified(),
|
|
109
|
+
pullSnapshot: () => apiClient.getSyncSnapshot(),
|
|
110
|
+
pushSnapshot: (snapshot, options) => apiClient.putSyncSnapshot(snapshot, options),
|
|
111
|
+
});
|
|
112
|
+
|
|
106
113
|
ctx.registerPane({
|
|
107
114
|
id: "chat",
|
|
108
115
|
name: "Chat",
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { DataTableColumn } from "../../../components";
|
|
2
2
|
import { formatCompact } from "../../../utils/format";
|
|
3
|
+
|
|
4
|
+
export { truncateWithEllipsis as truncate } from "../../../utils/text-wrap";
|
|
3
5
|
import type {
|
|
4
6
|
CloudCongressMemberPayload,
|
|
5
7
|
CloudCongressTradePayload,
|
|
@@ -76,13 +78,6 @@ export function formatAmountRange(low: number | null, high: number | null, raw?:
|
|
|
76
78
|
return formatMoneyShort(low ?? high);
|
|
77
79
|
}
|
|
78
80
|
|
|
79
|
-
export function truncate(value: string, width: number): string {
|
|
80
|
-
if (width <= 0) return "";
|
|
81
|
-
if (value.length <= width) return value;
|
|
82
|
-
if (width <= 3) return value.slice(0, width);
|
|
83
|
-
return `${value.slice(0, width - 3)}...`;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
81
|
function compareText(left: string, right: string): number {
|
|
87
82
|
return left.localeCompare(right, "en-US", { sensitivity: "base" });
|
|
88
83
|
}
|
|
@@ -18,7 +18,9 @@ import {
|
|
|
18
18
|
} from "./settings";
|
|
19
19
|
import { resolveCorrelationHeatmapCellColors } from "./colors";
|
|
20
20
|
import {
|
|
21
|
+
buildRelationshipGraphSettingsDef,
|
|
21
22
|
createRelationshipPaneTemplate,
|
|
23
|
+
RELATIONSHIP_GRAPH_PANE_ID,
|
|
22
24
|
RelationshipGraphPane,
|
|
23
25
|
} from "./relationship/pane";
|
|
24
26
|
import {
|
|
@@ -234,13 +236,14 @@ export const correlationPlugin: GloomPlugin = {
|
|
|
234
236
|
settings: buildCorrelationSettingsDef(),
|
|
235
237
|
},
|
|
236
238
|
{
|
|
237
|
-
id:
|
|
239
|
+
id: RELATIONSHIP_GRAPH_PANE_ID,
|
|
238
240
|
name: "Relationship Graph",
|
|
239
241
|
icon: "R",
|
|
240
242
|
component: RelationshipGraphPane,
|
|
241
243
|
defaultPosition: "right",
|
|
242
244
|
defaultMode: "floating",
|
|
243
245
|
defaultFloatingSize: { width: 100, height: 30 },
|
|
246
|
+
settings: buildRelationshipGraphSettingsDef(),
|
|
244
247
|
},
|
|
245
248
|
],
|
|
246
249
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Box, Text } from "../../../../ui";
|
|
2
2
|
import { colors } from "../../../../theme/colors";
|
|
3
|
+
import { Checkbox } from "../../../../components/ui/checkbox";
|
|
3
4
|
|
|
4
5
|
export function RelationshipMetricsTable({
|
|
5
6
|
rows,
|
|
@@ -39,7 +40,7 @@ export function RelationshipToggle({
|
|
|
39
40
|
}) {
|
|
40
41
|
return (
|
|
41
42
|
<Box
|
|
42
|
-
width={label.length +
|
|
43
|
+
width={label.length + 6}
|
|
43
44
|
height={1}
|
|
44
45
|
onMouseDown={(event: { preventDefault?: () => void; stopPropagation?: () => void }) => {
|
|
45
46
|
event.preventDefault?.();
|
|
@@ -47,7 +48,12 @@ export function RelationshipToggle({
|
|
|
47
48
|
onPress();
|
|
48
49
|
}}
|
|
49
50
|
>
|
|
50
|
-
<
|
|
51
|
+
<Checkbox
|
|
52
|
+
label={label}
|
|
53
|
+
checked={checked}
|
|
54
|
+
width={label.length + 6}
|
|
55
|
+
onChange={onPress}
|
|
56
|
+
/>
|
|
51
57
|
</Box>
|
|
52
58
|
);
|
|
53
59
|
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import type { ProjectedChartPoint } from "../../../../components/chart/core/data";
|
|
2
2
|
import type { TimeRange } from "../../../../components/chart/core/types";
|
|
3
3
|
import type { ScatterChartPoint } from "../../../../components/chart/scatter-chart-renderer";
|
|
4
|
-
import type { PaneTemplateCreateOptions } from "../../../../types/plugin";
|
|
4
|
+
import type { PaneSettingsDef, PaneTemplateCreateOptions } from "../../../../types/plugin";
|
|
5
5
|
import type { PricePoint } from "../../../../types/financials";
|
|
6
|
-
import { parseTickerListInput } from "../../../../tickers/list";
|
|
6
|
+
import { formatTickerListInput, parseTickerListInput } from "../../../../tickers/list";
|
|
7
7
|
|
|
8
8
|
export type RelationshipRange = Extract<TimeRange, "1M" | "3M" | "6M" | "1Y" | "5Y" | "ALL">;
|
|
9
9
|
|
|
10
|
+
export const RELATIONSHIP_GRAPH_PANE_ID = "relationship-graph";
|
|
10
11
|
const RELATIONSHIP_RANGES: RelationshipRange[] = ["1M", "3M", "6M", "1Y", "5Y", "ALL"];
|
|
11
12
|
export const DEFAULT_RELATIONSHIP_SECOND_SYMBOL = "SPY";
|
|
12
13
|
const RELATIONSHIP_CORRELATION_WINDOWS = [30, 60, 120, 252] as const;
|
|
@@ -277,3 +278,22 @@ export function relationshipTemplateSymbols(
|
|
|
277
278
|
return null;
|
|
278
279
|
}
|
|
279
280
|
}
|
|
281
|
+
|
|
282
|
+
export function buildRelationshipGraphPaneTitle(pair: readonly [string, string]): string {
|
|
283
|
+
return `GR ${pair[0]}/${pair[1]}`;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export function buildRelationshipGraphSettingsDef(): PaneSettingsDef {
|
|
287
|
+
return {
|
|
288
|
+
title: "Relationship Graph Settings",
|
|
289
|
+
fields: [
|
|
290
|
+
{
|
|
291
|
+
key: "symbolsText",
|
|
292
|
+
label: "Tickers",
|
|
293
|
+
description: `Enter one or two tickers. One ticker compares against ${DEFAULT_RELATIONSHIP_SECOND_SYMBOL}.`,
|
|
294
|
+
type: "text",
|
|
295
|
+
placeholder: formatTickerListInput(["AMD", "NVDA"]),
|
|
296
|
+
},
|
|
297
|
+
],
|
|
298
|
+
};
|
|
299
|
+
}
|
|
@@ -17,7 +17,9 @@ import { useRelationshipHistories } from "./history";
|
|
|
17
17
|
import {
|
|
18
18
|
DEFAULT_RELATIONSHIP_CORRELATION_WINDOW,
|
|
19
19
|
DEFAULT_RELATIONSHIP_SECOND_SYMBOL,
|
|
20
|
+
RELATIONSHIP_GRAPH_PANE_ID,
|
|
20
21
|
buildRelationshipAnalysis,
|
|
22
|
+
buildRelationshipGraphPaneTitle,
|
|
21
23
|
nextRelationshipRange,
|
|
22
24
|
nextRelationshipWindow,
|
|
23
25
|
relationshipSymbolsFromPaneSettings,
|
|
@@ -35,7 +37,11 @@ import {
|
|
|
35
37
|
formatNullableNumber,
|
|
36
38
|
} from "./view-model";
|
|
37
39
|
|
|
38
|
-
export {
|
|
40
|
+
export {
|
|
41
|
+
buildRelationshipAnalysis,
|
|
42
|
+
buildRelationshipGraphSettingsDef,
|
|
43
|
+
RELATIONSHIP_GRAPH_PANE_ID,
|
|
44
|
+
} from "./model";
|
|
39
45
|
|
|
40
46
|
type RelationshipGraphShortcut = "range" | "window" | "correlation" | "regression";
|
|
41
47
|
|
|
@@ -326,7 +332,7 @@ export function RelationshipGraphPane({ focused, width, height }: PaneProps) {
|
|
|
326
332
|
export function createRelationshipPaneTemplate(): PaneTemplateDef {
|
|
327
333
|
return {
|
|
328
334
|
id: "relationship-graph-pane",
|
|
329
|
-
paneId:
|
|
335
|
+
paneId: RELATIONSHIP_GRAPH_PANE_ID,
|
|
330
336
|
label: "Relationship Graph",
|
|
331
337
|
description: "Graph ratio, rolling correlation, and regression between two tickers.",
|
|
332
338
|
keywords: ["relationship", "ratio", "graph", "correlation", "regression", "gr"],
|
|
@@ -345,7 +351,7 @@ export function createRelationshipPaneTemplate(): PaneTemplateDef {
|
|
|
345
351
|
const pair = relationshipTemplateSymbols(context.activeTicker, options);
|
|
346
352
|
return pair
|
|
347
353
|
? {
|
|
348
|
-
title:
|
|
354
|
+
title: buildRelationshipGraphPaneTitle(pair),
|
|
349
355
|
binding: { kind: "fixed" as const, symbol: pair[0] },
|
|
350
356
|
placement: "floating" as const,
|
|
351
357
|
settings: {
|
|
@@ -103,15 +103,6 @@ function EarningsCalendarPane({ focused, width, height }: PaneProps) {
|
|
|
103
103
|
return false;
|
|
104
104
|
}, [reload]);
|
|
105
105
|
|
|
106
|
-
const selectDisplayRow = useCallback((row: EarningsDisplayRow) => {
|
|
107
|
-
if (row.kind !== "event") return;
|
|
108
|
-
if (row.eventIdx === activeEventIdx) {
|
|
109
|
-
openEvent(row.event);
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
setSelectedIdx(row.eventIdx);
|
|
113
|
-
}, [activeEventIdx, openEvent, setSelectedIdx]);
|
|
114
|
-
|
|
115
106
|
const renderCell = useCallback((
|
|
116
107
|
row: EarningsDisplayRow,
|
|
117
108
|
column: EarningsColumn,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Box
|
|
1
|
+
import { Box } from "../../../ui";
|
|
2
2
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
3
3
|
import { TextAttributes, type ScrollBoxRenderable } from "../../../ui";
|
|
4
4
|
import { DataTableStackView, usePaneFooter, type DataTableCell } from "../../../components";
|
|
@@ -243,10 +243,6 @@ function EconCalendarPane({ focused, width, height }: PaneProps) {
|
|
|
243
243
|
}), [countryFilter, cycleCountryFilter, cycleImpactFilter, error, impactFilter, load, loading, nextCountdown, nextEvent?.event, staleness]);
|
|
244
244
|
|
|
245
245
|
const handleHeaderClick = useCallback(() => {}, []);
|
|
246
|
-
const selectDisplayRow = useCallback((row: DisplayRow) => {
|
|
247
|
-
if (row.kind !== "event") return;
|
|
248
|
-
setSelectedIdx(row.eventIdx);
|
|
249
|
-
}, []);
|
|
250
246
|
const openDisplayRow = useCallback((row: DisplayRow) => {
|
|
251
247
|
if (row.kind !== "event") return;
|
|
252
248
|
setDetailEvent(row.event);
|