gloomberb 0.11.0 → 0.11.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 +29 -7
- package/README.zh-CN.md +6 -6
- package/package.json +10 -3
- package/src/api-client/auth.ts +14 -10
- package/src/api-client/data.ts +47 -0
- package/src/api-client/index.test.ts +77 -0
- package/src/api-client/index.ts +78 -4
- package/src/api-client/paths.ts +73 -0
- package/src/api-client/request.ts +15 -2
- package/src/api-client/socket-health.test.ts +1 -0
- package/src/api-client/socket.ts +2 -1
- package/src/api-client/types.ts +128 -0
- package/src/app/runtime/update.ts +8 -6
- package/src/app.tsx +12 -5
- package/src/architecture/import-boundaries.test.ts +5 -1
- package/src/capabilities/factories.ts +4 -0
- package/src/cli/index.ts +10 -4
- package/src/cli/registry.ts +1 -1
- package/src/components/chart/composite/composite-chart.tsx +4 -3
- package/src/components/chart/composite/format.test.ts +34 -0
- package/src/components/chart/composite/format.ts +33 -10
- package/src/components/chart/core/axis-format.ts +13 -3
- package/src/components/chart/core/index.test.ts +7 -0
- package/src/components/chart/core/renderer.ts +3 -4
- package/src/components/command-bar/surface/index.tsx +4 -1
- package/src/components/company-logo.test.ts +19 -0
- package/src/components/company-logo.tsx +60 -0
- package/src/components/feed-data-table/stack-view.tsx +7 -1
- package/src/components/index.ts +1 -0
- package/src/components/layout/detached-pane-shell.tsx +11 -5
- package/src/components/layout/header.tsx +5 -5
- package/src/components/layout/shell/index.tsx +3 -1
- package/src/components/onboarding/account-step/form-panel.tsx +4 -2
- package/src/components/onboarding/account-step/index.tsx +1 -1
- package/src/components/onboarding/account-step/signed-in-panel.tsx +1 -1
- package/src/components/onboarding/onboarding-wizard.test.tsx +44 -1
- package/src/components/onboarding/onboarding-wizard.tsx +13 -5
- package/src/components/onboarding/wizard-account.ts +4 -23
- package/src/components/table-view-shared.test.ts +11 -0
- package/src/components/table-view-shared.tsx +26 -0
- package/src/components/ui/data-table/index.tsx +19 -1
- package/src/components/ui/fields.tsx +5 -1
- package/src/i18n/es.ts +10 -1
- package/src/i18n/ja.ts +10 -1
- package/src/i18n/ko.ts +10 -1
- package/src/i18n/zh-cn.ts +10 -1
- package/src/i18n/zh-tw.ts +10 -1
- package/src/market-data/coordinator/index.test.ts +2 -2
- package/src/market-data/coordinator/index.ts +1 -1
- package/src/market-data/market/format.test.ts +7 -0
- package/src/market-data/market/freshness.ts +5 -2
- package/src/market-data/quotes/freshness.test.ts +16 -0
- package/src/news/aggregator.test.ts +92 -3
- package/src/news/aggregator.ts +98 -18
- package/src/news/hooks.test.tsx +2 -0
- package/src/news/hooks.ts +27 -1
- package/src/news/news-model.ts +5 -2
- package/src/news/types.ts +7 -0
- package/src/plugins/broker-sync/broker-sync.test.ts +29 -0
- package/src/plugins/broker-sync/normalize.ts +35 -5
- package/src/plugins/builtin/account-management/pane.tsx +5 -5
- package/src/plugins/builtin/alerts/alert-engine.test.ts +38 -1
- package/src/plugins/builtin/alerts/alert-engine.ts +30 -1
- package/src/plugins/builtin/alerts/index.test.tsx +49 -6
- package/src/plugins/builtin/alerts/index.tsx +7 -6
- package/src/plugins/builtin/alerts/pane.tsx +49 -1
- package/src/plugins/builtin/alerts/quotes.ts +11 -3
- package/src/plugins/builtin/alerts/types.ts +1 -0
- package/src/plugins/builtin/analytics/index.tsx +8 -1
- package/src/plugins/builtin/analytics/pane-model.ts +6 -4
- package/src/plugins/builtin/buildout/model/index.ts +1 -1
- package/src/plugins/builtin/buildout/pane/index.tsx +17 -18
- package/src/plugins/builtin/chart-composer/pane.tsx +22 -1
- package/src/plugins/builtin/chat/controller/channels.ts +7 -1
- package/src/plugins/builtin/chat/controller/index.ts +6 -8
- package/src/plugins/builtin/chat/controller/persistence.ts +0 -3
- package/src/plugins/builtin/chat/controller/realtime.ts +3 -3
- package/src/plugins/builtin/chat/controller/send.ts +1 -3
- package/src/plugins/builtin/chat/controller/session-runtime.ts +4 -9
- package/src/plugins/builtin/chat/controller/state.ts +1 -3
- package/src/plugins/builtin/chat/controller/storage.ts +0 -2
- package/src/plugins/builtin/chat/controller/view.ts +2 -2
- package/src/plugins/builtin/chat/controller.test.ts +78 -0
- package/src/plugins/builtin/chat/profile-popover.ts +1 -1
- package/src/plugins/builtin/cloud/auth-actions.tsx +14 -7
- package/src/plugins/builtin/cloud/auth-commands.ts +21 -47
- package/src/plugins/builtin/cloud/auth-dialog.tsx +296 -0
- package/src/{components/onboarding/account-step/model.test.ts → plugins/builtin/cloud/auth-model.test.ts} +1 -1
- package/src/{components/onboarding/account-step/model.ts → plugins/builtin/cloud/auth-model.ts} +34 -3
- package/src/plugins/builtin/cloud/browser.tsx +9 -0
- package/src/plugins/builtin/cloud/buildout-module.tsx +23 -0
- package/src/plugins/builtin/cloud/device-signin.ts +2 -8
- package/src/plugins/builtin/cloud/index.tsx +2 -0
- package/src/plugins/builtin/cloud/plugin.tsx +3 -23
- package/src/plugins/builtin/cloud/upgrade-command.ts +13 -6
- package/src/plugins/builtin/cloud-tweets/registration.ts +6 -1
- package/src/plugins/builtin/congress-trades/detail.tsx +43 -1
- package/src/plugins/builtin/congress-trades/model.test.ts +85 -0
- package/src/plugins/builtin/congress-trades/model.ts +69 -1
- package/src/plugins/builtin/congress-trades/pane.tsx +51 -2
- package/src/plugins/builtin/dividend-yield/client.ts +18 -0
- package/src/plugins/builtin/dividend-yield/pane.tsx +7 -6
- package/src/plugins/builtin/earnings/index.tsx +5 -3
- package/src/plugins/builtin/earnings/model.test.ts +19 -1
- package/src/plugins/builtin/earnings/model.ts +17 -15
- package/src/plugins/builtin/econ/index.test.tsx +6 -1
- package/src/plugins/builtin/holders/thirteenf-match.ts +1 -2
- package/src/plugins/builtin/insider/index.tsx +27 -11
- package/src/plugins/builtin/news/index.tsx +10 -6
- package/src/plugins/builtin/news/wire/breaking/notifications.test.ts +2 -0
- package/src/plugins/builtin/news/wire/breaking/pane.test.tsx +2 -0
- package/src/plugins/builtin/news/wire/breaking/pane.tsx +4 -1
- package/src/plugins/builtin/news/wire/index.tsx +1 -1
- package/src/plugins/builtin/news/wire/industry-pane.tsx +7 -2
- package/src/plugins/builtin/news/wire/news/footer.ts +34 -1
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +4 -1
- package/src/plugins/builtin/news/wire/news/table.tsx +8 -2
- package/src/plugins/builtin/news/wire/rss/source.test.ts +12 -0
- package/src/plugins/builtin/news/wire/rss/source.ts +1 -1
- package/src/plugins/builtin/notes/files.ts +80 -0
- package/src/plugins/builtin/notes/index.tsx +3 -0
- package/src/plugins/builtin/notes/sync.test.ts +49 -0
- package/src/plugins/builtin/notes/sync.ts +98 -0
- package/src/plugins/builtin/portfolio-list/account-metrics.test.ts +20 -0
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +11 -6
- package/src/plugins/builtin/portfolio-list/pane/index.test.tsx +1 -0
- package/src/plugins/builtin/portfolio-list/pane/streaming.ts +6 -1
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +14 -5
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +5 -12
- package/src/plugins/builtin/research/equity-diagnostic-pane.test.tsx +303 -0
- package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +485 -0
- package/src/plugins/builtin/research/index.test.ts +14 -0
- package/src/plugins/builtin/research/index.tsx +25 -0
- package/src/plugins/builtin/sec/index.tsx +24 -9
- package/src/plugins/builtin/sectors/index.tsx +14 -0
- package/src/plugins/builtin/shared/cloud-upgrade.ts +18 -11
- package/src/plugins/builtin/shared/public-share.ts +23 -0
- package/src/plugins/builtin/substack/api/cache.ts +7 -3
- package/src/plugins/builtin/substack/api/loaders.ts +36 -12
- package/src/plugins/builtin/substack/api/types.ts +2 -0
- package/src/plugins/builtin/substack/pane-state.ts +1 -1
- package/src/plugins/builtin/substack/pane.tsx +55 -12
- package/src/plugins/builtin/thirteenf/api.test.ts +1 -1
- package/src/plugins/builtin/thirteenf/api.ts +40 -18
- package/src/plugins/builtin/thirteenf/data.ts +15 -7
- package/src/plugins/builtin/thirteenf/pane.tsx +45 -10
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +12 -4
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +8 -2
- package/src/plugins/builtin/ticker-detail/settings.ts +12 -0
- package/src/plugins/catalog-browser.test.ts +68 -0
- package/src/plugins/catalog-browser.ts +134 -0
- package/src/plugins/ibkr/flex/index.test.ts +19 -0
- package/src/plugins/ibkr/flex/index.ts +1 -1
- package/src/plugins/prediction-markets/controller/catalog.ts +77 -2
- package/src/plugins/prediction-markets/controller/data.ts +7 -1
- package/src/plugins/prediction-markets/controller/index.ts +4 -1
- package/src/plugins/prediction-markets/pane.tsx +8 -0
- package/src/plugins/prediction-markets/services/kalshi/adapter.ts +49 -9
- package/src/plugins/prediction-markets/services/polymarket/adapter.ts +31 -0
- package/src/renderers/browser/app-services.ts +61 -0
- package/src/renderers/browser/cloud-transport.test.ts +32 -0
- package/src/renderers/browser/cloud-transport.ts +29 -0
- package/src/renderers/browser/config-host.ts +62 -0
- package/src/renderers/browser/error-boundary.tsx +23 -0
- package/src/renderers/browser/main.tsx +61 -0
- package/src/renderers/browser/persistence.ts +74 -0
- package/src/renderers/browser/storage.test.ts +66 -0
- package/src/renderers/browser/storage.ts +50 -0
- package/src/renderers/browser/ticker-repository.ts +59 -0
- package/src/renderers/browser/ui-host.tsx +40 -0
- package/src/renderers/cloudflare/worker.test.ts +133 -0
- package/src/renderers/cloudflare/worker.ts +115 -0
- package/src/renderers/electrobun/view/app-services.ts +14 -3
- package/src/renderers/electrobun/view/build-assets.ts +12 -10
- package/src/renderers/electrobun/view/data-table/index.tsx +10 -1
- package/src/renderers/electrobun/view/desktop/controls.tsx +2 -0
- package/src/renderers/electrobun/view/dom-ui-host.tsx +143 -0
- package/src/renderers/electrobun/view/host/box.test.tsx +33 -0
- package/src/renderers/electrobun/view/host/box.tsx +11 -6
- package/src/renderers/electrobun/view/host/chart-surface.tsx +7 -1
- package/src/renderers/electrobun/view/host/input.tsx +11 -7
- package/src/renderers/electrobun/view/main.tsx +2 -0
- package/src/renderers/electrobun/view/ui-host.tsx +3 -137
- package/src/renderers/opentui/start.tsx +2 -0
- package/src/renderers/share/main.tsx +54 -0
- package/src/renderers/share/styles.css +19 -0
- package/src/renderers/share/view.test.tsx +22 -0
- package/src/renderers/share/view.tsx +79 -0
- package/src/shares/api.test.ts +56 -0
- package/src/shares/api.ts +103 -0
- package/src/shares/chart-snapshot.test.ts +34 -0
- package/src/shares/chart-snapshot.ts +28 -0
- package/src/shares/payload.ts +104 -0
- package/src/sources/gloomberb-cloud/index.test.ts +61 -1
- package/src/sources/gloomberb-cloud/index.ts +86 -14
- package/src/sources/gloomberb-cloud/normalizers.test.ts +34 -0
- package/src/sources/gloomberb-cloud/normalizers.ts +5 -5
- package/src/sources/provider-router/history.test.ts +2 -2
- package/src/sources/provider-router/news.test.ts +34 -0
- package/src/sources/provider-router/news.ts +15 -1
- package/src/sources/sec-edgar.test.ts +59 -0
- package/src/sources/sec-edgar.ts +63 -23
- package/src/sources/yahoo-finance/mappers.ts +3 -0
- package/src/sources/yahoo-finance/snapshots.ts +10 -2
- package/src/sources/yahoo-finance/symbols.test.ts +10 -0
- package/src/sources/yahoo-finance/symbols.ts +13 -8
- package/src/sources/yahoo-finance.test.ts +1 -1
- package/src/sources/yahoo-finance.ts +6 -2
- package/src/sync/baseline.ts +32 -0
- package/src/sync/controller.test.ts +67 -0
- package/src/sync/controller.ts +32 -1
- package/src/sync/core-contributors.test.ts +44 -0
- package/src/sync/core-contributors.ts +59 -10
- package/src/sync/react.ts +9 -2
- package/src/sync/types.ts +12 -0
- package/src/tickers/search/index.test.ts +27 -0
- package/src/tickers/search/upsert.ts +10 -0
- package/src/types/capability-route-source.ts +2 -1
- package/src/ui/host.tsx +4 -0
- package/src/utils/price-history.ts +11 -10
- package/src/utils/sec.test.ts +6 -0
- package/src/utils/sec.ts +1 -1
package/src/api-client/paths.ts
CHANGED
|
@@ -19,7 +19,9 @@ export type CloudFredSeriesParams = {
|
|
|
19
19
|
export type CloudCongressHouseParams = {
|
|
20
20
|
year?: number;
|
|
21
21
|
limit?: number;
|
|
22
|
+
offset?: number;
|
|
22
23
|
filingLimit?: number;
|
|
24
|
+
filingOffset?: number;
|
|
23
25
|
member?: string;
|
|
24
26
|
ticker?: string;
|
|
25
27
|
refresh?: boolean;
|
|
@@ -60,6 +62,23 @@ export type CloudTweetSearchParams = {
|
|
|
60
62
|
hours?: number;
|
|
61
63
|
};
|
|
62
64
|
|
|
65
|
+
const LOGO_SYMBOL_RE = /^[A-Z0-9][A-Z0-9._^:-]{0,23}$/;
|
|
66
|
+
const CRYPTO_QUOTE = /[-/](USD|USDT|USDC|EUR|GBP|BTC)$/;
|
|
67
|
+
|
|
68
|
+
export type CloudLogoKind = "ticker" | "crypto";
|
|
69
|
+
|
|
70
|
+
export function normalizeCloudLogoSymbol(kind: CloudLogoKind, symbol: string): string | null {
|
|
71
|
+
let normalized = symbol.trim().toUpperCase();
|
|
72
|
+
if (kind === "crypto") normalized = normalized.replace(CRYPTO_QUOTE, "");
|
|
73
|
+
return LOGO_SYMBOL_RE.test(normalized) ? normalized : null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function cloudLogoPath(kind: CloudLogoKind, symbol: string): string | null {
|
|
77
|
+
const normalized = normalizeCloudLogoSymbol(kind, symbol);
|
|
78
|
+
if (!normalized) return null;
|
|
79
|
+
return `/cloud/logos/${kind}/${encodeURIComponent(normalized)}`;
|
|
80
|
+
}
|
|
81
|
+
|
|
63
82
|
function appendQuery(path: string, search: URLSearchParams): string {
|
|
64
83
|
const query = search.toString();
|
|
65
84
|
return `${path}${query ? `?${query}` : ""}`;
|
|
@@ -118,17 +137,71 @@ export function cloudFredSeriesPath(seriesId: string, params: CloudFredSeriesPar
|
|
|
118
137
|
return appendQuery(`/cloud/econ/series/${encodeURIComponent(seriesId)}`, search);
|
|
119
138
|
}
|
|
120
139
|
|
|
140
|
+
export type CloudSecFilingsParams = {
|
|
141
|
+
ticker: string;
|
|
142
|
+
limit?: number;
|
|
143
|
+
offset?: number;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export type CloudSecFilingParams = {
|
|
147
|
+
cik?: string;
|
|
148
|
+
accession?: string;
|
|
149
|
+
form?: string;
|
|
150
|
+
primaryDocument?: string;
|
|
151
|
+
primaryDocumentUrl?: string;
|
|
152
|
+
filingUrl?: string;
|
|
153
|
+
};
|
|
154
|
+
|
|
121
155
|
export function cloudCongressHousePath(params: CloudCongressHouseParams = {}): string {
|
|
122
156
|
const search = new URLSearchParams();
|
|
123
157
|
if (params.year != null) search.set("year", String(params.year));
|
|
124
158
|
if (params.limit != null) search.set("limit", String(params.limit));
|
|
159
|
+
if (params.offset != null) search.set("offset", String(params.offset));
|
|
125
160
|
if (params.filingLimit != null) search.set("filingLimit", String(params.filingLimit));
|
|
161
|
+
if (params.filingOffset != null) search.set("filingOffset", String(params.filingOffset));
|
|
126
162
|
if (params.member) search.set("member", params.member);
|
|
127
163
|
if (params.ticker) search.set("ticker", params.ticker);
|
|
128
164
|
if (params.refresh != null) search.set("refresh", String(params.refresh));
|
|
129
165
|
return appendQuery("/cloud/congress/house", search);
|
|
130
166
|
}
|
|
131
167
|
|
|
168
|
+
export function cloudSecFilingsPath(params: CloudSecFilingsParams): string {
|
|
169
|
+
const search = new URLSearchParams({ ticker: params.ticker });
|
|
170
|
+
if (params.limit != null) search.set("limit", String(params.limit));
|
|
171
|
+
if (params.offset != null) search.set("offset", String(params.offset));
|
|
172
|
+
return appendQuery("/cloud/sec/filings", search);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function cloudSecFilingDocumentsPath(params: CloudSecFilingParams): string {
|
|
176
|
+
const search = new URLSearchParams();
|
|
177
|
+
if (params.cik) search.set("cik", params.cik);
|
|
178
|
+
if (params.accession) search.set("accession", params.accession);
|
|
179
|
+
if (params.form) search.set("form", params.form);
|
|
180
|
+
if (params.primaryDocument) search.set("primaryDocument", params.primaryDocument);
|
|
181
|
+
if (params.filingUrl) search.set("filingUrl", params.filingUrl);
|
|
182
|
+
return appendQuery("/cloud/sec/filing/documents", search);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function cloudSecFilingContentPath(params: CloudSecFilingParams): string {
|
|
186
|
+
const search = new URLSearchParams();
|
|
187
|
+
if (params.cik) search.set("cik", params.cik);
|
|
188
|
+
if (params.accession) search.set("accession", params.accession);
|
|
189
|
+
if (params.form) search.set("form", params.form);
|
|
190
|
+
if (params.primaryDocument) search.set("primaryDocument", params.primaryDocument);
|
|
191
|
+
if (params.primaryDocumentUrl) search.set("primaryDocumentUrl", params.primaryDocumentUrl);
|
|
192
|
+
if (params.filingUrl) search.set("filingUrl", params.filingUrl);
|
|
193
|
+
return appendQuery("/cloud/sec/filing/content", search);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function cloudSec13FPath(path: string, params: Record<string, string | number | undefined> = {}): string {
|
|
197
|
+
const search = new URLSearchParams();
|
|
198
|
+
for (const [key, value] of Object.entries(params)) {
|
|
199
|
+
if (value !== undefined) search.set(key, String(value));
|
|
200
|
+
}
|
|
201
|
+
const normalized = path.startsWith("/") ? path.slice(1) : path;
|
|
202
|
+
return appendQuery(`/cloud/sec/13f/${normalized}`, search);
|
|
203
|
+
}
|
|
204
|
+
|
|
132
205
|
export function cloudNewsPath(params: CloudNewsParams = {}): string {
|
|
133
206
|
const search = new URLSearchParams();
|
|
134
207
|
if (params.feed) search.set("feed", params.feed);
|
|
@@ -24,10 +24,14 @@ export function setCloudApiFetchTransport(transport: CloudApiFetchTransport | nu
|
|
|
24
24
|
|
|
25
25
|
declare const __GLOOMBERB_API_URL__: string | undefined;
|
|
26
26
|
|
|
27
|
-
function getCloudApiBaseUrl(): string {
|
|
27
|
+
export function getCloudApiBaseUrl(): string {
|
|
28
28
|
// Browser bundles have no `process`; the build replaces this with a literal.
|
|
29
29
|
const bundled = typeof __GLOOMBERB_API_URL__ === "string" ? __GLOOMBERB_API_URL__ : "";
|
|
30
|
-
if (bundled)
|
|
30
|
+
if (bundled) {
|
|
31
|
+
return typeof location !== "undefined" && bundled === location.origin
|
|
32
|
+
? `${bundled}/api`
|
|
33
|
+
: bundled;
|
|
34
|
+
}
|
|
31
35
|
if (typeof process === "undefined") {
|
|
32
36
|
return DEFAULT_API_URL;
|
|
33
37
|
}
|
|
@@ -43,6 +47,7 @@ export class CloudApiRequestTransport {
|
|
|
43
47
|
private sessionToken: string | null = null;
|
|
44
48
|
private sessionCookieName: SessionCookieName | null = null;
|
|
45
49
|
private websocketToken: string | null = null;
|
|
50
|
+
private cookieSessionMode = false;
|
|
46
51
|
private readonly fetchTransport: CloudApiFetchTransport | null;
|
|
47
52
|
private readonly marketRequestTimeoutMs: number;
|
|
48
53
|
private readonly connectionHealth: ConnectionHealthRegistry;
|
|
@@ -67,6 +72,14 @@ export class CloudApiRequestTransport {
|
|
|
67
72
|
return this.websocketToken;
|
|
68
73
|
}
|
|
69
74
|
|
|
75
|
+
hasSessionCredential(): boolean {
|
|
76
|
+
return this.cookieSessionMode || !!this.sessionToken;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
setCookieSessionMode(enabled: boolean): void {
|
|
80
|
+
this.cookieSessionMode = enabled;
|
|
81
|
+
}
|
|
82
|
+
|
|
70
83
|
setSessionToken(token: string | null): void {
|
|
71
84
|
if (this.sessionToken !== token) {
|
|
72
85
|
this.sessionCookieName = null;
|
|
@@ -50,6 +50,7 @@ describe("CloudApiSocket connection health", () => {
|
|
|
50
50
|
const socket = new CloudApiSocket({
|
|
51
51
|
getBaseUrl: () => `http://127.0.0.1:${server.port}`,
|
|
52
52
|
getSocketAuthToken: () => null,
|
|
53
|
+
hasSessionCredential: () => false,
|
|
53
54
|
hasVerifiedUser: () => false,
|
|
54
55
|
isUsingWebSocketToken: () => false,
|
|
55
56
|
clearWebSocketTokenForFallback: () => false,
|
package/src/api-client/socket.ts
CHANGED
|
@@ -49,6 +49,7 @@ function mergeQuoteStreamSubscriptions(
|
|
|
49
49
|
type CloudApiSocketDelegate = {
|
|
50
50
|
getBaseUrl: () => string;
|
|
51
51
|
getSocketAuthToken: () => string | null;
|
|
52
|
+
hasSessionCredential: () => boolean;
|
|
52
53
|
hasVerifiedUser: () => boolean;
|
|
53
54
|
isUsingWebSocketToken: () => boolean;
|
|
54
55
|
clearWebSocketTokenForFallback: () => boolean;
|
|
@@ -427,7 +428,7 @@ export class CloudApiSocket {
|
|
|
427
428
|
|
|
428
429
|
private shouldKeepSocketOpen(): boolean {
|
|
429
430
|
if (this.quoteTargets.size > 0 || this.scannerListeners.size > 0) return true;
|
|
430
|
-
return
|
|
431
|
+
return this.delegate.hasSessionCredential()
|
|
431
432
|
&& this.delegate.hasVerifiedUser()
|
|
432
433
|
&& this.channelListeners.size > 0;
|
|
433
434
|
}
|
package/src/api-client/types.ts
CHANGED
|
@@ -399,10 +399,66 @@ export interface CloudCongressHousePayload {
|
|
|
399
399
|
indexUpdatedAt: string | null;
|
|
400
400
|
filingsScanned: number;
|
|
401
401
|
filingCount: number;
|
|
402
|
+
filingOffset?: number;
|
|
403
|
+
hasMore?: boolean;
|
|
404
|
+
hasMoreFilings?: boolean;
|
|
405
|
+
nextOffset?: number;
|
|
406
|
+
nextFilingOffset?: number;
|
|
402
407
|
trades: CloudCongressTradePayload[];
|
|
403
408
|
members: CloudCongressMemberPayload[];
|
|
404
409
|
}
|
|
405
410
|
|
|
411
|
+
export interface CloudSecFilingPayload {
|
|
412
|
+
accessionNumber: string;
|
|
413
|
+
form: string;
|
|
414
|
+
filingDate: string;
|
|
415
|
+
acceptedAt?: string;
|
|
416
|
+
primaryDocument?: string;
|
|
417
|
+
primaryDocDescription?: string;
|
|
418
|
+
items?: string;
|
|
419
|
+
cik: string;
|
|
420
|
+
companyName?: string;
|
|
421
|
+
filingUrl: string;
|
|
422
|
+
primaryDocumentUrl?: string;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
export interface CloudSecDocumentPayload {
|
|
426
|
+
sequence?: string;
|
|
427
|
+
type: string;
|
|
428
|
+
description?: string;
|
|
429
|
+
document: string;
|
|
430
|
+
url: string;
|
|
431
|
+
size?: string;
|
|
432
|
+
isPrimary: boolean;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
export interface CloudSecFilingsResponse {
|
|
436
|
+
filings: CloudSecFilingPayload[];
|
|
437
|
+
hasMore: boolean;
|
|
438
|
+
nextOffset: number;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
export interface CloudSecDocumentsResponse {
|
|
442
|
+
documents: CloudSecDocumentPayload[];
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
export interface CloudSecForm4Payload {
|
|
446
|
+
filingDate: string;
|
|
447
|
+
reportedName: string;
|
|
448
|
+
title: string;
|
|
449
|
+
transactionType: "P" | "S" | "A" | "D" | "";
|
|
450
|
+
shares: number;
|
|
451
|
+
pricePerShare: number | null;
|
|
452
|
+
totalValue: number | null;
|
|
453
|
+
sharesOwned: number | null;
|
|
454
|
+
form: string;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
export interface CloudSecContentResponse {
|
|
458
|
+
content: string | null;
|
|
459
|
+
form4: CloudSecForm4Payload | null;
|
|
460
|
+
}
|
|
461
|
+
|
|
406
462
|
interface CloudNewsEntityPayload {
|
|
407
463
|
id: string;
|
|
408
464
|
entityType: string;
|
|
@@ -617,6 +673,78 @@ export interface CloudMarketScreenerPayload {
|
|
|
617
673
|
items: CloudMarketScreenerItem[];
|
|
618
674
|
}
|
|
619
675
|
|
|
676
|
+
/** Cache reuse policy for one Equity Diagnostic request. */
|
|
677
|
+
export type CloudEquityDiagnosticMode = "cache-first" | "refresh";
|
|
678
|
+
|
|
679
|
+
export type CloudEquityDiagnosticFindingKind = "red_flag" | "green_flag" | "anomaly";
|
|
680
|
+
|
|
681
|
+
/** 3 is the most severe. */
|
|
682
|
+
export type CloudEquityDiagnosticSeverity = 1 | 2 | 3;
|
|
683
|
+
|
|
684
|
+
export interface CloudEquityDiagnosticFinding {
|
|
685
|
+
id: string;
|
|
686
|
+
kind: CloudEquityDiagnosticFindingKind;
|
|
687
|
+
severity: CloudEquityDiagnosticSeverity;
|
|
688
|
+
confidence: number;
|
|
689
|
+
title: string;
|
|
690
|
+
/** What the data says, with no reading attached. */
|
|
691
|
+
observation: string;
|
|
692
|
+
/** What the observation may mean; kept apart from the observation on purpose. */
|
|
693
|
+
interpretation: string;
|
|
694
|
+
/** Ids into `evidence`; unresolved ids are dropped by the client. */
|
|
695
|
+
evidenceIds: string[];
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
export interface CloudEquityDiagnosticCoverage {
|
|
699
|
+
dataset: string;
|
|
700
|
+
status: "available" | "no_data" | "unsupported" | "failed";
|
|
701
|
+
asOf?: string;
|
|
702
|
+
provider?: string;
|
|
703
|
+
note?: string;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
/** The server owns citation URLs; the client never builds them. */
|
|
707
|
+
export interface CloudEquityDiagnosticEvidence {
|
|
708
|
+
id: string;
|
|
709
|
+
dataset: string;
|
|
710
|
+
label: string;
|
|
711
|
+
asOf?: string;
|
|
712
|
+
provider?: string;
|
|
713
|
+
url?: string;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
export interface CloudEquityDiagnosticPending {
|
|
717
|
+
status: "generating";
|
|
718
|
+
retryAfterMs: number;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
export interface CloudEquityDiagnosticResponse {
|
|
722
|
+
schemaVersion: 1;
|
|
723
|
+
symbol: string;
|
|
724
|
+
exchange: string;
|
|
725
|
+
companyName?: string;
|
|
726
|
+
status: "complete" | "partial" | "insufficient_data";
|
|
727
|
+
verdict: "risk_skewed" | "balanced" | "opportunity_skewed" | "unclear";
|
|
728
|
+
summary: string;
|
|
729
|
+
confidence: number;
|
|
730
|
+
findings: CloudEquityDiagnosticFinding[];
|
|
731
|
+
watchItems: string[];
|
|
732
|
+
coverage: CloudEquityDiagnosticCoverage[];
|
|
733
|
+
evidence: CloudEquityDiagnosticEvidence[];
|
|
734
|
+
generatedAt: string;
|
|
735
|
+
expiresAt: string;
|
|
736
|
+
/** Before this instant a `refresh` request may still answer from cache. */
|
|
737
|
+
refreshAllowedAt: string;
|
|
738
|
+
cached: boolean;
|
|
739
|
+
stale: boolean;
|
|
740
|
+
promptVersion: 1;
|
|
741
|
+
model: "gpt-5.6-luna";
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
export type CloudEquityDiagnosticResult =
|
|
745
|
+
| CloudEquityDiagnosticPending
|
|
746
|
+
| CloudEquityDiagnosticResponse;
|
|
747
|
+
|
|
620
748
|
export interface CloudVerificationResponse {
|
|
621
749
|
sent: boolean;
|
|
622
750
|
email?: string;
|
|
@@ -13,6 +13,7 @@ import { VERSION } from "../../version";
|
|
|
13
13
|
const UPDATE_CHECK_INTERVAL_MS = 60 * 60_000; // hourly
|
|
14
14
|
|
|
15
15
|
export function useAppUpdateRuntime({
|
|
16
|
+
enabled = true,
|
|
16
17
|
dispatch,
|
|
17
18
|
isDetachedWindow,
|
|
18
19
|
pluginRegistry,
|
|
@@ -21,6 +22,7 @@ export function useAppUpdateRuntime({
|
|
|
21
22
|
updateCheckInProgress,
|
|
22
23
|
updateProgress,
|
|
23
24
|
}: {
|
|
25
|
+
enabled?: boolean;
|
|
24
26
|
dispatch: Dispatch<AppAction>;
|
|
25
27
|
isDetachedWindow: boolean;
|
|
26
28
|
pluginRegistry: PluginRegistry;
|
|
@@ -76,15 +78,15 @@ export function useAppUpdateRuntime({
|
|
|
76
78
|
}, [dispatch]);
|
|
77
79
|
|
|
78
80
|
useEffect(() => {
|
|
79
|
-
if (isDetachedWindow) return;
|
|
81
|
+
if (!enabled || isDetachedWindow) return;
|
|
80
82
|
void runUpdateCheck(false);
|
|
81
83
|
const interval = setInterval(() => { void runUpdateCheck(false); }, UPDATE_CHECK_INTERVAL_MS);
|
|
82
84
|
return () => { clearInterval(interval); };
|
|
83
|
-
}, [isDetachedWindow, runUpdateCheck]);
|
|
85
|
+
}, [enabled, isDetachedWindow, runUpdateCheck]);
|
|
84
86
|
|
|
85
87
|
// First launch on a new version: show that release's notes, then remember the version.
|
|
86
88
|
useEffect(() => {
|
|
87
|
-
if (isDetachedWindow) return;
|
|
89
|
+
if (!enabled || isDetachedWindow) return;
|
|
88
90
|
const config = stateRef.current.config;
|
|
89
91
|
if (config.lastLaunchedVersion === VERSION) return;
|
|
90
92
|
const isUpgrade = !!config.lastLaunchedVersion;
|
|
@@ -95,14 +97,14 @@ export function useAppUpdateRuntime({
|
|
|
95
97
|
void pluginRegistry.createPaneFromTemplateAsyncFn("changelog-pane", {
|
|
96
98
|
values: { version: VERSION },
|
|
97
99
|
}).catch(() => {});
|
|
98
|
-
}, [dispatch, isDetachedWindow, pluginRegistry, stateRef]);
|
|
100
|
+
}, [dispatch, enabled, isDetachedWindow, pluginRegistry, stateRef]);
|
|
99
101
|
|
|
100
102
|
useEffect(() => {
|
|
101
|
-
if (isDetachedWindow) return;
|
|
103
|
+
if (!enabled || isDetachedWindow) return;
|
|
102
104
|
if (!updateAvailable || updateProgress || updateCheckInProgress) return;
|
|
103
105
|
if (!canSelfUpdate(updateAvailable)) return;
|
|
104
106
|
startUpdate(updateAvailable);
|
|
105
|
-
}, [isDetachedWindow, startUpdate, updateAvailable, updateCheckInProgress, updateProgress]);
|
|
107
|
+
}, [enabled, isDetachedWindow, startUpdate, updateAvailable, updateCheckInProgress, updateProgress]);
|
|
106
108
|
|
|
107
109
|
return {
|
|
108
110
|
runUpdateCheck,
|
package/src/app.tsx
CHANGED
|
@@ -24,7 +24,7 @@ import type { AppServicesFactory, AppTickerRepositoryPort } from "./core/app-ser
|
|
|
24
24
|
import { useThemeColors } from "./theme/theme-context";
|
|
25
25
|
import type { AppConfig } from "./types/config";
|
|
26
26
|
import type { DesktopDeepLinkBridge } from "./types/desktop-deeplink";
|
|
27
|
-
import type { CliLaunchRequest } from "./types/plugin";
|
|
27
|
+
import type { CliLaunchRequest, GloomPlugin } from "./types/plugin";
|
|
28
28
|
import type { DataProvider } from "./types/data-provider";
|
|
29
29
|
import type { DesktopDockPreviewState, DesktopSharedStateSnapshot, DesktopThemePreviewState, DesktopWindowBridge } from "./types/desktop-window";
|
|
30
30
|
import type { DesktopApplicationMenuBridge } from "./types/desktop-menu";
|
|
@@ -32,7 +32,6 @@ import type { LayoutBounds } from "./plugins/pane-manager";
|
|
|
32
32
|
import type { AppSessionSnapshot } from "./core/state/session-persistence";
|
|
33
33
|
import type { MarketDataCoordinator } from "./market-data/coordinator";
|
|
34
34
|
import { createAppNotifier } from "./notifications/app-notifier";
|
|
35
|
-
import { getLoadablePlugins } from "./plugins/catalog";
|
|
36
35
|
import { useBrokerImportRuntime } from "./app/runtime/broker-import";
|
|
37
36
|
import { useDesktopDeepLinkRuntime } from "./app/runtime/desktop-deeplink";
|
|
38
37
|
import { useDesktopApplicationMenuRuntime } from "./app/runtime/desktop-menu";
|
|
@@ -68,6 +67,7 @@ interface AppInnerProps {
|
|
|
68
67
|
desktopApplicationMenuBridge?: DesktopApplicationMenuBridge;
|
|
69
68
|
desktopDeepLinkBridge?: DesktopDeepLinkBridge;
|
|
70
69
|
remoteControlAdapter?: RemoteControlAdapter;
|
|
70
|
+
updatesEnabled?: boolean;
|
|
71
71
|
onboardingActive?: boolean;
|
|
72
72
|
onOnboardingComplete?: (config: AppConfig) => void | Promise<void>;
|
|
73
73
|
}
|
|
@@ -100,6 +100,7 @@ function AppInner({
|
|
|
100
100
|
desktopApplicationMenuBridge,
|
|
101
101
|
desktopDeepLinkBridge,
|
|
102
102
|
remoteControlAdapter,
|
|
103
|
+
updatesEnabled = true,
|
|
103
104
|
onboardingActive = false,
|
|
104
105
|
onOnboardingComplete,
|
|
105
106
|
}: AppInnerProps) {
|
|
@@ -221,6 +222,7 @@ function AppInner({
|
|
|
221
222
|
});
|
|
222
223
|
|
|
223
224
|
const { runUpdateCheck, startUpdate } = useAppUpdateRuntime({
|
|
225
|
+
enabled: updatesEnabled,
|
|
224
226
|
dispatch,
|
|
225
227
|
isDetachedWindow,
|
|
226
228
|
pluginRegistry,
|
|
@@ -399,7 +401,7 @@ function AppInner({
|
|
|
399
401
|
tickerRepository={tickerRepository}
|
|
400
402
|
pluginRegistry={pluginRegistry}
|
|
401
403
|
quitApp={() => rendererHost.requestExit()}
|
|
402
|
-
onCheckForUpdates={() => runUpdateCheck(true)}
|
|
404
|
+
onCheckForUpdates={updatesEnabled ? () => runUpdateCheck(true) : undefined}
|
|
403
405
|
onNativeOccluderChange={setCommandBarNativeOccluder}
|
|
404
406
|
/>
|
|
405
407
|
)}
|
|
@@ -414,6 +416,7 @@ interface AppProps {
|
|
|
414
416
|
config: AppConfig;
|
|
415
417
|
servicesFactory: AppServicesFactory;
|
|
416
418
|
externalPlugins?: LoadedExternalPlugin[];
|
|
419
|
+
plugins: readonly GloomPlugin[];
|
|
417
420
|
cliLaunchRequest?: CliLaunchRequest | null;
|
|
418
421
|
desktopWindowBridge?: DesktopWindowBridge;
|
|
419
422
|
desktopApplicationMenuBridge?: DesktopApplicationMenuBridge;
|
|
@@ -421,12 +424,14 @@ interface AppProps {
|
|
|
421
424
|
desktopSnapshot?: DesktopSharedStateSnapshot | null;
|
|
422
425
|
desktopThemePreview?: DesktopThemePreviewState | null;
|
|
423
426
|
remoteControlAdapter?: RemoteControlAdapter;
|
|
427
|
+
updatesEnabled?: boolean;
|
|
424
428
|
}
|
|
425
429
|
|
|
426
430
|
export function App({
|
|
427
431
|
config: initialConfig,
|
|
428
432
|
servicesFactory,
|
|
429
433
|
externalPlugins: providedExternalPlugins,
|
|
434
|
+
plugins,
|
|
430
435
|
cliLaunchRequest = null,
|
|
431
436
|
desktopWindowBridge,
|
|
432
437
|
desktopApplicationMenuBridge,
|
|
@@ -434,6 +439,7 @@ export function App({
|
|
|
434
439
|
desktopSnapshot = null,
|
|
435
440
|
desktopThemePreview = null,
|
|
436
441
|
remoteControlAdapter,
|
|
442
|
+
updatesEnabled = true,
|
|
437
443
|
}: AppProps) {
|
|
438
444
|
useAppLanguage();
|
|
439
445
|
const externalPlugins = providedExternalPlugins ?? EMPTY_EXTERNAL_PLUGINS;
|
|
@@ -469,14 +475,14 @@ export function App({
|
|
|
469
475
|
return measurePerf("startup.app.create-services", () => (
|
|
470
476
|
servicesFactory({
|
|
471
477
|
config,
|
|
472
|
-
plugins
|
|
478
|
+
plugins,
|
|
473
479
|
})
|
|
474
480
|
), {
|
|
475
481
|
externalPluginCount: externalPlugins.length,
|
|
476
482
|
disabledPluginCount: config.disabledPlugins.length,
|
|
477
483
|
brokerInstanceCount: config.brokerInstances.length,
|
|
478
484
|
});
|
|
479
|
-
}, [config.dataDir, externalPlugins, servicesFactory]);
|
|
485
|
+
}, [config.dataDir, externalPlugins, plugins, servicesFactory]);
|
|
480
486
|
|
|
481
487
|
useEffect(() => {
|
|
482
488
|
return () => services.destroy();
|
|
@@ -514,6 +520,7 @@ export function App({
|
|
|
514
520
|
desktopApplicationMenuBridge={desktopApplicationMenuBridge}
|
|
515
521
|
desktopDeepLinkBridge={desktopDeepLinkBridge}
|
|
516
522
|
remoteControlAdapter={remoteControlAdapter}
|
|
523
|
+
updatesEnabled={updatesEnabled}
|
|
517
524
|
onboardingActive={showOnboarding}
|
|
518
525
|
onOnboardingComplete={(updatedConfig) => {
|
|
519
526
|
setConfig(updatedConfig);
|
|
@@ -61,7 +61,11 @@ describe("import boundaries", () => {
|
|
|
61
61
|
return !file.startsWith("src/renderers/electrobun/");
|
|
62
62
|
}
|
|
63
63
|
if (specifier === "react-dom" || specifier.startsWith("react-dom/")) {
|
|
64
|
-
return !
|
|
64
|
+
return ![
|
|
65
|
+
"src/renderers/electrobun/",
|
|
66
|
+
"src/renderers/browser/",
|
|
67
|
+
"src/renderers/share/",
|
|
68
|
+
].some((prefix) => file.startsWith(prefix));
|
|
65
69
|
}
|
|
66
70
|
return false;
|
|
67
71
|
});
|
|
@@ -147,6 +147,10 @@ export function newsProvider(options: {
|
|
|
147
147
|
supports: op((input: any) => options.provider.supports?.(input.query) ?? true, "query"),
|
|
148
148
|
getCachedNews: op((input: any) => options.provider.getCachedNews?.(input.query) ?? [], "query"),
|
|
149
149
|
fetchNews: op((input: any) => options.provider.fetchNews(input.query), "query"),
|
|
150
|
+
fetchNewsPage: op((input: any) => (
|
|
151
|
+
options.provider.fetchNewsPage?.(input.query)
|
|
152
|
+
?? options.provider.fetchNews(input.query).then((articles) => ({ articles, nextCursor: null }))
|
|
153
|
+
), "query"),
|
|
150
154
|
fetchNewsStory: op((input: any) => options.provider.fetchNewsStory?.(input.storyId) ?? null, "query"),
|
|
151
155
|
},
|
|
152
156
|
};
|
package/src/cli/index.ts
CHANGED
|
@@ -35,7 +35,10 @@ import {
|
|
|
35
35
|
} from "./commands/plugins";
|
|
36
36
|
import { runPaneCatalog, runPaneFunction, runPaneScreenshot } from "./pane-functions";
|
|
37
37
|
|
|
38
|
-
function createCoreCliCommands(
|
|
38
|
+
function createCoreCliCommands(
|
|
39
|
+
renderHelp: () => string,
|
|
40
|
+
allCommands: () => CliCommandDef[],
|
|
41
|
+
): CliCommandDef[] {
|
|
39
42
|
let commands: CliCommandDef[] = [];
|
|
40
43
|
const launchUiRequest: CliLaunchRequest = {
|
|
41
44
|
applyConfig: (config) => ({ config }),
|
|
@@ -53,7 +56,7 @@ function createCoreCliCommands(renderHelp: () => string): CliCommandDef[] {
|
|
|
53
56
|
console.log(renderHelp());
|
|
54
57
|
return;
|
|
55
58
|
}
|
|
56
|
-
ctx.printResult({ data:
|
|
59
|
+
ctx.printResult({ data: allCommands().map((command) => ({
|
|
57
60
|
name: command.name,
|
|
58
61
|
aliases: command.aliases ?? [],
|
|
59
62
|
description: command.description,
|
|
@@ -190,7 +193,7 @@ function createCoreCliCommands(renderHelp: () => string): CliCommandDef[] {
|
|
|
190
193
|
...marketDataCliCommands,
|
|
191
194
|
...overviewCliCommands,
|
|
192
195
|
remoteCliCommand,
|
|
193
|
-
...createSystemCliCommands(
|
|
196
|
+
...createSystemCliCommands(allCommands),
|
|
194
197
|
brokerCliCommand,
|
|
195
198
|
ibkrCliCommand,
|
|
196
199
|
aiCliCommand,
|
|
@@ -207,7 +210,10 @@ export interface DispatchCliOptions {
|
|
|
207
210
|
async function createRegistry(options: DispatchCliOptions = {}): Promise<CliCommandRegistry> {
|
|
208
211
|
const config = await loadCliConfigIfAvailable();
|
|
209
212
|
let registry: CliCommandRegistry | null = null;
|
|
210
|
-
const coreCommands = createCoreCliCommands(
|
|
213
|
+
const coreCommands = createCoreCliCommands(
|
|
214
|
+
() => renderCliHelp(registry!, VERSION),
|
|
215
|
+
() => registry!.commands.map((entry) => entry.command),
|
|
216
|
+
);
|
|
211
217
|
registry = buildCliCommandRegistry({
|
|
212
218
|
coreCommands,
|
|
213
219
|
externalPlugins: options.externalPlugins ?? [],
|
package/src/cli/registry.ts
CHANGED
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
compositeAxisTicks,
|
|
39
39
|
formatCompositeAxisValue,
|
|
40
40
|
formatCompositeCursorDate,
|
|
41
|
+
formatCompositeCursorValue,
|
|
41
42
|
formatCompositePointDetails,
|
|
42
43
|
formatCompositeSeriesValue,
|
|
43
44
|
formatCompositeTimeAxisDate,
|
|
@@ -291,7 +292,7 @@ function cursorAxisLabel(
|
|
|
291
292
|
const domain = panel.axes[side];
|
|
292
293
|
if (!domain || cursorYRatio === null) return null;
|
|
293
294
|
const value = unprojectCompositeValue(cursorYRatio, domain);
|
|
294
|
-
return value === null ? null :
|
|
295
|
+
return value === null ? null : formatCompositeCursorValue(value, domain);
|
|
295
296
|
}
|
|
296
297
|
|
|
297
298
|
const MINIMUM_AXIS_LABEL_WIDTH = 3;
|
|
@@ -707,10 +708,10 @@ function CompositePanelSurface({
|
|
|
707
708
|
domain: measureDomain,
|
|
708
709
|
}),
|
|
709
710
|
startValueLabel: measureDomain && startValue !== null
|
|
710
|
-
?
|
|
711
|
+
? formatCompositeCursorValue(startValue, measureDomain)
|
|
711
712
|
: null,
|
|
712
713
|
endValueLabel: measureDomain && endValue !== null
|
|
713
|
-
?
|
|
714
|
+
? formatCompositeCursorValue(endValue, measureDomain)
|
|
714
715
|
: null,
|
|
715
716
|
};
|
|
716
717
|
}, [measureDomain, scene, toolDrag]);
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
import type { CompositeChartScene } from "./types";
|
|
3
3
|
import {
|
|
4
|
+
formatChartLegendValue,
|
|
4
5
|
formatCompositeAxisValue,
|
|
5
6
|
formatCompositeCursorDate,
|
|
7
|
+
formatCompositeCursorValue,
|
|
6
8
|
formatCompositePointDetails,
|
|
7
9
|
formatCompositeSeriesValue,
|
|
8
10
|
formatCompositeTimeAxisDate,
|
|
@@ -147,4 +149,36 @@ describe("composite chart unit formatting", () => {
|
|
|
147
149
|
seriesIds: ["ratio"],
|
|
148
150
|
})).toBe("0.500");
|
|
149
151
|
});
|
|
152
|
+
|
|
153
|
+
test("shows full price precision in the legend and cursor, not compact axis ticks", () => {
|
|
154
|
+
const btc: ResolvedSeries = {
|
|
155
|
+
id: "btc",
|
|
156
|
+
label: "BTC-USD Price",
|
|
157
|
+
color: "#ffffff",
|
|
158
|
+
unit: "USD",
|
|
159
|
+
unitGroup: "price:USD",
|
|
160
|
+
nativeFrequency: "daily",
|
|
161
|
+
dataShape: "ohlcv",
|
|
162
|
+
style: "candles",
|
|
163
|
+
transform: "raw",
|
|
164
|
+
axis: "left",
|
|
165
|
+
panelId: "main",
|
|
166
|
+
interpolation: "none",
|
|
167
|
+
points: [],
|
|
168
|
+
};
|
|
169
|
+
const domain = {
|
|
170
|
+
side: "left" as const,
|
|
171
|
+
min: 70_000,
|
|
172
|
+
max: 80_000,
|
|
173
|
+
scale: "linear" as const,
|
|
174
|
+
unit: "USD",
|
|
175
|
+
unitGroup: "price:USD",
|
|
176
|
+
seriesIds: ["btc"],
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
expect(formatCompositeSeriesValue(79_432.18, btc)).toBe("$79,432.18");
|
|
180
|
+
expect(formatChartLegendValue(79_432.18, "USD", "price:USD")).toBe("$79,432.18");
|
|
181
|
+
expect(formatCompositeCursorValue(79_432.18, domain)).toBe("$79,432.18");
|
|
182
|
+
expect(formatCompositeAxisValue(79_432.18, domain)).toBe("$79K");
|
|
183
|
+
});
|
|
150
184
|
});
|