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.
Files changed (181) hide show
  1. package/package.json +1 -1
  2. package/src/api-client/index.test.ts +3 -3
  3. package/src/api-client/index.ts +0 -6
  4. package/src/api-client/request.test.ts +63 -0
  5. package/src/api-client/request.ts +53 -5
  6. package/src/api-client/types.ts +0 -2
  7. package/src/app/pane-runtime/layout-placement.ts +0 -1
  8. package/src/app.tsx +1 -0
  9. package/src/brokers/account-cache.ts +2 -10
  10. package/src/brokers/broker-portfolio-sync.ts +0 -1
  11. package/src/brokers/broker-ticker-sync.ts +20 -16
  12. package/src/brokers/remote-broker-adapter.ts +3 -0
  13. package/src/capabilities/registry.test.ts +25 -0
  14. package/src/capabilities/registry.ts +22 -5
  15. package/src/cli/commands/automation.ts +1 -3
  16. package/src/cli/commands/command-utils.ts +0 -7
  17. package/src/cli/commands/overview.ts +2 -2
  18. package/src/cli/commands/plugins.ts +34 -17
  19. package/src/cli/index.ts +2 -2
  20. package/src/cli.test.ts +0 -157
  21. package/src/components/chart/comparison/renderer.ts +0 -1
  22. package/src/components/chart/comparison-stock-chart/render-output.ts +3 -8
  23. package/src/components/chart/comparison-stock-chart/view.tsx +0 -2
  24. package/src/components/chart/core/index.test.ts +0 -24
  25. package/src/components/chart/core/renderer.ts +0 -4
  26. package/src/components/chart/core/resolution.test.ts +0 -17
  27. package/src/components/chart/core/resolution.ts +0 -26
  28. package/src/components/chart/core/scroll.test.ts +0 -6
  29. package/src/components/chart/core/scroll.ts +0 -22
  30. package/src/components/chart/core/viewport.test.ts +2 -23
  31. package/src/components/chart/core/viewport.ts +0 -27
  32. package/src/components/chart/market-session.ts +1 -1
  33. package/src/components/chart/native/surface/hooks.ts +10 -3
  34. package/src/components/chart/stock-chart/data-runtime.ts +0 -1
  35. package/src/components/chart/stock-chart/viewport/effects.ts +0 -3
  36. package/src/components/command-bar/helpers.test.ts +1 -80
  37. package/src/components/command-bar/helpers.ts +1 -36
  38. package/src/components/command-bar/pane-settings.ts +1 -1
  39. package/src/components/command-bar/panel/runtime.ts +0 -2
  40. package/src/components/command-bar/routes/ticker-search/results.ts +0 -11
  41. package/src/components/command-bar/surface/index.test.tsx +0 -16
  42. package/src/components/command-bar/surface/index.tsx +0 -1
  43. package/src/components/feed-data-table/stack-view.test.tsx +36 -130
  44. package/src/components/feed-data-table/stack-view.tsx +0 -1
  45. package/src/components/layout/shell/index.test.tsx +2 -113
  46. package/src/components/layout/shell/index.tsx +0 -1
  47. package/src/components/layout/shell/menu.ts +0 -1
  48. package/src/components/layout/shell/terminal-pointer-runtime.ts +0 -1
  49. package/src/components/onboarding/onboarding-wizard.tsx +3 -3
  50. package/src/components/onboarding/wizard-keyboard.ts +2 -23
  51. package/src/components/price-selector-dialog.tsx +3 -3
  52. package/src/components/ticker/badge/text.tsx +0 -1
  53. package/src/components/ui/page-stack-view.tsx +1 -1
  54. package/src/components/ui/ui.test.tsx +0 -78
  55. package/src/core/state/app/layout-reducer.ts +0 -1
  56. package/src/data/config/layout.ts +3 -3
  57. package/src/data/resource-store.ts +6 -0
  58. package/src/data/sqlite/cache.test.ts +5 -0
  59. package/src/data/ticker-store.ts +0 -7
  60. package/src/market-data/coordinator/auxiliary.ts +1 -44
  61. package/src/market-data/coordinator/entries.ts +0 -1
  62. package/src/market-data/coordinator/index.test.ts +0 -10
  63. package/src/market-data/coordinator/index.ts +1 -18
  64. package/src/market-data/coordinator/quotes.ts +0 -6
  65. package/src/market-data/coordinator/subscriptions.test.ts +1 -0
  66. package/src/market-data/market/status.ts +19 -1
  67. package/src/market-data/query-store.ts +0 -4
  68. package/src/market-data/quotes/resolution.ts +2 -2
  69. package/src/market-data/request-types.ts +0 -5
  70. package/src/market-data/result-types.ts +0 -11
  71. package/src/market-data/selectors.ts +1 -5
  72. package/src/news/aggregator.ts +5 -1
  73. package/src/pane-settings.test.ts +0 -18
  74. package/src/plugins/builtin/account-management/form-components.tsx +0 -120
  75. package/src/plugins/builtin/account-management/model.ts +3 -27
  76. package/src/plugins/builtin/broker-manager/index.test.tsx +0 -32
  77. package/src/plugins/builtin/buildout/cells.tsx +1 -1
  78. package/src/plugins/builtin/buildout/detail/satellite-images.tsx +1 -1
  79. package/src/plugins/builtin/buildout/detail/ui.tsx +0 -1
  80. package/src/plugins/builtin/buildout/format.ts +2 -11
  81. package/src/plugins/builtin/chat/chat.test.tsx +2 -1
  82. package/src/plugins/builtin/chat/content/index.tsx +1 -2
  83. package/src/plugins/builtin/chat/content/transcript.tsx +0 -1
  84. package/src/plugins/builtin/chat/controller/index.ts +0 -10
  85. package/src/plugins/builtin/chat/controller.test.ts +5 -5
  86. package/src/plugins/builtin/chat/sidebar.test.tsx +15 -5
  87. package/src/plugins/builtin/chat/test-harness.tsx +2 -2
  88. package/src/plugins/builtin/cloud/plugin.tsx +1 -1
  89. package/src/plugins/builtin/cloud-tweets/pane.tsx +0 -1
  90. package/src/plugins/builtin/congress-trades/model.ts +2 -7
  91. package/src/plugins/builtin/congress-trades/pane.tsx +0 -1
  92. package/src/plugins/builtin/earnings/index.tsx +0 -9
  93. package/src/plugins/builtin/econ/index.tsx +1 -5
  94. package/src/plugins/builtin/help/index.tsx +2 -2
  95. package/src/plugins/builtin/holders/pane.tsx +0 -5
  96. package/src/plugins/builtin/insider/index.tsx +1 -8
  97. package/src/plugins/builtin/kelly-sizer/index.test.tsx +0 -26
  98. package/src/plugins/builtin/kelly-sizer/index.tsx +0 -2
  99. package/src/plugins/builtin/market-movers/index.tsx +0 -2
  100. package/src/plugins/builtin/news/index.tsx +0 -1
  101. package/src/plugins/builtin/news/wire/news/table.tsx +0 -11
  102. package/src/plugins/builtin/options/view.test.tsx +1 -38
  103. package/src/plugins/builtin/plugin-groups.ts +17 -3
  104. package/src/plugins/builtin/portfolio-list/column-values.ts +15 -8
  105. package/src/plugins/builtin/portfolio-list/index.test.ts +1 -24
  106. package/src/plugins/builtin/portfolio-list/metrics.test.ts +44 -7
  107. package/src/plugins/builtin/sec/filing-documents.ts +0 -11
  108. package/src/plugins/builtin/sec/index.tsx +0 -2
  109. package/src/plugins/builtin/substack/article-detail.tsx +1 -1
  110. package/src/plugins/builtin/thirteenf/api.ts +0 -15
  111. package/src/plugins/builtin/thirteenf/pane.tsx +1 -3
  112. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/content.tsx +1 -1
  113. package/src/plugins/builtin/ticker-detail/financials/tab.tsx +0 -7
  114. package/src/plugins/builtin/ticker-detail/quote-monitor/index.test.tsx +5 -34
  115. package/src/plugins/builtin/world-indices/index.tsx +0 -4
  116. package/src/plugins/builtin/world-indices/model.ts +0 -7
  117. package/src/plugins/ibkr/account-cache.ts +1 -1
  118. package/src/plugins/ibkr/portfolio-performance.ts +1 -1
  119. package/src/plugins/ibkr/trade/utils.ts +1 -7
  120. package/src/plugins/loader.ts +2 -1
  121. package/src/plugins/prediction-markets/detail.test.tsx +0 -34
  122. package/src/plugins/registry/contributions.ts +22 -11
  123. package/src/plugins/registry/index.test.ts +44 -0
  124. package/src/plugins/registry/index.ts +44 -26
  125. package/src/remote/controller.ts +1 -7
  126. package/src/remote/json-patch.ts +15 -1
  127. package/src/remote/server.ts +16 -6
  128. package/src/renderers/electrobun/bun/application-menu/index.ts +1 -1
  129. package/src/renderers/electrobun/bun/core-capabilities.ts +2 -0
  130. package/src/renderers/electrobun/bun/desktop/http-fetch.test.ts +14 -0
  131. package/src/renderers/electrobun/bun/desktop/http-fetch.ts +8 -0
  132. package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -1
  133. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -0
  134. package/src/renderers/electrobun/view/data-table/dom.ts +0 -1
  135. package/src/renderers/electrobun/view/host/mouse.ts +0 -11
  136. package/src/renderers/electrobun/view/http-fetch.ts +15 -2
  137. package/src/renderers/electrobun/view/remote/asset-data-client.ts +9 -18
  138. package/src/renderers/electrobun/view/remote/capability-invoker.test.ts +50 -0
  139. package/src/renderers/electrobun/view/remote/capability-invoker.ts +36 -0
  140. package/src/renderers/opentui/host.tsx +11 -3
  141. package/src/renderers/opentui/input-host.tsx +5 -1
  142. package/src/sources/gloomberb-cloud/index.test.ts +0 -72
  143. package/src/sources/gloomberb-cloud/index.ts +1 -30
  144. package/src/sources/gloomberb-cloud/news.ts +0 -12
  145. package/src/sources/provider-router/financial-routes.ts +25 -3
  146. package/src/sources/provider-router/index.test.ts +167 -0
  147. package/src/sources/provider-router/search.ts +5 -0
  148. package/src/sources/provider-router/supplemental.ts +4 -3
  149. package/src/sources/yahoo-finance/history.ts +5 -1
  150. package/src/sources/yahoo-finance/options.ts +59 -14
  151. package/src/sources/yahoo-finance/quote-summary.ts +4 -1
  152. package/src/sources/yahoo-finance.test.ts +40 -21
  153. package/src/sources/yahoo-finance.ts +13 -8
  154. package/src/state/app/context/index.test.tsx +0 -81
  155. package/src/state/app/context/selector.test.tsx +6 -2
  156. package/src/sync/controller.test.ts +151 -0
  157. package/src/sync/controller.ts +92 -57
  158. package/src/sync/core-contributors.ts +45 -21
  159. package/src/sync/react.ts +6 -8
  160. package/src/sync/types.ts +3 -0
  161. package/src/test-support/data-provider.ts +0 -1
  162. package/src/ui/host.tsx +2 -1
  163. package/src/ui/index.tsx +1 -0
  164. package/src/updater.test.ts +0 -21
  165. package/src/updater.ts +20 -16
  166. package/src/utils/async-deadline.ts +26 -0
  167. package/src/utils/selection-clipboard.ts +2 -2
  168. package/src/version.ts +1 -1
  169. package/src/components/layout/pane/header.test.tsx +0 -39
  170. package/src/plugins/builtin/chat/channels.test.ts +0 -28
  171. package/src/plugins/builtin/chat/index.tsx +0 -4
  172. package/src/plugins/builtin/cloud-tweets/index.ts +0 -1
  173. package/src/plugins/builtin/earnings/index.test.ts +0 -12
  174. package/src/plugins/builtin/help/index.test.tsx +0 -242
  175. package/src/plugins/builtin/substack/article-detail.test.tsx +0 -10
  176. package/src/renderers/electrobun/bun/application-menu/index.test.ts +0 -21
  177. package/src/renderers/electrobun/bun/window/focus.test.ts +0 -35
  178. package/src/renderers/electrobun/view/host/mouse.test.ts +0 -37
  179. package/src/renderers/electrobun/view/host/text.test.ts +0 -22
  180. package/src/renderers/opentui/dialog-host.test.tsx +0 -44
  181. /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({
@@ -22,7 +22,6 @@ import {
22
22
  getScaledComparisonY,
23
23
  getSelectedComparisonSeries,
24
24
  type ComparisonChartColors,
25
- type ComparisonChartScene,
26
25
  type RenderComparisonChartOptions,
27
26
  } from "./scene";
28
27
 
@@ -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 { ChartAxisMode, ChartMarketSession, ResolvedChartRenderer } from "../core/types";
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, RIGHT_EDGE_ANCHOR_RATIO, resolveAnchoredChartZoom } from "./viewport";
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 Array<Date | string | number>,
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
- plot.onLifecyclePass = syncPlacement;
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 = null;
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,