gloomberb 0.9.3 → 0.9.5
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 +1 -0
- package/package.json +3 -2
- package/patches/@opentui%2Fcore@0.3.2.patch +231 -29
- package/patches/react-reconciler@0.33.0.patch +10 -0
- package/src/app/runtime/desktop-menu.ts +5 -1
- package/src/app/runtime/ticker-refresh.ts +4 -1
- package/src/app.tsx +10 -1
- package/src/brokers/account-cache.ts +1 -1
- package/src/brokers/sync-broker-instance.test.ts +107 -0
- package/src/brokers/sync-broker-instance.ts +22 -10
- package/src/cli/desktop-pane-shot.ts +2 -1
- package/src/cli/entry.ts +8 -1
- package/src/cli/native-smoke.ts +5 -0
- package/src/cli/pane-functions/screenshot.ts +13 -1
- package/src/components/chart/bar-chart-renderer.test.ts +108 -3
- package/src/components/chart/bar-chart-renderer.ts +226 -27
- package/src/components/chart/core/pointer.ts +32 -6
- package/src/components/chart/native/bitmap-support.test.ts +27 -0
- package/src/components/chart/native/bitmap-support.ts +55 -0
- package/src/components/chart/native/kitty/index.test.ts +47 -0
- package/src/components/chart/native/surface/visibility.ts +8 -1
- package/src/components/chart/price-axis-labels.tsx +3 -1
- package/src/components/chart/static/bar-chart-surface.test.tsx +97 -0
- package/src/components/chart/static/bar-chart-surface.tsx +151 -28
- package/src/components/chart/static/chart/axis-overlays.tsx +243 -0
- package/src/components/chart/static/chart/bitmap.ts +16 -9
- package/src/components/chart/static/chart/surface.test.tsx +92 -0
- package/src/components/chart/static/chart/surface.tsx +167 -24
- package/src/components/chart/stock-chart/index.test.ts +25 -0
- package/src/components/command-bar/layout-items/current-layout.ts +5 -1
- package/src/components/command-bar/list/view.tsx +1 -1
- package/src/components/command-bar/theme-picker.tsx +1 -1
- package/src/components/data-table/view.tsx +0 -12
- package/src/components/layout/detached-pane-shell.tsx +6 -3
- package/src/components/layout/floating-pane.tsx +6 -5
- package/src/components/layout/pane/content.tsx +20 -8
- package/src/components/layout/pane/footer/registration.tsx +6 -2
- package/src/components/layout/pane/frame.tsx +4 -4
- package/src/components/layout/pane/header.tsx +45 -4
- package/src/components/layout/pane/index.tsx +6 -4
- package/src/components/layout/pane/sizing.test.ts +43 -0
- package/src/components/layout/pane/sizing.ts +13 -4
- package/src/components/layout/shell/action-menu-overlay.tsx +1 -1
- package/src/components/layout/shell/cursor-occlusion.ts +204 -0
- package/src/components/layout/shell/drag/runtime.ts +9 -2
- package/src/components/layout/shell/index.test.tsx +92 -2
- package/src/components/layout/shell/index.tsx +45 -3
- package/src/components/layout/shell/layout-state.test.ts +33 -0
- package/src/components/layout/shell/layout-state.ts +4 -9
- package/src/components/layout/shell/pane/layers.tsx +10 -4
- package/src/components/layout/shell/visible-layout.ts +17 -0
- package/src/components/layout/status-bar.test.tsx +6 -1
- package/src/components/layout/status-bar.tsx +10 -6
- package/src/components/onboarding/onboarding-steps.tsx +8 -28
- package/src/components/onboarding/onboarding-wizard.test.tsx +13 -12
- package/src/components/onboarding/onboarding-wizard.tsx +2 -4
- package/src/components/onboarding/portfolio-step/index.tsx +0 -12
- package/src/components/onboarding/portfolio-step/types.ts +1 -3
- package/src/components/onboarding/wizard-keyboard.ts +2 -5
- package/src/components/pane-settings-dialog/desktop.tsx +1 -1
- package/src/components/price-performance.tsx +1 -1
- package/src/components/price-sparkline/view.tsx +15 -14
- package/src/components/speedometer-gauge/terminal.tsx +18 -15
- package/src/components/table-view-shared.tsx +12 -14
- package/src/components/ticker/list-table-view.tsx +1 -7
- package/src/components/ticker/list-table.test.tsx +0 -4
- package/src/components/ui/data-table/opentui/index.tsx +29 -22
- package/src/components/ui/data-table/types.ts +10 -4
- package/src/components/ui/fields.tsx +10 -3
- package/src/components/ui/list-view.tsx +1 -1
- package/src/components/ui/page-stack-view.tsx +6 -2
- package/src/components/ui/table-layout.test.ts +25 -0
- package/src/components/ui/table-layout.ts +22 -6
- package/src/components/ui/tabs.tsx +27 -12
- package/src/components/ui/ui.test.tsx +87 -9
- package/src/core/state/app/layout-reducer.ts +2 -2
- package/src/plugins/builtin/account-management/form-components.tsx +2 -2
- package/src/plugins/builtin/alerts/pane.tsx +1 -1
- package/src/plugins/builtin/analytics/index.test.tsx +6 -2
- package/src/plugins/builtin/analytics/pane-model.ts +43 -8
- package/src/plugins/builtin/buildout/cells.tsx +1 -1
- package/src/plugins/builtin/buildout/pane/index.tsx +1 -1
- package/src/plugins/builtin/chat/message/desktop.tsx +1 -1
- package/src/plugins/builtin/chat/message/inline-tokens.tsx +1 -1
- package/src/plugins/builtin/chat/message/terminal.tsx +2 -2
- package/src/plugins/builtin/chat/sidebar.tsx +1 -1
- package/src/plugins/builtin/chat/status-widget.tsx +1 -1
- package/src/plugins/builtin/cloud/auth-actions.tsx +2 -2
- package/src/plugins/builtin/correlation/matrix/symbol-cell.tsx +1 -1
- package/src/plugins/builtin/debug/index.tsx +3 -4
- package/src/plugins/builtin/help/components.tsx +1 -1
- package/src/plugins/builtin/holders/treemap.tsx +1 -1
- package/src/plugins/builtin/kelly-sizer/constants.ts +3 -0
- package/src/plugins/builtin/kelly-sizer/fields.ts +214 -0
- package/src/plugins/builtin/kelly-sizer/index.test.tsx +197 -0
- package/src/plugins/builtin/kelly-sizer/index.tsx +47 -0
- package/src/plugins/builtin/kelly-sizer/model.test.ts +152 -0
- package/src/plugins/builtin/kelly-sizer/model.ts +580 -0
- package/src/plugins/builtin/kelly-sizer/pane.tsx +558 -0
- package/src/plugins/builtin/kelly-sizer/portfolio.ts +71 -0
- package/src/plugins/builtin/kelly-sizer/sections.tsx +142 -0
- package/src/plugins/builtin/kelly-sizer/state.ts +67 -0
- package/src/plugins/builtin/kelly-sizer/test-support.tsx +174 -0
- package/src/plugins/builtin/kelly-sizer/types.ts +156 -0
- package/src/plugins/builtin/kelly-sizer/view.test.tsx +77 -0
- package/src/plugins/builtin/kelly-sizer/view.tsx +375 -0
- package/src/plugins/builtin/layout-manager/index.tsx +5 -1
- package/src/plugins/builtin/options/table.ts +5 -5
- package/src/plugins/builtin/options/view.test.tsx +141 -4
- package/src/plugins/builtin/options/view.tsx +76 -15
- package/src/plugins/builtin/portfolio-list/account-metrics.test.ts +26 -1
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +14 -0
- package/src/plugins/builtin/portfolio-list/index.test.ts +23 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +2 -0
- package/src/plugins/builtin/portfolio-list/summary/index.test.ts +59 -1
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +36 -5
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/content.tsx +149 -48
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/model.ts +25 -2
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph.tsx +5 -4
- package/src/plugins/builtin/ticker-detail/data-panes.test.ts +16 -0
- package/src/plugins/builtin/ticker-detail/index.test.tsx +143 -0
- package/src/plugins/catalog-ui.ts +2 -0
- package/src/plugins/ibkr/broker-adapter.ts +19 -0
- package/src/plugins/ibkr/config.test.ts +31 -2
- package/src/plugins/ibkr/config.ts +9 -2
- package/src/plugins/ibkr/flex/index.test.ts +18 -9
- package/src/plugins/ibkr/flex/index.ts +30 -0
- package/src/plugins/ibkr/flex/statement-client.ts +68 -14
- package/src/plugins/ibkr/gateway/account-loaders.ts +1 -1
- package/src/plugins/ibkr/gateway/account-summary.ts +1 -0
- package/src/plugins/ibkr/gateway/service/index.test.ts +8 -0
- package/src/plugins/ibkr/trade/ticket-panel.tsx +1 -1
- package/src/plugins/pane-manager/gridlock.ts +13 -5
- package/src/plugins/pane-manager/layout-state.ts +25 -0
- package/src/plugins/pane-manager.test.ts +33 -0
- package/src/plugins/pane-manager.ts +6 -1
- package/src/plugins/prediction-markets/chart.tsx +10 -4
- package/src/plugins/prediction-markets/detail/book.tsx +141 -96
- package/src/plugins/prediction-markets/detail/overview.tsx +3 -0
- package/src/plugins/prediction-markets/detail/pane.tsx +71 -64
- package/src/plugins/prediction-markets/detail/shared.tsx +19 -0
- package/src/plugins/prediction-markets/detail/trades.tsx +64 -19
- package/src/plugins/prediction-markets/detail.test.tsx +50 -0
- package/src/plugins/prediction-markets/pane.test.tsx +43 -0
- package/src/plugins/prediction-markets/pane.tsx +50 -22
- package/src/plugins/prediction-markets/test-helpers.tsx +41 -23
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +13 -2
- package/src/renderers/electrobun/view/data-table/index.tsx +19 -12
- package/src/renderers/electrobun/view/data-table/row.tsx +32 -18
- package/src/renderers/electrobun/view/desktop/controls.tsx +11 -5
- package/src/renderers/electrobun/view/host/box.tsx +6 -1
- package/src/renderers/electrobun/view/host/input.tsx +115 -18
- package/src/renderers/electrobun/view/host/style.ts +1 -0
- package/src/renderers/electrobun/view/host/tabs.tsx +7 -2
- package/src/renderers/electrobun/view/styles.css +18 -0
- package/src/renderers/opentui/host.tsx +17 -1
- package/src/renderers/opentui/native-smoke.ts +1 -0
- package/src/renderers/opentui/ui-host.tsx +57 -25
- package/src/sources/provider-router/batches.ts +16 -2
- package/src/sources/provider-router/financial-routes.ts +5 -0
- package/src/sources/provider-router/financials.ts +11 -23
- package/src/sources/provider-router/index.test.ts +189 -0
- package/src/sources/provider-router/primary.ts +3 -2
- package/src/sources/sec-edgar.test.ts +83 -0
- package/src/sources/sec-edgar.ts +231 -0
- package/src/sources/yahoo-finance.test.ts +51 -0
- package/src/sources/yahoo-finance.ts +49 -1
- package/src/state/app/context/index.test.tsx +5 -8
- package/src/types/broker.ts +1 -0
- package/src/types/config.ts +10 -0
- package/src/types/trading.ts +2 -0
- package/src/ui/host.tsx +16 -0
- package/src/ui/index.tsx +2 -0
- package/src/utils/financial-statements.ts +23 -0
- package/src/version.ts +1 -1
- package/src/components/onboarding/portfolio-step/manual-name-panel.tsx +0 -62
|
@@ -48,4 +48,54 @@ describe("prediction markets detail views", () => {
|
|
|
48
48
|
expect(frame).not.toContain("Kalshi");
|
|
49
49
|
expect(frame).not.toContain("targets");
|
|
50
50
|
});
|
|
51
|
+
|
|
52
|
+
test("keeps grouped outcomes in place while selected detail is loading", async () => {
|
|
53
|
+
testSetup = await testRender(<GroupedDetailHarness loading />, {
|
|
54
|
+
width: 64,
|
|
55
|
+
height: 24,
|
|
56
|
+
});
|
|
57
|
+
await flushFrames(testSetup);
|
|
58
|
+
|
|
59
|
+
const frame = testSetup.captureCharFrame();
|
|
60
|
+
expect(frame).toContain("Outcomes");
|
|
61
|
+
expect(frame).toContain("Above 4.25%");
|
|
62
|
+
expect(frame).toContain("Loading chart...");
|
|
63
|
+
expect(frame).not.toContain("No chart history.");
|
|
64
|
+
expect(frame).not.toContain("Loading market detail...");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("renders book levels in the shared data table", async () => {
|
|
68
|
+
testSetup = await testRender(<GroupedDetailHarness detailTab="book" />, {
|
|
69
|
+
width: 64,
|
|
70
|
+
height: 24,
|
|
71
|
+
});
|
|
72
|
+
await flushFrames(testSetup);
|
|
73
|
+
|
|
74
|
+
const frame = testSetup.captureCharFrame();
|
|
75
|
+
expect(frame).toContain("OUT");
|
|
76
|
+
expect(frame).toContain("SIDE");
|
|
77
|
+
expect(frame).toContain("PRICE");
|
|
78
|
+
expect(frame).toContain("SIZE");
|
|
79
|
+
expect(frame).toContain("YES");
|
|
80
|
+
expect(frame).toContain("BID");
|
|
81
|
+
expect(frame).toContain("47c");
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("renders recent trades in the shared data table", async () => {
|
|
85
|
+
testSetup = await testRender(<GroupedDetailHarness detailTab="trades" />, {
|
|
86
|
+
width: 64,
|
|
87
|
+
height: 24,
|
|
88
|
+
});
|
|
89
|
+
await flushFrames(testSetup);
|
|
90
|
+
|
|
91
|
+
const frame = testSetup.captureCharFrame();
|
|
92
|
+
expect(frame).toContain("TIME");
|
|
93
|
+
expect(frame).toContain("SIDE");
|
|
94
|
+
expect(frame).toContain("OUT");
|
|
95
|
+
expect(frame).toContain("PRICE");
|
|
96
|
+
expect(frame).toContain("SIZE");
|
|
97
|
+
expect(frame).toContain("BUY");
|
|
98
|
+
expect(frame).toContain("YES");
|
|
99
|
+
expect(frame).toContain("48c");
|
|
100
|
+
});
|
|
51
101
|
});
|
|
@@ -130,6 +130,32 @@ describe("prediction markets pane interactions", () => {
|
|
|
130
130
|
expect(frame).not.toContain("Was there a typo in the url or port?");
|
|
131
131
|
});
|
|
132
132
|
|
|
133
|
+
test("shows loading instead of an empty state while empty catalogs are pending", async () => {
|
|
134
|
+
attachPredictionMarketsPersistence(new MemoryPersistence());
|
|
135
|
+
|
|
136
|
+
const releaseFetches: Array<() => void> = [];
|
|
137
|
+
globalThis.fetch = (async (input: Request | string | URL) => {
|
|
138
|
+
await new Promise<void>((resolve) => {
|
|
139
|
+
releaseFetches.push(resolve);
|
|
140
|
+
});
|
|
141
|
+
const url = String(input);
|
|
142
|
+
if (url.includes("/trade-api/v2/events?")) {
|
|
143
|
+
return new Response(JSON.stringify({ events: [] }), { status: 200 });
|
|
144
|
+
}
|
|
145
|
+
return new Response(JSON.stringify([]), { status: 200 });
|
|
146
|
+
}) as unknown as typeof fetch;
|
|
147
|
+
|
|
148
|
+
testSetup = await testRender(<Harness />, { width: 120, height: 34 });
|
|
149
|
+
await flushFrames(testSetup);
|
|
150
|
+
|
|
151
|
+
const frame = testSetup.captureCharFrame();
|
|
152
|
+
expect(frame).toContain("Loading markets...");
|
|
153
|
+
expect(frame).not.toContain("No markets matched.");
|
|
154
|
+
|
|
155
|
+
for (const release of releaseFetches) release();
|
|
156
|
+
await flushFrames(testSetup);
|
|
157
|
+
});
|
|
158
|
+
|
|
133
159
|
test("selects a market on single click and opens detail on double click", async () => {
|
|
134
160
|
installPredictionMarketMocks();
|
|
135
161
|
|
|
@@ -172,6 +198,23 @@ describe("prediction markets pane interactions", () => {
|
|
|
172
198
|
|
|
173
199
|
frame = testSetup.captureCharFrame();
|
|
174
200
|
expect(frame).toContain("Kalshi primary rule");
|
|
201
|
+
expect(frame).toContain("\u2190 Back Will the Fed cut rates?");
|
|
202
|
+
expect(frame.match(/Will the Fed cut rates\?/g) ?? []).toHaveLength(1);
|
|
203
|
+
expect(frame).not.toContain("[/]search");
|
|
204
|
+
expect(frame).not.toContain("[w]atch");
|
|
205
|
+
expect(frame).not.toContain("[1-4]browse");
|
|
206
|
+
|
|
207
|
+
const metricsHeader = frame
|
|
208
|
+
.split("\n")
|
|
209
|
+
.find((line) =>
|
|
210
|
+
line.includes("YES") &&
|
|
211
|
+
line.includes("NO") &&
|
|
212
|
+
line.includes("24H VOL"),
|
|
213
|
+
);
|
|
214
|
+
expect(metricsHeader).toContain("TOTAL VOL");
|
|
215
|
+
expect(metricsHeader).toContain("OI");
|
|
216
|
+
expect(metricsHeader).toContain("SPREAD");
|
|
217
|
+
expect(metricsHeader).toContain("LAST");
|
|
175
218
|
});
|
|
176
219
|
|
|
177
220
|
test("focuses the pane when a market row is clicked", async () => {
|
|
@@ -2,6 +2,7 @@ import { Box, Input, Text } from "../../ui";
|
|
|
2
2
|
import { useCallback, useMemo, useRef } from "react";
|
|
3
3
|
import {
|
|
4
4
|
DataTableStackView,
|
|
5
|
+
Spinner,
|
|
5
6
|
Tabs,
|
|
6
7
|
usePaneFooter,
|
|
7
8
|
type DataTableKeyEvent,
|
|
@@ -13,6 +14,7 @@ import { colors } from "../../theme/colors";
|
|
|
13
14
|
import { PREDICTION_CATEGORY_OPTIONS } from "./categories";
|
|
14
15
|
import { usePredictionMarketsController } from "./controller";
|
|
15
16
|
import { PredictionMarketDetailPane } from "./detail/pane";
|
|
17
|
+
import { resolvePredictionDetailTitle } from "./detail/shared";
|
|
16
18
|
import { getPredictionColumnValue } from "./metrics";
|
|
17
19
|
import { BROWSE_TABS, VENUE_TABS } from "./navigation";
|
|
18
20
|
import { isPlainArrowUp, stopSearchFocusNavigation } from "../../utils/search-focus-navigation";
|
|
@@ -73,33 +75,45 @@ export function PredictionMarketsPane({ focused, width, height }: PaneProps) {
|
|
|
73
75
|
controller.catalogStatus?.tone === "danger"
|
|
74
76
|
? colors.negative
|
|
75
77
|
: colors.borderFocused;
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
id: "
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
78
|
+
const rowsLoading =
|
|
79
|
+
controller.visibleRows.length === 0 &&
|
|
80
|
+
(controller.catalogLoadCount > 0 || controller.searchLoading);
|
|
81
|
+
const detailTitle = resolvePredictionDetailTitle({
|
|
82
|
+
detail: controller.detail,
|
|
83
|
+
selectedRow: controller.selectedRow,
|
|
84
|
+
selectedSummary: controller.selectedSummary,
|
|
85
|
+
});
|
|
86
|
+
usePaneFooter("prediction-markets", () => {
|
|
87
|
+
if (controller.detailOpen) return null;
|
|
88
|
+
return {
|
|
89
|
+
info: [
|
|
90
|
+
...(controller.searchQuery.trim() ? [{ id: "search", parts: [{ text: `search: ${controller.searchQuery.trim()}`, tone: "value" as const }] }] : []),
|
|
91
|
+
...(controller.searchLoading ? [{ id: "search-loading", parts: [{ text: "searching", tone: "muted" as const }] }] : []),
|
|
92
|
+
...(controller.catalogStatus ? [{
|
|
93
|
+
id: "catalog",
|
|
94
|
+
parts: [{ text: controller.catalogStatus.message, tone: controller.catalogStatus.tone === "danger" ? "warning" as const : "muted" as const, color: catalogStatusColor }],
|
|
95
|
+
}] : []),
|
|
96
|
+
],
|
|
97
|
+
hints: [
|
|
98
|
+
{ id: "search", key: "/", label: "search", onPress: controller.actions.focusSearch },
|
|
99
|
+
{ id: "watch", key: "w", label: "atch", onPress: controller.selectedRow ? () => controller.actions.toggleWatchlist(controller.selectedRow!) : undefined, disabled: !controller.selectedRow },
|
|
100
|
+
{
|
|
101
|
+
id: "browse",
|
|
102
|
+
key: "1-4",
|
|
103
|
+
label: "browse",
|
|
104
|
+
onPress: () => {
|
|
105
|
+
const index = BROWSE_TABS.findIndex((tab) => tab.value === controller.browseTab);
|
|
106
|
+
controller.actions.selectBrowseTab(BROWSE_TABS[(index + 1) % BROWSE_TABS.length]!.value as PredictionBrowseTab);
|
|
107
|
+
},
|
|
95
108
|
},
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
109
|
+
],
|
|
110
|
+
};
|
|
111
|
+
}, [
|
|
99
112
|
catalogStatusColor,
|
|
100
113
|
controller.browseTab,
|
|
101
114
|
controller.catalogStatus?.message,
|
|
102
115
|
controller.catalogStatus?.tone,
|
|
116
|
+
controller.detailOpen,
|
|
103
117
|
controller.searchLoading,
|
|
104
118
|
controller.searchQuery,
|
|
105
119
|
controller.selectedRow,
|
|
@@ -275,6 +289,7 @@ export function PredictionMarketsPane({ focused, width, height }: PaneProps) {
|
|
|
275
289
|
detailOpen={controller.detailOpen && !!controller.selectedSummary}
|
|
276
290
|
onBack={controller.actions.closeDetail}
|
|
277
291
|
detailContent={detailContent}
|
|
292
|
+
detailTitle={detailTitle}
|
|
278
293
|
rootBefore={browseControls}
|
|
279
294
|
rootWidth={width}
|
|
280
295
|
rootHeight={height}
|
|
@@ -301,6 +316,19 @@ export function PredictionMarketsPane({ focused, width, height }: PaneProps) {
|
|
|
301
316
|
getItemKey={(row) => row.key}
|
|
302
317
|
virtualize
|
|
303
318
|
renderCell={renderCell}
|
|
319
|
+
emptyContent={
|
|
320
|
+
rowsLoading ? (
|
|
321
|
+
<Box width="100%" paddingX={1} paddingY={1}>
|
|
322
|
+
<Spinner
|
|
323
|
+
label={
|
|
324
|
+
controller.searchQuery.trim().length > 0
|
|
325
|
+
? "Searching markets..."
|
|
326
|
+
: "Loading markets..."
|
|
327
|
+
}
|
|
328
|
+
/>
|
|
329
|
+
</Box>
|
|
330
|
+
) : undefined
|
|
331
|
+
}
|
|
304
332
|
emptyStateTitle="No markets matched."
|
|
305
333
|
emptyStateHint="Change the venue, browse tab, or search query."
|
|
306
334
|
/>
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
attachPredictionMarketsPersistence,
|
|
25
25
|
} from "./services/fetch";
|
|
26
26
|
import { normalizeKalshiMarket } from "./services/kalshi/adapter";
|
|
27
|
+
import type { PredictionDetailTab } from "./types";
|
|
27
28
|
|
|
28
29
|
export const TEST_PANE_ID = "prediction-markets:main";
|
|
29
30
|
const originalFetch = globalThis.fetch;
|
|
@@ -408,7 +409,13 @@ export function ChartHarness({
|
|
|
408
409
|
);
|
|
409
410
|
}
|
|
410
411
|
|
|
411
|
-
export function GroupedDetailHarness(
|
|
412
|
+
export function GroupedDetailHarness({
|
|
413
|
+
detailTab = "overview",
|
|
414
|
+
loading = false,
|
|
415
|
+
}: {
|
|
416
|
+
detailTab?: PredictionDetailTab;
|
|
417
|
+
loading?: boolean;
|
|
418
|
+
} = {}) {
|
|
412
419
|
const [state, dispatch] = useReducer(
|
|
413
420
|
appReducer,
|
|
414
421
|
(() => {
|
|
@@ -475,29 +482,40 @@ export function GroupedDetailHarness() {
|
|
|
475
482
|
<AppContext value={{ state, dispatch }}>
|
|
476
483
|
<PaneInstanceProvider paneId={TEST_PANE_ID}>
|
|
477
484
|
<PredictionMarketDetailPane
|
|
478
|
-
detail={
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
485
|
+
detail={
|
|
486
|
+
loading
|
|
487
|
+
? null
|
|
488
|
+
: {
|
|
489
|
+
summary,
|
|
490
|
+
siblings: selectedRow!.markets.map((market) => ({
|
|
491
|
+
key: market.key,
|
|
492
|
+
marketId: market.marketId,
|
|
493
|
+
label: market.marketLabel,
|
|
494
|
+
yesPrice: market.yesPrice,
|
|
495
|
+
volume24h: market.volume24h,
|
|
496
|
+
})),
|
|
497
|
+
rules: [],
|
|
498
|
+
history: [],
|
|
499
|
+
book: {
|
|
500
|
+
yesBids: [{ price: 0.47, size: 120 }],
|
|
501
|
+
yesAsks: [{ price: 0.49, size: 95 }],
|
|
502
|
+
noBids: [{ price: 0.51, size: 80 }],
|
|
503
|
+
noAsks: [{ price: 0.53, size: 105 }],
|
|
504
|
+
lastTradePrice: null,
|
|
505
|
+
},
|
|
506
|
+
trades: [{
|
|
507
|
+
id: "trade-1",
|
|
508
|
+
timestamp: Date.parse("2026-04-01T12:34:56Z"),
|
|
509
|
+
side: "buy",
|
|
510
|
+
outcome: "yes",
|
|
511
|
+
price: 0.48,
|
|
512
|
+
size: 50,
|
|
513
|
+
}],
|
|
514
|
+
}
|
|
515
|
+
}
|
|
498
516
|
detailError={null}
|
|
499
|
-
detailLoadCount={0}
|
|
500
|
-
detailTab=
|
|
517
|
+
detailLoadCount={loading ? 1 : 0}
|
|
518
|
+
detailTab={detailTab}
|
|
501
519
|
detailWidth={58}
|
|
502
520
|
focused
|
|
503
521
|
height={24}
|
|
@@ -20,7 +20,7 @@ import { resolvePaneBodyFrame } from "../../../components/layout/pane/sizing";
|
|
|
20
20
|
import { getPaneDisplayTitle } from "../../../components/layout/pane/title";
|
|
21
21
|
import type { AppConfig } from "../../../types/config";
|
|
22
22
|
import type { CachedFinancialsTarget, DataProvider, QuoteSubscriptionTarget } from "../../../types/data-provider";
|
|
23
|
-
import type { TickerFinancials } from "../../../types/financials";
|
|
23
|
+
import type { OptionsChain, TickerFinancials } from "../../../types/financials";
|
|
24
24
|
import type { TickerRecord } from "../../../types/ticker";
|
|
25
25
|
import type { AppState, PaneRuntimeState } from "../../../core/state/app/state";
|
|
26
26
|
import type { PaneDef } from "../../../types/plugin";
|
|
@@ -32,6 +32,7 @@ interface CliPaneShotPayload {
|
|
|
32
32
|
heightCells: number;
|
|
33
33
|
tickers: TickerRecord[];
|
|
34
34
|
financials: Array<[string, TickerFinancials]>;
|
|
35
|
+
optionsChains: Array<[string, OptionsChain]>;
|
|
35
36
|
paneState: Record<string, PaneRuntimeState>;
|
|
36
37
|
}
|
|
37
38
|
|
|
@@ -56,6 +57,7 @@ const TRACKED_RESPONSE_METHODS = new Set<PropertyKey>([
|
|
|
56
57
|
|
|
57
58
|
let pendingShotWork = 0;
|
|
58
59
|
let didInstallShotFetchTracker = false;
|
|
60
|
+
let shotDataProvider: DataProvider | null = null;
|
|
59
61
|
|
|
60
62
|
const rendererHost: RendererHost = {
|
|
61
63
|
requestExit() {},
|
|
@@ -152,6 +154,7 @@ function waitForShotReadiness(): () => void {
|
|
|
152
154
|
|
|
153
155
|
function createShotDataProvider(payload: CliPaneShotPayload): DataProvider {
|
|
154
156
|
const financials = new Map(payload.financials.map(([symbol, data]) => [normalizeSymbol(symbol), data]));
|
|
157
|
+
const optionsChains = new Map((payload.optionsChains ?? []).map(([symbol, data]) => [normalizeSymbol(symbol), data]));
|
|
155
158
|
|
|
156
159
|
const getFinancials = (symbol: string) => {
|
|
157
160
|
const data = financials.get(normalizeSymbol(symbol));
|
|
@@ -187,6 +190,13 @@ function createShotDataProvider(payload: CliPaneShotPayload): DataProvider {
|
|
|
187
190
|
getExchangeRate() {
|
|
188
191
|
return resolveShotWork(1);
|
|
189
192
|
},
|
|
193
|
+
getOptionsChain(ticker) {
|
|
194
|
+
return trackShotWork(Promise.resolve().then(() => {
|
|
195
|
+
const chain = optionsChains.get(normalizeSymbol(ticker));
|
|
196
|
+
if (!chain) throw new Error(`No screenshot options data available for ${ticker}.`);
|
|
197
|
+
return chain;
|
|
198
|
+
}));
|
|
199
|
+
},
|
|
190
200
|
search(query) {
|
|
191
201
|
const normalized = normalizeSymbol(query);
|
|
192
202
|
return resolveShotWork(payload.tickers
|
|
@@ -217,6 +227,7 @@ function createShotDataProvider(payload: CliPaneShotPayload): DataProvider {
|
|
|
217
227
|
|
|
218
228
|
function installShotMarketData(payload: CliPaneShotPayload): void {
|
|
219
229
|
const provider = createShotDataProvider(payload);
|
|
230
|
+
shotDataProvider = provider;
|
|
220
231
|
const coordinator = new MarketDataCoordinator(provider);
|
|
221
232
|
coordinator.primeCachedFinancials(payload.tickers.flatMap((ticker) => {
|
|
222
233
|
const financials = payload.financials.find(([symbol]) => normalizeSymbol(symbol) === normalizeSymbol(ticker.metadata.ticker))?.[1];
|
|
@@ -236,7 +247,7 @@ function createRuntime(payload: CliPaneShotPayload): PluginRuntimeAccess {
|
|
|
236
247
|
return (payload.config.pluginConfig[pluginId]?.[key] as T | undefined) ?? null;
|
|
237
248
|
}
|
|
238
249
|
return {
|
|
239
|
-
getMarketData: () =>
|
|
250
|
+
getMarketData: () => shotDataProvider,
|
|
240
251
|
getCapability: () => null,
|
|
241
252
|
getBrokerAdapter: () => null,
|
|
242
253
|
connectBrokerInstance: async () => {},
|
|
@@ -53,12 +53,11 @@ export function WebDataTable<T, C extends DataTableColumn = DataTableColumn>({
|
|
|
53
53
|
scrollRef,
|
|
54
54
|
syncHeaderScroll: _syncHeaderScroll,
|
|
55
55
|
onBodyScrollActivity,
|
|
56
|
-
hoveredIdx,
|
|
57
|
-
setHoveredIdx,
|
|
58
56
|
getItemKey,
|
|
59
57
|
isSelected,
|
|
60
58
|
onSelect,
|
|
61
59
|
onActivate,
|
|
60
|
+
onTableMouseDown,
|
|
62
61
|
onRowMouseDown,
|
|
63
62
|
onRowContextMenu,
|
|
64
63
|
rowContextMenuSurface = false,
|
|
@@ -71,6 +70,9 @@ export function WebDataTable<T, C extends DataTableColumn = DataTableColumn>({
|
|
|
71
70
|
emptyStateHint,
|
|
72
71
|
virtualize = true,
|
|
73
72
|
overscan = 3,
|
|
73
|
+
columnGap = 1,
|
|
74
|
+
horizontalPadding = 1,
|
|
75
|
+
fillAvailableWidth = true,
|
|
74
76
|
showHorizontalScrollbar = true,
|
|
75
77
|
scrollToIndex,
|
|
76
78
|
scrollToIndexAlign = "nearest",
|
|
@@ -85,10 +87,13 @@ export function WebDataTable<T, C extends DataTableColumn = DataTableColumn>({
|
|
|
85
87
|
const bodyVertical = useScrollbarState(true);
|
|
86
88
|
const [viewportWidth, setViewportWidth] = useState(0);
|
|
87
89
|
const [scrollbarActive, markScrollbarActive] = useScrollbarActivity();
|
|
88
|
-
const tableWidth = useMemo(
|
|
90
|
+
const tableWidth = useMemo(
|
|
91
|
+
() => getTableWidth(columns, columnGap, horizontalPadding),
|
|
92
|
+
[columnGap, columns, horizontalPadding],
|
|
93
|
+
);
|
|
89
94
|
const gridTemplateColumns = useMemo(
|
|
90
|
-
() => buildTableGridTemplateColumns(columns),
|
|
91
|
-
[columns],
|
|
95
|
+
() => buildTableGridTemplateColumns(columns, fillAvailableWidth),
|
|
96
|
+
[columns, fillAvailableWidth],
|
|
92
97
|
);
|
|
93
98
|
const selectRow = useCallback((item: T, index: number) => {
|
|
94
99
|
onSelect(item, index);
|
|
@@ -136,8 +141,9 @@ export function WebDataTable<T, C extends DataTableColumn = DataTableColumn>({
|
|
|
136
141
|
: WEB_CELL_HEIGHT + items.length * WEB_CELL_HEIGHT;
|
|
137
142
|
const bodyAfterHeight = bodyAfter ? WEB_CELL_HEIGHT * 6 : 0;
|
|
138
143
|
const horizontalScrollEnabled = showHorizontalScrollbar
|
|
139
|
-
&& hasMeaningfulTableHorizontalOverflow(tableWidth, viewportWidth);
|
|
144
|
+
&& hasMeaningfulTableHorizontalOverflow(tableWidth, viewportWidth, columnGap);
|
|
140
145
|
const scrollContentWidth = horizontalScrollEnabled
|
|
146
|
+
|| !fillAvailableWidth
|
|
141
147
|
? Math.max(1, tableWidth * WEB_CELL_WIDTH)
|
|
142
148
|
: "100%";
|
|
143
149
|
const measureViewportWidth = useCallback(() => {
|
|
@@ -247,9 +253,7 @@ export function WebDataTable<T, C extends DataTableColumn = DataTableColumn>({
|
|
|
247
253
|
style={bodyScrollerStyle}
|
|
248
254
|
onMouseDown={() => {
|
|
249
255
|
focusPane();
|
|
250
|
-
|
|
251
|
-
onMouseLeave={() => {
|
|
252
|
-
if (hoveredIdx !== null) setHoveredIdx(null);
|
|
256
|
+
onTableMouseDown?.({});
|
|
253
257
|
}}
|
|
254
258
|
onScroll={() => {
|
|
255
259
|
markScrollbarActive();
|
|
@@ -270,7 +274,10 @@ export function WebDataTable<T, C extends DataTableColumn = DataTableColumn>({
|
|
|
270
274
|
>
|
|
271
275
|
<WebDataTableHeader
|
|
272
276
|
columns={columns}
|
|
277
|
+
columnGap={columnGap}
|
|
278
|
+
horizontalPadding={horizontalPadding}
|
|
273
279
|
focusPane={focusPane}
|
|
280
|
+
onTableMouseDown={onTableMouseDown}
|
|
274
281
|
gridTemplateColumns={gridTemplateColumns}
|
|
275
282
|
onHeaderClick={onHeaderClick}
|
|
276
283
|
sortColumnId={sortColumnId}
|
|
@@ -303,7 +310,6 @@ export function WebDataTable<T, C extends DataTableColumn = DataTableColumn>({
|
|
|
303
310
|
const item = items[row.index];
|
|
304
311
|
if (!item) return null;
|
|
305
312
|
const selected = isSelected(item, row.index);
|
|
306
|
-
const hovered = hoveredIdx === row.index && !selected;
|
|
307
313
|
const itemKey = getItemKey(item, row.index);
|
|
308
314
|
return (
|
|
309
315
|
<WebDataTableRow<T, C>
|
|
@@ -314,19 +320,20 @@ export function WebDataTable<T, C extends DataTableColumn = DataTableColumn>({
|
|
|
314
320
|
item={item}
|
|
315
321
|
itemKey={itemKey}
|
|
316
322
|
columns={columns}
|
|
323
|
+
columnGap={columnGap}
|
|
324
|
+
horizontalPadding={horizontalPadding}
|
|
317
325
|
focusPane={focusPane}
|
|
326
|
+
onTableMouseDown={onTableMouseDown}
|
|
318
327
|
gridTemplateColumns={gridTemplateColumns}
|
|
319
328
|
onActivateRow={onActivate ? activateRow : undefined}
|
|
320
329
|
onRowContextMenu={onRowContextMenu}
|
|
321
330
|
onRowMouseDown={onRowMouseDown}
|
|
322
331
|
onSelectRow={selectRow}
|
|
323
|
-
hovered={hovered}
|
|
324
332
|
getRowBackgroundColor={getRowBackgroundColor}
|
|
325
333
|
renderCell={renderCell}
|
|
326
334
|
renderSectionHeader={renderSectionHeader}
|
|
327
335
|
rowContextMenuSurface={rowContextMenuSurface}
|
|
328
336
|
selected={selected}
|
|
329
|
-
setHoveredIdx={setHoveredIdx}
|
|
330
337
|
/>
|
|
331
338
|
);
|
|
332
339
|
}),
|
|
@@ -12,7 +12,6 @@ import type {
|
|
|
12
12
|
import { WEB_CELL_HEIGHT } from "../input-host";
|
|
13
13
|
import {
|
|
14
14
|
CSS_BG,
|
|
15
|
-
CSS_HOVER_BG,
|
|
16
15
|
CSS_PANEL,
|
|
17
16
|
CSS_SELECTED,
|
|
18
17
|
CSS_SELECTED_TEXT,
|
|
@@ -44,16 +43,30 @@ function contentJustifyForAlign(align: string | undefined): CSSProperties["justi
|
|
|
44
43
|
return "flex-start";
|
|
45
44
|
}
|
|
46
45
|
|
|
46
|
+
function columnGapCss(columnGap: number): CSSProperties["columnGap"] {
|
|
47
|
+
return columnGap === 0 ? 0 : `calc(${columnGap} * var(--cell-w))`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function inlinePaddingPx(horizontalPadding: number): number {
|
|
51
|
+
return TABLE_INLINE_PADDING_PX * horizontalPadding;
|
|
52
|
+
}
|
|
53
|
+
|
|
47
54
|
export function WebDataTableHeader<C extends DataTableColumn>({
|
|
48
55
|
columns,
|
|
56
|
+
columnGap,
|
|
57
|
+
horizontalPadding,
|
|
49
58
|
focusPane,
|
|
59
|
+
onTableMouseDown,
|
|
50
60
|
gridTemplateColumns,
|
|
51
61
|
onHeaderClick,
|
|
52
62
|
sortColumnId,
|
|
53
63
|
sortDirection,
|
|
54
64
|
}: {
|
|
55
65
|
columns: C[];
|
|
66
|
+
columnGap: number;
|
|
67
|
+
horizontalPadding: number;
|
|
56
68
|
focusPane: () => void;
|
|
69
|
+
onTableMouseDown?: (event: any) => void;
|
|
57
70
|
gridTemplateColumns: string;
|
|
58
71
|
onHeaderClick: (columnId: string) => void;
|
|
59
72
|
sortColumnId: string | null;
|
|
@@ -69,13 +82,13 @@ export function WebDataTableHeader<C extends DataTableColumn>({
|
|
|
69
82
|
zIndex: 2,
|
|
70
83
|
display: "grid",
|
|
71
84
|
gridTemplateColumns,
|
|
72
|
-
columnGap:
|
|
85
|
+
columnGap: columnGapCss(columnGap),
|
|
73
86
|
alignItems: "center",
|
|
74
87
|
width: "100%",
|
|
75
88
|
minWidth: 0,
|
|
76
89
|
height: WEB_CELL_HEIGHT,
|
|
77
|
-
paddingLeft:
|
|
78
|
-
paddingRight:
|
|
90
|
+
paddingLeft: inlinePaddingPx(horizontalPadding),
|
|
91
|
+
paddingRight: inlinePaddingPx(horizontalPadding),
|
|
79
92
|
boxSizing: "border-box",
|
|
80
93
|
backgroundColor: CSS_PANEL,
|
|
81
94
|
}}
|
|
@@ -99,6 +112,7 @@ export function WebDataTableHeader<C extends DataTableColumn>({
|
|
|
99
112
|
}}
|
|
100
113
|
onMouseDown={(event) => {
|
|
101
114
|
focusPane();
|
|
115
|
+
onTableMouseDown?.(event);
|
|
102
116
|
event.preventDefault();
|
|
103
117
|
onHeaderClick(column.id);
|
|
104
118
|
}}
|
|
@@ -128,12 +142,14 @@ function WebDataTableRowInner<
|
|
|
128
142
|
C extends DataTableColumn,
|
|
129
143
|
>({
|
|
130
144
|
columns,
|
|
145
|
+
columnGap,
|
|
146
|
+
horizontalPadding,
|
|
131
147
|
focusPane,
|
|
148
|
+
onTableMouseDown,
|
|
132
149
|
onActivateRow,
|
|
133
150
|
onRowContextMenu,
|
|
134
151
|
onRowMouseDown,
|
|
135
152
|
onSelectRow,
|
|
136
|
-
hovered,
|
|
137
153
|
index,
|
|
138
154
|
item,
|
|
139
155
|
itemKey,
|
|
@@ -145,15 +161,16 @@ function WebDataTableRowInner<
|
|
|
145
161
|
rowStart,
|
|
146
162
|
rowContextMenuSurface,
|
|
147
163
|
selected,
|
|
148
|
-
setHoveredIdx,
|
|
149
164
|
}: {
|
|
150
165
|
columns: C[];
|
|
166
|
+
columnGap: number;
|
|
167
|
+
horizontalPadding: number;
|
|
151
168
|
focusPane: () => void;
|
|
169
|
+
onTableMouseDown?: (event: any) => void;
|
|
152
170
|
onActivateRow?: (item: T, index: number) => void;
|
|
153
171
|
onRowContextMenu?: DataTableProps<T, C>["onRowContextMenu"];
|
|
154
172
|
onRowMouseDown?: DataTableProps<T, C>["onRowMouseDown"];
|
|
155
173
|
onSelectRow: (item: T, index: number) => void;
|
|
156
|
-
hovered: boolean;
|
|
157
174
|
index: number;
|
|
158
175
|
item: T;
|
|
159
176
|
itemKey: string;
|
|
@@ -165,7 +182,6 @@ function WebDataTableRowInner<
|
|
|
165
182
|
rowStart: number;
|
|
166
183
|
rowContextMenuSurface: boolean;
|
|
167
184
|
selected: boolean;
|
|
168
|
-
setHoveredIdx: (index: number | null) => void;
|
|
169
185
|
}) {
|
|
170
186
|
useThemeColors();
|
|
171
187
|
const sectionHeader: DataTableSectionHeader | null =
|
|
@@ -177,13 +193,13 @@ function WebDataTableRowInner<
|
|
|
177
193
|
transform: `translateY(${rowStart}px)`,
|
|
178
194
|
display: "grid",
|
|
179
195
|
gridTemplateColumns,
|
|
180
|
-
columnGap:
|
|
196
|
+
columnGap: columnGapCss(columnGap),
|
|
181
197
|
alignItems: "center",
|
|
182
198
|
width: "100%",
|
|
183
199
|
minWidth: 0,
|
|
184
200
|
height: rowSize,
|
|
185
|
-
paddingLeft:
|
|
186
|
-
paddingRight:
|
|
201
|
+
paddingLeft: inlinePaddingPx(horizontalPadding),
|
|
202
|
+
paddingRight: inlinePaddingPx(horizontalPadding),
|
|
187
203
|
boxSizing: "border-box",
|
|
188
204
|
lineHeight: "var(--cell-h)",
|
|
189
205
|
};
|
|
@@ -199,6 +215,7 @@ function WebDataTableRowInner<
|
|
|
199
215
|
}}
|
|
200
216
|
onMouseDown={(event) => {
|
|
201
217
|
focusPane();
|
|
218
|
+
onTableMouseDown?.(event);
|
|
202
219
|
event.preventDefault();
|
|
203
220
|
}}
|
|
204
221
|
>
|
|
@@ -220,13 +237,11 @@ function WebDataTableRowInner<
|
|
|
220
237
|
);
|
|
221
238
|
}
|
|
222
239
|
|
|
223
|
-
const rowState = { selected
|
|
240
|
+
const rowState = { selected };
|
|
224
241
|
const rowBackgroundColor = getRowBackgroundColor?.(item, index, rowState);
|
|
225
242
|
const rowBg = selected
|
|
226
243
|
? CSS_SELECTED
|
|
227
|
-
:
|
|
228
|
-
? CSS_HOVER_BG
|
|
229
|
-
: rowBackgroundColor ?? CSS_BG;
|
|
244
|
+
: rowBackgroundColor ?? CSS_BG;
|
|
230
245
|
|
|
231
246
|
return (
|
|
232
247
|
<div
|
|
@@ -238,11 +253,9 @@ function WebDataTableRowInner<
|
|
|
238
253
|
...baseRowStyle,
|
|
239
254
|
backgroundColor: rowBg,
|
|
240
255
|
}}
|
|
241
|
-
onMouseMove={() => {
|
|
242
|
-
if (!hovered) setHoveredIdx(index);
|
|
243
|
-
}}
|
|
244
256
|
onMouseDown={(event) => {
|
|
245
257
|
focusPane();
|
|
258
|
+
onTableMouseDown?.(event);
|
|
246
259
|
if (onRowMouseDown?.(item, index, eventWithCellCoordinates(event)) === true) {
|
|
247
260
|
return;
|
|
248
261
|
}
|
|
@@ -274,6 +287,7 @@ function WebDataTableRowInner<
|
|
|
274
287
|
}}
|
|
275
288
|
onMouseDown={(event) => {
|
|
276
289
|
focusPane();
|
|
290
|
+
onTableMouseDown?.(event);
|
|
277
291
|
if (cell.onMouseDown) {
|
|
278
292
|
cell.onMouseDown(eventWithCellCoordinates(event));
|
|
279
293
|
return;
|