gloomberb 0.3.7 → 0.4.0
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 +29 -65
- 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 +359 -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 +1 -1
- package/src/components/layout/pane-header.tsx +26 -2
- package/src/components/layout/shell.tsx +62 -50
- 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 +174 -97
- package/src/components/ui/data-table.tsx +27 -2
- 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 +370 -3
- package/src/plugins/builtin/chat-controller.ts +310 -35
- package/src/plugins/builtin/chat.test.tsx +118 -6
- package/src/plugins/builtin/chat.tsx +175 -116
- package/src/plugins/builtin/comparison-chart.test.tsx +66 -5
- package/src/plugins/builtin/comparison-chart.tsx +21 -1
- 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 +45 -1
- package/src/plugins/ibkr/gateway-service.ts +141 -7
- 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 +11 -3
- package/src/plugins/prediction-markets/controller.ts +141 -121
- package/src/plugins/prediction-markets/index.tsx +2 -0
- 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 +119 -57
- package/src/plugins/prediction-markets/pane.tsx +74 -153
- package/src/plugins/prediction-markets/table.tsx +6 -3
- package/src/plugins/prediction-markets/types.ts +0 -1
- 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/utils/api-client.test.ts +57 -0
- package/src/utils/api-client.ts +35 -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,7 @@ describe("prediction markets pane interactions", () => {
|
|
|
83
83
|
expect(frame).toContain("Will the Fed cut rates?");
|
|
84
84
|
});
|
|
85
85
|
|
|
86
|
-
test("
|
|
86
|
+
test("selects a market on single click and opens detail on double click", async () => {
|
|
87
87
|
installPredictionMarketMocks();
|
|
88
88
|
|
|
89
89
|
testSetup = await testRender(<Harness />, { width: 120, height: 34 });
|
|
@@ -115,6 +115,15 @@ describe("prediction markets pane interactions", () => {
|
|
|
115
115
|
|
|
116
116
|
frame = testSetup.captureCharFrame();
|
|
117
117
|
expect(frame).toContain("Will the Fed cut rates?");
|
|
118
|
+
expect(frame).not.toContain("Kalshi primary rule");
|
|
119
|
+
|
|
120
|
+
await act(async () => {
|
|
121
|
+
await testSetup!.mockMouse.click(kalshiCol + 1, kalshiRow);
|
|
122
|
+
await testSetup!.renderOnce();
|
|
123
|
+
});
|
|
124
|
+
await flushFrames(testSetup);
|
|
125
|
+
|
|
126
|
+
frame = testSetup.captureCharFrame();
|
|
118
127
|
expect(frame).toContain("Kalshi primary rule");
|
|
119
128
|
});
|
|
120
129
|
|
|
@@ -134,6 +143,8 @@ describe("prediction markets pane interactions", () => {
|
|
|
134
143
|
await act(async () => {
|
|
135
144
|
await testSetup!.mockMouse.click(kalshiCol + 1, kalshiRow);
|
|
136
145
|
await testSetup!.renderOnce();
|
|
146
|
+
await testSetup!.mockMouse.click(kalshiCol + 1, kalshiRow);
|
|
147
|
+
await testSetup!.renderOnce();
|
|
137
148
|
});
|
|
138
149
|
await flushFrames(testSetup, 8);
|
|
139
150
|
|
|
@@ -157,7 +168,7 @@ describe("prediction markets pane interactions", () => {
|
|
|
157
168
|
expect(testSetup.captureCharFrame()).not.toContain("Loading market detail...");
|
|
158
169
|
});
|
|
159
170
|
|
|
160
|
-
test("moves selection through the list with keyboard navigation", async () => {
|
|
171
|
+
test("moves selection through the list with keyboard navigation without opening detail", async () => {
|
|
161
172
|
installPredictionMarketMocks();
|
|
162
173
|
|
|
163
174
|
testSetup = await testRender(<Harness />, { width: 120, height: 34 });
|
|
@@ -176,6 +187,102 @@ describe("prediction markets pane interactions", () => {
|
|
|
176
187
|
|
|
177
188
|
const frame = testSetup.captureCharFrame();
|
|
178
189
|
expect(frame).toContain("Will the Fed cut rates?");
|
|
190
|
+
expect(frame).not.toContain("Kalshi primary rule");
|
|
191
|
+
expect(frame).not.toContain("<- Back");
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test("keeps a clicked header sort instead of snapping back to the default sort", async () => {
|
|
195
|
+
installPredictionMarketMocks();
|
|
196
|
+
|
|
197
|
+
testSetup = await testRender(<Harness />, { width: 120, height: 34 });
|
|
198
|
+
await flushFrames(testSetup);
|
|
199
|
+
|
|
200
|
+
const frame = testSetup.captureCharFrame();
|
|
201
|
+
const lines = frame.split("\n");
|
|
202
|
+
const headerRow = lines.findIndex((line) => line.includes("VENUE"));
|
|
203
|
+
const headerCol = lines[headerRow]?.indexOf("VENUE") ?? -1;
|
|
204
|
+
|
|
205
|
+
expect(headerRow).toBeGreaterThan(-1);
|
|
206
|
+
expect(headerCol).toBeGreaterThan(-1);
|
|
207
|
+
|
|
208
|
+
await act(async () => {
|
|
209
|
+
await testSetup!.mockMouse.click(headerCol + 1, headerRow);
|
|
210
|
+
await testSetup!.renderOnce();
|
|
211
|
+
});
|
|
212
|
+
await flushFrames(testSetup, 4);
|
|
213
|
+
|
|
214
|
+
const nextFrame = testSetup.captureCharFrame();
|
|
215
|
+
const nextLines = nextFrame.split("\n");
|
|
216
|
+
const kalshiRow = nextLines.findIndex((line) =>
|
|
217
|
+
line.includes("Will the Fed cut rates?"),
|
|
218
|
+
);
|
|
219
|
+
const polymarketRow = nextLines.findIndex((line) =>
|
|
220
|
+
line.includes("Will inflation fall?"),
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
expect(
|
|
224
|
+
harnessStateRef.current?.paneState[TEST_PANE_ID]?.pluginState?.[
|
|
225
|
+
"prediction-markets"
|
|
226
|
+
]?.sortPreference,
|
|
227
|
+
).toEqual({ columnId: "venue", direction: "asc" });
|
|
228
|
+
expect(nextLines[headerRow]).toContain("VENUE ▲");
|
|
229
|
+
expect(kalshiRow).toBeGreaterThan(-1);
|
|
230
|
+
expect(polymarketRow).toBeGreaterThan(-1);
|
|
231
|
+
expect(kalshiRow).toBeLessThan(polymarketRow);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test("cycles the ends header through reverse date order before resetting", async () => {
|
|
235
|
+
installPredictionMarketMocks();
|
|
236
|
+
|
|
237
|
+
testSetup = await testRender(<Harness />, { width: 120, height: 34 });
|
|
238
|
+
await flushFrames(testSetup);
|
|
239
|
+
|
|
240
|
+
const frame = testSetup.captureCharFrame();
|
|
241
|
+
const lines = frame.split("\n");
|
|
242
|
+
const headerRow = lines.findIndex((line) => line.includes("ENDS"));
|
|
243
|
+
const headerCol = lines[headerRow]?.indexOf("ENDS") ?? -1;
|
|
244
|
+
|
|
245
|
+
expect(headerRow).toBeGreaterThan(-1);
|
|
246
|
+
expect(headerCol).toBeGreaterThan(-1);
|
|
247
|
+
|
|
248
|
+
await act(async () => {
|
|
249
|
+
await testSetup!.mockMouse.click(headerCol + 1, headerRow);
|
|
250
|
+
await testSetup!.renderOnce();
|
|
251
|
+
});
|
|
252
|
+
await flushFrames(testSetup, 2);
|
|
253
|
+
|
|
254
|
+
const sortedFrame = testSetup.captureCharFrame();
|
|
255
|
+
const sortedLines = sortedFrame.split("\n");
|
|
256
|
+
const sortedHeaderRow = sortedLines.findIndex((line) => line.includes("ENDS"));
|
|
257
|
+
const sortedHeaderCol = sortedLines[sortedHeaderRow]?.indexOf("ENDS") ?? -1;
|
|
258
|
+
|
|
259
|
+
expect(sortedHeaderRow).toBeGreaterThan(-1);
|
|
260
|
+
expect(sortedHeaderCol).toBeGreaterThan(-1);
|
|
261
|
+
|
|
262
|
+
await act(async () => {
|
|
263
|
+
await testSetup!.mockMouse.click(sortedHeaderCol + 1, sortedHeaderRow);
|
|
264
|
+
await testSetup!.renderOnce();
|
|
265
|
+
});
|
|
266
|
+
await flushFrames(testSetup, 4);
|
|
267
|
+
|
|
268
|
+
const nextFrame = testSetup.captureCharFrame();
|
|
269
|
+
const nextLines = nextFrame.split("\n");
|
|
270
|
+
const kalshiRow = nextLines.findIndex((line) =>
|
|
271
|
+
line.includes("Will the Fed cut rates?"),
|
|
272
|
+
);
|
|
273
|
+
const polymarketRow = nextLines.findIndex((line) =>
|
|
274
|
+
line.includes("Will inflation fall?"),
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
expect(
|
|
278
|
+
harnessStateRef.current?.paneState[TEST_PANE_ID]?.pluginState?.[
|
|
279
|
+
"prediction-markets"
|
|
280
|
+
]?.sortPreference,
|
|
281
|
+
).toEqual({ columnId: "ends", direction: "desc" });
|
|
282
|
+
expect(nextLines[headerRow]).toContain("ENDS ▼");
|
|
283
|
+
expect(kalshiRow).toBeGreaterThan(-1);
|
|
284
|
+
expect(polymarketRow).toBeGreaterThan(-1);
|
|
285
|
+
expect(kalshiRow).toBeLessThan(polymarketRow);
|
|
179
286
|
});
|
|
180
287
|
|
|
181
288
|
test("does not wrap to the last row when moving up without a selection", async () => {
|
|
@@ -197,7 +304,7 @@ describe("prediction markets pane interactions", () => {
|
|
|
197
304
|
).toBe("polymarket:pm-1");
|
|
198
305
|
});
|
|
199
306
|
|
|
200
|
-
test("supports detail outcome navigation and
|
|
307
|
+
test("supports detail outcome navigation and backspace return from the keyboard", async () => {
|
|
201
308
|
attachPredictionMarketsPersistence(new MemoryPersistence());
|
|
202
309
|
|
|
203
310
|
globalThis.fetch = (async (input: Request | string | URL) => {
|
|
@@ -326,17 +433,15 @@ describe("prediction markets pane interactions", () => {
|
|
|
326
433
|
]?.selectedDetailMarketKey,
|
|
327
434
|
).toBe("kalshi:KXFED-27APR-T4.50");
|
|
328
435
|
|
|
329
|
-
await emitKeypress(testSetup, { name: "
|
|
436
|
+
await emitKeypress(testSetup, { name: "backspace", sequence: "\u007f" });
|
|
330
437
|
await flushFrames(testSetup);
|
|
331
438
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
]?.focusRegion,
|
|
336
|
-
).toBe("list");
|
|
439
|
+
const frame = testSetup.captureCharFrame();
|
|
440
|
+
expect(frame).toContain("search markets");
|
|
441
|
+
expect(frame).not.toContain("Rule 2");
|
|
337
442
|
});
|
|
338
443
|
|
|
339
|
-
test("
|
|
444
|
+
test("opens full-page detail from a row double click and preserves the browse selection", async () => {
|
|
340
445
|
installPredictionMarketMocks();
|
|
341
446
|
|
|
342
447
|
testSetup = await testRender(<Harness />, { width: 120, height: 34 });
|
|
@@ -352,62 +457,19 @@ describe("prediction markets pane interactions", () => {
|
|
|
352
457
|
await act(async () => {
|
|
353
458
|
await testSetup!.mockMouse.click(kalshiCol + 1, kalshiRow);
|
|
354
459
|
await testSetup!.renderOnce();
|
|
355
|
-
});
|
|
356
|
-
await flushFrames(testSetup);
|
|
357
|
-
|
|
358
|
-
await act(async () => {
|
|
359
460
|
await testSetup!.mockMouse.click(kalshiCol + 1, kalshiRow);
|
|
360
461
|
await testSetup!.renderOnce();
|
|
361
462
|
});
|
|
362
463
|
await flushFrames(testSetup);
|
|
363
464
|
|
|
465
|
+
const detailFrame = testSetup.captureCharFrame();
|
|
466
|
+
expect(detailFrame).toContain("<- Back");
|
|
467
|
+
expect(detailFrame).toContain("Kalshi primary rule");
|
|
364
468
|
expect(
|
|
365
469
|
harnessStateRef.current?.paneState[TEST_PANE_ID]?.pluginState?.[
|
|
366
470
|
"prediction-markets"
|
|
367
471
|
]?.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);
|
|
472
|
+
).toBe("kalshi:KAL-1");
|
|
411
473
|
});
|
|
412
474
|
|
|
413
475
|
test("switches categories with arrows and venues with shift+arrows", async () => {
|
|
@@ -1,106 +1,25 @@
|
|
|
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 rootRef = useRef<BoxRenderable>(null);
|
|
20
|
-
const splitDragRef = useRef<{ startX: number; startRatio: number } | null>(
|
|
21
|
-
null,
|
|
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
|
-
}
|
|
71
14
|
|
|
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 (
|
|
15
|
+
const browseContent = (
|
|
95
16
|
<box
|
|
96
|
-
ref={rootRef}
|
|
97
17
|
flexDirection="column"
|
|
98
18
|
width={width}
|
|
99
19
|
height={height}
|
|
100
20
|
backgroundColor={colors.panel}
|
|
101
|
-
onMouse={handlePaneMouse}
|
|
102
21
|
>
|
|
103
|
-
{!controller.paneSettings.hideTabs
|
|
22
|
+
{!controller.paneSettings.hideTabs ? (
|
|
104
23
|
<TabBar
|
|
105
24
|
tabs={VENUE_TABS.map((tab) => ({
|
|
106
25
|
label: tab.label,
|
|
@@ -110,7 +29,7 @@ export function PredictionMarketsPane({ focused, width, height }: PaneProps) {
|
|
|
110
29
|
onSelect={controller.actions.setVenue}
|
|
111
30
|
compact
|
|
112
31
|
/>
|
|
113
|
-
)}
|
|
32
|
+
) : null}
|
|
114
33
|
|
|
115
34
|
<box flexDirection="row" height={1} paddingX={1} gap={2}>
|
|
116
35
|
<box
|
|
@@ -118,9 +37,7 @@ export function PredictionMarketsPane({ focused, width, height }: PaneProps) {
|
|
|
118
37
|
onMouseDown={controller.actions.focusSearch}
|
|
119
38
|
width={Math.max(18, Math.floor(width * 0.32))}
|
|
120
39
|
>
|
|
121
|
-
<text fg={colors.textDim}>
|
|
122
|
-
{controller.searchFocused ? "?" : "/"}
|
|
123
|
-
</text>
|
|
40
|
+
<text fg={colors.textDim}>{controller.searchFocused ? "?" : "/"}</text>
|
|
124
41
|
<box width={1} />
|
|
125
42
|
{controller.searchFocused ? (
|
|
126
43
|
<input
|
|
@@ -168,7 +85,7 @@ export function PredictionMarketsPane({ focused, width, height }: PaneProps) {
|
|
|
168
85
|
</box>
|
|
169
86
|
</box>
|
|
170
87
|
|
|
171
|
-
{PREDICTION_CATEGORY_OPTIONS.length > 1
|
|
88
|
+
{PREDICTION_CATEGORY_OPTIONS.length > 1 ? (
|
|
172
89
|
<scrollbox height={1} scrollX focusable={false}>
|
|
173
90
|
<box flexDirection="row" paddingX={1} gap={2}>
|
|
174
91
|
{PREDICTION_CATEGORY_OPTIONS.map((category) => {
|
|
@@ -194,71 +111,75 @@ export function PredictionMarketsPane({ focused, width, height }: PaneProps) {
|
|
|
194
111
|
})}
|
|
195
112
|
</box>
|
|
196
113
|
</scrollbox>
|
|
197
|
-
)}
|
|
114
|
+
) : null}
|
|
198
115
|
|
|
199
|
-
{controller.catalogError
|
|
116
|
+
{controller.catalogError ? (
|
|
200
117
|
<box height={1} paddingX={1}>
|
|
201
118
|
<text fg={colors.negative}>{controller.catalogError}</text>
|
|
202
119
|
</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
|
-
)}
|
|
120
|
+
) : null}
|
|
121
|
+
|
|
122
|
+
<box flexDirection="column" flexGrow={1}>
|
|
123
|
+
<PredictionMarketsTable
|
|
124
|
+
columns={controller.visibleColumns}
|
|
125
|
+
rows={controller.visibleRows}
|
|
126
|
+
selectedRowKey={controller.selectedRow?.key ?? null}
|
|
127
|
+
hoveredIdx={controller.hoveredIdx}
|
|
128
|
+
setHoveredIdx={controller.actions.setHoveredIdx}
|
|
129
|
+
onSelectRow={(rowKey) => controller.actions.setBrowseSelection(rowKey)}
|
|
130
|
+
onOpenRow={controller.actions.openSelectedRow}
|
|
131
|
+
watchlist={controller.watchlistSet}
|
|
132
|
+
onToggleWatchlist={controller.actions.toggleWatchlist}
|
|
133
|
+
sortPreference={controller.sortPreference}
|
|
134
|
+
onHeaderClick={controller.actions.handleSortHeaderClick}
|
|
135
|
+
headerScrollRef={controller.headerScrollRef}
|
|
136
|
+
scrollRef={controller.scrollRef}
|
|
137
|
+
syncHeaderScroll={controller.layout.syncHeaderScroll}
|
|
138
|
+
onBodyScrollActivity={controller.layout.onBodyScrollActivity}
|
|
139
|
+
/>
|
|
261
140
|
</box>
|
|
262
141
|
</box>
|
|
263
142
|
);
|
|
143
|
+
|
|
144
|
+
const detailContent =
|
|
145
|
+
controller.selectedSummary && controller.selectedRow ? (
|
|
146
|
+
<box
|
|
147
|
+
flexDirection="column"
|
|
148
|
+
flexGrow={1}
|
|
149
|
+
width={width}
|
|
150
|
+
height={Math.max(height - 1, 1)}
|
|
151
|
+
paddingX={1}
|
|
152
|
+
backgroundColor={colors.panel}
|
|
153
|
+
>
|
|
154
|
+
<PredictionMarketDetailPane
|
|
155
|
+
detail={controller.detail}
|
|
156
|
+
detailError={controller.detailError}
|
|
157
|
+
detailLoadCount={controller.detailLoadCount}
|
|
158
|
+
detailTab={controller.detailTab}
|
|
159
|
+
detailWidth={Math.max(width - 2, 24)}
|
|
160
|
+
focused={focused && controller.detailOpen}
|
|
161
|
+
height={Math.max(height - 1, 1)}
|
|
162
|
+
historyRange={controller.historyRange}
|
|
163
|
+
onDetailTabChange={controller.actions.setDetailTab}
|
|
164
|
+
onHistoryRangeChange={controller.actions.setHistoryRange}
|
|
165
|
+
onPreviewOrder={controller.actions.previewOrder}
|
|
166
|
+
onSelectMarket={controller.actions.selectMarket}
|
|
167
|
+
scrollRef={controller.detailScrollRef}
|
|
168
|
+
selectedRow={controller.selectedRow}
|
|
169
|
+
selectedSummary={controller.selectedSummary}
|
|
170
|
+
/>
|
|
171
|
+
</box>
|
|
172
|
+
) : (
|
|
173
|
+
<box flexGrow={1} backgroundColor={colors.panel} />
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
return (
|
|
177
|
+
<PageStackView
|
|
178
|
+
focused={focused}
|
|
179
|
+
detailOpen={controller.detailOpen && !!controller.selectedSummary}
|
|
180
|
+
onBack={controller.actions.closeDetail}
|
|
181
|
+
rootContent={browseContent}
|
|
182
|
+
detailContent={detailContent}
|
|
183
|
+
/>
|
|
184
|
+
);
|
|
264
185
|
}
|
|
@@ -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) =>
|
|
@@ -5,7 +5,6 @@ export type PredictionVenue = "polymarket" | "kalshi";
|
|
|
5
5
|
export type PredictionVenueScope = "all" | PredictionVenue;
|
|
6
6
|
export type PredictionBrowseTab = "top" | "ending" | "new" | "watchlist";
|
|
7
7
|
export type PredictionDetailTab = "overview" | "book" | "trades" | "rules";
|
|
8
|
-
export type PredictionFocusRegion = "list" | "detail";
|
|
9
8
|
export type PredictionHistoryRange = "1D" | "1W" | "1M" | "ALL";
|
|
10
9
|
export type PredictionVolumeUnit = "usd" | "contracts";
|
|
11
10
|
export type PredictionSortDirection = "asc" | "desc";
|
|
@@ -26,6 +26,11 @@ afterEach(() => {
|
|
|
26
26
|
});
|
|
27
27
|
|
|
28
28
|
describe("GloomberbCloudProvider", () => {
|
|
29
|
+
test("reports its manual chart resolution capabilities", () => {
|
|
30
|
+
const provider = new GloomberbCloudProvider();
|
|
31
|
+
expect(provider.getChartResolutionCapabilities()).toEqual(["1m", "5m", "15m", "30m", "45m", "1h", "1d", "1wk", "1mo"]);
|
|
32
|
+
});
|
|
33
|
+
|
|
29
34
|
test("fetches detailed intraday chart history with Twelve Data intervals", async () => {
|
|
30
35
|
apiClient.ensureVerifiedSession = async () => verifiedUser;
|
|
31
36
|
|
|
@@ -91,6 +96,30 @@ describe("GloomberbCloudProvider", () => {
|
|
|
91
96
|
});
|
|
92
97
|
});
|
|
93
98
|
|
|
99
|
+
test("fetches fixed-resolution chart history with the requested interval", async () => {
|
|
100
|
+
apiClient.ensureVerifiedSession = async () => verifiedUser;
|
|
101
|
+
|
|
102
|
+
let requestArgs: Record<string, string | number | undefined> | null = null;
|
|
103
|
+
apiClient.getCloudHistory = async (_symbol, _exchange, params = {}) => {
|
|
104
|
+
requestArgs = params;
|
|
105
|
+
return {
|
|
106
|
+
status: "success",
|
|
107
|
+
data: [{
|
|
108
|
+
date: "2026-03-27",
|
|
109
|
+
close: 250.12,
|
|
110
|
+
}],
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const provider = new GloomberbCloudProvider();
|
|
115
|
+
const history = await provider.getPriceHistoryForResolution("AAPL", "NASDAQ", "1Y", "1wk");
|
|
116
|
+
|
|
117
|
+
expect(requestArgs?.interval).toBe("1week");
|
|
118
|
+
expect(requestArgs?.startDate).toBeDefined();
|
|
119
|
+
expect(requestArgs?.endDate).toBeDefined();
|
|
120
|
+
expect(history[0]?.close).toBe(250.12);
|
|
121
|
+
});
|
|
122
|
+
|
|
94
123
|
test("normalizes sub-unit cloud quotes to their main currency", async () => {
|
|
95
124
|
apiClient.ensureVerifiedSession = async () => verifiedUser;
|
|
96
125
|
apiClient.getCloudQuote = async () => ({
|