gloomberb 0.3.7 → 0.4.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.
Files changed (143) hide show
  1. package/README.md +19 -8
  2. package/package.json +1 -1
  3. package/src/app.tsx +45 -69
  4. package/src/cli/base-converter.ts +31 -0
  5. package/src/cli/commands/search.ts +11 -3
  6. package/src/cli/commands/ticker.ts +22 -11
  7. package/src/cli/context.ts +27 -32
  8. package/src/cli/helpers.ts +1 -4
  9. package/src/cli/index.ts +140 -123
  10. package/src/cli/registry.test.ts +226 -0
  11. package/src/cli/registry.ts +201 -0
  12. package/src/cli.test.ts +219 -0
  13. package/src/components/chart/chart-controller.ts +309 -0
  14. package/src/components/chart/chart-data.ts +5 -6
  15. package/src/components/chart/chart-pane-settings.ts +27 -0
  16. package/src/components/chart/chart-resolution.test.ts +56 -0
  17. package/src/components/chart/chart-resolution.ts +362 -0
  18. package/src/components/chart/chart-types.ts +12 -4
  19. package/src/components/chart/chart-viewport.test.ts +25 -0
  20. package/src/components/chart/chart-viewport.ts +28 -0
  21. package/src/components/chart/comparison-chart-data.ts +20 -27
  22. package/src/components/chart/comparison-stock-chart.tsx +374 -99
  23. package/src/components/chart/stock-chart-renderer-switch.test.tsx +124 -0
  24. package/src/components/chart/stock-chart.tsx +455 -106
  25. package/src/components/command-bar/__snapshots__/command-bar.test.tsx.snap +4 -4
  26. package/src/components/command-bar/command-bar.test.tsx +650 -5
  27. package/src/components/command-bar/command-bar.tsx +363 -63
  28. package/src/components/command-bar/command-registry.ts +8 -0
  29. package/src/components/command-bar/helpers.test.ts +23 -1
  30. package/src/components/command-bar/helpers.ts +14 -5
  31. package/src/components/command-bar/pane-template-display.ts +12 -0
  32. package/src/components/command-bar/root-shortcuts.ts +2 -1
  33. package/src/components/command-bar/workflow-ops.ts +4 -2
  34. package/src/components/detail-feed-view.test.tsx +124 -49
  35. package/src/components/detail-feed-view.tsx +396 -160
  36. package/src/components/layout/floating-pane.tsx +4 -2
  37. package/src/components/layout/header.tsx +3 -3
  38. package/src/components/layout/pane-header.tsx +26 -2
  39. package/src/components/layout/pane-sizing.ts +6 -0
  40. package/src/components/layout/pane.tsx +5 -1
  41. package/src/components/layout/shell.test.tsx +77 -5
  42. package/src/components/layout/shell.tsx +67 -52
  43. package/src/components/layout/status-bar.test.tsx +66 -0
  44. package/src/components/layout/status-bar.tsx +26 -5
  45. package/src/components/onboarding/onboarding-wizard.test.tsx +65 -0
  46. package/src/components/onboarding/onboarding-wizard.tsx +5 -6
  47. package/src/components/ticker-list-table.test.tsx +83 -0
  48. package/src/components/ticker-list-table.tsx +181 -97
  49. package/src/components/ui/data-table.tsx +50 -5
  50. package/src/components/ui/index.ts +1 -0
  51. package/src/components/ui/page-stack-view.tsx +60 -0
  52. package/src/components/use-double-click-activation.ts +31 -0
  53. package/src/index.tsx +10 -10
  54. package/src/market-data/coordinator.test.ts +92 -4
  55. package/src/market-data/coordinator.ts +32 -5
  56. package/src/market-data/hooks.test.tsx +120 -0
  57. package/src/market-data/hooks.tsx +53 -16
  58. package/src/market-data/request-types.ts +4 -2
  59. package/src/market-data/selectors.ts +24 -58
  60. package/src/plugins/builtin/chat-controller.test.ts +400 -3
  61. package/src/plugins/builtin/chat-controller.ts +328 -35
  62. package/src/plugins/builtin/chat.test.tsx +118 -6
  63. package/src/plugins/builtin/chat.tsx +180 -116
  64. package/src/plugins/builtin/comparison-chart.test.tsx +66 -5
  65. package/src/plugins/builtin/comparison-chart.tsx +21 -1
  66. package/src/plugins/builtin/layout-manager.tsx +9 -0
  67. package/src/plugins/builtin/news.tsx +1 -11
  68. package/src/plugins/builtin/portfolio-list/cli/commands.ts +656 -0
  69. package/src/plugins/builtin/portfolio-list/command-bar.ts +122 -0
  70. package/src/plugins/builtin/portfolio-list/header.tsx +16 -34
  71. package/src/plugins/builtin/portfolio-list/index.tsx +5 -0
  72. package/src/plugins/builtin/portfolio-list/metrics.test.ts +61 -0
  73. package/src/plugins/builtin/portfolio-list/metrics.ts +2 -2
  74. package/src/plugins/builtin/portfolio-list/mutations.test.ts +187 -0
  75. package/src/plugins/builtin/portfolio-list/mutations.ts +226 -0
  76. package/src/plugins/builtin/portfolio-list/pane.tsx +39 -17
  77. package/src/plugins/builtin/portfolio-list/table.tsx +11 -2
  78. package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.test.tsx +163 -0
  79. package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.ts +81 -0
  80. package/src/plugins/builtin/sec.tsx +1 -11
  81. package/src/plugins/builtin/ticker-detail/chart-tab.tsx +13 -2
  82. package/src/plugins/builtin/ticker-detail/financials-tab.tsx +21 -0
  83. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +45 -10
  84. package/src/plugins/builtin/ticker-detail/pane.tsx +35 -17
  85. package/src/plugins/builtin/ticker-detail/settings.ts +19 -1
  86. package/src/plugins/builtin/ticker-detail.test.tsx +47 -0
  87. package/src/plugins/catalog.ts +65 -0
  88. package/src/plugins/ibkr/broker-adapter.ts +28 -0
  89. package/src/plugins/ibkr/gateway-service.test.ts +67 -1
  90. package/src/plugins/ibkr/gateway-service.ts +147 -7
  91. package/src/plugins/ibkr/index.tsx +7 -1
  92. package/src/plugins/pane-manager.test.ts +1 -1
  93. package/src/plugins/plugin-runtime.tsx +27 -7
  94. package/src/plugins/prediction-markets/cli.ts +28 -0
  95. package/src/plugins/prediction-markets/controller-data.ts +132 -17
  96. package/src/plugins/prediction-markets/controller.ts +142 -122
  97. package/src/plugins/prediction-markets/index.tsx +7 -1
  98. package/src/plugins/prediction-markets/launch.ts +51 -16
  99. package/src/plugins/prediction-markets/metrics.ts +9 -5
  100. package/src/plugins/prediction-markets/pane.test.tsx +197 -57
  101. package/src/plugins/prediction-markets/pane.tsx +80 -155
  102. package/src/plugins/prediction-markets/services/fetch.ts +4 -0
  103. package/src/plugins/prediction-markets/table.tsx +6 -3
  104. package/src/plugins/prediction-markets/test-helpers.tsx +20 -5
  105. package/src/plugins/prediction-markets/types.ts +0 -1
  106. package/src/plugins/registry.ts +23 -0
  107. package/src/sources/gloomberb-cloud.test.ts +29 -0
  108. package/src/sources/gloomberb-cloud.ts +89 -1
  109. package/src/sources/provider-router.test.ts +265 -3
  110. package/src/sources/provider-router.ts +239 -11
  111. package/src/sources/yahoo-finance.test.ts +26 -0
  112. package/src/sources/yahoo-finance.ts +54 -3
  113. package/src/state/app-context-selector.test.tsx +99 -0
  114. package/src/state/app-context.test.ts +18 -0
  115. package/src/state/app-context.tsx +170 -65
  116. package/src/state/selectors.ts +25 -0
  117. package/src/theme/colors.ts +8 -8
  118. package/src/theme/themes.test.ts +2 -0
  119. package/src/theme/themes.ts +22 -10
  120. package/src/types/broker.ts +21 -0
  121. package/src/types/config.ts +17 -1
  122. package/src/types/data-provider.ts +18 -0
  123. package/src/types/financials.ts +31 -1
  124. package/src/types/plugin.ts +70 -0
  125. package/src/updater.test.ts +2 -0
  126. package/src/utils/api-client.test.ts +57 -0
  127. package/src/utils/api-client.ts +47 -12
  128. package/src/utils/back-navigation.ts +27 -0
  129. package/src/utils/format.test.ts +14 -0
  130. package/src/utils/format.ts +3 -3
  131. package/src/utils/market-status.ts +46 -1
  132. package/src/utils/quote-freshness.ts +127 -0
  133. package/src/utils/quote-resolution.test.ts +277 -0
  134. package/src/utils/quote-resolution.ts +641 -0
  135. package/src/utils/slugify.ts +3 -0
  136. package/src/utils/timestamp.ts +25 -0
  137. package/src/version.ts +1 -1
  138. package/src/cli/commands/portfolio.ts +0 -210
  139. package/src/cli/commands/watchlist.ts +0 -210
  140. package/src/plugins/builtin/data-providers.ts +0 -16
  141. package/src/sources/exchange-rates.ts +0 -18
  142. package/src/utils/ascii-chart.ts +0 -172
  143. package/src/utils/ticker-tokenizer.test.ts +0 -48
