gloomberb 0.9.8 → 0.9.10
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/index.test.ts +3 -3
- package/src/api-client/index.ts +0 -6
- package/src/api-client/request.test.ts +63 -0
- package/src/api-client/request.ts +53 -5
- package/src/api-client/types.ts +0 -2
- package/src/app/pane-runtime/layout-placement.ts +0 -1
- package/src/app.tsx +1 -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/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/stock-chart/data-runtime.ts +0 -1
- 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/panel/runtime.ts +0 -2
- package/src/components/command-bar/routes/ticker-search/results.ts +0 -11
- package/src/components/command-bar/surface/index.test.tsx +0 -16
- package/src/components/command-bar/surface/index.tsx +0 -1
- 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/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/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/index.test.ts +0 -10
- package/src/market-data/coordinator/index.ts +1 -18
- package/src/market-data/coordinator/quotes.ts +0 -6
- package/src/market-data/coordinator/subscriptions.test.ts +1 -0
- package/src/market-data/market/status.ts +19 -1
- package/src/market-data/query-store.ts +0 -4
- package/src/market-data/quotes/resolution.ts +2 -2
- package/src/market-data/request-types.ts +0 -5
- 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/form-components.tsx +0 -120
- package/src/plugins/builtin/account-management/model.ts +3 -27
- 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 +2 -1
- package/src/plugins/builtin/chat/content/index.tsx +1 -2
- package/src/plugins/builtin/chat/content/transcript.tsx +0 -1
- 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/sidebar.test.tsx +15 -5
- package/src/plugins/builtin/chat/test-harness.tsx +2 -2
- package/src/plugins/builtin/cloud/plugin.tsx +1 -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/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 +1 -24
- package/src/plugins/builtin/portfolio-list/metrics.test.ts +44 -7
- 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 +1 -1
- package/src/plugins/builtin/ticker-detail/financials/tab.tsx +0 -7
- 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/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/contributions.ts +22 -11
- package/src/plugins/registry/index.test.ts +44 -0
- package/src/plugins/registry/index.ts +44 -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/view/cli-pane-shot-entry.tsx +1 -0
- package/src/renderers/electrobun/view/data-table/dom.ts +0 -1
- package/src/renderers/electrobun/view/host/mouse.ts +0 -11
- package/src/renderers/electrobun/view/http-fetch.ts +15 -2
- 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/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/financial-routes.ts +25 -3
- package/src/sources/provider-router/index.test.ts +167 -0
- package/src/sources/provider-router/search.ts +5 -0
- package/src/sources/provider-router/supplemental.ts +4 -3
- package/src/sources/yahoo-finance/history.ts +5 -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.test.ts +40 -21
- package/src/sources/yahoo-finance.ts +13 -8
- package/src/state/app/context/index.test.tsx +0 -81
- package/src/state/app/context/selector.test.tsx +6 -2
- package/src/sync/controller.test.ts +151 -0
- package/src/sync/controller.ts +92 -57
- package/src/sync/core-contributors.ts +45 -21
- package/src/sync/react.ts +6 -8
- package/src/sync/types.ts +3 -0
- package/src/test-support/data-provider.ts +0 -1
- package/src/ui/host.tsx +2 -1
- package/src/ui/index.tsx +1 -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/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
package/src/cli.test.ts
CHANGED
|
@@ -6,14 +6,11 @@ import { AppPersistence } from "./data/app-persistence";
|
|
|
6
6
|
import { loadConfig, saveConfig } from "./data/config/store";
|
|
7
7
|
import {
|
|
8
8
|
buildSearchReport,
|
|
9
|
-
buildTickerReport,
|
|
10
9
|
runCli,
|
|
11
10
|
searchCandidatesForCli,
|
|
12
11
|
} from "./cli/index";
|
|
13
12
|
import { createDefaultConfig } from "./types/config";
|
|
14
13
|
import { TickerRepository } from "./data/ticker-repository";
|
|
15
|
-
import type { TickerFinancials } from "./types/financials";
|
|
16
|
-
import type { NewsItem, SecFilingItem } from "./types/data-provider";
|
|
17
14
|
import type { TickerRecord } from "./types/ticker";
|
|
18
15
|
import { createTestDataProvider } from "./test-support/data-provider";
|
|
19
16
|
|
|
@@ -375,160 +372,6 @@ describe("CLI portfolio commands", () => {
|
|
|
375
372
|
});
|
|
376
373
|
});
|
|
377
374
|
|
|
378
|
-
describe("buildTickerReport", () => {
|
|
379
|
-
test("includes deeper fundamentals and recent statement sections", async () => {
|
|
380
|
-
const config = createDefaultConfig("/tmp/gloomberb-cli-report");
|
|
381
|
-
config.watchlists = [{ id: "growth", name: "Growth" }];
|
|
382
|
-
|
|
383
|
-
const ticker = makeTicker({
|
|
384
|
-
portfolios: ["main"],
|
|
385
|
-
watchlists: ["growth"],
|
|
386
|
-
positions: [{
|
|
387
|
-
portfolio: "main",
|
|
388
|
-
shares: 10,
|
|
389
|
-
avgCost: 400,
|
|
390
|
-
broker: "manual",
|
|
391
|
-
}],
|
|
392
|
-
assetCategory: "STK",
|
|
393
|
-
sector: "Technology",
|
|
394
|
-
industry: "Semiconductors",
|
|
395
|
-
});
|
|
396
|
-
|
|
397
|
-
const financials: TickerFinancials = {
|
|
398
|
-
quote: {
|
|
399
|
-
symbol: "NVDA",
|
|
400
|
-
price: 912.34,
|
|
401
|
-
currency: "USD",
|
|
402
|
-
change: 12.5,
|
|
403
|
-
changePercent: 1.39,
|
|
404
|
-
lastUpdated: Date.UTC(2026, 3, 1, 14, 30),
|
|
405
|
-
marketCap: 2_240_000_000_000,
|
|
406
|
-
volume: 48_000_000,
|
|
407
|
-
name: "NVIDIA Corporation",
|
|
408
|
-
exchangeName: "NMS",
|
|
409
|
-
marketState: "REGULAR",
|
|
410
|
-
bid: 912.3,
|
|
411
|
-
ask: 912.4,
|
|
412
|
-
bidSize: 100,
|
|
413
|
-
askSize: 200,
|
|
414
|
-
open: 905,
|
|
415
|
-
high: 918,
|
|
416
|
-
low: 901,
|
|
417
|
-
high52w: 980,
|
|
418
|
-
low52w: 500,
|
|
419
|
-
dataSource: "live",
|
|
420
|
-
},
|
|
421
|
-
fundamentals: {
|
|
422
|
-
trailingPE: 35.2,
|
|
423
|
-
forwardPE: 29.8,
|
|
424
|
-
pegRatio: 1.4,
|
|
425
|
-
enterpriseValue: 2_300_000_000_000,
|
|
426
|
-
operatingCashFlow: 82_000_000_000,
|
|
427
|
-
freeCashFlow: 67_000_000_000,
|
|
428
|
-
dividendYield: 0.001,
|
|
429
|
-
revenue: 128_000_000_000,
|
|
430
|
-
netIncome: 73_000_000_000,
|
|
431
|
-
eps: 2.97,
|
|
432
|
-
operatingMargin: 0.58,
|
|
433
|
-
profitMargin: 0.57,
|
|
434
|
-
revenueGrowth: 0.42,
|
|
435
|
-
return1Y: 0.88,
|
|
436
|
-
return3Y: 2.45,
|
|
437
|
-
lastQuarterGrowth: 0.61,
|
|
438
|
-
sharesOutstanding: 2_450_000_000,
|
|
439
|
-
},
|
|
440
|
-
profile: {
|
|
441
|
-
description: "Designs GPUs and accelerated computing platforms.",
|
|
442
|
-
sector: "Technology",
|
|
443
|
-
industry: "Semiconductors",
|
|
444
|
-
},
|
|
445
|
-
annualStatements: [{
|
|
446
|
-
date: "2025-12-31",
|
|
447
|
-
totalRevenue: 128_000_000_000,
|
|
448
|
-
grossProfit: 97_000_000_000,
|
|
449
|
-
operatingIncome: 76_000_000_000,
|
|
450
|
-
netIncome: 73_000_000_000,
|
|
451
|
-
ebitda: 79_000_000_000,
|
|
452
|
-
operatingCashFlow: 82_000_000_000,
|
|
453
|
-
freeCashFlow: 67_000_000_000,
|
|
454
|
-
cashAndCashEquivalents: 40_000_000_000,
|
|
455
|
-
totalAssets: 110_000_000_000,
|
|
456
|
-
totalLiabilities: 36_000_000_000,
|
|
457
|
-
totalDebt: 9_000_000_000,
|
|
458
|
-
totalEquity: 74_000_000_000,
|
|
459
|
-
eps: 11.84,
|
|
460
|
-
dilutedShares: 2_450_000_000,
|
|
461
|
-
}],
|
|
462
|
-
quarterlyStatements: [{
|
|
463
|
-
date: "2026-03-31",
|
|
464
|
-
totalRevenue: 38_000_000_000,
|
|
465
|
-
grossProfit: 28_000_000_000,
|
|
466
|
-
operatingIncome: 22_000_000_000,
|
|
467
|
-
netIncome: 21_000_000_000,
|
|
468
|
-
ebitda: 23_000_000_000,
|
|
469
|
-
operatingCashFlow: 24_000_000_000,
|
|
470
|
-
freeCashFlow: 19_000_000_000,
|
|
471
|
-
cashAndCashEquivalents: 42_000_000_000,
|
|
472
|
-
totalAssets: 118_000_000_000,
|
|
473
|
-
totalLiabilities: 38_000_000_000,
|
|
474
|
-
totalDebt: 9_000_000_000,
|
|
475
|
-
totalEquity: 80_000_000_000,
|
|
476
|
-
eps: 3.4,
|
|
477
|
-
dilutedShares: 2_455_000_000,
|
|
478
|
-
}],
|
|
479
|
-
priceHistory: [],
|
|
480
|
-
};
|
|
481
|
-
|
|
482
|
-
const recentNews: NewsItem[] = [{
|
|
483
|
-
title: "NVIDIA unveils next platform",
|
|
484
|
-
source: "Example News",
|
|
485
|
-
url: "https://example.com/nvda-platform",
|
|
486
|
-
publishedAt: "2026-04-01T15:45:00.000Z" as unknown as Date,
|
|
487
|
-
summary: "Analysts expect the launch to expand datacenter demand.",
|
|
488
|
-
}];
|
|
489
|
-
|
|
490
|
-
const recentSecFilings: SecFilingItem[] = [{
|
|
491
|
-
accessionNumber: "0000000000-26-000001",
|
|
492
|
-
form: "8-K",
|
|
493
|
-
filingDate: "2026-03-31T00:00:00.000Z" as unknown as Date,
|
|
494
|
-
cik: "0001045810",
|
|
495
|
-
filingUrl: "https://www.sec.gov/Archives/example-8k",
|
|
496
|
-
primaryDocument: "nvda-8k.htm",
|
|
497
|
-
primaryDocDescription: "Current report announcing a product launch",
|
|
498
|
-
items: "2.02, 7.01",
|
|
499
|
-
}];
|
|
500
|
-
|
|
501
|
-
const report = await buildTickerReport({
|
|
502
|
-
symbol: "NVDA",
|
|
503
|
-
tickerFile: ticker,
|
|
504
|
-
financials,
|
|
505
|
-
config,
|
|
506
|
-
toBase: async (value) => value,
|
|
507
|
-
notes: "Conviction remains high.\nWatch gross margin guidance.",
|
|
508
|
-
recentNews,
|
|
509
|
-
recentSecFilings,
|
|
510
|
-
});
|
|
511
|
-
|
|
512
|
-
expect(report).toContain("Fundamentals");
|
|
513
|
-
expect(report).toContain("(+1.39%)");
|
|
514
|
-
expect(report).not.toContain("++1.39%");
|
|
515
|
-
expect(report).toContain("Operating Cash Flow");
|
|
516
|
-
expect(report).toContain("Latest Annual (2025-12-31)");
|
|
517
|
-
expect(report).toContain("Latest Quarter (2026-03-31)");
|
|
518
|
-
expect(report).toContain("Watchlists Growth");
|
|
519
|
-
expect(report).toContain("Designs GPUs and accelerated computing platforms.");
|
|
520
|
-
expect(report).toContain("Notes");
|
|
521
|
-
expect(report).toContain("Conviction remains high.");
|
|
522
|
-
expect(report).toContain("Recent News");
|
|
523
|
-
expect(report).toContain("NVIDIA unveils next platform");
|
|
524
|
-
expect(report).toContain("Example News");
|
|
525
|
-
expect(report).toContain("Apr 1, 2026");
|
|
526
|
-
expect(report).toContain("Recent SEC Filings");
|
|
527
|
-
expect(report).toContain("8-K | Mar 31, 2026");
|
|
528
|
-
expect(report).toContain("Current report announcing a product launch");
|
|
529
|
-
});
|
|
530
|
-
});
|
|
531
|
-
|
|
532
375
|
describe("CLI search helpers", () => {
|
|
533
376
|
test("merges local ticker matches with provider company search results", async () => {
|
|
534
377
|
const candidates = await searchCandidatesForCli({
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { useMemo } from "react";
|
|
2
|
-
import type { DisplayCursorState } from "../core/pointer";
|
|
3
2
|
import type { ComparisonChartProjection } from "../comparison/data";
|
|
4
3
|
import {
|
|
5
4
|
buildComparisonChartScene,
|
|
@@ -8,16 +7,14 @@ import {
|
|
|
8
7
|
type ComparisonChartScene,
|
|
9
8
|
type RenderComparisonChartResult,
|
|
10
9
|
} from "../comparison/renderer";
|
|
11
|
-
import type {
|
|
10
|
+
import type { ChartMarketSession, ResolvedChartRenderer } from "../core/types";
|
|
12
11
|
|
|
13
12
|
type ComparisonChartColors = Parameters<typeof buildComparisonChartScene>[1]["colors"];
|
|
14
13
|
|
|
15
14
|
interface UseComparisonChartRenderOutputOptions {
|
|
16
|
-
axisMode: ChartAxisMode;
|
|
17
15
|
chartColors: ComparisonChartColors;
|
|
18
16
|
chartHeight: number;
|
|
19
17
|
chartWidth: number;
|
|
20
|
-
displayCursor: DisplayCursorState;
|
|
21
18
|
displayCursorX: number | null;
|
|
22
19
|
displayCursorY: number | null;
|
|
23
20
|
effectiveRenderer: ResolvedChartRenderer;
|
|
@@ -34,22 +31,20 @@ interface UseComparisonChartRenderOutputResult {
|
|
|
34
31
|
cursorRow: number | null;
|
|
35
32
|
cursorTimeAxisColumn: number | null;
|
|
36
33
|
cursorTimeAxisDate: Date | null;
|
|
37
|
-
displayScene: ComparisonChartScene;
|
|
34
|
+
displayScene: ComparisonChartScene | null;
|
|
38
35
|
hasDisplayCursor: boolean;
|
|
39
36
|
legendActiveIndex: number | null;
|
|
40
37
|
result: RenderComparisonChartResult;
|
|
41
38
|
staticResult: RenderComparisonChartResult;
|
|
42
|
-
staticScene: ComparisonChartScene;
|
|
39
|
+
staticScene: ComparisonChartScene | null;
|
|
43
40
|
timeAxisLabel: string;
|
|
44
41
|
visiblePriceRange: number | undefined;
|
|
45
42
|
}
|
|
46
43
|
|
|
47
44
|
export function useComparisonChartRenderOutput({
|
|
48
|
-
axisMode,
|
|
49
45
|
chartColors,
|
|
50
46
|
chartHeight,
|
|
51
47
|
chartWidth,
|
|
52
|
-
displayCursor,
|
|
53
48
|
displayCursorX,
|
|
54
49
|
displayCursorY,
|
|
55
50
|
effectiveRenderer,
|
|
@@ -402,11 +402,9 @@ function ComparisonStockChartView({
|
|
|
402
402
|
staticScene,
|
|
403
403
|
timeAxisLabel,
|
|
404
404
|
} = useComparisonChartRenderOutput({
|
|
405
|
-
axisMode,
|
|
406
405
|
chartColors,
|
|
407
406
|
chartHeight,
|
|
408
407
|
chartWidth,
|
|
409
|
-
displayCursor,
|
|
410
408
|
displayCursorX,
|
|
411
409
|
displayCursorY,
|
|
412
410
|
effectiveRenderer,
|
|
@@ -918,30 +918,6 @@ describe("renderChart", () => {
|
|
|
918
918
|
}
|
|
919
919
|
});
|
|
920
920
|
|
|
921
|
-
test("keeps the default area renderer stable", () => {
|
|
922
|
-
const projection = projectChartData(chartFixture, 12, undefined, false);
|
|
923
|
-
const result = renderChart(projection.points, {
|
|
924
|
-
width: 12,
|
|
925
|
-
height: 5,
|
|
926
|
-
showVolume: false,
|
|
927
|
-
volumeHeight: 0,
|
|
928
|
-
cursorX: null,
|
|
929
|
-
cursorY: null,
|
|
930
|
-
mode: projection.effectiveMode,
|
|
931
|
-
colors: palette,
|
|
932
|
-
});
|
|
933
|
-
|
|
934
|
-
expect(projection.requestedMode).toBe("area");
|
|
935
|
-
expect(projection.effectiveMode).toBe("area");
|
|
936
|
-
expect(textLines(result)).toEqual([
|
|
937
|
-
"⠁ ⡠⠔⢣ ⠁ ⠁ ",
|
|
938
|
-
"⠔⠊⣿⣿⠈⢆⠄ ⠄ ",
|
|
939
|
-
"⣿⣿⣿⣿⣿⠘⡄ ⡠",
|
|
940
|
-
"⣿⣿⣿⣿⣿⣿⠱⡀ ⢠⠊⣿",
|
|
941
|
-
"⣿⣿⣿⣿⣿⣿⣿⢣⠔⠁⣿⣿",
|
|
942
|
-
]);
|
|
943
|
-
});
|
|
944
|
-
|
|
945
921
|
test("formats y-axis labels as percent change when requested", () => {
|
|
946
922
|
const projection = projectChartData(chartFixture, 12, "line", false);
|
|
947
923
|
const result = renderChart(projection.points, {
|
|
@@ -2,20 +2,16 @@ import type { PixelBuffer } from "./types";
|
|
|
2
2
|
import type { ProjectedChartPoint } from "./data";
|
|
3
3
|
import { buildTimeAxis } from "./time-axis";
|
|
4
4
|
import {
|
|
5
|
-
formatAxisCell,
|
|
6
5
|
formatAxisValue,
|
|
7
|
-
formatCursorAxisValue,
|
|
8
6
|
formatPriceWithCurrency,
|
|
9
7
|
getAxisFractionDigitFloor,
|
|
10
8
|
resolveAxisFractionDigits,
|
|
11
|
-
resolveChartAxisWidth,
|
|
12
9
|
} from "./axis-format";
|
|
13
10
|
import {
|
|
14
11
|
bufferToBrailleLines,
|
|
15
12
|
createPixelBuffer,
|
|
16
13
|
drawCrosshair,
|
|
17
14
|
drawGridLines,
|
|
18
|
-
drawLine,
|
|
19
15
|
type StyledContent,
|
|
20
16
|
} from "../rendering/pixel-buffer";
|
|
21
17
|
import {
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
import {
|
|
3
3
|
CHART_RESOLUTION_STEP_MS,
|
|
4
|
-
clampTimeRangeForResolution,
|
|
5
|
-
getActiveRangePreset,
|
|
6
4
|
getBestSupportedResolutionForVisibleWindow,
|
|
7
5
|
getExpandedBufferRange,
|
|
8
6
|
getPresetResolution,
|
|
@@ -22,26 +20,11 @@ describe("chart-resolution", () => {
|
|
|
22
20
|
expect(getPresetResolution("ALL")).toBe("1mo");
|
|
23
21
|
});
|
|
24
22
|
|
|
25
|
-
test("derives the active range preset only for the exact reset preset pair", () => {
|
|
26
|
-
expect(getActiveRangePreset("1Y", "1d", 1, 0)).toBe("1Y");
|
|
27
|
-
expect(getActiveRangePreset("1Y", "auto", 1, 0)).toBeNull();
|
|
28
|
-
expect(getActiveRangePreset("1Y", "1d", 1.2, 0)).toBeNull();
|
|
29
|
-
expect(getActiveRangePreset("1Y", "1d", 1, 2)).toBeNull();
|
|
30
|
-
});
|
|
31
|
-
|
|
32
23
|
test("checks whether a range preset is supported by the visible capability set", () => {
|
|
33
24
|
expect(isRangePresetSupported("1Y", ["1d", "1wk"])).toBe(true);
|
|
34
25
|
expect(isRangePresetSupported("ALL", ["1d", "1wk"])).toBe(false);
|
|
35
26
|
});
|
|
36
27
|
|
|
37
|
-
test("clamps overly-wide ranges for manual intraday resolutions", () => {
|
|
38
|
-
expect(clampTimeRangeForResolution("5Y", "5m")).toBe("1W");
|
|
39
|
-
expect(clampTimeRangeForResolution("6M", "1h")).toBe("3M");
|
|
40
|
-
expect(clampTimeRangeForResolution("1M", "15m")).toBe("1M");
|
|
41
|
-
expect(clampTimeRangeForResolution("5Y", "1d")).toBe("5Y");
|
|
42
|
-
expect(clampTimeRangeForResolution("5Y", "auto")).toBe("5Y");
|
|
43
|
-
});
|
|
44
|
-
|
|
45
28
|
test("expands buffers only toward wider supported ranges", () => {
|
|
46
29
|
const support = new Map([
|
|
47
30
|
["1h", "3M"],
|
|
@@ -239,22 +239,6 @@ export function clampTimeRangeToMaxRange(range: TimeRange, maxRange: TimeRange):
|
|
|
239
239
|
return isTimeRangeAtOrBelow(range, maxRange) ? range : maxRange;
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
-
export function clampTimeRangeForResolution(range: TimeRange, resolution: ChartResolution): TimeRange {
|
|
243
|
-
if (resolution === "auto") return range;
|
|
244
|
-
const maxRange = getSupportMaxRange(
|
|
245
|
-
normalizeChartResolutionSupport([{ resolution, maxRange: "ALL" }]),
|
|
246
|
-
resolution,
|
|
247
|
-
) ?? "ALL";
|
|
248
|
-
const intrinsicMaxRange = resolution === "1m" || resolution === "5m"
|
|
249
|
-
? "1W"
|
|
250
|
-
: resolution === "15m"
|
|
251
|
-
? "1M"
|
|
252
|
-
: resolution === "30m" || resolution === "45m" || resolution === "1h"
|
|
253
|
-
? "3M"
|
|
254
|
-
: "ALL";
|
|
255
|
-
return clampTimeRangeToMaxRange(range, minTimeRange(maxRange, intrinsicMaxRange));
|
|
256
|
-
}
|
|
257
|
-
|
|
258
242
|
export function isIntradayResolution(resolution: ManualChartResolution): boolean {
|
|
259
243
|
return resolution === "1m"
|
|
260
244
|
|| resolution === "5m"
|
|
@@ -277,16 +261,6 @@ export function isRangePresetSupported(
|
|
|
277
261
|
return maxRange !== null && isTimeRangeAtOrBelow(range, maxRange);
|
|
278
262
|
}
|
|
279
263
|
|
|
280
|
-
export function getActiveRangePreset(
|
|
281
|
-
range: TimeRange,
|
|
282
|
-
resolution: ChartResolution,
|
|
283
|
-
zoomLevel: number,
|
|
284
|
-
panOffset: number,
|
|
285
|
-
): TimeRange | null {
|
|
286
|
-
if (zoomLevel !== 1 || panOffset !== 0) return null;
|
|
287
|
-
return RANGE_PRESET_RESOLUTION[range] === resolution ? range : null;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
264
|
export function getWidestPresetForResolution(
|
|
291
265
|
resolution: ManualChartResolution,
|
|
292
266
|
maxRange: TimeRange,
|
|
@@ -3,10 +3,8 @@ import {
|
|
|
3
3
|
applyBufferedPanExpansion,
|
|
4
4
|
consumeScrollPanCellDelta,
|
|
5
5
|
consumeScrollPanMovement,
|
|
6
|
-
getDragPanPointDelta,
|
|
7
6
|
getDragPanWindowRatio,
|
|
8
7
|
getKeyboardPanCellCount,
|
|
9
|
-
resolveDragPanOffset,
|
|
10
8
|
resolveHorizontalScrollPanDirection,
|
|
11
9
|
} from "./scroll";
|
|
12
10
|
|
|
@@ -34,11 +32,7 @@ describe("chart-scroll", () => {
|
|
|
34
32
|
|
|
35
33
|
test("keeps keyboard pan small while matching drag to the visible window", () => {
|
|
36
34
|
expect(getKeyboardPanCellCount(100)).toBe(2);
|
|
37
|
-
expect(getDragPanPointDelta(10, 100, 200)).toBe(20);
|
|
38
35
|
expect(getDragPanWindowRatio(10, 100)).toBeCloseTo(0.1);
|
|
39
|
-
expect(resolveDragPanOffset(20, 10, 100, 200, 40)).toBe(40);
|
|
40
|
-
expect(resolveDragPanOffset(20, -10, 100, 200, 40)).toBe(0);
|
|
41
|
-
expect(resolveDragPanOffset(38, 10, 100, 200, 40)).toBe(40);
|
|
42
36
|
});
|
|
43
37
|
|
|
44
38
|
test("resolves wheel direction, cell delta, remainder, and ratio together", () => {
|
|
@@ -21,36 +21,14 @@ export interface ScrollPanMovement extends ScrollPanCellDelta {
|
|
|
21
21
|
ratio: number;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
function clamp(value: number, min: number, max: number): number {
|
|
25
|
-
return Math.max(min, Math.min(max, value));
|
|
26
|
-
}
|
|
27
|
-
|
|
28
24
|
export function getKeyboardPanCellCount(chartWidth: number): number {
|
|
29
25
|
return Math.max(Math.round(chartWidth * KEYBOARD_PAN_WIDTH_RATIO), 1);
|
|
30
26
|
}
|
|
31
27
|
|
|
32
|
-
export function getDragPanPointDelta(deltaCells: number, chartWidth: number, visibleCount: number): number {
|
|
33
|
-
return Math.round((deltaCells / Math.max(chartWidth, 1)) * Math.max(visibleCount, 1) * DRAG_PAN_VISIBLE_RATIO);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
28
|
export function getDragPanWindowRatio(deltaCells: number, chartWidth: number): number {
|
|
37
29
|
return (deltaCells / Math.max(chartWidth, 1)) * DRAG_PAN_VISIBLE_RATIO;
|
|
38
30
|
}
|
|
39
31
|
|
|
40
|
-
export function resolveDragPanOffset(
|
|
41
|
-
startPanOffset: number,
|
|
42
|
-
deltaCells: number,
|
|
43
|
-
chartWidth: number,
|
|
44
|
-
visibleCount: number,
|
|
45
|
-
maxPanOffset: number,
|
|
46
|
-
): number {
|
|
47
|
-
return clamp(
|
|
48
|
-
startPanOffset + getDragPanPointDelta(deltaCells, chartWidth, visibleCount),
|
|
49
|
-
0,
|
|
50
|
-
Math.max(maxPanOffset, 0),
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
32
|
export function consumeScrollPanCellDelta(
|
|
55
33
|
chartWidth: number,
|
|
56
34
|
delta: number | undefined,
|
|
@@ -1,28 +1,7 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { getVisiblePointCount
|
|
3
|
-
|
|
4
|
-
describe("resolveAnchoredChartZoom", () => {
|
|
5
|
-
test("keeps keyboard zoom pinned to the latest data when anchored to the right edge", () => {
|
|
6
|
-
expect(resolveAnchoredChartZoom(252, 1, 0, 2, RIGHT_EDGE_ANCHOR_RATIO)).toEqual({
|
|
7
|
-
zoomLevel: 2,
|
|
8
|
-
panOffset: 0,
|
|
9
|
-
});
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
test("preserves the current right edge when zooming a panned view", () => {
|
|
13
|
-
expect(resolveAnchoredChartZoom(252, 2, 20, 3, RIGHT_EDGE_ANCHOR_RATIO)).toEqual({
|
|
14
|
-
zoomLevel: 3,
|
|
15
|
-
panOffset: 20,
|
|
16
|
-
});
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
test("still supports center-anchored zoom for pointer-driven interactions", () => {
|
|
20
|
-
expect(resolveAnchoredChartZoom(252, 1, 0, 2, 0.5)).toEqual({
|
|
21
|
-
zoomLevel: 2,
|
|
22
|
-
panOffset: 63,
|
|
23
|
-
});
|
|
24
|
-
});
|
|
2
|
+
import { getVisiblePointCount } from "./viewport";
|
|
25
3
|
|
|
4
|
+
describe("chart viewport", () => {
|
|
26
5
|
test("allows zooming down to a two-point viewport", () => {
|
|
27
6
|
expect(getVisiblePointCount(433, 1000)).toBe(2);
|
|
28
7
|
});
|
|
@@ -22,30 +22,3 @@ export function getVisiblePointCount(totalPoints: number, zoomLevel: number): nu
|
|
|
22
22
|
const clampedZoom = clampChartZoom(totalPoints, zoomLevel);
|
|
23
23
|
return Math.min(totalPoints, Math.max(Math.floor(totalPoints / clampedZoom), MIN_VISIBLE_POINTS));
|
|
24
24
|
}
|
|
25
|
-
|
|
26
|
-
export function resolveAnchoredChartZoom(
|
|
27
|
-
totalPoints: number,
|
|
28
|
-
currentZoomLevel: number,
|
|
29
|
-
currentPanOffset: number,
|
|
30
|
-
nextZoomLevel: number,
|
|
31
|
-
anchorRatio: number,
|
|
32
|
-
): { zoomLevel: number; panOffset: number } {
|
|
33
|
-
if (totalPoints <= 0) {
|
|
34
|
-
return { zoomLevel: 1, panOffset: 0 };
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const clampedZoom = clampChartZoom(totalPoints, nextZoomLevel);
|
|
38
|
-
const currentVisibleCount = getVisiblePointCount(totalPoints, currentZoomLevel);
|
|
39
|
-
const nextVisibleCount = getVisiblePointCount(totalPoints, clampedZoom);
|
|
40
|
-
const currentPan = clamp(currentPanOffset, 0, Math.max(totalPoints - currentVisibleCount, 0));
|
|
41
|
-
const ratio = clamp(anchorRatio, 0, 1);
|
|
42
|
-
const anchorIndex = totalPoints - currentPan - currentVisibleCount + ratio * Math.max(currentVisibleCount - 1, 0);
|
|
43
|
-
const nextStart = Math.round(anchorIndex - ratio * Math.max(nextVisibleCount - 1, 0));
|
|
44
|
-
const clampedStart = clamp(nextStart, 0, Math.max(totalPoints - nextVisibleCount, 0));
|
|
45
|
-
const nextPanOffset = totalPoints - nextVisibleCount - clampedStart;
|
|
46
|
-
|
|
47
|
-
return {
|
|
48
|
-
zoomLevel: clampedZoom,
|
|
49
|
-
panOffset: clamp(nextPanOffset, 0, Math.max(totalPoints - nextVisibleCount, 0)),
|
|
50
|
-
};
|
|
51
|
-
}
|
|
@@ -148,7 +148,7 @@ export function getChartMarketSessionKey(session: ChartMarketSession | null | un
|
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
export function resolveExtendedHoursBackgroundSpans(
|
|
151
|
-
values: readonly
|
|
151
|
+
values: readonly (Date | string | number)[],
|
|
152
152
|
session: ChartMarketSession | null | undefined,
|
|
153
153
|
): ChartSessionBackgroundSpan[] {
|
|
154
154
|
if (!session) return [];
|
|
@@ -71,8 +71,9 @@ export function useNativeChartSurfaceLifecycle({
|
|
|
71
71
|
|
|
72
72
|
useEffect(() => {
|
|
73
73
|
if (effectiveRenderer !== "kitty" || !nativeReady || !plotRef.current) return;
|
|
74
|
-
const plot = plotRef.current;
|
|
74
|
+
const plot = plotRef.current as BoxRenderable & { onLifecyclePass?: (() => void) | null };
|
|
75
75
|
let mountTimer: ReturnType<typeof setTimeout> | null = null;
|
|
76
|
+
const previousLifecyclePass = plot.onLifecyclePass;
|
|
76
77
|
|
|
77
78
|
const syncPlacement = () => {
|
|
78
79
|
if (effectiveRenderer !== "kitty" || !nativeReady || !plotRef.current) return;
|
|
@@ -108,7 +109,11 @@ export function useNativeChartSurfaceLifecycle({
|
|
|
108
109
|
);
|
|
109
110
|
};
|
|
110
111
|
|
|
111
|
-
|
|
112
|
+
const lifecyclePass = () => {
|
|
113
|
+
previousLifecyclePass?.();
|
|
114
|
+
syncPlacement();
|
|
115
|
+
};
|
|
116
|
+
plot.onLifecyclePass = lifecyclePass;
|
|
112
117
|
renderer.registerLifecyclePass(plot);
|
|
113
118
|
syncPlacement();
|
|
114
119
|
mountTimer = setTimeout(() => {
|
|
@@ -118,7 +123,9 @@ export function useNativeChartSurfaceLifecycle({
|
|
|
118
123
|
|
|
119
124
|
return () => {
|
|
120
125
|
if (mountTimer) clearTimeout(mountTimer);
|
|
121
|
-
plot.onLifecyclePass
|
|
126
|
+
if (plot.onLifecyclePass === lifecyclePass) {
|
|
127
|
+
plot.onLifecyclePass = previousLifecyclePass;
|
|
128
|
+
}
|
|
122
129
|
renderer.unregisterLifecyclePass(plot);
|
|
123
130
|
lastGeometryRef.current = null;
|
|
124
131
|
};
|
|
@@ -3,7 +3,6 @@ import type { InstrumentRef } from "../../../market-data/request-types";
|
|
|
3
3
|
import type { DataProvider } from "../../../types/data-provider";
|
|
4
4
|
import type { PricePoint, TickerFinancials } from "../../../types/financials";
|
|
5
5
|
import { appendLiveQuotePoint, resolveStableOhlcProjectionOptions } from "../core/data";
|
|
6
|
-
import type { DateWindowRange } from "../core/controller";
|
|
7
6
|
import {
|
|
8
7
|
getChartResolutionLabel,
|
|
9
8
|
sortChartResolutions,
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { useEffect, useRef, type Dispatch, type MutableRefObject, type SetStateAction } from "react";
|
|
2
|
-
import { applyBufferedPanExpansion } from "../../core/scroll";
|
|
3
2
|
import {
|
|
4
3
|
applyDateWindowViewport,
|
|
5
4
|
buildVisibleDateWindow,
|
|
@@ -15,7 +14,6 @@ import {
|
|
|
15
14
|
type StoredChartSelectionSyncState,
|
|
16
15
|
} from "../../core/pane-settings";
|
|
17
16
|
import {
|
|
18
|
-
getNextBufferRange,
|
|
19
17
|
getSupportMaxRange,
|
|
20
18
|
type ManualChartResolution,
|
|
21
19
|
} from "../../core/resolution";
|
|
@@ -31,7 +29,6 @@ import type {
|
|
|
31
29
|
import type { ChartCursorMotionKind } from "../../cursor-motion";
|
|
32
30
|
import type { AutoRenderedView } from "../auto";
|
|
33
31
|
import {
|
|
34
|
-
clearAutoViewportState,
|
|
35
32
|
resolveViewportPresetSelection,
|
|
36
33
|
resolveViewportStoredSelection,
|
|
37
34
|
type PendingExpansionAction,
|