gloomberb 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (310) hide show
  1. package/README.md +20 -19
  2. package/bin/gloomberb +3 -0
  3. package/package.json +5 -4
  4. package/patches/@opentui%2Fcore@0.1.90.patch +356 -0
  5. package/src/app.tsx +1330 -0
  6. package/src/brokers/sync-broker-instance.test.ts +100 -0
  7. package/src/brokers/sync-broker-instance.ts +341 -0
  8. package/src/cli/commands/plugins.ts +173 -0
  9. package/src/cli/commands/portfolio.ts +210 -0
  10. package/src/cli/commands/search.ts +120 -0
  11. package/src/cli/commands/ticker.ts +375 -0
  12. package/src/cli/commands/watchlist.ts +210 -0
  13. package/src/cli/context.ts +62 -0
  14. package/src/cli/errors.ts +13 -0
  15. package/src/cli/helpers.ts +85 -0
  16. package/src/cli/index.ts +143 -0
  17. package/src/cli/ticker-resolution.ts +39 -0
  18. package/src/cli/types.ts +15 -0
  19. package/src/cli.test.ts +361 -0
  20. package/src/components/chart/chart-data.ts +239 -0
  21. package/src/components/chart/chart-renderer.ts +1199 -0
  22. package/src/components/chart/chart-types.ts +82 -0
  23. package/src/components/chart/chart-viewport.ts +21 -0
  24. package/src/components/chart/chart.test.ts +439 -0
  25. package/src/components/chart/comparison-chart-data.test.ts +109 -0
  26. package/src/components/chart/comparison-chart-data.ts +316 -0
  27. package/src/components/chart/comparison-chart-renderer.test.ts +84 -0
  28. package/src/components/chart/comparison-chart-renderer.ts +354 -0
  29. package/src/components/chart/comparison-stock-chart.tsx +1486 -0
  30. package/src/components/chart/cursor-motion.ts +48 -0
  31. package/src/components/chart/native/chart-rasterizer.ts +732 -0
  32. package/src/components/chart/native/kitty-adapter.ts +12 -0
  33. package/src/components/chart/native/kitty-manager.ts +142 -0
  34. package/src/components/chart/native/kitty-protocol.ts +110 -0
  35. package/src/components/chart/native/kitty-support.ts +71 -0
  36. package/src/components/chart/native/kitty.test.ts +620 -0
  37. package/src/components/chart/native/renderer-selection.ts +24 -0
  38. package/src/components/chart/native/surface-manager.ts +225 -0
  39. package/src/components/chart/native/surface-sync.ts +34 -0
  40. package/src/components/chart/stock-chart-renderer-switch.test.tsx +165 -0
  41. package/src/components/chart/stock-chart.test.ts +107 -0
  42. package/src/components/chart/stock-chart.tsx +1556 -0
  43. package/src/components/command-bar/__snapshots__/command-bar.test.tsx.snap +137 -0
  44. package/src/components/command-bar/command-bar.test.tsx +1524 -0
  45. package/src/components/command-bar/command-bar.tsx +4823 -0
  46. package/src/components/command-bar/command-registry.ts +261 -0
  47. package/src/components/command-bar/helpers.test.ts +224 -0
  48. package/src/components/command-bar/helpers.ts +371 -0
  49. package/src/components/command-bar/root-shortcuts.ts +158 -0
  50. package/src/components/command-bar/view-model.test.ts +152 -0
  51. package/src/components/command-bar/view-model.ts +185 -0
  52. package/src/components/command-bar/workflow-ops.test.ts +70 -0
  53. package/src/components/command-bar/workflow-ops.ts +433 -0
  54. package/src/components/command-bar/workflow-types.ts +127 -0
  55. package/src/components/detail-feed-view.test.tsx +105 -0
  56. package/src/components/detail-feed-view.tsx +295 -0
  57. package/src/components/index.ts +32 -0
  58. package/src/components/layout/floating-pane.tsx +49 -0
  59. package/src/components/layout/header.tsx +129 -0
  60. package/src/components/layout/pane-header.tsx +44 -0
  61. package/src/components/layout/pane.tsx +49 -0
  62. package/src/components/layout/pixel-mouse-mode.test.ts +122 -0
  63. package/src/components/layout/pixel-mouse.test.ts +40 -0
  64. package/src/components/layout/shell.test.tsx +625 -0
  65. package/src/components/layout/shell.tsx +1200 -0
  66. package/src/components/layout/status-bar.test.tsx +173 -0
  67. package/src/components/layout/status-bar.tsx +138 -0
  68. package/src/components/markdown-editor.tsx +134 -0
  69. package/src/components/onboarding/onboarding-wizard.test.tsx +184 -0
  70. package/src/components/onboarding/onboarding-wizard.tsx +1365 -0
  71. package/src/components/pane-settings-dialog.tsx +468 -0
  72. package/src/components/pane-template-wizard.test.tsx +52 -0
  73. package/src/components/pane-template-wizard.tsx +199 -0
  74. package/src/components/price-selector-dialog.tsx +195 -0
  75. package/src/components/spinner.tsx +2 -0
  76. package/src/components/tab-bar.tsx +17 -0
  77. package/src/components/ticker-badge-text.test.tsx +160 -0
  78. package/src/components/ticker-badge-text.tsx +58 -0
  79. package/src/components/ticker-badge.tsx +88 -0
  80. package/src/components/ticker-list-table.tsx +188 -0
  81. package/src/components/toggle-list.tsx +74 -0
  82. package/src/components/ui/button.tsx +77 -0
  83. package/src/components/ui/data-table.tsx +210 -0
  84. package/src/components/ui/external-link.tsx +22 -0
  85. package/src/components/ui/fields.tsx +187 -0
  86. package/src/components/ui/frame.tsx +73 -0
  87. package/src/components/ui/index.ts +53 -0
  88. package/src/components/ui/list-view.tsx +166 -0
  89. package/src/components/ui/loading.tsx +70 -0
  90. package/src/components/ui/status.tsx +83 -0
  91. package/src/components/ui/tabs.tsx +47 -0
  92. package/src/components/ui/toggle.tsx +164 -0
  93. package/src/components/ui/ui.test.tsx +134 -0
  94. package/src/data/app-persistence.ts +25 -0
  95. package/src/data/config-store.test.ts +310 -0
  96. package/src/data/config-store.ts +469 -0
  97. package/src/data/plugin-state-store.ts +102 -0
  98. package/src/data/resource-store.ts +323 -0
  99. package/src/data/session-store.ts +52 -0
  100. package/src/data/sqlite-cache.test.ts +103 -0
  101. package/src/data/sqlite-database.ts +62 -0
  102. package/src/data/sqlite-json.ts +19 -0
  103. package/src/data/ticker-repository.ts +45 -0
  104. package/src/data/ticker-store.ts +46 -0
  105. package/src/index.tsx +67 -0
  106. package/src/market-data/coordinator.test.ts +369 -0
  107. package/src/market-data/coordinator.ts +580 -0
  108. package/src/market-data/hooks.tsx +234 -0
  109. package/src/market-data/query-store.ts +28 -0
  110. package/src/market-data/request-types.test.ts +64 -0
  111. package/src/market-data/request-types.ts +70 -0
  112. package/src/market-data/result-types.ts +56 -0
  113. package/src/market-data/selectors.ts +166 -0
  114. package/src/market-data/trace.ts +7 -0
  115. package/src/pane-settings.test.ts +71 -0
  116. package/src/pane-settings.ts +73 -0
  117. package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +338 -0
  118. package/src/plugins/builtin/ai/gloomberb-cli.ts +70 -0
  119. package/src/plugins/builtin/ai/index.test.tsx +139 -0
  120. package/src/plugins/builtin/ai/index.tsx +86 -0
  121. package/src/plugins/builtin/ai/providers.ts +99 -0
  122. package/src/plugins/builtin/ai/runner.ts +82 -0
  123. package/src/plugins/builtin/ai/screener-contract.test.ts +117 -0
  124. package/src/plugins/builtin/ai/screener-contract.ts +174 -0
  125. package/src/plugins/builtin/ai/screener-pane.test.tsx +365 -0
  126. package/src/plugins/builtin/ai/screener-pane.tsx +1161 -0
  127. package/src/plugins/builtin/ai/settings.ts +79 -0
  128. package/src/plugins/builtin/ai/ticker-context.ts +64 -0
  129. package/src/plugins/builtin/ai/utils.ts +71 -0
  130. package/src/plugins/builtin/ask-ai.test.tsx +258 -0
  131. package/src/plugins/builtin/ask-ai.tsx +9 -0
  132. package/src/plugins/builtin/chat-controller.test.ts +217 -0
  133. package/src/plugins/builtin/chat-controller.ts +340 -0
  134. package/src/plugins/builtin/chat.test.tsx +451 -0
  135. package/src/plugins/builtin/chat.tsx +620 -0
  136. package/src/plugins/builtin/comparison-chart.test.tsx +277 -0
  137. package/src/plugins/builtin/comparison-chart.tsx +169 -0
  138. package/src/plugins/builtin/data-providers.ts +16 -0
  139. package/src/plugins/builtin/debug.tsx +351 -0
  140. package/src/plugins/builtin/help.test.tsx +110 -0
  141. package/src/plugins/builtin/help.tsx +281 -0
  142. package/src/plugins/builtin/layout-manager.tsx +264 -0
  143. package/src/plugins/builtin/manual-entry.tsx +25 -0
  144. package/src/plugins/builtin/news.tsx +143 -0
  145. package/src/plugins/builtin/notes-files.ts +58 -0
  146. package/src/plugins/builtin/notes.tsx +396 -0
  147. package/src/plugins/builtin/options-availability.test.tsx +179 -0
  148. package/src/plugins/builtin/options-availability.ts +158 -0
  149. package/src/plugins/builtin/options.tsx +364 -0
  150. package/src/plugins/builtin/portfolio-list/header.tsx +227 -0
  151. package/src/plugins/builtin/portfolio-list/index.tsx +99 -0
  152. package/src/plugins/builtin/portfolio-list/metrics.ts +436 -0
  153. package/src/plugins/builtin/portfolio-list/pane.tsx +533 -0
  154. package/src/plugins/builtin/portfolio-list/settings.ts +280 -0
  155. package/src/plugins/builtin/portfolio-list/summary.tsx +270 -0
  156. package/src/plugins/builtin/portfolio-list/table.tsx +64 -0
  157. package/src/plugins/builtin/portfolio-list.test.ts +74 -0
  158. package/src/plugins/builtin/portfolio-list.test.tsx +1164 -0
  159. package/src/plugins/builtin/portfolio-metrics.test.ts +153 -0
  160. package/src/plugins/builtin/quote-monitor.test.tsx +114 -0
  161. package/src/plugins/builtin/sec.tsx +250 -0
  162. package/src/plugins/builtin/ticker-detail/chart-tab.tsx +43 -0
  163. package/src/plugins/builtin/ticker-detail/financials-tab.tsx +390 -0
  164. package/src/plugins/builtin/ticker-detail/index.tsx +80 -0
  165. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +242 -0
  166. package/src/plugins/builtin/ticker-detail/pane.tsx +183 -0
  167. package/src/plugins/builtin/ticker-detail/quote-monitor.tsx +83 -0
  168. package/src/plugins/builtin/ticker-detail/settings.ts +132 -0
  169. package/src/plugins/builtin/ticker-detail-chart-tab-switch.test.tsx +193 -0
  170. package/src/plugins/builtin/ticker-detail.test.tsx +578 -0
  171. package/src/plugins/builtin/yahoo.ts +18 -0
  172. package/src/plugins/event-bus.ts +37 -0
  173. package/src/plugins/ibkr/__snapshots__/trade-tab.test.tsx.snap +35 -0
  174. package/src/plugins/ibkr/account-cache.test.ts +101 -0
  175. package/src/plugins/ibkr/account-cache.ts +137 -0
  176. package/src/plugins/ibkr/broker-adapter.ts +168 -0
  177. package/src/plugins/ibkr/config.test.ts +68 -0
  178. package/src/plugins/ibkr/config.ts +262 -0
  179. package/src/plugins/ibkr/dialogs.tsx +83 -0
  180. package/src/plugins/ibkr/flex.test.ts +107 -0
  181. package/src/plugins/ibkr/flex.ts +267 -0
  182. package/src/plugins/ibkr/fundamental-parser.ts +149 -0
  183. package/src/plugins/ibkr/gateway-helpers.ts +28 -0
  184. package/src/plugins/ibkr/gateway-selection.ts +51 -0
  185. package/src/plugins/ibkr/gateway-service.test.ts +450 -0
  186. package/src/plugins/ibkr/gateway-service.ts +2228 -0
  187. package/src/plugins/ibkr/index.test.tsx +183 -0
  188. package/src/plugins/ibkr/index.tsx +153 -0
  189. package/src/plugins/ibkr/instance-selection.test.ts +121 -0
  190. package/src/plugins/ibkr/instance-selection.ts +36 -0
  191. package/src/plugins/ibkr/price-normalization.test.ts +38 -0
  192. package/src/plugins/ibkr/price-normalization.ts +59 -0
  193. package/src/plugins/ibkr/trade-badge.tsx +27 -0
  194. package/src/plugins/ibkr/trade-tab.test.tsx +225 -0
  195. package/src/plugins/ibkr/trade-tab.tsx +1050 -0
  196. package/src/plugins/ibkr/trade-utils.test.ts +175 -0
  197. package/src/plugins/ibkr/trade-utils.ts +149 -0
  198. package/src/plugins/ibkr/trading-pane.tsx +438 -0
  199. package/src/plugins/ibkr/trading-state.test.ts +109 -0
  200. package/src/plugins/ibkr/trading-state.ts +339 -0
  201. package/src/plugins/loader.ts +71 -0
  202. package/src/plugins/pane-manager.test.ts +103 -0
  203. package/src/plugins/pane-manager.ts +974 -0
  204. package/src/plugins/plugin-persistence.ts +85 -0
  205. package/src/plugins/plugin-runtime.test.tsx +185 -0
  206. package/src/plugins/plugin-runtime.tsx +175 -0
  207. package/src/plugins/prediction-markets/cache.ts +114 -0
  208. package/src/plugins/prediction-markets/categories.ts +223 -0
  209. package/src/plugins/prediction-markets/chart.tsx +512 -0
  210. package/src/plugins/prediction-markets/columns.ts +133 -0
  211. package/src/plugins/prediction-markets/controller-data.ts +614 -0
  212. package/src/plugins/prediction-markets/controller.ts +725 -0
  213. package/src/plugins/prediction-markets/detail/book.tsx +118 -0
  214. package/src/plugins/prediction-markets/detail/outcomes.tsx +102 -0
  215. package/src/plugins/prediction-markets/detail/overview.tsx +73 -0
  216. package/src/plugins/prediction-markets/detail/pane.tsx +308 -0
  217. package/src/plugins/prediction-markets/detail/rules.tsx +16 -0
  218. package/src/plugins/prediction-markets/detail/shared.tsx +29 -0
  219. package/src/plugins/prediction-markets/detail/trades.tsx +30 -0
  220. package/src/plugins/prediction-markets/detail.test.tsx +47 -0
  221. package/src/plugins/prediction-markets/index.tsx +102 -0
  222. package/src/plugins/prediction-markets/keyboard.ts +64 -0
  223. package/src/plugins/prediction-markets/launch.test.ts +126 -0
  224. package/src/plugins/prediction-markets/launch.ts +240 -0
  225. package/src/plugins/prediction-markets/metrics.ts +311 -0
  226. package/src/plugins/prediction-markets/navigation.ts +90 -0
  227. package/src/plugins/prediction-markets/outcome-order.ts +19 -0
  228. package/src/plugins/prediction-markets/pane.test.tsx +425 -0
  229. package/src/plugins/prediction-markets/pane.tsx +294 -0
  230. package/src/plugins/prediction-markets/plugin.test.tsx +570 -0
  231. package/src/plugins/prediction-markets/rows.ts +229 -0
  232. package/src/plugins/prediction-markets/services/fetch.ts +97 -0
  233. package/src/plugins/prediction-markets/services/kalshi-adapter.ts +555 -0
  234. package/src/plugins/prediction-markets/services/polymarket-adapter.ts +660 -0
  235. package/src/plugins/prediction-markets/services/polymarket-ws.ts +108 -0
  236. package/src/plugins/prediction-markets/settings.ts +164 -0
  237. package/src/plugins/prediction-markets/status.ts +24 -0
  238. package/src/plugins/prediction-markets/table.tsx +84 -0
  239. package/src/plugins/prediction-markets/test-helpers.tsx +636 -0
  240. package/src/plugins/prediction-markets/types.ts +186 -0
  241. package/src/plugins/registry.ts +633 -0
  242. package/src/sources/exchange-rates.ts +18 -0
  243. package/src/sources/gloomberb-cloud.test.ts +199 -0
  244. package/src/sources/gloomberb-cloud.ts +326 -0
  245. package/src/sources/provider-errors.ts +19 -0
  246. package/src/sources/provider-router.test.ts +1476 -0
  247. package/src/sources/provider-router.ts +1405 -0
  248. package/src/sources/sec-edgar.test.ts +286 -0
  249. package/src/sources/sec-edgar.ts +526 -0
  250. package/src/sources/yahoo-finance.test.ts +14 -0
  251. package/src/sources/yahoo-finance.ts +1017 -0
  252. package/src/state/app-activity.test.ts +41 -0
  253. package/src/state/app-activity.ts +106 -0
  254. package/src/state/app-bootstrap.test.ts +471 -0
  255. package/src/state/app-bootstrap.ts +308 -0
  256. package/src/state/app-context.test.ts +406 -0
  257. package/src/state/app-context.test.tsx +132 -0
  258. package/src/state/app-context.tsx +969 -0
  259. package/src/state/selectors.ts +67 -0
  260. package/src/state/session-persistence.test.ts +128 -0
  261. package/src/state/session-persistence.ts +161 -0
  262. package/src/state/ticker-refresh-queue.test.ts +46 -0
  263. package/src/state/ticker-refresh-queue.ts +40 -0
  264. package/src/state/use-inline-tickers.ts +174 -0
  265. package/src/state/use-quote-streaming.test.ts +37 -0
  266. package/src/state/use-quote-streaming.test.tsx +70 -0
  267. package/src/state/use-quote-streaming.ts +80 -0
  268. package/src/state/use-session-snapshot.ts +84 -0
  269. package/src/test-support/data-provider.ts +22 -0
  270. package/src/theme/color-utils.ts +67 -0
  271. package/src/theme/colors.ts +128 -0
  272. package/src/theme/themes.test.ts +45 -0
  273. package/src/theme/themes.ts +491 -0
  274. package/src/types/broker.ts +105 -0
  275. package/src/types/config.ts +434 -0
  276. package/src/types/data-provider.ts +81 -0
  277. package/src/types/financials.ts +142 -0
  278. package/src/types/instrument.ts +29 -0
  279. package/src/types/persistence.ts +21 -0
  280. package/src/types/plugin.ts +354 -0
  281. package/src/types/ticker.ts +58 -0
  282. package/src/types/trading.ts +85 -0
  283. package/src/updater.test.ts +162 -0
  284. package/src/updater.ts +247 -0
  285. package/src/utils/api-client.ts +666 -0
  286. package/src/utils/ascii-chart.ts +172 -0
  287. package/src/utils/broker-instances.ts +47 -0
  288. package/src/utils/cli-output.ts +83 -0
  289. package/src/utils/currency-units.test.ts +49 -0
  290. package/src/utils/currency-units.ts +82 -0
  291. package/src/utils/debug-log.ts +133 -0
  292. package/src/utils/exchange-rate-map.ts +8 -0
  293. package/src/utils/format.ts +158 -0
  294. package/src/utils/fuzzy-search.ts +34 -0
  295. package/src/utils/market-status.ts +95 -0
  296. package/src/utils/options.ts +86 -0
  297. package/src/utils/price-history.ts +39 -0
  298. package/src/utils/quote-time.test.ts +22 -0
  299. package/src/utils/quote-time.ts +50 -0
  300. package/src/utils/sec.test.ts +55 -0
  301. package/src/utils/sec.ts +45 -0
  302. package/src/utils/ticker-list.test.ts +22 -0
  303. package/src/utils/ticker-list.ts +35 -0
  304. package/src/utils/ticker-metadata.ts +51 -0
  305. package/src/utils/ticker-search.test.ts +227 -0
  306. package/src/utils/ticker-search.ts +764 -0
  307. package/src/utils/ticker-tokenizer.test.ts +48 -0
  308. package/src/utils/ticker-tokenizer.ts +72 -0
  309. package/src/version.ts +1 -0
  310. package/install.mjs +0 -68