@@ -1,6 +1,7 @@
1
1
  import { afterEach, describe, expect, test } from "bun:test";
2
2
  import { act } from "react";
3
3
  import { testRender } from "@opentui/react/test-utils";
4
+ import { MarketDataCoordinator, setSharedMarketDataCoordinator } from "../../market-data/coordinator";
4
5
  import {
5
6
  AppContext,
6
7
  createInitialState,
@@ -23,6 +24,7 @@ import {
23
24
  const TEST_PANE_ID = "comparison-chart:test";
24
25
 
25
26
  let testSetup: Awaited<ReturnType<typeof testRender>> | undefined;
27
+ let sharedCoordinator: MarketDataCoordinator | null = null;
26
28
 
27
29
  function makeTicker(symbol: string, currency: string): TickerRecord {
28
30
  return {
@@ -76,12 +78,19 @@ function createProvider(historyBySymbol: Record<string, number[]>, currencyBySym
76
78
  search: async () => [],
77
79
  getNews: async () => [],
78
80
  getArticleSummary: async () => null,
81
+ getChartResolutionCapabilities: async () => ["5m", "15m", "1h", "1d", "1wk", "1mo"],
79
82
  getPriceHistory: async (symbol) => (
80
83
  historyBySymbol[symbol] ?? []
81
84
  ).map((close, index) => ({
82
85
  date: new Date(2024, 0, index + 2),
83
86
  close,
84
87
  })),
88
+ getPriceHistoryForResolution: async (symbol) => (
89
+ historyBySymbol[symbol] ?? []
90
+ ).map((close, index) => ({
91
+ date: new Date(2024, 0, index + 2),
92
+ close,
93
+ })),
85
94
  };
86
95
  }
87
96
 
@@ -154,6 +163,8 @@ afterEach(() => {
154
163
  testSetup.renderer.destroy();
155
164
  testSetup = undefined;
156
165
  }
166
+ sharedCoordinator = null;
167
+ setSharedMarketDataCoordinator(null);
157
168
  setSharedRegistryForTests(undefined);
158
169
  setSharedDataProviderForTests(undefined);
159
170
  });
@@ -178,6 +189,8 @@ describe("comparisonChartPlugin", () => {
178
189
  title: "AAPL · MSFT · NVDA",
179
190
  settings: {
180
191
  axisMode: "percent",
192
+ rangePreset: "1Y",
193
+ chartResolution: "1d",
181
194
  symbols: ["AAPL", "MSFT", "NVDA"],
182
195
  symbolsText: "AAPL, MSFT, NVDA",
183
196
  },
@@ -190,19 +203,24 @@ describe("comparisonChartPlugin", () => {
190
203
  symbols: ["AAPL", "MSFT"],
191
204
  })).toEqual({
192
205
  axisMode: "percent",
206
+ rangePreset: "1Y",
207
+ chartResolution: "1d",
193
208
  symbols: ["AAPL", "MSFT"],
194
209
  symbolsText: "AAPL, MSFT",
195
210
  });
196
211
  });
197
212
 
198
213
  test("renders one shared overlay chart with the mixed-currency warning", async () => {
199
- setSharedDataProviderForTests(createProvider({
214
+ const provider = createProvider({
200
215
  AAPL: [100, 102, 104, 106],
201
216
  "7203": [2000, 2020, 2050, 2100],
202
217
  }, {
203
218
  AAPL: "USD",
204
219
  "7203": "JPY",
205
- }));
220
+ });
221
+ setSharedDataProviderForTests(provider);
222
+ sharedCoordinator = new MarketDataCoordinator(provider);
223
+ setSharedMarketDataCoordinator(sharedCoordinator);
206
224
  setSharedRegistryForTests(createRegistrySpy({ selected: [], focused: [] }));
207
225
 
208
226
  testSetup = await testRender(
@@ -224,14 +242,54 @@ describe("comparisonChartPlugin", () => {
224
242
 
225
243
  const frame = testSetup.captureCharFrame();
226
244
  expect(frame).toContain("Mixed currencies detected; showing percent change.");
227
- expect(frame).toContain("1:1W");
245
+ expect(frame).toContain("1:1D");
246
+ expect(frame).toContain("2:1W");
247
+ expect(frame).toContain("1D");
248
+ expect(frame).toContain("view:");
228
249
  expect(frame).toContain("arrows legend");
229
250
  expect(frame).not.toContain("side by side");
230
251
  });
231
252
 
253
+ test("keeps controls visible when comparison history is empty", async () => {
254
+ const provider = createProvider({
255
+ AAPL: [],
256
+ MSFT: [],
257
+ }, {
258
+ AAPL: "USD",
259
+ MSFT: "USD",
260
+ });
261
+ setSharedDataProviderForTests(provider);
262
+ sharedCoordinator = new MarketDataCoordinator(provider);
263
+ setSharedMarketDataCoordinator(sharedCoordinator);
264
+ setSharedRegistryForTests(createRegistrySpy({ selected: [], focused: [] }));
265
+
266
+ testSetup = await testRender(
267
+ createComparisonHarness({
268
+ axisMode: "price",
269
+ symbols: ["AAPL", "MSFT"],
270
+ symbolsText: "AAPL, MSFT",
271
+ }, [
272
+ makeTicker("AAPL", "USD"),
273
+ makeTicker("MSFT", "USD"),
274
+ ], [
275
+ ["AAPL", makeFinancials("AAPL", "USD", [])],
276
+ ["MSFT", makeFinancials("MSFT", "USD", [])],
277
+ ]),
278
+ { width: 120, height: 20 },
279
+ );
280
+
281
+ await flushFrames();
282
+
283
+ const frame = testSetup.captureCharFrame();
284
+ expect(frame).toContain("No chart data yet.");
285
+ expect(frame).toContain("1:1D");
286
+ expect(frame).toContain("2:1W");
287
+ expect(frame).toContain("AUTO");
288
+ });
289
+
232
290
  test("moves legend selection with the keyboard and opens the selected ticker on Enter", async () => {
233
291
  const spy = { selected: [] as string[], focused: [] as string[] };
234
- setSharedDataProviderForTests(createProvider({
292
+ const provider = createProvider({
235
293
  AAPL: [100, 102, 104],
236
294
  MSFT: [200, 202, 204],
237
295
  NVDA: [300, 305, 310],
@@ -239,7 +297,10 @@ describe("comparisonChartPlugin", () => {
239
297
  AAPL: "USD",
240
298
  MSFT: "USD",
241
299
  NVDA: "USD",
242
- }));
300
+ });
301
+ setSharedDataProviderForTests(provider);
302
+ sharedCoordinator = new MarketDataCoordinator(provider);
303
+ setSharedMarketDataCoordinator(sharedCoordinator);
243
304
  setSharedRegistryForTests(createRegistrySpy(spy));
244
305
 
245
306
  testSetup = await testRender(
@@ -1,7 +1,12 @@
1
1
  import { useCallback, useMemo } from "react";
2
2
  import { EmptyState } from "../../components";
3
3
  import { ComparisonStockChart } from "../../components/chart/comparison-stock-chart";
4
- import type { ChartAxisMode } from "../../components/chart/chart-types";
4
+ import type { ChartAxisMode, TimeRange } from "../../components/chart/chart-types";
5
+ import {
6
+ DEFAULT_COMPARISON_CHART_RANGE_PRESET,
7
+ DEFAULT_COMPARISON_CHART_RESOLUTION,
8
+ normalizeChartResolution,
9
+ } from "../../components/chart/chart-resolution";
5
10
  import { getSharedRegistry } from "../../plugins/registry";
6
11
  import { useAppState } from "../../state/app-context";
7
12
  import { colors } from "../../theme/colors";
@@ -13,6 +18,8 @@ export const COMPARISON_CHART_TEMPLATE_ID = "comparison-chart-pane";
13
18
 
14
19
  interface ComparisonChartPaneSettings {
15
20
  axisMode: ChartAxisMode;
21
+ rangePreset: TimeRange;
22
+ chartResolution: ReturnType<typeof normalizeChartResolution>;
16
23
  symbols: string[];
17
24
  symbolsText: string;
18
25
  }
@@ -46,6 +53,17 @@ export function getComparisonChartPaneSettings(settings: Record<string, unknown>
46
53
 
47
54
  return {
48
55
  axisMode: isChartAxisMode(settings?.axisMode) ? settings.axisMode : "price",
56
+ rangePreset: settings?.rangePreset === "1D"
57
+ || settings?.rangePreset === "1W"
58
+ || settings?.rangePreset === "1M"
59
+ || settings?.rangePreset === "3M"
60
+ || settings?.rangePreset === "6M"
61
+ || settings?.rangePreset === "1Y"
62
+ || settings?.rangePreset === "5Y"
63
+ || settings?.rangePreset === "ALL"
64
+ ? settings.rangePreset
65
+ : DEFAULT_COMPARISON_CHART_RANGE_PRESET,
66
+ chartResolution: normalizeChartResolution(settings?.chartResolution, DEFAULT_COMPARISON_CHART_RESOLUTION),
49
67
  symbols,
50
68
  symbolsText: storedText.trim().length > 0 ? storedText : formatTickerListInput(symbols),
51
69
  };
@@ -159,6 +177,8 @@ export const comparisonChartPlugin: GloomPlugin = {
159
177
  placement: "floating",
160
178
  settings: {
161
179
  axisMode: "percent",
180
+ rangePreset: DEFAULT_COMPARISON_CHART_RANGE_PRESET,
181
+ chartResolution: DEFAULT_COMPARISON_CHART_RESOLUTION,
162
182
  symbols,
163
183
  symbolsText: formatTickerListInput(symbols),
164
184
  },
@@ -25,6 +25,11 @@ export function setLayoutManagerDispatch(
25
25
  getStateRef = getState;
26
26
  }
27
27
 
28
+ export function clearLayoutManagerDispatch() {
29
+ dispatchRef = null;
30
+ getStateRef = null;
31
+ }
32
+
28
33
  function persistLayout(layout: LayoutConfig) {
29
34
  if (!dispatchRef) return;
30
35
  dispatchRef({ type: "PUSH_LAYOUT_HISTORY" });
@@ -261,4 +266,8 @@ export const layoutManagerPlugin: GloomPlugin = {
261
266
  },
262
267
  });
263
268
  },
269
+
270
+ dispose() {
271
+ clearLayoutManagerDispatch();
272
+ },
264
273
  };
@@ -1,5 +1,4 @@
1
1
  import { useRef, useEffect, useState } from "react";
2
- import { useKeyboard } from "@opentui/react";
3
2
  import type { GloomPlugin, DetailTabProps } from "../../types/plugin";
4
3
  import { usePaneTicker } from "../../state/app-context";
5
4
  import { colors } from "../../theme/colors";
@@ -88,15 +87,6 @@ function NewsTab({ width, height, focused }: DetailTabProps) {
88
87
  setSummaryCache((prev) => new Map(prev).set(selected.url, selectedSummary));
89
88
  }, [selected?.url, selectedSummary]);
90
89
 
91
- useKeyboard((event) => {
92
- if (!focused || news.length === 0) return;
93
- if (event.name === "j" || event.name === "down") {
94
- setSelectedIdx((index) => Math.min(index + 1, news.length - 1));
95
- } else if (event.name === "k" || event.name === "up") {
96
- setSelectedIdx((index) => Math.max(index - 1, 0));
97
- }
98
- });
99
-
100
90
  useEffect(() => {
101
91
  if (news.length > 0 && selectedIdx >= news.length) {
102
92
  setSelectedIdx(Math.max(0, news.length - 1));
@@ -114,13 +104,13 @@ function NewsTab({ width, height, focused }: DetailTabProps) {
114
104
  <DetailFeedView
115
105
  width={width}
116
106
  height={height}
107
+ focused={focused}
117
108
  items={items}
118
109
  selectedIdx={selectedIdx}
119
110
  hoveredIdx={hoveredIdx}
120
111
  onSelect={setSelectedIdx}
121
112
  onHover={setHoveredIdx}
122
113
  listVariant="single-line"
123
- splitListWidthRatio={0.36}
124
114
  />
125
115
  );
126
116
  }