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.
- package/README.md +19 -8
- package/package.json +1 -1
- package/src/app.tsx +45 -69
- package/src/cli/base-converter.ts +31 -0
- package/src/cli/commands/search.ts +11 -3
- package/src/cli/commands/ticker.ts +22 -11
- package/src/cli/context.ts +27 -32
- package/src/cli/helpers.ts +1 -4
- package/src/cli/index.ts +140 -123
- package/src/cli/registry.test.ts +226 -0
- package/src/cli/registry.ts +201 -0
- package/src/cli.test.ts +219 -0
- package/src/components/chart/chart-controller.ts +309 -0
- package/src/components/chart/chart-data.ts +5 -6
- package/src/components/chart/chart-pane-settings.ts +27 -0
- package/src/components/chart/chart-resolution.test.ts +56 -0
- package/src/components/chart/chart-resolution.ts +362 -0
- package/src/components/chart/chart-types.ts +12 -4
- package/src/components/chart/chart-viewport.test.ts +25 -0
- package/src/components/chart/chart-viewport.ts +28 -0
- package/src/components/chart/comparison-chart-data.ts +20 -27
- package/src/components/chart/comparison-stock-chart.tsx +374 -99
- package/src/components/chart/stock-chart-renderer-switch.test.tsx +124 -0
- package/src/components/chart/stock-chart.tsx +455 -106
- package/src/components/command-bar/__snapshots__/command-bar.test.tsx.snap +4 -4
- package/src/components/command-bar/command-bar.test.tsx +650 -5
- package/src/components/command-bar/command-bar.tsx +363 -63
- package/src/components/command-bar/command-registry.ts +8 -0
- package/src/components/command-bar/helpers.test.ts +23 -1
- package/src/components/command-bar/helpers.ts +14 -5
- package/src/components/command-bar/pane-template-display.ts +12 -0
- package/src/components/command-bar/root-shortcuts.ts +2 -1
- package/src/components/command-bar/workflow-ops.ts +4 -2
- package/src/components/detail-feed-view.test.tsx +124 -49
- package/src/components/detail-feed-view.tsx +396 -160
- package/src/components/layout/floating-pane.tsx +4 -2
- package/src/components/layout/header.tsx +3 -3
- package/src/components/layout/pane-header.tsx +26 -2
- package/src/components/layout/pane-sizing.ts +6 -0
- package/src/components/layout/pane.tsx +5 -1
- package/src/components/layout/shell.test.tsx +77 -5
- package/src/components/layout/shell.tsx +67 -52
- package/src/components/layout/status-bar.test.tsx +66 -0
- package/src/components/layout/status-bar.tsx +26 -5
- package/src/components/onboarding/onboarding-wizard.test.tsx +65 -0
- package/src/components/onboarding/onboarding-wizard.tsx +5 -6
- package/src/components/ticker-list-table.test.tsx +83 -0
- package/src/components/ticker-list-table.tsx +181 -97
- package/src/components/ui/data-table.tsx +50 -5
- package/src/components/ui/index.ts +1 -0
- package/src/components/ui/page-stack-view.tsx +60 -0
- package/src/components/use-double-click-activation.ts +31 -0
- package/src/index.tsx +10 -10
- package/src/market-data/coordinator.test.ts +92 -4
- package/src/market-data/coordinator.ts +32 -5
- package/src/market-data/hooks.test.tsx +120 -0
- package/src/market-data/hooks.tsx +53 -16
- package/src/market-data/request-types.ts +4 -2
- package/src/market-data/selectors.ts +24 -58
- package/src/plugins/builtin/chat-controller.test.ts +400 -3
- package/src/plugins/builtin/chat-controller.ts +328 -35
- package/src/plugins/builtin/chat.test.tsx +118 -6
- package/src/plugins/builtin/chat.tsx +180 -116
- package/src/plugins/builtin/comparison-chart.test.tsx +66 -5
- package/src/plugins/builtin/comparison-chart.tsx +21 -1
- package/src/plugins/builtin/layout-manager.tsx +9 -0
- package/src/plugins/builtin/news.tsx +1 -11
- package/src/plugins/builtin/portfolio-list/cli/commands.ts +656 -0
- package/src/plugins/builtin/portfolio-list/command-bar.ts +122 -0
- package/src/plugins/builtin/portfolio-list/header.tsx +16 -34
- package/src/plugins/builtin/portfolio-list/index.tsx +5 -0
- package/src/plugins/builtin/portfolio-list/metrics.test.ts +61 -0
- package/src/plugins/builtin/portfolio-list/metrics.ts +2 -2
- package/src/plugins/builtin/portfolio-list/mutations.test.ts +187 -0
- package/src/plugins/builtin/portfolio-list/mutations.ts +226 -0
- package/src/plugins/builtin/portfolio-list/pane.tsx +39 -17
- package/src/plugins/builtin/portfolio-list/table.tsx +11 -2
- package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.test.tsx +163 -0
- package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.ts +81 -0
- package/src/plugins/builtin/sec.tsx +1 -11
- package/src/plugins/builtin/ticker-detail/chart-tab.tsx +13 -2
- package/src/plugins/builtin/ticker-detail/financials-tab.tsx +21 -0
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +45 -10
- package/src/plugins/builtin/ticker-detail/pane.tsx +35 -17
- package/src/plugins/builtin/ticker-detail/settings.ts +19 -1
- package/src/plugins/builtin/ticker-detail.test.tsx +47 -0
- package/src/plugins/catalog.ts +65 -0
- package/src/plugins/ibkr/broker-adapter.ts +28 -0
- package/src/plugins/ibkr/gateway-service.test.ts +67 -1
- package/src/plugins/ibkr/gateway-service.ts +147 -7
- package/src/plugins/ibkr/index.tsx +7 -1
- package/src/plugins/pane-manager.test.ts +1 -1
- package/src/plugins/plugin-runtime.tsx +27 -7
- package/src/plugins/prediction-markets/cli.ts +28 -0
- package/src/plugins/prediction-markets/controller-data.ts +132 -17
- package/src/plugins/prediction-markets/controller.ts +142 -122
- package/src/plugins/prediction-markets/index.tsx +7 -1
- package/src/plugins/prediction-markets/launch.ts +51 -16
- package/src/plugins/prediction-markets/metrics.ts +9 -5
- package/src/plugins/prediction-markets/pane.test.tsx +197 -57
- package/src/plugins/prediction-markets/pane.tsx +80 -155
- package/src/plugins/prediction-markets/services/fetch.ts +4 -0
- package/src/plugins/prediction-markets/table.tsx +6 -3
- package/src/plugins/prediction-markets/test-helpers.tsx +20 -5
- package/src/plugins/prediction-markets/types.ts +0 -1
- package/src/plugins/registry.ts +23 -0
- package/src/sources/gloomberb-cloud.test.ts +29 -0
- package/src/sources/gloomberb-cloud.ts +89 -1
- package/src/sources/provider-router.test.ts +265 -3
- package/src/sources/provider-router.ts +239 -11
- package/src/sources/yahoo-finance.test.ts +26 -0
- package/src/sources/yahoo-finance.ts +54 -3
- package/src/state/app-context-selector.test.tsx +99 -0
- package/src/state/app-context.test.ts +18 -0
- package/src/state/app-context.tsx +170 -65
- package/src/state/selectors.ts +25 -0
- package/src/theme/colors.ts +8 -8
- package/src/theme/themes.test.ts +2 -0
- package/src/theme/themes.ts +22 -10
- package/src/types/broker.ts +21 -0
- package/src/types/config.ts +17 -1
- package/src/types/data-provider.ts +18 -0
- package/src/types/financials.ts +31 -1
- package/src/types/plugin.ts +70 -0
- package/src/updater.test.ts +2 -0
- package/src/utils/api-client.test.ts +57 -0
- package/src/utils/api-client.ts +47 -12
- package/src/utils/back-navigation.ts +27 -0
- package/src/utils/format.test.ts +14 -0
- package/src/utils/format.ts +3 -3
- package/src/utils/market-status.ts +46 -1
- package/src/utils/quote-freshness.ts +127 -0
- package/src/utils/quote-resolution.test.ts +277 -0
- package/src/utils/quote-resolution.ts +641 -0
- package/src/utils/slugify.ts +3 -0
- package/src/utils/timestamp.ts +25 -0
- package/src/version.ts +1 -1
- package/src/cli/commands/portfolio.ts +0 -210
- package/src/cli/commands/watchlist.ts +0 -210
- package/src/plugins/builtin/data-providers.ts +0 -16
- package/src/sources/exchange-rates.ts +0 -18
- package/src/utils/ascii-chart.ts +0 -172
- 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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
}
|