gloomberb 0.11.0 → 0.11.2

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 (392) hide show
  1. package/README.md +38 -15
  2. package/README.zh-CN.md +12 -12
  3. package/package.json +10 -3
  4. package/src/api-client/auth.ts +14 -10
  5. package/src/api-client/data.ts +54 -0
  6. package/src/api-client/index.test.ts +124 -0
  7. package/src/api-client/index.ts +139 -4
  8. package/src/api-client/paths.ts +89 -0
  9. package/src/api-client/request.ts +15 -2
  10. package/src/api-client/socket-health.test.ts +1 -0
  11. package/src/api-client/socket.ts +2 -1
  12. package/src/api-client/types.ts +163 -0
  13. package/src/app/pane-runtime/index.tsx +4 -1
  14. package/src/app/pane-runtime/plugin-bindings.test.ts +117 -0
  15. package/src/app/pane-runtime/plugin-bindings.ts +25 -4
  16. package/src/app/pane-runtime/ticker-inspector-runtime.ts +25 -14
  17. package/src/app/runtime/desktop-deeplink.test.ts +16 -0
  18. package/src/app/runtime/desktop-deeplink.ts +66 -2
  19. package/src/app/runtime/desktop-menu.ts +3 -0
  20. package/src/app/runtime/plugin-bindings.ts +30 -28
  21. package/src/app/runtime/update.ts +8 -6
  22. package/src/app.tsx +21 -7
  23. package/src/architecture/import-boundaries.test.ts +5 -1
  24. package/src/capabilities/factories.ts +4 -0
  25. package/src/cli/commands/automation.test.ts +9 -0
  26. package/src/cli/commands/automation.ts +22 -73
  27. package/src/cli/index.ts +10 -6
  28. package/src/cli/registry.ts +1 -1
  29. package/src/components/chart/composite/composite-chart.test.tsx +45 -2
  30. package/src/components/chart/composite/composite-chart.tsx +16 -12
  31. package/src/components/chart/composite/format.test.ts +34 -0
  32. package/src/components/chart/composite/format.ts +33 -10
  33. package/src/components/chart/composite/scene.ts +0 -21
  34. package/src/components/chart/composite/tools.ts +49 -0
  35. package/src/components/chart/core/axis-format.ts +13 -3
  36. package/src/components/chart/core/index.test.ts +7 -0
  37. package/src/components/chart/core/renderer.ts +3 -4
  38. package/src/components/command-bar/commands/direct/index.ts +4 -0
  39. package/src/components/command-bar/commands/registry.ts +8 -1
  40. package/src/components/command-bar/layout-items/current-layout.ts +1 -1
  41. package/src/components/command-bar/pane-templates/workflow.ts +4 -0
  42. package/src/components/command-bar/routes/root/shortcut-feedback.ts +3 -0
  43. package/src/components/command-bar/routes/root/shortcuts.test.ts +19 -0
  44. package/src/components/command-bar/routes/root/shortcuts.ts +3 -1
  45. package/src/components/command-bar/surface/index.test.tsx +6 -6
  46. package/src/components/command-bar/surface/index.tsx +4 -1
  47. package/src/components/command-bar/surface/pane.test.tsx +66 -2
  48. package/src/components/command-bar/view-model.test.ts +2 -1
  49. package/src/components/command-bar/workflow/ops.test.ts +54 -0
  50. package/src/components/command-bar/workflow/ops.ts +1 -1
  51. package/src/components/company-logo.test.ts +19 -0
  52. package/src/components/company-logo.tsx +60 -0
  53. package/src/components/feed-data-table/stack-view.tsx +7 -1
  54. package/src/components/index.ts +1 -0
  55. package/src/components/layout/detached-pane-shell.tsx +85 -15
  56. package/src/components/layout/header.tsx +5 -5
  57. package/src/components/layout/pane/title.ts +31 -3
  58. package/src/components/layout/shell/active-drag.ts +4 -11
  59. package/src/components/layout/shell/drag/index.ts +4 -20
  60. package/src/components/layout/shell/drag/runtime.ts +3 -4
  61. package/src/components/layout/shell/index.test.tsx +17 -3
  62. package/src/components/layout/shell/index.tsx +89 -31
  63. package/src/components/layout/shell/menu.test.ts +37 -1
  64. package/src/components/layout/shell/menu.ts +14 -0
  65. package/src/components/layout/shell/native/pointer-runtime.ts +22 -8
  66. package/src/components/layout/shell/pane/actions.ts +0 -5
  67. package/src/components/layout/shell/pane/management-shortcuts.ts +9 -4
  68. package/src/components/layout/shell/shortcuts.ts +6 -3
  69. package/src/components/layout/shell/ticker-link-menu.test.ts +82 -0
  70. package/src/components/layout/shell/ticker-link-menu.ts +78 -0
  71. package/src/components/layout/status-bar.test.tsx +62 -61
  72. package/src/components/layout/status-bar.tsx +53 -75
  73. package/src/components/onboarding/account-step/form-panel.tsx +4 -2
  74. package/src/components/onboarding/account-step/index.tsx +1 -1
  75. package/src/components/onboarding/account-step/signed-in-panel.tsx +1 -1
  76. package/src/components/onboarding/onboarding-wizard.test.tsx +44 -1
  77. package/src/components/onboarding/onboarding-wizard.tsx +13 -5
  78. package/src/components/onboarding/wizard-account.ts +4 -23
  79. package/src/components/table-view-shared.test.ts +11 -0
  80. package/src/components/table-view-shared.tsx +26 -0
  81. package/src/components/ui/data-table/index.tsx +19 -1
  82. package/src/components/ui/fields.tsx +5 -1
  83. package/src/core/state/app/layout-reducer.ts +36 -0
  84. package/src/core/state/app/layout.ts +10 -5
  85. package/src/core/state/app/state.ts +0 -13
  86. package/src/core/state/app/types.ts +6 -4
  87. package/src/data/config/chart-settings.ts +1 -1
  88. package/src/data/config/layout.ts +2 -0
  89. package/src/data/config/store/index.test.ts +27 -6
  90. package/src/data/config/store/normalize.ts +3 -5
  91. package/src/data/plugin-state-store.ts +11 -0
  92. package/src/data/sqlite/cache.test.ts +10 -0
  93. package/src/i18n/es.ts +13 -5
  94. package/src/i18n/ja.ts +13 -5
  95. package/src/i18n/ko.ts +13 -5
  96. package/src/i18n/zh-cn.ts +13 -5
  97. package/src/i18n/zh-tw.ts +13 -5
  98. package/src/layout-marketplace/api.test.ts +54 -0
  99. package/src/layout-marketplace/api.ts +65 -0
  100. package/src/layout-marketplace/gallery-desktop.test.tsx +262 -0
  101. package/src/layout-marketplace/gallery-desktop.tsx +375 -0
  102. package/src/layout-marketplace/gallery-terminal.tsx +376 -0
  103. package/src/layout-marketplace/gallery.test.tsx +84 -0
  104. package/src/layout-marketplace/gallery.tsx +323 -0
  105. package/src/layout-marketplace/geometry.test.ts +47 -0
  106. package/src/layout-marketplace/geometry.ts +80 -0
  107. package/src/layout-marketplace/mini-workspace.test.tsx +122 -0
  108. package/src/layout-marketplace/mini-workspace.tsx +428 -0
  109. package/src/layout-marketplace/model.test.ts +118 -0
  110. package/src/layout-marketplace/model.ts +171 -0
  111. package/src/layout-marketplace/name-dialog.tsx +60 -0
  112. package/src/layout-marketplace/pane-imagery.ts +85 -0
  113. package/src/layout-marketplace/pane.tsx +20 -0
  114. package/src/layout-marketplace/payload.test.ts +246 -0
  115. package/src/layout-marketplace/payload.ts +624 -0
  116. package/src/layout-marketplace/use-marketplace.ts +63 -0
  117. package/src/market-data/coordinator/index.test.ts +2 -2
  118. package/src/market-data/coordinator/index.ts +1 -1
  119. package/src/market-data/hooks.tsx +0 -41
  120. package/src/market-data/market/format.test.ts +7 -0
  121. package/src/market-data/market/freshness.ts +5 -2
  122. package/src/market-data/quotes/freshness.test.ts +16 -0
  123. package/src/news/aggregator.test.ts +92 -3
  124. package/src/news/aggregator.ts +98 -18
  125. package/src/news/hooks.test.tsx +2 -0
  126. package/src/news/hooks.ts +27 -1
  127. package/src/news/news-model.ts +5 -2
  128. package/src/news/types.ts +7 -0
  129. package/src/plugins/broker-sync/broker-sync.test.ts +58 -0
  130. package/src/plugins/broker-sync/normalize.ts +42 -5
  131. package/src/plugins/builtin/account-management/pane.tsx +5 -5
  132. package/src/plugins/builtin/ai/index.tsx +18 -2
  133. package/src/plugins/builtin/ai/screener/pane.test.tsx +47 -4
  134. package/src/plugins/builtin/ai/screener/pane.tsx +35 -12
  135. package/src/plugins/builtin/alerts/alert-engine.test.ts +53 -1
  136. package/src/plugins/builtin/alerts/alert-engine.ts +34 -1
  137. package/src/plugins/builtin/alerts/index.test.tsx +49 -6
  138. package/src/plugins/builtin/alerts/index.tsx +11 -6
  139. package/src/plugins/builtin/alerts/pane.tsx +51 -9
  140. package/src/plugins/builtin/alerts/quotes.ts +11 -3
  141. package/src/plugins/builtin/alerts/types.ts +1 -0
  142. package/src/plugins/builtin/analytics/index.tsx +11 -1
  143. package/src/plugins/builtin/analytics/pane-model.ts +6 -4
  144. package/src/plugins/builtin/broker-manager/index.tsx +3 -0
  145. package/src/plugins/builtin/buildout/model/index.ts +1 -1
  146. package/src/plugins/builtin/buildout/pane/index.tsx +17 -18
  147. package/src/plugins/builtin/cds/client.test.ts +83 -0
  148. package/src/plugins/builtin/cds/client.ts +67 -0
  149. package/src/plugins/builtin/cds/index.tsx +41 -0
  150. package/src/plugins/builtin/cds/model.test.ts +232 -0
  151. package/src/plugins/builtin/cds/model.ts +401 -0
  152. package/src/plugins/builtin/cds/pane.test.tsx +132 -0
  153. package/src/plugins/builtin/cds/pane.tsx +358 -0
  154. package/src/plugins/builtin/chart-composer/chart-spec.ts +31 -0
  155. package/src/plugins/builtin/chart-composer/index.tsx +88 -2
  156. package/src/plugins/builtin/chart-composer/pane-share.test.ts +71 -0
  157. package/src/plugins/builtin/chart-composer/pane.tsx +103 -15
  158. package/src/plugins/builtin/chat/content/index.tsx +4 -1
  159. package/src/plugins/builtin/chat/controller/channels.ts +7 -1
  160. package/src/plugins/builtin/chat/controller/index.ts +14 -9
  161. package/src/plugins/builtin/chat/controller/notifications.ts +13 -3
  162. package/src/plugins/builtin/chat/controller/persistence.ts +0 -3
  163. package/src/plugins/builtin/chat/controller/realtime.ts +3 -3
  164. package/src/plugins/builtin/chat/controller/send.ts +1 -3
  165. package/src/plugins/builtin/chat/controller/session-runtime.ts +18 -11
  166. package/src/plugins/builtin/chat/controller/state.ts +1 -3
  167. package/src/plugins/builtin/chat/controller/storage.ts +0 -2
  168. package/src/plugins/builtin/chat/controller/utils.ts +4 -6
  169. package/src/plugins/builtin/chat/controller/view.ts +2 -2
  170. package/src/plugins/builtin/chat/controller.test.ts +142 -14
  171. package/src/plugins/builtin/chat/message/desktop.test.tsx +88 -0
  172. package/src/plugins/builtin/chat/message/desktop.tsx +3 -1
  173. package/src/plugins/builtin/chat/message/profile-popover.test.tsx +1 -29
  174. package/src/plugins/builtin/chat/profile-popover.ts +53 -22
  175. package/src/plugins/builtin/chat/sidebar.test.tsx +81 -0
  176. package/src/plugins/builtin/chat/sidebar.tsx +63 -0
  177. package/src/plugins/builtin/chat/test-harness.tsx +29 -1
  178. package/src/plugins/builtin/cloud/auth-actions.tsx +14 -7
  179. package/src/plugins/builtin/cloud/auth-commands.ts +21 -51
  180. package/src/plugins/builtin/cloud/auth-dialog.tsx +296 -0
  181. package/src/{components/onboarding/account-step/model.test.ts → plugins/builtin/cloud/auth-model.test.ts} +1 -1
  182. package/src/{components/onboarding/account-step/model.ts → plugins/builtin/cloud/auth-model.ts} +37 -3
  183. package/src/plugins/builtin/cloud/browser.tsx +9 -0
  184. package/src/plugins/builtin/cloud/buildout-module.tsx +23 -0
  185. package/src/plugins/builtin/cloud/device-signin.ts +2 -8
  186. package/src/plugins/builtin/cloud/index.tsx +2 -0
  187. package/src/plugins/builtin/cloud/plugin.tsx +14 -24
  188. package/src/plugins/builtin/cloud/upgrade-command.ts +13 -6
  189. package/src/plugins/builtin/cloud-tweets/pane-share.test.ts +56 -0
  190. package/src/plugins/builtin/cloud-tweets/pane.tsx +20 -5
  191. package/src/plugins/builtin/cloud-tweets/registration.ts +50 -6
  192. package/src/plugins/builtin/composite-plugins.ts +3 -1
  193. package/src/plugins/builtin/congress-trades/detail.tsx +43 -1
  194. package/src/plugins/builtin/congress-trades/model.test.ts +85 -0
  195. package/src/plugins/builtin/congress-trades/model.ts +69 -1
  196. package/src/plugins/builtin/congress-trades/pane.tsx +51 -2
  197. package/src/plugins/builtin/connections/index.ts +3 -0
  198. package/src/plugins/builtin/dividend-yield/client.ts +18 -0
  199. package/src/plugins/builtin/dividend-yield/pane.tsx +7 -6
  200. package/src/plugins/builtin/earnings/index.tsx +5 -3
  201. package/src/plugins/builtin/earnings/model.test.ts +19 -1
  202. package/src/plugins/builtin/earnings/model.ts +17 -15
  203. package/src/plugins/builtin/econ/calendar-model.ts +0 -5
  204. package/src/plugins/builtin/econ/index.test.tsx +6 -1
  205. package/src/plugins/builtin/fear-greed/charts.tsx +12 -8
  206. package/src/plugins/builtin/help/index.tsx +2 -2
  207. package/src/plugins/builtin/holders/thirteenf-match.ts +1 -2
  208. package/src/plugins/builtin/insider/index.tsx +27 -11
  209. package/src/plugins/builtin/kelly-sizer/index.tsx +4 -1
  210. package/src/plugins/builtin/layout-manager/index.tsx +17 -4
  211. package/src/plugins/builtin/news/index.tsx +11 -6
  212. package/src/plugins/builtin/news/wire/breaking/notifications.test.ts +2 -0
  213. package/src/plugins/builtin/news/wire/breaking/pane.test.tsx +2 -0
  214. package/src/plugins/builtin/news/wire/breaking/pane.tsx +4 -1
  215. package/src/plugins/builtin/news/wire/feed-config.test.ts +15 -1
  216. package/src/plugins/builtin/news/wire/feed-config.ts +25 -5
  217. package/src/plugins/builtin/news/wire/index.tsx +3 -3
  218. package/src/plugins/builtin/news/wire/industry-pane.tsx +7 -2
  219. package/src/plugins/builtin/news/wire/news/footer.ts +34 -1
  220. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +4 -1
  221. package/src/plugins/builtin/news/wire/news/table.tsx +8 -2
  222. package/src/plugins/builtin/news/wire/rss/source.test.ts +12 -0
  223. package/src/plugins/builtin/news/wire/rss/source.ts +1 -1
  224. package/src/plugins/builtin/notes/files.ts +80 -0
  225. package/src/plugins/builtin/notes/index.tsx +3 -0
  226. package/src/plugins/builtin/notes/sync.test.ts +49 -0
  227. package/src/plugins/builtin/notes/sync.ts +98 -0
  228. package/src/plugins/builtin/options/index.tsx +1 -0
  229. package/src/plugins/builtin/options/view.test.tsx +25 -3
  230. package/src/plugins/builtin/options/view.tsx +1 -0
  231. package/src/plugins/builtin/portfolio-list/account-metrics.test.ts +22 -0
  232. package/src/plugins/builtin/portfolio-list/account-metrics.ts +12 -7
  233. package/src/plugins/builtin/portfolio-list/cli/render.ts +5 -3
  234. package/src/plugins/builtin/portfolio-list/column-values.ts +15 -4
  235. package/src/plugins/builtin/portfolio-list/header.tsx +13 -3
  236. package/src/plugins/builtin/portfolio-list/index.tsx +8 -0
  237. package/src/plugins/builtin/portfolio-list/metrics.test.ts +31 -1
  238. package/src/plugins/builtin/portfolio-list/pane/index.test.tsx +1 -0
  239. package/src/plugins/builtin/portfolio-list/pane/index.tsx +10 -7
  240. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +6 -1
  241. package/src/plugins/builtin/portfolio-list/position-metrics.ts +25 -6
  242. package/src/plugins/builtin/portfolio-list/summary/index.tsx +35 -21
  243. package/src/plugins/builtin/portfolio-list/summary/totals.ts +20 -6
  244. package/src/plugins/builtin/research/corporate-actions-pane.tsx +5 -12
  245. package/src/plugins/builtin/research/equity-diagnostic-pane.test.tsx +329 -0
  246. package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +582 -0
  247. package/src/plugins/builtin/research/index.test.ts +14 -0
  248. package/src/plugins/builtin/research/index.tsx +29 -0
  249. package/src/plugins/builtin/sec/index.tsx +24 -9
  250. package/src/plugins/builtin/sectors/index.tsx +14 -0
  251. package/src/plugins/builtin/shared/cloud-upgrade.ts +18 -11
  252. package/src/plugins/builtin/shared/public-pane.test.ts +12 -0
  253. package/src/plugins/builtin/shared/public-pane.ts +9 -0
  254. package/src/plugins/builtin/shared/public-share.ts +23 -0
  255. package/src/plugins/builtin/shared/ticker-source.ts +32 -0
  256. package/src/plugins/builtin/shared/ticker-surface.test.ts +27 -0
  257. package/src/plugins/builtin/shared/ticker-surface.ts +17 -0
  258. package/src/plugins/builtin/substack/api/cache.ts +7 -3
  259. package/src/plugins/builtin/substack/api/loaders.ts +36 -12
  260. package/src/plugins/builtin/substack/api/types.ts +2 -0
  261. package/src/plugins/builtin/substack/pane-state.ts +1 -1
  262. package/src/plugins/builtin/substack/pane.tsx +55 -12
  263. package/src/plugins/builtin/thirteenf/api.test.ts +1 -1
  264. package/src/plugins/builtin/thirteenf/api.ts +40 -18
  265. package/src/plugins/builtin/thirteenf/data.ts +15 -7
  266. package/src/plugins/builtin/thirteenf/pane.tsx +45 -10
  267. package/src/plugins/builtin/ticker-detail/index.tsx +10 -0
  268. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +12 -4
  269. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +8 -2
  270. package/src/plugins/builtin/ticker-detail/settings.ts +12 -0
  271. package/src/plugins/builtin/treasury-auctions/index.tsx +2 -0
  272. package/src/plugins/builtin/world-indices/index.tsx +2 -0
  273. package/src/plugins/catalog-browser.test.ts +78 -0
  274. package/src/plugins/catalog-browser.ts +136 -0
  275. package/src/plugins/gridlock-notification.ts +2 -1
  276. package/src/plugins/ibkr/flex/index.test.ts +19 -0
  277. package/src/plugins/ibkr/flex/index.ts +1 -1
  278. package/src/plugins/pane-manager/gridlock.ts +87 -3
  279. package/src/plugins/pane-manager.test.ts +30 -0
  280. package/src/plugins/pane-manager.ts +6 -1
  281. package/src/plugins/prediction-markets/chart.tsx +2 -2
  282. package/src/plugins/prediction-markets/controller/catalog.ts +77 -2
  283. package/src/plugins/prediction-markets/controller/data.ts +7 -1
  284. package/src/plugins/prediction-markets/controller/index.ts +9 -2
  285. package/src/plugins/prediction-markets/controller/keyboard.ts +10 -14
  286. package/src/plugins/prediction-markets/detail/overview.tsx +21 -0
  287. package/src/plugins/prediction-markets/detail/pane.tsx +43 -34
  288. package/src/plugins/prediction-markets/detail/trades.tsx +34 -9
  289. package/src/plugins/prediction-markets/detail.test.tsx +29 -2
  290. package/src/plugins/prediction-markets/index.tsx +8 -4
  291. package/src/plugins/prediction-markets/navigation.ts +0 -1
  292. package/src/plugins/prediction-markets/pane.test.tsx +77 -0
  293. package/src/plugins/prediction-markets/pane.tsx +12 -0
  294. package/src/plugins/prediction-markets/plugin.test.tsx +36 -0
  295. package/src/plugins/prediction-markets/services/kalshi/adapter.ts +49 -9
  296. package/src/plugins/prediction-markets/services/polymarket/adapter.ts +31 -0
  297. package/src/plugins/prediction-markets/test-helpers.tsx +9 -0
  298. package/src/plugins/registry/index.ts +2 -0
  299. package/src/plugins/ticker-navigation.test.ts +24 -0
  300. package/src/plugins/ticker-navigation.ts +40 -0
  301. package/src/remote/schema.ts +1 -1
  302. package/src/renderers/browser/app-services.ts +61 -0
  303. package/src/renderers/browser/cloud-transport.test.ts +68 -0
  304. package/src/renderers/browser/cloud-transport.ts +48 -0
  305. package/src/renderers/browser/config-host.ts +58 -0
  306. package/src/renderers/browser/deeplink-bridge.test.ts +41 -0
  307. package/src/renderers/browser/deeplink-bridge.ts +22 -0
  308. package/src/renderers/browser/error-boundary.tsx +23 -0
  309. package/src/renderers/browser/main.tsx +64 -0
  310. package/src/renderers/browser/persistence.ts +74 -0
  311. package/src/renderers/browser/storage.test.ts +66 -0
  312. package/src/renderers/browser/storage.ts +50 -0
  313. package/src/renderers/browser/ticker-repository.ts +59 -0
  314. package/src/renderers/browser/ui-host.tsx +40 -0
  315. package/src/renderers/cloudflare/worker.test.ts +136 -0
  316. package/src/renderers/cloudflare/worker.ts +116 -0
  317. package/src/renderers/electrobun/bun/application-menu/index.ts +3 -2
  318. package/src/renderers/electrobun/bun/desktop/initialization.ts +1 -0
  319. package/src/renderers/electrobun/bun/desktop/plugin-state.ts +7 -1
  320. package/src/renderers/electrobun/view/app-services.ts +14 -3
  321. package/src/renderers/electrobun/view/build-assets.ts +12 -10
  322. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -1
  323. package/src/renderers/electrobun/view/data-table/index.tsx +10 -1
  324. package/src/renderers/electrobun/view/desktop/controls.tsx +2 -0
  325. package/src/renderers/electrobun/view/dom-ui-host.tsx +143 -0
  326. package/src/renderers/electrobun/view/host/box.test.tsx +33 -0
  327. package/src/renderers/electrobun/view/host/box.tsx +11 -6
  328. package/src/renderers/electrobun/view/host/chart-surface.tsx +7 -1
  329. package/src/renderers/electrobun/view/host/input.tsx +11 -7
  330. package/src/renderers/electrobun/view/host/native.ts +0 -5
  331. package/src/renderers/electrobun/view/main.tsx +2 -0
  332. package/src/renderers/electrobun/view/remote/persistence.ts +5 -0
  333. package/src/renderers/electrobun/view/styles.css +107 -7
  334. package/src/renderers/electrobun/view/toast-host.test.tsx +78 -0
  335. package/src/renderers/electrobun/view/toast-host.tsx +69 -43
  336. package/src/renderers/electrobun/view/ui-host.tsx +6 -137
  337. package/src/renderers/opentui/start.tsx +2 -0
  338. package/src/renderers/share/layout-view.test.tsx +58 -0
  339. package/src/renderers/share/layout-view.tsx +133 -0
  340. package/src/renderers/share/main.tsx +102 -0
  341. package/src/renderers/share/styles.css +54 -0
  342. package/src/renderers/share/view.test.tsx +100 -0
  343. package/src/renderers/share/view.tsx +150 -0
  344. package/src/shares/api.test.ts +105 -0
  345. package/src/shares/api.ts +127 -0
  346. package/src/shares/chart-snapshot.test.ts +34 -0
  347. package/src/shares/chart-snapshot.ts +28 -0
  348. package/src/shares/location.test.ts +19 -0
  349. package/src/shares/location.ts +11 -0
  350. package/src/shares/pane.test.ts +110 -0
  351. package/src/shares/pane.ts +54 -0
  352. package/src/shares/payload.ts +177 -0
  353. package/src/sources/gloomberb-cloud/index.test.ts +61 -1
  354. package/src/sources/gloomberb-cloud/index.ts +86 -14
  355. package/src/sources/gloomberb-cloud/normalizers.test.ts +34 -0
  356. package/src/sources/gloomberb-cloud/normalizers.ts +5 -5
  357. package/src/sources/provider-router/history.test.ts +2 -2
  358. package/src/sources/provider-router/news.test.ts +34 -0
  359. package/src/sources/provider-router/news.ts +15 -1
  360. package/src/sources/sec-edgar.test.ts +59 -0
  361. package/src/sources/sec-edgar.ts +63 -23
  362. package/src/sources/yahoo-finance/mappers.ts +3 -0
  363. package/src/sources/yahoo-finance/snapshots.ts +10 -2
  364. package/src/sources/yahoo-finance/symbols.test.ts +10 -0
  365. package/src/sources/yahoo-finance/symbols.ts +13 -8
  366. package/src/sources/yahoo-finance.test.ts +1 -1
  367. package/src/sources/yahoo-finance.ts +6 -2
  368. package/src/state/app/bootstrap.ts +6 -7
  369. package/src/state/app/context/index.test.ts +41 -1
  370. package/src/state/app/context/index.test.tsx +34 -16
  371. package/src/state/selectors-ui.ts +0 -8
  372. package/src/sync/baseline.ts +32 -0
  373. package/src/sync/controller.test.ts +124 -1
  374. package/src/sync/controller.ts +54 -12
  375. package/src/sync/core-contributors.test.ts +44 -0
  376. package/src/sync/core-contributors.ts +59 -10
  377. package/src/sync/react.ts +22 -2
  378. package/src/sync/types.ts +12 -0
  379. package/src/theme/font-scale.ts +0 -11
  380. package/src/tickers/search/index.test.ts +27 -0
  381. package/src/tickers/search/upsert.ts +10 -0
  382. package/src/types/capability-route-source.ts +2 -1
  383. package/src/types/config.ts +35 -5
  384. package/src/types/desktop-menu.ts +1 -0
  385. package/src/types/plugin.ts +36 -0
  386. package/src/ui/context-menu.tsx +1 -1
  387. package/src/ui/host.tsx +4 -0
  388. package/src/ui/toast.tsx +2 -0
  389. package/src/utils/back-navigation.ts +0 -4
  390. package/src/utils/price-history.ts +11 -10
  391. package/src/utils/sec.test.ts +6 -0
  392. package/src/utils/sec.ts +1 -1
