gloomberb 0.3.3 → 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.
Files changed (143) hide show
  1. package/README.md +27 -16
  2. package/package.json +1 -1
  3. package/src/app.tsx +29 -65
  4. package/src/cli/base-converter.ts +31 -0
  5. package/src/cli/commands/search.ts +11 -3
  6. package/src/cli/commands/ticker.ts +22 -11
  7. package/src/cli/context.ts +27 -32
  8. package/src/cli/helpers.ts +1 -4
  9. package/src/cli/index.ts +140 -123
  10. package/src/cli/registry.test.ts +226 -0
  11. package/src/cli/registry.ts +201 -0
  12. package/src/cli.test.ts +219 -0
  13. package/src/components/chart/chart-controller.ts +309 -0
  14. package/src/components/chart/chart-data.ts +5 -6
  15. package/src/components/chart/chart-pane-settings.ts +27 -0
  16. package/src/components/chart/chart-renderer.ts +7 -2
  17. package/src/components/chart/chart-resolution.test.ts +56 -0
  18. package/src/components/chart/chart-resolution.ts +362 -0
  19. package/src/components/chart/chart-types.ts +12 -4
  20. package/src/components/chart/chart-viewport.test.ts +25 -0
  21. package/src/components/chart/chart-viewport.ts +28 -0
  22. package/src/components/chart/chart.test.ts +26 -0
  23. package/src/components/chart/comparison-chart-data.ts +20 -27
  24. package/src/components/chart/comparison-stock-chart.tsx +374 -99
  25. package/src/components/chart/stock-chart-renderer-switch.test.tsx +124 -0
  26. package/src/components/chart/stock-chart.tsx +484 -112
  27. package/src/components/command-bar/__snapshots__/command-bar.test.tsx.snap +4 -4
  28. package/src/components/command-bar/command-bar.test.tsx +650 -5
  29. package/src/components/command-bar/command-bar.tsx +359 -63
  30. package/src/components/command-bar/command-registry.ts +8 -0
  31. package/src/components/command-bar/helpers.test.ts +23 -1
  32. package/src/components/command-bar/helpers.ts +14 -5
  33. package/src/components/command-bar/pane-template-display.ts +12 -0
  34. package/src/components/command-bar/root-shortcuts.ts +2 -1
  35. package/src/components/command-bar/workflow-ops.ts +4 -2
  36. package/src/components/detail-feed-view.test.tsx +124 -49
  37. package/src/components/detail-feed-view.tsx +396 -160
  38. package/src/components/layout/floating-pane.tsx +1 -1
  39. package/src/components/layout/pane-header.tsx +26 -2
  40. package/src/components/layout/shell.tsx +62 -50
  41. package/src/components/layout/status-bar.test.tsx +66 -0
  42. package/src/components/layout/status-bar.tsx +26 -5
  43. package/src/components/onboarding/onboarding-wizard.test.tsx +65 -0
  44. package/src/components/onboarding/onboarding-wizard.tsx +5 -6
  45. package/src/components/ticker-list-table.test.tsx +83 -0
  46. package/src/components/ticker-list-table.tsx +174 -97
  47. package/src/components/ui/data-table.tsx +27 -2
  48. package/src/components/ui/index.ts +1 -0
  49. package/src/components/ui/page-stack-view.tsx +60 -0
  50. package/src/components/use-double-click-activation.ts +31 -0
  51. package/src/index.tsx +10 -10
  52. package/src/market-data/coordinator.test.ts +92 -4
  53. package/src/market-data/coordinator.ts +32 -5
  54. package/src/market-data/hooks.test.tsx +120 -0
  55. package/src/market-data/hooks.tsx +53 -16
  56. package/src/market-data/request-types.ts +4 -2
  57. package/src/market-data/selectors.ts +24 -58
  58. package/src/plugins/builtin/chat-controller.test.ts +370 -3
  59. package/src/plugins/builtin/chat-controller.ts +310 -35
  60. package/src/plugins/builtin/chat.test.tsx +118 -6
  61. package/src/plugins/builtin/chat.tsx +175 -116
  62. package/src/plugins/builtin/comparison-chart.test.tsx +66 -5
  63. package/src/plugins/builtin/comparison-chart.tsx +21 -1
  64. package/src/plugins/builtin/news.tsx +1 -11
  65. package/src/plugins/builtin/portfolio-list/cli/commands.ts +656 -0
  66. package/src/plugins/builtin/portfolio-list/command-bar.ts +122 -0
  67. package/src/plugins/builtin/portfolio-list/header.tsx +16 -34
  68. package/src/plugins/builtin/portfolio-list/index.tsx +5 -0
  69. package/src/plugins/builtin/portfolio-list/metrics.test.ts +61 -0
  70. package/src/plugins/builtin/portfolio-list/metrics.ts +2 -2
  71. package/src/plugins/builtin/portfolio-list/mutations.test.ts +187 -0
  72. package/src/plugins/builtin/portfolio-list/mutations.ts +226 -0
  73. package/src/plugins/builtin/portfolio-list/pane.tsx +39 -17
  74. package/src/plugins/builtin/portfolio-list/table.tsx +11 -2
  75. package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.test.tsx +163 -0
  76. package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.ts +81 -0
  77. package/src/plugins/builtin/sec.tsx +1 -11
  78. package/src/plugins/builtin/ticker-detail/chart-tab.tsx +13 -2
  79. package/src/plugins/builtin/ticker-detail/financials-tab.tsx +21 -0
  80. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +45 -10
  81. package/src/plugins/builtin/ticker-detail/pane.tsx +35 -17
  82. package/src/plugins/builtin/ticker-detail/settings.ts +19 -1
  83. package/src/plugins/builtin/ticker-detail.test.tsx +47 -0
  84. package/src/plugins/catalog.ts +65 -0
  85. package/src/plugins/ibkr/broker-adapter.ts +28 -0
  86. package/src/plugins/ibkr/gateway-service.test.ts +75 -1
  87. package/src/plugins/ibkr/gateway-service.ts +179 -9
  88. package/src/plugins/pane-manager.test.ts +1 -1
  89. package/src/plugins/plugin-runtime.tsx +27 -7
  90. package/src/plugins/prediction-markets/chart.test.tsx +139 -0
  91. package/src/plugins/prediction-markets/chart.tsx +65 -436
  92. package/src/plugins/prediction-markets/cli.ts +28 -0
  93. package/src/plugins/prediction-markets/controller-data.ts +17 -5
  94. package/src/plugins/prediction-markets/controller.ts +141 -121
  95. package/src/plugins/prediction-markets/detail/outcomes.tsx +2 -6
  96. package/src/plugins/prediction-markets/detail/pane.tsx +4 -25
  97. package/src/plugins/prediction-markets/detail/shared.tsx +0 -1
  98. package/src/plugins/prediction-markets/detail.test.tsx +4 -0
  99. package/src/plugins/prediction-markets/index.tsx +2 -0
  100. package/src/plugins/prediction-markets/launch.ts +51 -16
  101. package/src/plugins/prediction-markets/metrics.ts +9 -5
  102. package/src/plugins/prediction-markets/pane.test.tsx +160 -58
  103. package/src/plugins/prediction-markets/pane.tsx +74 -183
  104. package/src/plugins/prediction-markets/table.tsx +6 -3
  105. package/src/plugins/prediction-markets/types.ts +0 -1
  106. package/src/sources/gloomberb-cloud.test.ts +29 -0
  107. package/src/sources/gloomberb-cloud.ts +89 -1
  108. package/src/sources/provider-router.test.ts +308 -3
  109. package/src/sources/provider-router.ts +247 -17
  110. package/src/sources/yahoo-finance.test.ts +26 -0
  111. package/src/sources/yahoo-finance.ts +54 -3
  112. package/src/state/app-context-selector.test.tsx +99 -0
  113. package/src/state/app-context.test.ts +18 -0
  114. package/src/state/app-context.tsx +170 -65
  115. package/src/state/selectors.ts +25 -0
  116. package/src/theme/colors.ts +8 -8
  117. package/src/theme/themes.test.ts +2 -0
  118. package/src/theme/themes.ts +22 -10
  119. package/src/types/broker.ts +21 -0
  120. package/src/types/config.ts +17 -1
  121. package/src/types/data-provider.ts +18 -0
  122. package/src/types/financials.ts +31 -1
  123. package/src/types/plugin.ts +70 -0
  124. package/src/utils/api-client.test.ts +57 -0
  125. package/src/utils/api-client.ts +35 -12
  126. package/src/utils/back-navigation.ts +27 -0
  127. package/src/utils/format.test.ts +14 -0
  128. package/src/utils/format.ts +3 -3
  129. package/src/utils/market-status.ts +46 -1
  130. package/src/utils/price-history.test.ts +25 -0
  131. package/src/utils/price-history.ts +29 -5
  132. package/src/utils/quote-freshness.ts +127 -0
  133. package/src/utils/quote-resolution.test.ts +277 -0
  134. package/src/utils/quote-resolution.ts +641 -0
  135. package/src/utils/slugify.ts +3 -0
  136. package/src/utils/timestamp.ts +25 -0
  137. package/src/version.ts +1 -1
  138. package/src/cli/commands/portfolio.ts +0 -210
  139. package/src/cli/commands/watchlist.ts +0 -210
  140. package/src/plugins/builtin/data-providers.ts +0 -16
  141. package/src/sources/exchange-rates.ts +0 -18
  142. package/src/utils/ascii-chart.ts +0 -172
  143. package/src/utils/ticker-tokenizer.test.ts +0 -48
