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
|
@@ -83,7 +83,58 @@ describe("prediction markets pane interactions", () => {
|
|
|
83
83
|
expect(frame).toContain("Will the Fed cut rates?");
|
|
84
84
|
});
|
|
85
85
|
|
|
86
|
-
test("
|
|
86
|
+
test("shows a venue warning instead of a raw fetch error when one catalog fails", async () => {
|
|
87
|
+
attachPredictionMarketsPersistence(new MemoryPersistence());
|
|
88
|
+
|
|
89
|
+
globalThis.fetch = (async (input: Request | string | URL) => {
|
|
90
|
+
const url = String(input);
|
|
91
|
+
if (url.includes("gamma-api.polymarket.com")) {
|
|
92
|
+
throw new Error("Unable to connect. Was there a typo in the url or port?");
|
|
93
|
+
}
|
|
94
|
+
if (url.includes("/trade-api/v2/events?")) {
|
|
95
|
+
return new Response(
|
|
96
|
+
JSON.stringify({
|
|
97
|
+
events: [
|
|
98
|
+
{
|
|
99
|
+
title: "Fed series",
|
|
100
|
+
category: "Economics",
|
|
101
|
+
event_ticker: "FED-1",
|
|
102
|
+
series_ticker: "FED",
|
|
103
|
+
markets: [
|
|
104
|
+
{
|
|
105
|
+
ticker: "KAL-1",
|
|
106
|
+
title: "Will the Fed cut rates?",
|
|
107
|
+
yes_sub_title: "Yes",
|
|
108
|
+
event_ticker: "FED-1",
|
|
109
|
+
status: "open",
|
|
110
|
+
market_type: "binary",
|
|
111
|
+
last_price_dollars: "0.48",
|
|
112
|
+
volume_24h_fp: "15000",
|
|
113
|
+
volume_fp: "90000",
|
|
114
|
+
open_interest_fp: "45000",
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
}),
|
|
120
|
+
{ status: 200 },
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
return new Response(JSON.stringify({}), { status: 200 });
|
|
124
|
+
}) as unknown as typeof fetch;
|
|
125
|
+
|
|
126
|
+
testSetup = await testRender(<Harness />, { width: 120, height: 34 });
|
|
127
|
+
await flushFrames(testSetup);
|
|
128
|
+
|
|
129
|
+
const frame = testSetup.captureCharFrame();
|
|
130
|
+
expect(frame).toContain("Will the Fed cut rates?");
|
|
131
|
+
expect(frame).toContain(
|
|
132
|
+
"Polymarket unavailable right now; showing Kalshi markets.",
|
|
133
|
+
);
|
|
134
|
+
expect(frame).not.toContain("Was there a typo in the url or port?");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("selects a market on single click and opens detail on double click", async () => {
|
|
87
138
|
installPredictionMarketMocks();
|
|
88
139
|
|
|
89
140
|
testSetup = await testRender(<Harness />, { width: 120, height: 34 });
|
|
@@ -115,9 +166,45 @@ describe("prediction markets pane interactions", () => {
|
|
|
115
166
|
|
|
116
167
|
frame = testSetup.captureCharFrame();
|
|
117
168
|
expect(frame).toContain("Will the Fed cut rates?");
|
|
169
|
+
expect(frame).not.toContain("Kalshi primary rule");
|
|
170
|
+
|
|
171
|
+
await act(async () => {
|
|
172
|
+
await testSetup!.mockMouse.click(kalshiCol + 1, kalshiRow);
|
|
173
|
+
await testSetup!.renderOnce();
|
|
174
|
+
});
|
|
175
|
+
await flushFrames(testSetup);
|
|
176
|
+
|
|
177
|
+
frame = testSetup.captureCharFrame();
|
|
118
178
|
expect(frame).toContain("Kalshi primary rule");
|
|
119
179
|
});
|
|
120
180
|
|
|
181
|
+
test("focuses the pane when a market row is clicked", async () => {
|
|
182
|
+
installPredictionMarketMocks();
|
|
183
|
+
|
|
184
|
+
testSetup = await testRender(
|
|
185
|
+
<Harness initialFocusedPaneId="portfolio-list:main" />,
|
|
186
|
+
{ width: 120, height: 34 },
|
|
187
|
+
);
|
|
188
|
+
await flushFrames(testSetup);
|
|
189
|
+
|
|
190
|
+
expect(harnessStateRef.current?.focusedPaneId).toBe("portfolio-list:main");
|
|
191
|
+
|
|
192
|
+
const frame = testSetup.captureCharFrame();
|
|
193
|
+
const lines = frame.split("\n");
|
|
194
|
+
const kalshiRow = lines.findIndex((line) =>
|
|
195
|
+
line.includes("Will the Fed cut rates?"),
|
|
196
|
+
);
|
|
197
|
+
const kalshiCol = lines[kalshiRow]?.indexOf("Will the Fed cut rates?") ?? -1;
|
|
198
|
+
|
|
199
|
+
await act(async () => {
|
|
200
|
+
await testSetup!.mockMouse.click(kalshiCol + 1, kalshiRow);
|
|
201
|
+
await testSetup!.renderOnce();
|
|
202
|
+
});
|
|
203
|
+
await flushFrames(testSetup);
|
|
204
|
+
|
|
205
|
+
expect(harnessStateRef.current?.focusedPaneId).toBe(TEST_PANE_ID);
|
|
206
|
+
});
|
|
207
|
+
|
|
121
208
|
test("loads selected detail once instead of refetching in a render loop", async () => {
|
|
122
209
|
const { fetchUrls } = installPredictionMarketMocks();
|
|
123
210
|
|
|
@@ -134,6 +221,8 @@ describe("prediction markets pane interactions", () => {
|
|
|
134
221
|
await act(async () => {
|
|
135
222
|
await testSetup!.mockMouse.click(kalshiCol + 1, kalshiRow);
|
|
136
223
|
await testSetup!.renderOnce();
|
|
224
|
+
await testSetup!.mockMouse.click(kalshiCol + 1, kalshiRow);
|
|
225
|
+
await testSetup!.renderOnce();
|
|
137
226
|
});
|
|
138
227
|
await flushFrames(testSetup, 8);
|
|
139
228
|
|
|
@@ -157,7 +246,7 @@ describe("prediction markets pane interactions", () => {
|
|
|
157
246
|
expect(testSetup.captureCharFrame()).not.toContain("Loading market detail...");
|
|
158
247
|
});
|
|
159
248
|
|
|
160
|
-
test("moves selection through the list with keyboard navigation", async () => {
|
|
249
|
+
test("moves selection through the list with keyboard navigation without opening detail", async () => {
|
|
161
250
|
installPredictionMarketMocks();
|
|
162
251
|
|
|
163
252
|
testSetup = await testRender(<Harness />, { width: 120, height: 34 });
|
|
@@ -176,6 +265,102 @@ describe("prediction markets pane interactions", () => {
|
|
|
176
265
|
|
|
177
266
|
const frame = testSetup.captureCharFrame();
|
|
178
267
|
expect(frame).toContain("Will the Fed cut rates?");
|
|
268
|
+
expect(frame).not.toContain("Kalshi primary rule");
|
|
269
|
+
expect(frame).not.toContain("<- Back");
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test("keeps a clicked header sort instead of snapping back to the default sort", async () => {
|
|
273
|
+
installPredictionMarketMocks();
|
|
274
|
+
|
|
275
|
+
testSetup = await testRender(<Harness />, { width: 120, height: 34 });
|
|
276
|
+
await flushFrames(testSetup);
|
|
277
|
+
|
|
278
|
+
const frame = testSetup.captureCharFrame();
|
|
279
|
+
const lines = frame.split("\n");
|
|
280
|
+
const headerRow = lines.findIndex((line) => line.includes("VENUE"));
|
|
281
|
+
const headerCol = lines[headerRow]?.indexOf("VENUE") ?? -1;
|
|
282
|
+
|
|
283
|
+
expect(headerRow).toBeGreaterThan(-1);
|
|
284
|
+
expect(headerCol).toBeGreaterThan(-1);
|
|
285
|
+
|
|
286
|
+
await act(async () => {
|
|
287
|
+
await testSetup!.mockMouse.click(headerCol + 1, headerRow);
|
|
288
|
+
await testSetup!.renderOnce();
|
|
289
|
+
});
|
|
290
|
+
await flushFrames(testSetup, 4);
|
|
291
|
+
|
|
292
|
+
const nextFrame = testSetup.captureCharFrame();
|
|
293
|
+
const nextLines = nextFrame.split("\n");
|
|
294
|
+
const kalshiRow = nextLines.findIndex((line) =>
|
|
295
|
+
line.includes("Will the Fed cut rates?"),
|
|
296
|
+
);
|
|
297
|
+
const polymarketRow = nextLines.findIndex((line) =>
|
|
298
|
+
line.includes("Will inflation fall?"),
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
expect(
|
|
302
|
+
harnessStateRef.current?.paneState[TEST_PANE_ID]?.pluginState?.[
|
|
303
|
+
"prediction-markets"
|
|
304
|
+
]?.sortPreference,
|
|
305
|
+
).toEqual({ columnId: "venue", direction: "asc" });
|
|
306
|
+
expect(nextLines[headerRow]).toContain("VENUE ▲");
|
|
307
|
+
expect(kalshiRow).toBeGreaterThan(-1);
|
|
308
|
+
expect(polymarketRow).toBeGreaterThan(-1);
|
|
309
|
+
expect(kalshiRow).toBeLessThan(polymarketRow);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
test("cycles the ends header through reverse date order before resetting", async () => {
|
|
313
|
+
installPredictionMarketMocks();
|
|
314
|
+
|
|
315
|
+
testSetup = await testRender(<Harness />, { width: 120, height: 34 });
|
|
316
|
+
await flushFrames(testSetup);
|
|
317
|
+
|
|
318
|
+
const frame = testSetup.captureCharFrame();
|
|
319
|
+
const lines = frame.split("\n");
|
|
320
|
+
const headerRow = lines.findIndex((line) => line.includes("ENDS"));
|
|
321
|
+
const headerCol = lines[headerRow]?.indexOf("ENDS") ?? -1;
|
|
322
|
+
|
|
323
|
+
expect(headerRow).toBeGreaterThan(-1);
|
|
324
|
+
expect(headerCol).toBeGreaterThan(-1);
|
|
325
|
+
|
|
326
|
+
await act(async () => {
|
|
327
|
+
await testSetup!.mockMouse.click(headerCol + 1, headerRow);
|
|
328
|
+
await testSetup!.renderOnce();
|
|
329
|
+
});
|
|
330
|
+
await flushFrames(testSetup, 2);
|
|
331
|
+
|
|
332
|
+
const sortedFrame = testSetup.captureCharFrame();
|
|
333
|
+
const sortedLines = sortedFrame.split("\n");
|
|
334
|
+
const sortedHeaderRow = sortedLines.findIndex((line) => line.includes("ENDS"));
|
|
335
|
+
const sortedHeaderCol = sortedLines[sortedHeaderRow]?.indexOf("ENDS") ?? -1;
|
|
336
|
+
|
|
337
|
+
expect(sortedHeaderRow).toBeGreaterThan(-1);
|
|
338
|
+
expect(sortedHeaderCol).toBeGreaterThan(-1);
|
|
339
|
+
|
|
340
|
+
await act(async () => {
|
|
341
|
+
await testSetup!.mockMouse.click(sortedHeaderCol + 1, sortedHeaderRow);
|
|
342
|
+
await testSetup!.renderOnce();
|
|
343
|
+
});
|
|
344
|
+
await flushFrames(testSetup, 4);
|
|
345
|
+
|
|
346
|
+
const nextFrame = testSetup.captureCharFrame();
|
|
347
|
+
const nextLines = nextFrame.split("\n");
|
|
348
|
+
const kalshiRow = nextLines.findIndex((line) =>
|
|
349
|
+
line.includes("Will the Fed cut rates?"),
|
|
350
|
+
);
|
|
351
|
+
const polymarketRow = nextLines.findIndex((line) =>
|
|
352
|
+
line.includes("Will inflation fall?"),
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
expect(
|
|
356
|
+
harnessStateRef.current?.paneState[TEST_PANE_ID]?.pluginState?.[
|
|
357
|
+
"prediction-markets"
|
|
358
|
+
]?.sortPreference,
|
|
359
|
+
).toEqual({ columnId: "ends", direction: "desc" });
|
|
360
|
+
expect(nextLines[headerRow]).toContain("ENDS ▼");
|
|
361
|
+
expect(kalshiRow).toBeGreaterThan(-1);
|
|
362
|
+
expect(polymarketRow).toBeGreaterThan(-1);
|
|
363
|
+
expect(kalshiRow).toBeLessThan(polymarketRow);
|
|
179
364
|
});
|
|
180
365
|
|
|
181
366
|
test("does not wrap to the last row when moving up without a selection", async () => {
|
|
@@ -197,7 +382,7 @@ describe("prediction markets pane interactions", () => {
|
|
|
197
382
|
).toBe("polymarket:pm-1");
|
|
198
383
|
});
|
|
199
384
|
|
|
200
|
-
test("supports detail outcome navigation and
|
|
385
|
+
test("supports detail outcome navigation and backspace return from the keyboard", async () => {
|
|
201
386
|
attachPredictionMarketsPersistence(new MemoryPersistence());
|
|
202
387
|
|
|
203
388
|
globalThis.fetch = (async (input: Request | string | URL) => {
|
|
@@ -326,17 +511,15 @@ describe("prediction markets pane interactions", () => {
|
|
|
326
511
|
]?.selectedDetailMarketKey,
|
|
327
512
|
).toBe("kalshi:KXFED-27APR-T4.50");
|
|
328
513
|
|
|
329
|
-
await emitKeypress(testSetup, { name: "
|
|
514
|
+
await emitKeypress(testSetup, { name: "backspace", sequence: "\u007f" });
|
|
330
515
|
await flushFrames(testSetup);
|
|
331
516
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
]?.focusRegion,
|
|
336
|
-
).toBe("list");
|
|
517
|
+
const frame = testSetup.captureCharFrame();
|
|
518
|
+
expect(frame).toContain("search markets");
|
|
519
|
+
expect(frame).not.toContain("Rule 2");
|
|
337
520
|
});
|
|
338
521
|
|
|
339
|
-
test("
|
|
522
|
+
test("opens full-page detail from a row double click and preserves the browse selection", async () => {
|
|
340
523
|
installPredictionMarketMocks();
|
|
341
524
|
|
|
342
525
|
testSetup = await testRender(<Harness />, { width: 120, height: 34 });
|
|
@@ -352,62 +535,19 @@ describe("prediction markets pane interactions", () => {
|
|
|
352
535
|
await act(async () => {
|
|
353
536
|
await testSetup!.mockMouse.click(kalshiCol + 1, kalshiRow);
|
|
354
537
|
await testSetup!.renderOnce();
|
|
355
|
-
});
|
|
356
|
-
await flushFrames(testSetup);
|
|
357
|
-
|
|
358
|
-
await act(async () => {
|
|
359
538
|
await testSetup!.mockMouse.click(kalshiCol + 1, kalshiRow);
|
|
360
539
|
await testSetup!.renderOnce();
|
|
361
540
|
});
|
|
362
541
|
await flushFrames(testSetup);
|
|
363
542
|
|
|
543
|
+
const detailFrame = testSetup.captureCharFrame();
|
|
544
|
+
expect(detailFrame).toContain("<- Back");
|
|
545
|
+
expect(detailFrame).toContain("Kalshi primary rule");
|
|
364
546
|
expect(
|
|
365
547
|
harnessStateRef.current?.paneState[TEST_PANE_ID]?.pluginState?.[
|
|
366
548
|
"prediction-markets"
|
|
367
549
|
]?.selectedRowKey,
|
|
368
|
-
).
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
test("resizes the list/detail split by dragging the divider", async () => {
|
|
372
|
-
installPredictionMarketMocks();
|
|
373
|
-
|
|
374
|
-
testSetup = await testRender(<Harness />, { width: 120, height: 34 });
|
|
375
|
-
await flushFrames(testSetup);
|
|
376
|
-
|
|
377
|
-
let frame = testSetup.captureCharFrame();
|
|
378
|
-
const lines = frame.split("\n");
|
|
379
|
-
const kalshiRow = lines.findIndex((line) =>
|
|
380
|
-
line.includes("Will the Fed cut rates?"),
|
|
381
|
-
);
|
|
382
|
-
const kalshiCol = lines[kalshiRow]?.indexOf("Will the Fed cut rates?") ?? -1;
|
|
383
|
-
|
|
384
|
-
await act(async () => {
|
|
385
|
-
await testSetup!.mockMouse.click(kalshiCol + 1, kalshiRow);
|
|
386
|
-
await testSetup!.renderOnce();
|
|
387
|
-
});
|
|
388
|
-
await flushFrames(testSetup);
|
|
389
|
-
|
|
390
|
-
frame = testSetup.captureCharFrame();
|
|
391
|
-
const dividerLines = frame.split("\n");
|
|
392
|
-
const dividerRow = dividerLines.findIndex((line) => line.includes("│"));
|
|
393
|
-
const dividerCol = dividerLines[dividerRow]?.indexOf("│") ?? -1;
|
|
394
|
-
|
|
395
|
-
await act(async () => {
|
|
396
|
-
await testSetup!.mockMouse.drag(
|
|
397
|
-
dividerCol,
|
|
398
|
-
dividerRow,
|
|
399
|
-
dividerCol + 10,
|
|
400
|
-
dividerRow,
|
|
401
|
-
);
|
|
402
|
-
await testSetup!.renderOnce();
|
|
403
|
-
});
|
|
404
|
-
await flushFrames(testSetup);
|
|
405
|
-
|
|
406
|
-
expect(
|
|
407
|
-
harnessStateRef.current?.paneState[TEST_PANE_ID]?.pluginState?.[
|
|
408
|
-
"prediction-markets"
|
|
409
|
-
]?.detailSplitRatio,
|
|
410
|
-
).toBeGreaterThan(0.42);
|
|
550
|
+
).toBe("kalshi:KAL-1");
|
|
411
551
|
});
|
|
412
552
|
|
|
413
553
|
test("switches categories with arrows and venues with shift+arrows", async () => {
|
|
@@ -1,106 +1,29 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { TextAttributes } from "@opentui/core";
|
|
2
|
+
import { PageStackView, TabBar } from "../../components";
|
|
3
|
+
import type { PaneProps } from "../../types/plugin";
|
|
4
|
+
import { colors } from "../../theme/colors";
|
|
5
|
+
import { PREDICTION_CATEGORY_OPTIONS } from "./categories";
|
|
4
6
|
import { usePredictionMarketsController } from "./controller";
|
|
5
7
|
import { PredictionMarketDetailPane } from "./detail/pane";
|
|
6
8
|
import { BROWSE_TABS, VENUE_TABS } from "./navigation";
|
|
7
9
|
import { PredictionMarketsTable } from "./table";
|
|
8
|
-
import { PREDICTION_CATEGORY_OPTIONS } from "./categories";
|
|
9
10
|
import type { PredictionBrowseTab, PredictionCategoryId } from "./types";
|
|
10
|
-
import type { PaneProps } from "../../types/plugin";
|
|
11
|
-
import { colors } from "../../theme/colors";
|
|
12
|
-
|
|
13
|
-
function clamp(value: number, min: number, max: number): number {
|
|
14
|
-
return Math.min(Math.max(value, min), max);
|
|
15
|
-
}
|
|
16
11
|
|
|
17
12
|
export function PredictionMarketsPane({ focused, width, height }: PaneProps) {
|
|
18
13
|
const controller = usePredictionMarketsController({ focused });
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const minTableWidth = 44;
|
|
24
|
-
const minDetailWidth = 36;
|
|
25
|
-
const effectiveMinTableWidth = controller.selectedSummary
|
|
26
|
-
? Math.min(minTableWidth, Math.max(width - minDetailWidth - 1, 20))
|
|
27
|
-
: width;
|
|
28
|
-
const effectiveMinDetailWidth = controller.selectedSummary
|
|
29
|
-
? Math.min(minDetailWidth, Math.max(width - effectiveMinTableWidth - 1, 20))
|
|
30
|
-
: 0;
|
|
31
|
-
const listWidth = controller.selectedSummary
|
|
32
|
-
? clamp(
|
|
33
|
-
Math.round(width * controller.detailSplitRatio),
|
|
34
|
-
effectiveMinTableWidth,
|
|
35
|
-
Math.max(effectiveMinTableWidth, width - effectiveMinDetailWidth - 1),
|
|
36
|
-
)
|
|
37
|
-
: width;
|
|
38
|
-
const detailWidth = controller.selectedSummary
|
|
39
|
-
? Math.max(width - listWidth - 1, effectiveMinDetailWidth)
|
|
40
|
-
: 0;
|
|
41
|
-
const dividerColor =
|
|
42
|
-
controller.focusRegion === "detail" ? colors.borderFocused : colors.border;
|
|
43
|
-
|
|
44
|
-
const handlePaneMouse = (event: {
|
|
45
|
-
type?: string;
|
|
46
|
-
x?: number;
|
|
47
|
-
preventDefault?: () => void;
|
|
48
|
-
stopPropagation?: () => void;
|
|
49
|
-
}) => {
|
|
50
|
-
if (!controller.selectedSummary || !rootRef.current) {
|
|
51
|
-
splitDragRef.current = null;
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const localX = (event.x ?? 0) - rootRef.current.x;
|
|
56
|
-
const dividerX = listWidth;
|
|
57
|
-
|
|
58
|
-
if (
|
|
59
|
-
event.type === "down" &&
|
|
60
|
-
localX >= dividerX &&
|
|
61
|
-
localX <= dividerX + 1
|
|
62
|
-
) {
|
|
63
|
-
splitDragRef.current = {
|
|
64
|
-
startX: localX,
|
|
65
|
-
startRatio: controller.detailSplitRatio,
|
|
66
|
-
};
|
|
67
|
-
event.preventDefault?.();
|
|
68
|
-
event.stopPropagation?.();
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
14
|
+
const catalogStatusColor =
|
|
15
|
+
controller.catalogStatus?.tone === "danger"
|
|
16
|
+
? colors.negative
|
|
17
|
+
: colors.borderFocused;
|
|
71
18
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (event.type === "drag") {
|
|
75
|
-
const delta = localX - splitDragRef.current.startX;
|
|
76
|
-
const nextListWidth = clamp(
|
|
77
|
-
Math.round(width * splitDragRef.current.startRatio) + delta,
|
|
78
|
-
effectiveMinTableWidth,
|
|
79
|
-
Math.max(effectiveMinTableWidth, width - effectiveMinDetailWidth - 1),
|
|
80
|
-
);
|
|
81
|
-
controller.actions.setDetailSplitRatio(nextListWidth / Math.max(width, 1));
|
|
82
|
-
event.preventDefault?.();
|
|
83
|
-
event.stopPropagation?.();
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (event.type === "up" || event.type === "drag-end") {
|
|
88
|
-
splitDragRef.current = null;
|
|
89
|
-
event.preventDefault?.();
|
|
90
|
-
event.stopPropagation?.();
|
|
91
|
-
}
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
return (
|
|
19
|
+
const browseContent = (
|
|
95
20
|
<box
|
|
96
|
-
ref={rootRef}
|
|
97
21
|
flexDirection="column"
|
|
98
22
|
width={width}
|
|
99
23
|
height={height}
|
|
100
24
|
backgroundColor={colors.panel}
|
|
101
|
-
onMouse={handlePaneMouse}
|
|
102
25
|
>
|
|
103
|
-
{!controller.paneSettings.hideTabs
|
|
26
|
+
{!controller.paneSettings.hideTabs ? (
|
|
104
27
|
<TabBar
|
|
105
28
|
tabs={VENUE_TABS.map((tab) => ({
|
|
106
29
|
label: tab.label,
|
|
@@ -110,7 +33,7 @@ export function PredictionMarketsPane({ focused, width, height }: PaneProps) {
|
|
|
110
33
|
onSelect={controller.actions.setVenue}
|
|
111
34
|
compact
|
|
112
35
|
/>
|
|
113
|
-
)}
|
|
36
|
+
) : null}
|
|
114
37
|
|
|
115
38
|
<box flexDirection="row" height={1} paddingX={1} gap={2}>
|
|
116
39
|
<box
|
|
@@ -118,9 +41,7 @@ export function PredictionMarketsPane({ focused, width, height }: PaneProps) {
|
|
|
118
41
|
onMouseDown={controller.actions.focusSearch}
|
|
119
42
|
width={Math.max(18, Math.floor(width * 0.32))}
|
|
120
43
|
>
|
|
121
|
-
<text fg={colors.textDim}>
|
|
122
|
-
{controller.searchFocused ? "?" : "/"}
|
|
123
|
-
</text>
|
|
44
|
+
<text fg={colors.textDim}>{controller.searchFocused ? "?" : "/"}</text>
|
|
124
45
|
<box width={1} />
|
|
125
46
|
{controller.searchFocused ? (
|
|
126
47
|
<input
|
|
@@ -168,7 +89,7 @@ export function PredictionMarketsPane({ focused, width, height }: PaneProps) {
|
|
|
168
89
|
</box>
|
|
169
90
|
</box>
|
|
170
91
|
|
|
171
|
-
{PREDICTION_CATEGORY_OPTIONS.length > 1
|
|
92
|
+
{PREDICTION_CATEGORY_OPTIONS.length > 1 ? (
|
|
172
93
|
<scrollbox height={1} scrollX focusable={false}>
|
|
173
94
|
<box flexDirection="row" paddingX={1} gap={2}>
|
|
174
95
|
{PREDICTION_CATEGORY_OPTIONS.map((category) => {
|
|
@@ -194,71 +115,75 @@ export function PredictionMarketsPane({ focused, width, height }: PaneProps) {
|
|
|
194
115
|
})}
|
|
195
116
|
</box>
|
|
196
117
|
</scrollbox>
|
|
197
|
-
)}
|
|
118
|
+
) : null}
|
|
198
119
|
|
|
199
|
-
{controller.
|
|
200
|
-
<box height={1} paddingX={1}>
|
|
201
|
-
<text fg={
|
|
120
|
+
{controller.catalogStatus ? (
|
|
121
|
+
<box height={1} paddingX={1} width={width} backgroundColor={colors.panel}>
|
|
122
|
+
<text fg={catalogStatusColor}>{controller.catalogStatus.message}</text>
|
|
202
123
|
</box>
|
|
203
|
-
)}
|
|
204
|
-
|
|
205
|
-
<box flexDirection="
|
|
206
|
-
<
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
scrollRef={controller.scrollRef}
|
|
224
|
-
syncHeaderScroll={controller.layout.syncHeaderScroll}
|
|
225
|
-
onBodyScrollActivity={controller.layout.onBodyScrollActivity}
|
|
226
|
-
/>
|
|
227
|
-
</box>
|
|
228
|
-
|
|
229
|
-
{controller.selectedSummary && (
|
|
230
|
-
<>
|
|
231
|
-
<box width={1}>
|
|
232
|
-
<text fg={dividerColor}>│</text>
|
|
233
|
-
</box>
|
|
234
|
-
|
|
235
|
-
<box
|
|
236
|
-
width={detailWidth}
|
|
237
|
-
flexDirection="column"
|
|
238
|
-
paddingX={1}
|
|
239
|
-
onMouseDown={controller.actions.focusDetail}
|
|
240
|
-
>
|
|
241
|
-
<PredictionMarketDetailPane
|
|
242
|
-
detail={controller.detail}
|
|
243
|
-
detailError={controller.detailError}
|
|
244
|
-
detailLoadCount={controller.detailLoadCount}
|
|
245
|
-
detailTab={controller.detailTab}
|
|
246
|
-
detailWidth={Math.max(detailWidth - 2, 24)}
|
|
247
|
-
focused={controller.focusRegion === "detail"}
|
|
248
|
-
height={height}
|
|
249
|
-
historyRange={controller.historyRange}
|
|
250
|
-
onDetailTabChange={controller.actions.setDetailTab}
|
|
251
|
-
onHistoryRangeChange={controller.actions.setHistoryRange}
|
|
252
|
-
onPreviewOrder={controller.actions.previewOrder}
|
|
253
|
-
onSelectMarket={controller.actions.selectMarket}
|
|
254
|
-
scrollRef={controller.detailScrollRef}
|
|
255
|
-
selectedRow={controller.selectedRow}
|
|
256
|
-
selectedSummary={controller.selectedSummary}
|
|
257
|
-
/>
|
|
258
|
-
</box>
|
|
259
|
-
</>
|
|
260
|
-
)}
|
|
124
|
+
) : null}
|
|
125
|
+
|
|
126
|
+
<box flexDirection="column" flexGrow={1}>
|
|
127
|
+
<PredictionMarketsTable
|
|
128
|
+
columns={controller.visibleColumns}
|
|
129
|
+
rows={controller.visibleRows}
|
|
130
|
+
selectedRowKey={controller.selectedRow?.key ?? null}
|
|
131
|
+
hoveredIdx={controller.hoveredIdx}
|
|
132
|
+
setHoveredIdx={controller.actions.setHoveredIdx}
|
|
133
|
+
onSelectRow={(rowKey) => controller.actions.setBrowseSelection(rowKey)}
|
|
134
|
+
onOpenRow={controller.actions.openSelectedRow}
|
|
135
|
+
watchlist={controller.watchlistSet}
|
|
136
|
+
onToggleWatchlist={controller.actions.toggleWatchlist}
|
|
137
|
+
sortPreference={controller.sortPreference}
|
|
138
|
+
onHeaderClick={controller.actions.handleSortHeaderClick}
|
|
139
|
+
headerScrollRef={controller.headerScrollRef}
|
|
140
|
+
scrollRef={controller.scrollRef}
|
|
141
|
+
syncHeaderScroll={controller.layout.syncHeaderScroll}
|
|
142
|
+
onBodyScrollActivity={controller.layout.onBodyScrollActivity}
|
|
143
|
+
/>
|
|
261
144
|
</box>
|
|
262
145
|
</box>
|
|
263
146
|
);
|
|
147
|
+
|
|
148
|
+
const detailContent =
|
|
149
|
+
controller.selectedSummary && controller.selectedRow ? (
|
|
150
|
+
<box
|
|
151
|
+
flexDirection="column"
|
|
152
|
+
flexGrow={1}
|
|
153
|
+
width={width}
|
|
154
|
+
height={Math.max(height - 1, 1)}
|
|
155
|
+
paddingX={1}
|
|
156
|
+
backgroundColor={colors.panel}
|
|
157
|
+
>
|
|
158
|
+
<PredictionMarketDetailPane
|
|
159
|
+
detail={controller.detail}
|
|
160
|
+
detailError={controller.detailError}
|
|
161
|
+
detailLoadCount={controller.detailLoadCount}
|
|
162
|
+
detailTab={controller.detailTab}
|
|
163
|
+
detailWidth={Math.max(width - 2, 24)}
|
|
164
|
+
focused={focused && controller.detailOpen}
|
|
165
|
+
height={Math.max(height - 1, 1)}
|
|
166
|
+
historyRange={controller.historyRange}
|
|
167
|
+
onDetailTabChange={controller.actions.setDetailTab}
|
|
168
|
+
onHistoryRangeChange={controller.actions.setHistoryRange}
|
|
169
|
+
onPreviewOrder={controller.actions.previewOrder}
|
|
170
|
+
onSelectMarket={controller.actions.selectMarket}
|
|
171
|
+
scrollRef={controller.detailScrollRef}
|
|
172
|
+
selectedRow={controller.selectedRow}
|
|
173
|
+
selectedSummary={controller.selectedSummary}
|
|
174
|
+
/>
|
|
175
|
+
</box>
|
|
176
|
+
) : (
|
|
177
|
+
<box flexGrow={1} backgroundColor={colors.panel} />
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
return (
|
|
181
|
+
<PageStackView
|
|
182
|
+
focused={focused}
|
|
183
|
+
detailOpen={controller.detailOpen && !!controller.selectedSummary}
|
|
184
|
+
onBack={controller.actions.closeDetail}
|
|
185
|
+
rootContent={browseContent}
|
|
186
|
+
detailContent={detailContent}
|
|
187
|
+
/>
|
|
188
|
+
);
|
|
264
189
|
}
|
|
@@ -19,6 +19,10 @@ export function attachPredictionMarketsPersistence(
|
|
|
19
19
|
predictionMarketsPersistence = persistence;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
export function resetPredictionMarketsPersistence(): void {
|
|
23
|
+
predictionMarketsPersistence = null;
|
|
24
|
+
}
|
|
25
|
+
|
|
22
26
|
export function getPredictionMarketsPersistence(): PluginPersistence | null {
|
|
23
27
|
return predictionMarketsPersistence;
|
|
24
28
|
}
|
|
@@ -14,7 +14,8 @@ export function PredictionMarketsTable({
|
|
|
14
14
|
selectedRowKey,
|
|
15
15
|
hoveredIdx,
|
|
16
16
|
setHoveredIdx,
|
|
17
|
-
|
|
17
|
+
onSelectRow,
|
|
18
|
+
onOpenRow,
|
|
18
19
|
watchlist,
|
|
19
20
|
onToggleWatchlist,
|
|
20
21
|
sortPreference,
|
|
@@ -29,7 +30,8 @@ export function PredictionMarketsTable({
|
|
|
29
30
|
selectedRowKey: string | null;
|
|
30
31
|
hoveredIdx: number | null;
|
|
31
32
|
setHoveredIdx: (index: number | null) => void;
|
|
32
|
-
|
|
33
|
+
onSelectRow: (rowKey: string) => void;
|
|
34
|
+
onOpenRow: (rowKey: string) => void;
|
|
33
35
|
watchlist: Set<string>;
|
|
34
36
|
onToggleWatchlist: (row: PredictionListRow) => void;
|
|
35
37
|
sortPreference: PredictionSortPreference;
|
|
@@ -56,7 +58,8 @@ export function PredictionMarketsTable({
|
|
|
56
58
|
setHoveredIdx={setHoveredIdx}
|
|
57
59
|
getItemKey={(row) => row.key}
|
|
58
60
|
isSelected={(row) => selectedRowKey === row.key}
|
|
59
|
-
onSelect={(row) =>
|
|
61
|
+
onSelect={(row) => onSelectRow(row.key)}
|
|
62
|
+
onActivate={(row) => onOpenRow(row.key)}
|
|
60
63
|
virtualize
|
|
61
64
|
renderCell={(row, column, _index, rowState) => {
|
|
62
65
|
const watchlisted = row.watchMarketKeys.some((marketKey) =>
|