@@ -2,10 +2,12 @@ import { describe, expect, test } from "bun:test";
2
2
  import { cloneLayout, createDefaultConfig, createPaneInstance, type LayoutConfig } from "../types/config";
3
3
  import {
4
4
  addPaneFloating,
5
+ analyzeFloatingPaneVisibility,
5
6
  applyDrop,
6
7
  floatAtRect,
7
8
  getDockResizeTargets,
8
9
  gridlockAllPanes,
10
+ shouldShowTidyWindows,
9
11
  getDockDividerLayouts,
10
12
  getDockLeafLayouts,
11
13
  getDockedPaneIds,
@@ -122,6 +124,34 @@ describe("pane-manager split-tree drops", () => {
122
124
  expect(getDockedPaneIds(next)).toHaveLength(layout.instances.length);
123
125
  });
124
126
 
127
+ test("detects a floating pane whose title bar is buried", () => {
128
+ const layout: LayoutConfig = {
129
+ dockRoot: null,
130
+ instances: [
131
+ createPaneInstance("chat", { instanceId: "lower" }),
132
+ createPaneInstance("chat", { instanceId: "upper" }),
133
+ ],
134
+ floating: [
135
+ { instanceId: "lower", x: 0, y: 0, width: 40, height: 20, zIndex: 10 },
136
+ { instanceId: "upper", x: 0, y: 0, width: 40, height: 4, zIndex: 20 },
137
+ ],
138
+ detached: [],
139
+ };
140
+
141
+ const visibility = analyzeFloatingPaneVisibility(layout);
142
+ const lower = visibility.find((pane) => pane.instanceId === "lower");
143
+ expect(lower?.visibleRatio).toBeCloseTo(0.8);
144
+ expect(lower?.titleBarVisibleRatio).toBe(0);
145
+ expect(lower?.buried).toBe(true);
146
+ expect(shouldShowTidyWindows(layout)).toBe(true);
147
+
148
+ const separated = {
149
+ ...layout,
150
+ floating: [layout.floating[0]!, { ...layout.floating[1]!, x: 50 }],
151
+ };
152
+ expect(shouldShowTidyWindows(separated)).toBe(false);
153
+ });
154
+
125
155
  test("gridlock drops pane types that cannot render before tiling", () => {
126
156
  const layout: LayoutConfig = {
127
157
  dockRoot: {
@@ -55,7 +55,12 @@ export {
55
55
  removeUnavailablePaneTypes,
56
56
  type PaneTypeAvailability,
57
57
  } from "./pane-manager/layout-state";
58
- export { gridlockAllPanes } from "./pane-manager/gridlock";
58
+ export {
59
+ analyzeFloatingPaneVisibility,
60
+ gridlockAllPanes,
61
+ shouldShowTidyWindows,
62
+ type FloatingPaneVisibility,
63
+ } from "./pane-manager/gridlock";
59
64
  export type {
60
65
  DropTarget,
61
66
  FloatingResizeCorner,
@@ -146,8 +146,8 @@ export function PredictionMarketChart({
146
146
  <CompositeChart
147
147
  width={width}
148
148
  height={chartHeight}
149
- focused={false}
150
- interactive={false}
149
+ focused={focused}
150
+ interactive
151
151
  series={[priceSeries]}
152
152
  panels={[{ id: "price" }]}
153
153
  axisWidth={8}
@@ -13,8 +13,8 @@ import {
13
13
  } from "./status";
14
14
  import { useAutoRefresh } from "../../builtin/shared/auto-refresh";
15
15
  import { getCachedPredictionResource } from "../services/fetch";
16
- import { loadKalshiCatalog } from "../services/kalshi/adapter";
17
- import { loadPolymarketCatalog } from "../services/polymarket/adapter";
16
+ import { kalshiCatalogCursor, loadKalshiCatalog, loadMoreKalshiCatalog } from "../services/kalshi/adapter";
17
+ import { loadMorePolymarketCatalog, loadPolymarketCatalog, nextPolymarketCatalogOffset } from "../services/polymarket/adapter";
18
18
  import type {
19
19
  PredictionCategoryId,
20
20
  PredictionMarketSummary,
@@ -46,6 +46,9 @@ export function usePredictionCatalogData({
46
46
  const [debouncedSearchQuery, setDebouncedSearchQuery] = useState(searchQuery);
47
47
  const [polymarketLoadedAt, setPolymarketLoadedAt] = useState<number | null>(null);
48
48
  const [kalshiLoadedAt, setKalshiLoadedAt] = useState<number | null>(null);
49
+ const [polymarketNextOffset, setPolymarketNextOffset] = useState<number | null>(null);
50
+ const [kalshiNextCursor, setKalshiNextCursor] = useState<string | null>(null);
51
+ const [loadingMore, setLoadingMore] = useState(false);
49
52
  const activeCatalogRef = useRef<PredictionCatalogCache>({});
50
53
 
51
54
  const normalizedCatalogQuery = debouncedSearchQuery.trim().toLowerCase();
@@ -192,6 +195,7 @@ export function usePredictionCatalogData({
192
195
  setCatalogErrors((current) =>
193
196
  updatePredictionErrorState(current, cacheKey, null),
194
197
  );
198
+ setPolymarketNextOffset(nextPolymarketCatalogOffset(category, search));
195
199
  } catch (error) {
196
200
  setCatalogErrors((current) =>
197
201
  updatePredictionErrorState(
@@ -243,6 +247,7 @@ export function usePredictionCatalogData({
243
247
  setCatalogErrors((current) =>
244
248
  updatePredictionErrorState(current, cacheKey, null),
245
249
  );
250
+ setKalshiNextCursor(kalshiCatalogCursor(search, category));
246
251
  } catch (error) {
247
252
  setCatalogErrors((current) =>
248
253
  updatePredictionErrorState(
@@ -310,11 +315,81 @@ export function usePredictionCatalogData({
310
315
  void loadKalshi(kalshiCatalogKey, debouncedSearchQuery, categoryId);
311
316
  }, [categoryId, debouncedSearchQuery, kalshiCatalogKey, loadKalshi]));
312
317
 
318
+ const loadMoreCatalog = useCallback(async () => {
319
+ if (loadingMore) return;
320
+ const canLoadPolymarket = includePolymarket && polymarketNextOffset != null;
321
+ const canLoadKalshi = includeKalshi && !!kalshiNextCursor;
322
+ if (!canLoadPolymarket && !canLoadKalshi) return;
323
+ setLoadingMore(true);
324
+ try {
325
+ if (canLoadPolymarket && polymarketNextOffset != null) {
326
+ const page = await loadMorePolymarketCatalog(
327
+ debouncedSearchQuery,
328
+ categoryId,
329
+ polymarketNextOffset,
330
+ );
331
+ setCatalogCache((current) => ({
332
+ ...current,
333
+ [polymarketCatalogKey]: mergeCatalogMarkets(
334
+ current[polymarketCatalogKey] ?? activeCatalogRef.current[polymarketCatalogKey] ?? [],
335
+ page.markets,
336
+ ),
337
+ }));
338
+ setPolymarketNextOffset(page.hasMore ? page.nextOffset : null);
339
+ }
340
+ if (canLoadKalshi && kalshiNextCursor) {
341
+ const page = await loadMoreKalshiCatalog(
342
+ debouncedSearchQuery,
343
+ categoryId,
344
+ kalshiNextCursor,
345
+ );
346
+ setCatalogCache((current) => ({
347
+ ...current,
348
+ [kalshiCatalogKey]: mergeCatalogMarkets(
349
+ current[kalshiCatalogKey] ?? activeCatalogRef.current[kalshiCatalogKey] ?? [],
350
+ page.markets,
351
+ ),
352
+ }));
353
+ setKalshiNextCursor(page.nextCursor);
354
+ }
355
+ } finally {
356
+ setLoadingMore(false);
357
+ }
358
+ }, [
359
+ categoryId,
360
+ debouncedSearchQuery,
361
+ includeKalshi,
362
+ includePolymarket,
363
+ kalshiCatalogKey,
364
+ kalshiNextCursor,
365
+ loadingMore,
366
+ polymarketCatalogKey,
367
+ polymarketNextOffset,
368
+ ]);
369
+
313
370
  return {
314
371
  allMarkets,
372
+ catalogHasMore: (includePolymarket && polymarketNextOffset != null) || (includeKalshi && !!kalshiNextCursor),
315
373
  catalogLoadCount,
374
+ catalogLoadingMore: loadingMore,
316
375
  catalogStatus,
317
376
  debouncedSearchQuery,
377
+ loadMoreCatalog,
318
378
  setCatalogCache,
319
379
  };
320
380
  }
381
+
382
+ function mergeCatalogMarkets(
383
+ current: PredictionMarketSummary[],
384
+ extra: PredictionMarketSummary[],
385
+ ): PredictionMarketSummary[] {
386
+ if (extra.length === 0) return current;
387
+ const seen = new Set(current.map((market) => market.key));
388
+ const merged = [...current];
389
+ for (const market of extra) {
390
+ if (seen.has(market.key)) continue;
391
+ seen.add(market.key);
392
+ merged.push(market);
393
+ }
394
+ return merged;
395
+ }
@@ -53,9 +53,12 @@ export function usePredictionMarketsDataState({
53
53
  }) {
54
54
  const {
55
55
  allMarkets,
56
+ catalogHasMore,
56
57
  catalogLoadCount,
58
+ catalogLoadingMore,
57
59
  catalogStatus,
58
60
  debouncedSearchQuery,
61
+ loadMoreCatalog,
59
62
  setCatalogCache,
60
63
  } = usePredictionCatalogData({
61
64
  categoryId,
@@ -150,13 +153,16 @@ export function usePredictionMarketsDataState({
150
153
  });
151
154
 
152
155
  return {
153
- catalogStatus,
156
+ catalogHasMore,
154
157
  catalogLoadCount,
158
+ catalogLoadingMore,
159
+ catalogStatus,
155
160
  debouncedSearchQuery,
156
161
  detail,
157
162
  detailError,
158
163
  detailLoadCount,
159
164
  lastRefreshAt,
165
+ loadMoreCatalog,
160
166
  selectedIndex,
161
167
  selectedRow,
162
168
  selectedSummary,
@@ -56,10 +56,14 @@ export function usePredictionMarketsController({
56
56
  "browseTab",
57
57
  paneSettings.defaultBrowseTab,
58
58
  );
59
- const [detailTab, setDetailTab] = usePluginPaneState<PredictionDetailTab>(
59
+ const [storedDetailTab, setDetailTab] = usePluginPaneState<PredictionDetailTab>(
60
60
  "detailTab",
61
61
  "overview",
62
62
  );
63
+ const detailTab = storedDetailTab === "chart" ? "overview" : storedDetailTab;
64
+ useEffect(() => {
65
+ if (storedDetailTab === "chart") setDetailTab("overview");
66
+ }, [setDetailTab, storedDetailTab]);
63
67
  const [searchQuery, setSearchQuery] = usePluginPaneState<string>(
64
68
  "searchQuery",
65
69
  "",
@@ -316,8 +320,10 @@ export function usePredictionMarketsController({
316
320
  paneSettings,
317
321
  browseTab,
318
322
  categoryId,
319
- catalogStatus: data.catalogStatus,
323
+ catalogHasMore: data.catalogHasMore,
320
324
  catalogLoadCount: data.catalogLoadCount,
325
+ catalogLoadingMore: data.catalogLoadingMore,
326
+ catalogStatus: data.catalogStatus,
321
327
  detail: data.detail,
322
328
  detailError: data.detailError,
323
329
  detailLoadCount: data.detailLoadCount,
@@ -344,6 +350,7 @@ export function usePredictionMarketsController({
344
350
  actions: {
345
351
  blurSearch,
346
352
  closeDetail,
353
+ loadMoreCatalog: data.loadMoreCatalog,
347
354
  focusSearch,
348
355
  handleSortHeaderClick,
349
356
  openSelectedRow,
@@ -134,27 +134,23 @@ export function usePredictionControllerKeyboard({
134
134
  }
135
135
 
136
136
  if (detailOpen) {
137
- if (command === "move-down") {
138
- event.stopPropagation?.();
139
- event.preventDefault?.();
140
- if (detailTab === "overview" && sortedOutcomeMarkets.length > 0) {
141
- cycleDetailOutcome("next");
142
- } else {
143
- scrollDetailBy(1);
137
+ if (detailTab === "overview" && sortedOutcomeMarkets.length > 0) {
138
+ if (command === "move-down" || command === "move-up") {
139
+ event.stopPropagation?.();
140
+ event.preventDefault?.();
141
+ cycleDetailOutcome(command === "move-down" ? "next" : "previous");
144
142
  }
145
143
  return;
146
144
  }
147
145
 
148
- if (command === "move-up") {
146
+ if (
147
+ (detailTab === "overview" || detailTab === "rules")
148
+ && (command === "move-down" || command === "move-up")
149
+ ) {
149
150
  event.stopPropagation?.();
150
151
  event.preventDefault?.();
151
- if (detailTab === "overview" && sortedOutcomeMarkets.length > 0) {
152
- cycleDetailOutcome("previous");
153
- } else {
154
- scrollDetailBy(-1);
155
- }
152
+ scrollDetailBy(command === "move-down" ? 1 : -1);
156
153
  }
157
-
158
154
  return;
159
155
  }
160
156
 
@@ -2,7 +2,9 @@ import { Box, Text } from "../../../ui";
2
2
  import { TextAttributes } from "../../../ui";
3
3
  import { colors } from "../../../theme/colors";
4
4
  import { formatPercentRaw } from "../../../utils/format";
5
+ import { PredictionMarketChart } from "../chart";
5
6
  import type {
7
+ PredictionHistoryRange,
6
8
  PredictionListRow,
7
9
  PredictionMarketDetail,
8
10
  PredictionMarketSummary,
@@ -13,12 +15,22 @@ import { SummaryLink } from "./shared";
13
15
  export function PredictionMarketOverviewView({
14
16
  detail,
15
17
  detailWidth,
18
+ focused,
19
+ height,
20
+ historyRange,
21
+ loading,
22
+ onHistoryRangeChange,
16
23
  onSelectMarket,
17
24
  selectedRow,
18
25
  summary,
19
26
  }: {
20
27
  detail: PredictionMarketDetail | null;
21
28
  detailWidth: number;
29
+ focused: boolean;
30
+ height: number;
31
+ historyRange: PredictionHistoryRange;
32
+ loading: boolean;
33
+ onHistoryRangeChange: (range: PredictionHistoryRange) => void;
22
34
  onSelectMarket: (marketKey: string) => void;
23
35
  selectedRow: PredictionListRow | null;
24
36
  summary: PredictionMarketSummary;
@@ -35,6 +47,15 @@ export function PredictionMarketOverviewView({
35
47
  selectedRow={selectedRow}
36
48
  />
37
49
  )}
50
+ <PredictionMarketChart
51
+ history={detail?.history ?? []}
52
+ width={detailWidth}
53
+ height={Math.max(Math.floor(height * 0.36), 10)}
54
+ loading={loading}
55
+ focused={focused}
56
+ range={historyRange}
57
+ onRangeSelect={onHistoryRangeChange}
58
+ />
38
59
  <SummaryLink
39
60
  url={summary.url}
40
61
  maxLength={Math.max(detailWidth - 8, 12)}
@@ -25,7 +25,6 @@ import { PredictionMarketOverviewView } from "./overview";
25
25
  import { PredictionMarketRulesView } from "./rules";
26
26
  import { truncatePredictionText } from "./shared";
27
27
  import { PredictionMarketTradesView } from "./trades";
28
- import { PredictionMarketChart } from "../chart";
29
28
 
30
29
  type RelatedMarketPointerEvent = { preventDefault(): void };
31
30
 
@@ -210,7 +209,14 @@ export function PredictionMarketDetailPane({
210
209
  const detailTextWidth = Math.max(detailWidth, 12);
211
210
 
212
211
  return (
213
- <>
212
+ <Box
213
+ flexDirection="column"
214
+ flexGrow={1}
215
+ flexShrink={1}
216
+ flexBasis={0}
217
+ minHeight={0}
218
+ overflow="hidden"
219
+ >
214
220
  {headerHeight > 0 && (
215
221
  <Box flexDirection="column" height={headerHeight} paddingBottom={1}>
216
222
  <Box flexDirection="row" height={1}>
@@ -274,11 +280,22 @@ export function PredictionMarketDetailPane({
274
280
  )}
275
281
 
276
282
  {detailTab === "overview" || detailTab === "rules" ? (
277
- <ScrollBox ref={scrollRef} flexGrow={1} scrollY>
283
+ <ScrollBox
284
+ ref={scrollRef}
285
+ flexGrow={1}
286
+ flexShrink={1}
287
+ flexBasis={0}
288
+ scrollY
289
+ >
278
290
  {detailTab === "overview" && (
279
291
  <PredictionMarketOverviewView
280
292
  detail={detail}
281
293
  detailWidth={detailWidth}
294
+ focused={focused}
295
+ height={height}
296
+ historyRange={historyRange}
297
+ loading={detailLoading}
298
+ onHistoryRangeChange={onHistoryRangeChange}
282
299
  onSelectMarket={onSelectMarket}
283
300
  selectedRow={selectedRow}
284
301
  summary={summaryMetrics}
@@ -294,42 +311,34 @@ export function PredictionMarketDetailPane({
294
311
  </ScrollBox>
295
312
  ) : null}
296
313
 
297
- {detailTab === "chart" && (
298
- <PredictionMarketChart
299
- history={detail?.history ?? []}
300
- width={detailWidth}
301
- height={Math.max(height - 8, 8)}
302
- loading={detailLoading}
303
- focused={focused}
304
- range={historyRange}
305
- onRangeSelect={onHistoryRangeChange}
306
- />
307
- )}
308
-
309
314
  {detailTab === "book" && (
310
- detail ? (
311
- <PredictionMarketBookView
312
- detail={detail}
313
- focused={focused}
314
- width={detailWidth}
315
- />
316
- ) : (
317
- <Box flexGrow={1} justifyContent="center">
318
- <EmptyState
319
- title={detailLoading ? "Loading order book." : "No order book."}
320
- hint="This venue did not return current order book depth."
315
+ <Box flexGrow={1} flexShrink={1} flexBasis={0} overflow="hidden">
316
+ {detail ? (
317
+ <PredictionMarketBookView
318
+ detail={detail}
319
+ focused={focused}
320
+ width={detailWidth}
321
321
  />
322
- </Box>
323
- )
322
+ ) : (
323
+ <Box flexGrow={1} justifyContent="center">
324
+ <EmptyState
325
+ title={detailLoading ? "Loading order book." : "No order book."}
326
+ hint="This venue did not return current order book depth."
327
+ />
328
+ </Box>
329
+ )}
330
+ </Box>
324
331
  )}
325
332
 
326
333
  {detailTab === "trades" && (
327
- <PredictionMarketTradesView
328
- focused={focused}
329
- trades={detail?.trades ?? []}
330
- width={detailWidth}
331
- />
334
+ <Box flexGrow={1} flexShrink={1} flexBasis={0} overflow="hidden">
335
+ <PredictionMarketTradesView
336
+ focused={focused}
337
+ trades={detail?.trades ?? []}
338
+ width={detailWidth}
339
+ />
340
+ </Box>
332
341
  )}
333
- </>
342
+ </Box>
334
343
  );
335
344
  }
@@ -1,3 +1,4 @@
1
+ import { useEffect, useState } from "react";
1
2
  import { DataTableView, type DataTableColumn } from "../../../components";
2
3
  import { colors } from "../../../theme/colors";
3
4
  import { formatNumber } from "../../../utils/format";
@@ -24,20 +25,44 @@ export function PredictionMarketTradesView({
24
25
  trades: PredictionTrade[];
25
26
  width: number;
26
27
  }) {
28
+ const visibleTrades = trades.slice(0, 30);
29
+ const [selectedIndex, setSelectedIndex] = useState<number | null>(() =>
30
+ visibleTrades.length > 0 ? 0 : null,
31
+ );
32
+
33
+ useEffect(() => {
34
+ setSelectedIndex((current) => {
35
+ if (visibleTrades.length === 0) return null;
36
+ if (current == null || current >= visibleTrades.length) return 0;
37
+ return current;
38
+ });
39
+ }, [visibleTrades.length]);
40
+
27
41
  return (
28
42
  <DataTableView<PredictionTrade, TradeColumn>
29
43
  focused={focused}
30
- keyboardNavigation={false}
44
+ keyboardNavigation={focused}
31
45
  rootWidth={width}
32
46
  rootBackgroundColor={colors.panel}
33
- selection={{ kind: "none" }}
47
+ selection={{
48
+ kind: "index",
49
+ selectedIndex,
50
+ onChange: (index) => setSelectedIndex(index),
51
+ }}
34
52
  columns={TRADE_COLUMNS}
35
- items={trades.slice(0, 30)}
53
+ items={visibleTrades}
36
54
  sortColumnId={null}
37
55
  sortDirection="asc"
38
56
  onHeaderClick={() => {}}
39
57
  getItemKey={(trade) => trade.id}
40
- renderCell={(trade, column) => {
58
+ onRowMouseDown={(_trade, index, event) => {
59
+ event.preventDefault();
60
+ setSelectedIndex(index);
61
+ return true;
62
+ }}
63
+ renderCell={(trade, column, _index, rowState) => {
64
+ const color = (fallback: string) =>
65
+ rowState.selected ? undefined : fallback;
41
66
  const tradeColor = trade.side === "buy" ? colors.positive : colors.negative;
42
67
  switch (column.id) {
43
68
  case "time":
@@ -45,27 +70,27 @@ export function PredictionMarketTradesView({
45
70
  text: new Date(trade.timestamp).toLocaleTimeString("en-US", {
46
71
  hour12: false,
47
72
  }),
48
- color: colors.textDim,
73
+ color: color(colors.textDim),
49
74
  };
50
75
  case "side":
51
76
  return {
52
77
  text: trade.side.toUpperCase(),
53
- color: tradeColor,
78
+ color: color(tradeColor),
54
79
  };
55
80
  case "outcome":
56
81
  return {
57
82
  text: trade.outcome.toUpperCase(),
58
- color: colors.text,
83
+ color: color(colors.text),
59
84
  };
60
85
  case "price":
61
86
  return {
62
87
  text: formatPredictionProbability(trade.price),
63
- color: tradeColor,
88
+ color: color(tradeColor),
64
89
  };
65
90
  case "size":
66
91
  return {
67
92
  text: formatNumber(trade.size, 0),
68
- color: colors.textDim,
93
+ color: color(colors.textDim),
69
94
  };
70
95
  }
71
96
  }}
@@ -1,4 +1,5 @@
1
1
  import { afterEach, describe, expect, test } from "bun:test";
2
+ import { act } from "react";
2
3
  import { testRender } from "../../renderers/opentui/test-utils";
3
4
  import {
4
5
  ChartHarness,
@@ -32,7 +33,7 @@ describe("prediction markets detail views", () => {
32
33
  expect(frame).not.toContain("TypeError");
33
34
  });
34
35
 
35
- test("renders grouped selections as ranked outcomes in the detail overview", async () => {
36
+ test("renders grouped outcomes with their chart directly below", async () => {
36
37
  testSetup = await testRender(<GroupedDetailHarness />, {
37
38
  width: 64,
38
39
  height: 24,
@@ -43,6 +44,9 @@ describe("prediction markets detail views", () => {
43
44
  expect(frame).toContain("Outcomes");
44
45
  expect(frame).toContain("Above 4.25%");
45
46
  expect(frame).toContain("Above 4.50%");
47
+ expect(frame).toContain("1M");
48
+ expect(frame.indexOf("1M")).toBeGreaterThan(frame.indexOf("Above 4.50%"));
49
+ expect(frame).not.toContain(" Chart ");
46
50
  expect(frame).not.toContain("Ranked by implied YES probability.");
47
51
  expect(frame).not.toContain("TOP Above 4.25%");
48
52
  expect(frame).not.toContain("Kalshi");
@@ -59,9 +63,32 @@ describe("prediction markets detail views", () => {
59
63
  const frame = testSetup.captureCharFrame();
60
64
  expect(frame).toContain("Outcomes");
61
65
  expect(frame).toContain("Above 4.25%");
62
- expect(frame).toContain("Chart");
66
+ expect(frame).toContain("Loading chart...");
63
67
  expect(frame).not.toContain("No chart history.");
64
68
  expect(frame).not.toContain("Loading market detail...");
65
69
  });
66
70
 
71
+ test("shows the chart crosshair on pointer movement", async () => {
72
+ testSetup = await testRender(
73
+ <ChartHarness
74
+ history={[
75
+ { date: "2026-04-01T00:00:00Z", close: 0.45 },
76
+ { date: "2026-04-02T00:00:00Z", close: 0.48 },
77
+ { date: "2026-04-03T00:00:00Z", close: 0.51 },
78
+ ]}
79
+ />,
80
+ { width: 80, height: 12 },
81
+ );
82
+ await flushFrames(testSetup);
83
+ const initialFrame = testSetup.captureCharFrame();
84
+
85
+ await act(async () => {
86
+ await testSetup!.mockMouse.moveTo(30, 5);
87
+ await testSetup!.renderOnce();
88
+ });
89
+ await flushFrames(testSetup);
90
+
91
+ expect(testSetup.captureCharFrame()).not.toBe(initialFrame);
92
+ });
93
+
67
94
  });
@@ -1,4 +1,5 @@
1
1
  import type { GloomPlugin, GloomPluginContext } from "../../types/plugin";
2
+ import { resolvePaneInstance } from "../../types/config";
2
3
  import { parsePredictionSearchShortcut } from "./navigation";
3
4
  import { PredictionMarketsPane } from "./pane";
4
5
  import { attachPredictionMarketsPersistence, resetPredictionMarketsPersistence } from "./services/fetch";
@@ -15,10 +16,13 @@ const MAIN_INSTANCE_ID = `${PANE_ID}:main`;
15
16
 
16
17
  function openPredictionMarkets(ctx: GloomPluginContext, query = ""): void {
17
18
  const parsed = parsePredictionSearchShortcut(query);
18
- ctx.resume.setPaneState(MAIN_INSTANCE_ID, "venueScope", parsed.venueScope);
19
- ctx.resume.setPaneState(MAIN_INSTANCE_ID, "searchQuery", parsed.searchQuery);
20
- ctx.resume.setPaneState(MAIN_INSTANCE_ID, "selectedMarketKey", null);
21
- ctx.focusPane(PANE_ID);
19
+ const existingInstanceId = resolvePaneInstance(ctx.getConfig().layout, PANE_ID)?.instanceId;
20
+ const targetInstanceId = existingInstanceId ?? MAIN_INSTANCE_ID;
21
+ ctx.resume.setPaneState(targetInstanceId, "venueScope", parsed.venueScope);
22
+ ctx.resume.setPaneState(targetInstanceId, "searchQuery", parsed.searchQuery);
23
+ ctx.resume.setPaneState(targetInstanceId, "selectedRowKey", null);
24
+ ctx.resume.setPaneState(targetInstanceId, "selectedDetailMarketKey", null);
25
+ ctx.focusPane(existingInstanceId ?? PANE_ID);
22
26
  }
23
27
 
24
28
  export const predictionMarketsPlugin: GloomPlugin = {
@@ -28,7 +28,6 @@ export const DETAIL_TABS: ReadonlyArray<{
28
28
  value: PredictionDetailTab;
29
29
  }> = [
30
30
  { label: "Overview", value: "overview" },
31
- { label: "Chart", value: "chart" },
32
31
  { label: "Book", value: "book" },
33
32
  { label: "Trades", value: "trades" },
34
33
  { label: "Rules", value: "rules" },