@@ -7,7 +7,7 @@ import {
7
7
  type AppConfig,
8
8
  type SavedLayout,
9
9
  } from "../../types/config";
10
- import type { PaneDef } from "../../types/plugin";
10
+ import type { CliLaunchRequest, PaneDef } from "../../types/plugin";
11
11
  import type { AppSessionSnapshot } from "../../state/session-persistence";
12
12
  import { PREDICTION_CATEGORY_OPTIONS, type PredictionCategoryId } from "./categories";
13
13
  import { BROWSE_TABS } from "./navigation";
@@ -48,20 +48,7 @@ function normalizeArg(value: string): string {
48
48
  return value.trim().toLowerCase();
49
49
  }
50
50
 
51
- export function parsePredictionLaunchArgs(
52
- args: string[],
53
- ): PredictionLaunchIntent | null {
54
- const [command, ...rest] = args;
55
- if (!command) return null;
56
- const normalizedCommand = normalizeArg(command);
57
- if (
58
- normalizedCommand !== "predictions" &&
59
- normalizedCommand !== "prediction-markets" &&
60
- normalizedCommand !== "pm"
61
- ) {
62
- return null;
63
- }
64
-
51
+ export function parsePredictionCommandArgs(args: string[]): PredictionLaunchIntent {
65
52
  let venueScope: PredictionVenueScope = "all";
66
53
  let categoryId: PredictionCategoryId = "all";
67
54
  let browseTab: PredictionBrowseTab = "top";
@@ -70,7 +57,7 @@ export function parsePredictionLaunchArgs(
70
57
  let browseExplicit = false;
71
58
  const searchTokens: string[] = [];
72
59
 
73
- for (const arg of rest) {
60
+ for (const arg of args) {
74
61
  const normalized = normalizeArg(arg);
75
62
  if (!normalized) continue;
76
63
 
@@ -100,6 +87,23 @@ export function parsePredictionLaunchArgs(
100
87
  };
101
88
  }
102
89
 
90
+ export function parsePredictionLaunchArgs(
91
+ args: string[],
92
+ ): PredictionLaunchIntent | null {
93
+ const [command, ...rest] = args;
94
+ if (!command) return null;
95
+ const normalizedCommand = normalizeArg(command);
96
+ if (
97
+ normalizedCommand !== "predictions" &&
98
+ normalizedCommand !== "prediction-markets" &&
99
+ normalizedCommand !== "pm"
100
+ ) {
101
+ return null;
102
+ }
103
+
104
+ return parsePredictionCommandArgs(rest);
105
+ }
106
+
103
107
  function syncActiveLayout(
104
108
  layouts: SavedLayout[],
105
109
  activeLayoutIndex: number,
@@ -238,3 +242,34 @@ export function applyPredictionLaunchIntentToSessionSnapshot(
238
242
  savedAt: Date.now(),
239
243
  };
240
244
  }
245
+
246
+ export function createPredictionLaunchRequest(
247
+ intent: PredictionLaunchIntent,
248
+ ): CliLaunchRequest<{ paneInstanceId: string; intent: PredictionLaunchIntent }> {
249
+ return {
250
+ applyConfig(config, env) {
251
+ const result = applyPredictionLaunchIntentToConfig(config, intent, {
252
+ width: Math.max(env.terminalWidth, 120),
253
+ height: Math.max(env.terminalHeight, 40),
254
+ });
255
+ return {
256
+ config: result.config,
257
+ launchState: {
258
+ paneInstanceId: result.paneInstanceId,
259
+ intent,
260
+ },
261
+ };
262
+ },
263
+ applySessionSnapshot(config, snapshot, launchState) {
264
+ if (!launchState) {
265
+ return applyPredictionLaunchIntentToSessionSnapshot(config, snapshot, PREDICTION_MAIN_INSTANCE_ID, intent);
266
+ }
267
+ return applyPredictionLaunchIntentToSessionSnapshot(
268
+ config,
269
+ snapshot,
270
+ launchState.paneInstanceId,
271
+ launchState.intent,
272
+ );
273
+ },
274
+ };
275
+ }
@@ -170,13 +170,17 @@ export function getNextPredictionSort(
170
170
  current: PredictionSortPreference,
171
171
  columnId: string,
172
172
  ): PredictionSortPreference {
173
+ const initialDirection =
174
+ columnId === "ends" || TEXT_SORT_COLUMNS.has(columnId) ? "asc" : "desc";
175
+ const alternateDirection = initialDirection === "asc" ? "desc" : "asc";
176
+
173
177
  if (current.columnId !== columnId) {
174
- if (columnId === "ends") return { columnId, direction: "asc" };
175
- if (TEXT_SORT_COLUMNS.has(columnId)) return { columnId, direction: "asc" };
176
- return { columnId, direction: "desc" };
178
+ return { columnId, direction: initialDirection };
179
+ }
180
+ if (current.direction === initialDirection) {
181
+ return { columnId, direction: alternateDirection };
177
182
  }
178
- if (current.direction === "desc") return { columnId, direction: "asc" };
179
- return { columnId: null, direction: "desc" };
183
+ return { columnId: null, direction: initialDirection };
180
184
  }
181
185
 
182
186
  export function filterPredictionMarkets(
@@ -83,14 +83,15 @@ describe("prediction markets pane interactions", () => {
83
83
  expect(frame).toContain("Will the Fed cut rates?");
84
84
  });
85
85
 
86
- test("renders the merged market browser and supports venue switching", async () => {
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 });
90
90
  await flushFrames(testSetup);
91
91
 
92
92
  let frame = testSetup.captureCharFrame();
93
- expect(frame).toContain("Prediction Markets");
93
+ expect(frame).toContain("All venues");
94
+ expect(frame).not.toContain("VOL = native venue units");
94
95
  expect(frame).toContain("Will inflation fall?");
95
96
  expect(frame).toContain("Kalshi");
96
97
 
@@ -114,10 +115,60 @@ describe("prediction markets pane interactions", () => {
114
115
 
115
116
  frame = testSetup.captureCharFrame();
116
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();
117
127
  expect(frame).toContain("Kalshi primary rule");
118
128
  });
119
129
 
120
- test("moves selection through the list with keyboard navigation", async () => {
130
+ test("loads selected detail once instead of refetching in a render loop", async () => {
131
+ const { fetchUrls } = installPredictionMarketMocks();
132
+
133
+ testSetup = await testRender(<Harness />, { width: 120, height: 34 });
134
+ await flushFrames(testSetup);
135
+
136
+ const frame = testSetup.captureCharFrame();
137
+ const lines = frame.split("\n");
138
+ const kalshiRow = lines.findIndex((line) =>
139
+ line.includes("Will the Fed cut rates?"),
140
+ );
141
+ const kalshiCol = lines[kalshiRow]?.indexOf("Will the Fed cut rates?") ?? -1;
142
+
143
+ await act(async () => {
144
+ await testSetup!.mockMouse.click(kalshiCol + 1, kalshiRow);
145
+ await testSetup!.renderOnce();
146
+ await testSetup!.mockMouse.click(kalshiCol + 1, kalshiRow);
147
+ await testSetup!.renderOnce();
148
+ });
149
+ await flushFrames(testSetup, 8);
150
+
151
+ const eventFetches = fetchUrls.filter((url) =>
152
+ url.includes("/trade-api/v2/events/FED-1"),
153
+ );
154
+ const orderbookFetches = fetchUrls.filter((url) =>
155
+ url.includes("/trade-api/v2/markets/KAL-1/orderbook"),
156
+ );
157
+ const tradeFetches = fetchUrls.filter((url) =>
158
+ url.includes("/trade-api/v2/markets/trades?ticker=KAL-1"),
159
+ );
160
+ const historyFetches = fetchUrls.filter((url) =>
161
+ url.includes("/trade-api/v2/series/FED/markets/KAL-1/candlesticks"),
162
+ );
163
+
164
+ expect(eventFetches).toHaveLength(2);
165
+ expect(orderbookFetches).toHaveLength(1);
166
+ expect(tradeFetches).toHaveLength(1);
167
+ expect(historyFetches).toHaveLength(1);
168
+ expect(testSetup.captureCharFrame()).not.toContain("Loading market detail...");
169
+ });
170
+
171
+ test("moves selection through the list with keyboard navigation without opening detail", async () => {
121
172
  installPredictionMarketMocks();
122
173
 
123
174
  testSetup = await testRender(<Harness />, { width: 120, height: 34 });
@@ -136,6 +187,102 @@ describe("prediction markets pane interactions", () => {
136
187
 
137
188
  const frame = testSetup.captureCharFrame();
138
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);
139
286
  });
140
287
 
141
288
  test("does not wrap to the last row when moving up without a selection", async () => {
@@ -157,7 +304,7 @@ describe("prediction markets pane interactions", () => {
157
304
  ).toBe("polymarket:pm-1");
158
305
  });
159
306
 
160
- test("supports detail outcome navigation and escape focus return from the keyboard", async () => {
307
+ test("supports detail outcome navigation and backspace return from the keyboard", async () => {
161
308
  attachPredictionMarketsPersistence(new MemoryPersistence());
162
309
 
163
310
  globalThis.fetch = (async (input: Request | string | URL) => {
@@ -286,17 +433,15 @@ describe("prediction markets pane interactions", () => {
286
433
  ]?.selectedDetailMarketKey,
287
434
  ).toBe("kalshi:KXFED-27APR-T4.50");
288
435
 
289
- await emitKeypress(testSetup, { name: "esc", sequence: "\u001b" });
436
+ await emitKeypress(testSetup, { name: "backspace", sequence: "\u007f" });
290
437
  await flushFrames(testSetup);
291
438
 
292
- expect(
293
- harnessStateRef.current?.paneState[TEST_PANE_ID]?.pluginState?.[
294
- "prediction-markets"
295
- ]?.focusRegion,
296
- ).toBe("list");
439
+ const frame = testSetup.captureCharFrame();
440
+ expect(frame).toContain("search markets");
441
+ expect(frame).not.toContain("Rule 2");
297
442
  });
298
443
 
299
- test("starts with no detail pane and toggles the detail pane off on repeat selection", async () => {
444
+ test("opens full-page detail from a row double click and preserves the browse selection", async () => {
300
445
  installPredictionMarketMocks();
301
446
 
302
447
  testSetup = await testRender(<Harness />, { width: 120, height: 34 });
@@ -312,62 +457,19 @@ describe("prediction markets pane interactions", () => {
312
457
  await act(async () => {
313
458
  await testSetup!.mockMouse.click(kalshiCol + 1, kalshiRow);
314
459
  await testSetup!.renderOnce();
315
- });
316
- await flushFrames(testSetup);
317
-
318
- await act(async () => {
319
460
  await testSetup!.mockMouse.click(kalshiCol + 1, kalshiRow);
320
461
  await testSetup!.renderOnce();
321
462
  });
322
463
  await flushFrames(testSetup);
323
464
 
465
+ const detailFrame = testSetup.captureCharFrame();
466
+ expect(detailFrame).toContain("<- Back");
467
+ expect(detailFrame).toContain("Kalshi primary rule");
324
468
  expect(
325
469
  harnessStateRef.current?.paneState[TEST_PANE_ID]?.pluginState?.[
326
470
  "prediction-markets"
327
471
  ]?.selectedRowKey,
328
- ).toBeNull();
329
- });
330
-
331
- test("resizes the list/detail split by dragging the divider", async () => {
332
- installPredictionMarketMocks();
333
-
334
- testSetup = await testRender(<Harness />, { width: 120, height: 34 });
335
- await flushFrames(testSetup);
336
-
337
- let frame = testSetup.captureCharFrame();
338
- const lines = frame.split("\n");
339
- const kalshiRow = lines.findIndex((line) =>
340
- line.includes("Will the Fed cut rates?"),
341
- );
342
- const kalshiCol = lines[kalshiRow]?.indexOf("Will the Fed cut rates?") ?? -1;
343
-
344
- await act(async () => {
345
- await testSetup!.mockMouse.click(kalshiCol + 1, kalshiRow);
346
- await testSetup!.renderOnce();
347
- });
348
- await flushFrames(testSetup);
349
-
350
- frame = testSetup.captureCharFrame();
351
- const dividerLines = frame.split("\n");
352
- const dividerRow = dividerLines.findIndex((line) => line.includes("│"));
353
- const dividerCol = dividerLines[dividerRow]?.indexOf("│") ?? -1;
354
-
355
- await act(async () => {
356
- await testSetup!.mockMouse.drag(
357
- dividerCol,
358
- dividerRow,
359
- dividerCol + 10,
360
- dividerRow,
361
- );
362
- await testSetup!.renderOnce();
363
- });
364
- await flushFrames(testSetup);
365
-
366
- expect(
367
- harnessStateRef.current?.paneState[TEST_PANE_ID]?.pluginState?.[
368
- "prediction-markets"
369
- ]?.detailSplitRatio,
370
- ).toBeGreaterThan(0.42);
472
+ ).toBe("kalshi:KAL-1");
371
473
  });
372
474
 
373
475
  test("switches categories with arrows and venues with shift+arrows", async () => {