@@ -0,0 +1,425 @@
1
+ import { afterEach, describe, expect, test } from "bun:test";
2
+ import { act } from "react";
3
+ import { testRender } from "@opentui/react/test-utils";
4
+ import {
5
+ Harness,
6
+ MemoryPersistence,
7
+ PREDICTION_CACHE_POLICIES,
8
+ TEST_PANE_ID,
9
+ cleanupPredictionTest,
10
+ emitKeypress,
11
+ flushFrames,
12
+ harnessStateRef,
13
+ installPredictionMarketMocks,
14
+ } from "./test-helpers";
15
+ import { attachPredictionMarketsPersistence } from "./services/fetch";
16
+ import {
17
+ loadKalshiCatalog,
18
+ normalizeKalshiMarket,
19
+ } from "./services/kalshi-adapter";
20
+ import { normalizePolymarketMarket } from "./services/polymarket-adapter";
21
+
22
+ let testSetup: Awaited<ReturnType<typeof testRender>> | undefined;
23
+
24
+ afterEach(async () => {
25
+ await cleanupPredictionTest(testSetup);
26
+ testSetup = undefined;
27
+ });
28
+
29
+ describe("prediction markets pane interactions", () => {
30
+ test("renders cached catalogs immediately on reopen", async () => {
31
+ const persistence = new MemoryPersistence();
32
+ attachPredictionMarketsPersistence(persistence);
33
+
34
+ const cachedPolymarket = normalizePolymarketMarket({
35
+ id: "pm-1",
36
+ question: "Will inflation fall?",
37
+ conditionId: "cond-1",
38
+ outcomes: '["Yes","No"]',
39
+ outcomePrices: '["0.62","0.38"]',
40
+ clobTokenIds: '["yes-1","no-1"]',
41
+ volume24hr: 250000,
42
+ events: [
43
+ {
44
+ id: "event-1",
45
+ title: "US inflation",
46
+ openInterest: 12,
47
+ tags: [{ label: "Macro" }],
48
+ } as any,
49
+ ],
50
+ } as any);
51
+ const cachedKalshi = normalizeKalshiMarket({
52
+ ticker: "KAL-1",
53
+ title: "Will the Fed cut rates?",
54
+ yes_sub_title: "Yes",
55
+ event_ticker: "FED-1",
56
+ status: "open",
57
+ market_type: "binary",
58
+ last_price_dollars: "0.48",
59
+ volume_24h_fp: "15000",
60
+ } as any);
61
+
62
+ persistence.setResource(
63
+ "catalog",
64
+ "polymarket:all:all",
65
+ [cachedPolymarket].filter(Boolean),
66
+ { cachePolicy: PREDICTION_CACHE_POLICIES.catalog, sourceKey: "remote" },
67
+ );
68
+ persistence.setResource(
69
+ "catalog",
70
+ "kalshi:all:all",
71
+ [cachedKalshi].filter(Boolean),
72
+ { cachePolicy: PREDICTION_CACHE_POLICIES.catalog, sourceKey: "remote" },
73
+ );
74
+
75
+ globalThis.fetch = (async () =>
76
+ new Response("{}", { status: 500 })) as unknown as typeof fetch;
77
+
78
+ testSetup = await testRender(<Harness />, { width: 120, height: 34 });
79
+ await flushFrames(testSetup);
80
+
81
+ const frame = testSetup.captureCharFrame();
82
+ expect(frame).toContain("Will inflation fall?");
83
+ expect(frame).toContain("Will the Fed cut rates?");
84
+ });
85
+
86
+ test("renders the merged market browser and supports venue switching", async () => {
87
+ installPredictionMarketMocks();
88
+
89
+ testSetup = await testRender(<Harness />, { width: 120, height: 34 });
90
+ await flushFrames(testSetup);
91
+
92
+ let frame = testSetup.captureCharFrame();
93
+ expect(frame).toContain("Prediction Markets");
94
+ expect(frame).toContain("Will inflation fall?");
95
+ expect(frame).toContain("Kalshi");
96
+
97
+ const lines = frame.split("\n");
98
+ const kalshiRow = lines.findIndex((line) =>
99
+ line.includes("Will the Fed cut rates?"),
100
+ );
101
+ const kalshiCol = lines[kalshiRow]?.indexOf("Will the Fed cut rates?") ?? -1;
102
+
103
+ await act(async () => {
104
+ await testSetup!.mockMouse.click(kalshiCol + 1, kalshiRow);
105
+ await testSetup!.renderOnce();
106
+ });
107
+ await flushFrames(testSetup);
108
+
109
+ expect(
110
+ harnessStateRef.current?.paneState[TEST_PANE_ID]?.pluginState?.[
111
+ "prediction-markets"
112
+ ]?.selectedRowKey,
113
+ ).toBe("kalshi:KAL-1");
114
+
115
+ frame = testSetup.captureCharFrame();
116
+ expect(frame).toContain("Will the Fed cut rates?");
117
+ expect(frame).toContain("Kalshi primary rule");
118
+ });
119
+
120
+ test("moves selection through the list with keyboard navigation", async () => {
121
+ installPredictionMarketMocks();
122
+
123
+ testSetup = await testRender(<Harness />, { width: 120, height: 34 });
124
+ await flushFrames(testSetup);
125
+
126
+ await emitKeypress(testSetup, { name: "j", sequence: "j" });
127
+ await flushFrames(testSetup);
128
+ expect(
129
+ harnessStateRef.current?.paneState[TEST_PANE_ID]?.pluginState?.[
130
+ "prediction-markets"
131
+ ]?.selectedRowKey,
132
+ ).not.toBeNull();
133
+
134
+ await emitKeypress(testSetup, { name: "j", sequence: "j" });
135
+ await flushFrames(testSetup);
136
+
137
+ const frame = testSetup.captureCharFrame();
138
+ expect(frame).toContain("Will the Fed cut rates?");
139
+ });
140
+
141
+ test("does not wrap to the last row when moving up without a selection", async () => {
142
+ installPredictionMarketMocks();
143
+
144
+ testSetup = await testRender(<Harness />, { width: 120, height: 34 });
145
+ await flushFrames(testSetup);
146
+
147
+ await emitKeypress(testSetup, {
148
+ name: "up",
149
+ sequence: "\u001b[A",
150
+ });
151
+ await flushFrames(testSetup);
152
+
153
+ expect(
154
+ harnessStateRef.current?.paneState[TEST_PANE_ID]?.pluginState?.[
155
+ "prediction-markets"
156
+ ]?.selectedRowKey,
157
+ ).toBe("polymarket:pm-1");
158
+ });
159
+
160
+ test("supports detail outcome navigation and escape focus return from the keyboard", async () => {
161
+ attachPredictionMarketsPersistence(new MemoryPersistence());
162
+
163
+ globalThis.fetch = (async (input: Request | string | URL) => {
164
+ const url = String(input);
165
+ if (url.includes("gamma-api.polymarket.com/events?")) {
166
+ return new Response(JSON.stringify([]), { status: 200 });
167
+ }
168
+ if (url.includes("/trade-api/v2/events?")) {
169
+ return new Response(
170
+ JSON.stringify({
171
+ events: [
172
+ {
173
+ title: "Federal funds target rate after April 2026 FOMC",
174
+ sub_title: "Upper bound",
175
+ category: "Economics",
176
+ event_ticker: "FED-1",
177
+ series_ticker: "FED",
178
+ markets: [
179
+ {
180
+ ticker: "KXFED-27APR-T4.25",
181
+ title:
182
+ "Will the upper bound of the federal funds target rate be above 4.25%?",
183
+ yes_sub_title: "Above 4.25%",
184
+ event_ticker: "FED-1",
185
+ status: "open",
186
+ market_type: "binary",
187
+ last_price_dollars: "0.48",
188
+ volume_24h_fp: "15000",
189
+ volume_fp: "90000",
190
+ open_interest_fp: "45000",
191
+ liquidity_dollars: "250000",
192
+ rules_primary: "Rule 1",
193
+ },
194
+ {
195
+ ticker: "KXFED-27APR-T4.50",
196
+ title:
197
+ "Will the upper bound of the federal funds target rate be above 4.50%?",
198
+ yes_sub_title: "Above 4.50%",
199
+ event_ticker: "FED-1",
200
+ status: "open",
201
+ market_type: "binary",
202
+ last_price_dollars: "0.31",
203
+ volume_24h_fp: "12000",
204
+ volume_fp: "70000",
205
+ open_interest_fp: "35000",
206
+ liquidity_dollars: "190000",
207
+ rules_primary: "Rule 2",
208
+ },
209
+ ],
210
+ },
211
+ ],
212
+ }),
213
+ { status: 200 },
214
+ );
215
+ }
216
+ if (url.includes("/trade-api/v2/events/FED-1")) {
217
+ return new Response(
218
+ JSON.stringify({
219
+ event: {
220
+ title: "Federal funds target rate after April 2026 FOMC",
221
+ sub_title: "Upper bound",
222
+ category: "Economics",
223
+ event_ticker: "FED-1",
224
+ series_ticker: "FED",
225
+ },
226
+ markets: [
227
+ {
228
+ ticker: "KXFED-27APR-T4.25",
229
+ title:
230
+ "Will the upper bound of the federal funds target rate be above 4.25%?",
231
+ yes_sub_title: "Above 4.25%",
232
+ event_ticker: "FED-1",
233
+ status: "open",
234
+ market_type: "binary",
235
+ last_price_dollars: "0.48",
236
+ volume_24h_fp: "15000",
237
+ rules_primary: "Rule 1",
238
+ },
239
+ {
240
+ ticker: "KXFED-27APR-T4.50",
241
+ title:
242
+ "Will the upper bound of the federal funds target rate be above 4.50%?",
243
+ yes_sub_title: "Above 4.50%",
244
+ event_ticker: "FED-1",
245
+ status: "open",
246
+ market_type: "binary",
247
+ last_price_dollars: "0.31",
248
+ volume_24h_fp: "12000",
249
+ rules_primary: "Rule 2",
250
+ },
251
+ ],
252
+ }),
253
+ { status: 200 },
254
+ );
255
+ }
256
+ if (url.includes("/trade-api/v2/series/FED/markets/")) {
257
+ return new Response(JSON.stringify({ candlesticks: [] }), {
258
+ status: 200,
259
+ });
260
+ }
261
+ if (url.includes("/trade-api/v2/markets/")) {
262
+ return new Response(
263
+ JSON.stringify({ orderbook_fp: { yes_dollars: [], no_dollars: [] } }),
264
+ { status: 200 },
265
+ );
266
+ }
267
+ if (url.includes("/trade-api/v2/markets/trades?ticker=")) {
268
+ return new Response(JSON.stringify({ trades: [] }), { status: 200 });
269
+ }
270
+ return new Response(JSON.stringify({}), { status: 200 });
271
+ }) as unknown as typeof fetch;
272
+
273
+ testSetup = await testRender(<Harness />, { width: 120, height: 34 });
274
+ await flushFrames(testSetup);
275
+
276
+ await emitKeypress(testSetup, { name: "j", sequence: "j" });
277
+ await flushFrames(testSetup);
278
+ await emitKeypress(testSetup, { name: "enter", sequence: "\r" });
279
+ await flushFrames(testSetup);
280
+ await emitKeypress(testSetup, { name: "down", sequence: "\u001b[B" });
281
+ await flushFrames(testSetup);
282
+
283
+ expect(
284
+ harnessStateRef.current?.paneState[TEST_PANE_ID]?.pluginState?.[
285
+ "prediction-markets"
286
+ ]?.selectedDetailMarketKey,
287
+ ).toBe("kalshi:KXFED-27APR-T4.50");
288
+
289
+ await emitKeypress(testSetup, { name: "esc", sequence: "\u001b" });
290
+ await flushFrames(testSetup);
291
+
292
+ expect(
293
+ harnessStateRef.current?.paneState[TEST_PANE_ID]?.pluginState?.[
294
+ "prediction-markets"
295
+ ]?.focusRegion,
296
+ ).toBe("list");
297
+ });
298
+
299
+ test("starts with no detail pane and toggles the detail pane off on repeat selection", async () => {
300
+ installPredictionMarketMocks();
301
+
302
+ testSetup = await testRender(<Harness />, { width: 120, height: 34 });
303
+ await flushFrames(testSetup);
304
+
305
+ const frame = testSetup.captureCharFrame();
306
+ const lines = frame.split("\n");
307
+ const kalshiRow = lines.findIndex((line) =>
308
+ line.includes("Will the Fed cut rates?"),
309
+ );
310
+ const kalshiCol = lines[kalshiRow]?.indexOf("Will the Fed cut rates?") ?? -1;
311
+
312
+ await act(async () => {
313
+ await testSetup!.mockMouse.click(kalshiCol + 1, kalshiRow);
314
+ await testSetup!.renderOnce();
315
+ });
316
+ await flushFrames(testSetup);
317
+
318
+ await act(async () => {
319
+ await testSetup!.mockMouse.click(kalshiCol + 1, kalshiRow);
320
+ await testSetup!.renderOnce();
321
+ });
322
+ await flushFrames(testSetup);
323
+
324
+ expect(
325
+ harnessStateRef.current?.paneState[TEST_PANE_ID]?.pluginState?.[
326
+ "prediction-markets"
327
+ ]?.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);
371
+ });
372
+
373
+ test("switches categories with arrows and venues with shift+arrows", async () => {
374
+ installPredictionMarketMocks();
375
+
376
+ testSetup = await testRender(<Harness />, { width: 120, height: 34 });
377
+ await flushFrames(testSetup);
378
+
379
+ await emitKeypress(testSetup, {
380
+ name: "right",
381
+ sequence: "\u001b[C",
382
+ });
383
+ await flushFrames(testSetup);
384
+ await emitKeypress(testSetup, {
385
+ name: "right",
386
+ sequence: "\u001b[1;2C",
387
+ shift: true,
388
+ });
389
+ await flushFrames(testSetup);
390
+
391
+ expect(
392
+ harnessStateRef.current?.paneState[TEST_PANE_ID]?.pluginState?.[
393
+ "prediction-markets"
394
+ ]?.categoryId,
395
+ ).toBe("politics");
396
+ expect(
397
+ harnessStateRef.current?.paneState[TEST_PANE_ID]?.pluginState?.[
398
+ "prediction-markets"
399
+ ]?.venueScope,
400
+ ).toBe("polymarket");
401
+ });
402
+
403
+ test("toggles the watch column without triggering a render loop", async () => {
404
+ installPredictionMarketMocks();
405
+
406
+ testSetup = await testRender(<Harness />, { width: 120, height: 34 });
407
+ await flushFrames(testSetup);
408
+
409
+ const frame = testSetup.captureCharFrame();
410
+ const lines = frame.split("\n");
411
+ const firstMarketRow = lines.findIndex((line) =>
412
+ line.includes("Will inflation fall?"),
413
+ );
414
+
415
+ await act(async () => {
416
+ await testSetup!.mockMouse.click(2, firstMarketRow);
417
+ await testSetup!.renderOnce();
418
+ });
419
+ await flushFrames(testSetup);
420
+
421
+ const nextFrame = testSetup.captureCharFrame();
422
+ expect(nextFrame).not.toContain("Maximum update depth exceeded");
423
+ expect(nextFrame).toContain("★");
424
+ });
425
+ });