gloomberb 0.5.1 → 0.6.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 (500) hide show
  1. package/README.md +91 -11
  2. package/package.json +12 -5
  3. package/src/app.tsx +414 -191
  4. package/src/architecture/import-boundaries.test.ts +118 -0
  5. package/src/assets/gloomberb-logo.png +0 -0
  6. package/src/assets/gloomberb-logo.svg +73 -0
  7. package/src/brokers/account-cache.ts +157 -0
  8. package/src/brokers/profile-form.test.ts +73 -0
  9. package/src/brokers/profile-form.ts +109 -0
  10. package/src/brokers/remote-broker-adapter.ts +91 -0
  11. package/src/brokers/sync-broker-instance.test.ts +124 -1
  12. package/src/brokers/sync-broker-instance.ts +113 -17
  13. package/src/capabilities/factories.ts +100 -0
  14. package/src/capabilities/index.ts +4 -0
  15. package/src/capabilities/registry.test.ts +137 -0
  16. package/src/capabilities/registry.ts +131 -0
  17. package/src/capabilities/service-capabilities.ts +27 -0
  18. package/src/capabilities/types.ts +84 -0
  19. package/src/cli/commands/ticker.ts +11 -3
  20. package/src/cli/context.ts +19 -13
  21. package/src/cli/helpers.ts +1 -0
  22. package/src/cli/types.ts +2 -2
  23. package/src/components/chart/chart-controller.ts +4 -24
  24. package/src/components/chart/chart-data.ts +3 -14
  25. package/src/components/chart/chart-indicator-selector.tsx +28 -6
  26. package/src/components/chart/chart-pane-settings.ts +15 -28
  27. package/src/components/chart/chart-renderer.ts +74 -99
  28. package/src/components/chart/chart-resolution.ts +10 -10
  29. package/src/components/chart/chart-scroll.test.ts +6 -5
  30. package/src/components/chart/chart-scroll.ts +2 -2
  31. package/src/components/chart/chart-types.ts +0 -1
  32. package/src/components/chart/chart-viewport.ts +2 -1
  33. package/src/components/chart/chart.test.ts +37 -0
  34. package/src/components/chart/comparison-chart-data.ts +3 -17
  35. package/src/components/chart/comparison-chart-renderer.test.ts +27 -4
  36. package/src/components/chart/comparison-chart-renderer.ts +1 -1
  37. package/src/components/chart/comparison-stock-chart.tsx +304 -108
  38. package/src/components/chart/cursor-motion.ts +3 -2
  39. package/src/components/chart/indicators/bands.test.ts +0 -14
  40. package/src/components/chart/indicators/moving-averages.test.ts +0 -15
  41. package/src/components/chart/indicators/options.ts +3 -3
  42. package/src/components/chart/indicators/oscillators.test.ts +0 -21
  43. package/src/components/chart/indicators/types.ts +1 -2
  44. package/src/components/chart/native/chart-rasterizer.ts +37 -44
  45. package/src/components/chart/native/kitty-adapter.ts +2 -5
  46. package/src/components/chart/native/kitty-manager.ts +1 -1
  47. package/src/components/chart/native/kitty-protocol.ts +0 -1
  48. package/src/components/chart/native/kitty-support.ts +1 -1
  49. package/src/components/chart/native/kitty.test.ts +30 -13
  50. package/src/components/chart/native/renderer-selection.ts +1 -1
  51. package/src/components/chart/native/surface-manager.ts +1 -1
  52. package/src/components/chart/static-chart-surface.test.tsx +51 -0
  53. package/src/components/chart/static-chart-surface.tsx +191 -0
  54. package/src/components/chart/stock-chart-renderer-switch.test.tsx +242 -35
  55. package/src/components/chart/stock-chart.tsx +438 -141
  56. package/src/components/command-bar/__snapshots__/command-bar.test.tsx.snap +4 -4
  57. package/src/components/command-bar/command-bar.test.tsx +269 -20
  58. package/src/components/command-bar/command-bar.tsx +830 -353
  59. package/src/components/command-bar/command-registry.ts +10 -20
  60. package/src/components/command-bar/helpers.test.ts +1 -1
  61. package/src/components/command-bar/helpers.ts +9 -11
  62. package/src/components/command-bar/pane-template-display.ts +4 -8
  63. package/src/components/command-bar/root-shortcuts.ts +12 -11
  64. package/src/components/command-bar/view-model.test.ts +8 -42
  65. package/src/components/command-bar/view-model.ts +8 -36
  66. package/src/components/command-bar/workflow-ops.test.ts +76 -2
  67. package/src/components/command-bar/workflow-ops.ts +40 -9
  68. package/src/components/command-bar/workflow-types.ts +2 -1
  69. package/src/components/data-table-stack-view.test.tsx +206 -0
  70. package/src/components/data-table-stack-view.tsx +152 -0
  71. package/src/components/data-table-view.test.tsx +188 -0
  72. package/src/components/data-table-view.tsx +279 -0
  73. package/src/components/feed-data-table-stack-view.test.tsx +225 -0
  74. package/src/components/feed-data-table-stack-view.tsx +365 -0
  75. package/src/components/index.ts +26 -31
  76. package/src/components/layout/detached-pane-shell.tsx +185 -0
  77. package/src/components/layout/floating-pane.tsx +87 -14
  78. package/src/components/layout/header.tsx +139 -49
  79. package/src/components/layout/pane-content.tsx +40 -0
  80. package/src/components/layout/pane-footer.test.tsx +195 -0
  81. package/src/components/layout/pane-footer.tsx +410 -0
  82. package/src/components/layout/pane-header.test.tsx +39 -0
  83. package/src/components/layout/pane-header.tsx +192 -14
  84. package/src/components/layout/pane-sizing.ts +9 -10
  85. package/src/components/layout/pane-title.ts +35 -0
  86. package/src/components/layout/pane.tsx +52 -11
  87. package/src/components/layout/shell.test.tsx +315 -107
  88. package/src/components/layout/shell.tsx +1010 -368
  89. package/src/components/layout/status-bar.test.tsx +17 -10
  90. package/src/components/layout/status-bar.tsx +367 -100
  91. package/src/components/layout/titlebar-overlay.ts +2 -0
  92. package/src/components/markdown-editor.tsx +29 -14
  93. package/src/components/markdown-text.tsx +25 -24
  94. package/src/components/onboarding/onboarding-wizard.test.tsx +8 -3
  95. package/src/components/onboarding/onboarding-wizard.tsx +347 -473
  96. package/src/components/pane-settings-dialog.tsx +521 -89
  97. package/src/components/pane-template-wizard.test.tsx +1 -1
  98. package/src/components/pane-template-wizard.tsx +38 -37
  99. package/src/components/price-selector-dialog.tsx +22 -21
  100. package/src/components/ticker-badge-list.tsx +53 -0
  101. package/src/components/ticker-badge-text.test.tsx +44 -1
  102. package/src/components/ticker-badge-text.tsx +78 -38
  103. package/src/components/ticker-badge.tsx +28 -7
  104. package/src/components/ticker-list-table-view.tsx +248 -0
  105. package/src/components/ticker-list-table.test.tsx +47 -1
  106. package/src/components/ticker-list-table.tsx +212 -41
  107. package/src/components/toggle-list.tsx +114 -8
  108. package/src/components/ui/button.tsx +24 -16
  109. package/src/components/ui/choice-dialog.tsx +89 -0
  110. package/src/components/ui/data-table.tsx +313 -114
  111. package/src/components/ui/external-link.tsx +52 -13
  112. package/src/components/ui/fields.tsx +44 -30
  113. package/src/components/ui/frame.tsx +24 -57
  114. package/src/components/ui/index.ts +15 -50
  115. package/src/components/ui/list-view.tsx +65 -25
  116. package/src/components/ui/loading.tsx +5 -60
  117. package/src/components/ui/message-composer.test.tsx +81 -0
  118. package/src/components/ui/message-composer.tsx +139 -0
  119. package/src/components/ui/multi-select-dialog.tsx +100 -32
  120. package/src/components/ui/multi-select.ts +112 -0
  121. package/src/components/ui/native-select.tsx +120 -0
  122. package/src/components/ui/page-stack-view.tsx +49 -19
  123. package/src/components/ui/remote-image.tsx +75 -0
  124. package/src/components/ui/row-value-cache.test.ts +17 -0
  125. package/src/components/ui/row-value-cache.ts +41 -0
  126. package/src/components/ui/shortcut-hint.tsx +55 -0
  127. package/src/components/ui/status.tsx +12 -67
  128. package/src/components/ui/table-layout.ts +59 -0
  129. package/src/components/ui/tabs.tsx +249 -27
  130. package/src/components/ui/toggle.tsx +19 -127
  131. package/src/components/ui/ui.test.tsx +266 -53
  132. package/src/components/use-double-click-activation.ts +7 -2
  133. package/src/components/use-external-link-footer.ts +79 -0
  134. package/src/core/app-services.ts +95 -0
  135. package/src/core/state/app-state.ts +886 -0
  136. package/src/{state → core/state}/session-persistence.ts +7 -6
  137. package/src/data/config-layout.ts +225 -0
  138. package/src/data/config-store-node.ts +390 -0
  139. package/src/data/config-store.test.ts +72 -0
  140. package/src/data/config-store.ts +35 -478
  141. package/src/data/sqlite-json.ts +2 -5
  142. package/src/data/sqlite-retry.ts +1 -0
  143. package/src/index.tsx +2 -68
  144. package/src/market-data/coordinator-subscriptions.test.ts +125 -0
  145. package/src/market-data/coordinator.test.ts +73 -4
  146. package/src/market-data/coordinator.ts +190 -24
  147. package/src/market-data/hooks.test.tsx +1 -1
  148. package/src/market-data/hooks.tsx +109 -87
  149. package/src/market-data/query-store.ts +3 -3
  150. package/src/market-data/request-types.test.ts +0 -14
  151. package/src/market-data/result-types.ts +1 -0
  152. package/src/news/aggregator.test.ts +267 -0
  153. package/src/news/aggregator.ts +452 -0
  154. package/src/news/hooks.ts +39 -0
  155. package/src/news/ticker-symbols.ts +21 -0
  156. package/src/news/types.ts +77 -0
  157. package/src/notifications/app-notifier.ts +21 -10
  158. package/src/pane-settings.test.ts +0 -2
  159. package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +124 -134
  160. package/src/plugins/builtin/ai/gloomberb-cli.ts +14 -13
  161. package/src/plugins/builtin/ai/providers.ts +1 -9
  162. package/src/plugins/builtin/ai/runner.ts +41 -5
  163. package/src/plugins/builtin/ai/screener-pane.test.tsx +37 -55
  164. package/src/plugins/builtin/ai/screener-pane.tsx +231 -276
  165. package/src/plugins/builtin/ai/settings.ts +0 -1
  166. package/src/plugins/builtin/ai/utils.ts +1 -71
  167. package/src/plugins/builtin/alerts/alert-engine.test.ts +1 -38
  168. package/src/plugins/builtin/alerts/index.test.tsx +61 -82
  169. package/src/plugins/builtin/alerts/index.tsx +263 -155
  170. package/src/plugins/builtin/alerts/types.ts +7 -0
  171. package/src/plugins/builtin/analytics/index.test.tsx +13 -8
  172. package/src/plugins/builtin/analytics/index.tsx +78 -120
  173. package/src/plugins/builtin/analytics/metrics.test.ts +0 -4
  174. package/src/plugins/builtin/ask-ai.test.tsx +55 -54
  175. package/src/plugins/builtin/broker-manager/index.test.tsx +168 -0
  176. package/src/plugins/builtin/broker-manager/index.tsx +759 -0
  177. package/src/plugins/builtin/broker-manager/model.test.ts +107 -0
  178. package/src/plugins/builtin/broker-manager/model.ts +142 -0
  179. package/src/plugins/builtin/chat-controller.test.ts +129 -0
  180. package/src/plugins/builtin/chat-controller.ts +85 -14
  181. package/src/plugins/builtin/chat.test.tsx +283 -36
  182. package/src/plugins/builtin/chat.tsx +363 -170
  183. package/src/plugins/builtin/cloud-auth-actions.tsx +72 -0
  184. package/src/plugins/builtin/cloud-tweets.tsx +896 -0
  185. package/src/plugins/builtin/comparison-chart.test.tsx +46 -57
  186. package/src/plugins/builtin/comparison-chart.tsx +11 -11
  187. package/src/plugins/builtin/correlation/compute.test.ts +0 -17
  188. package/src/plugins/builtin/correlation/index.tsx +51 -46
  189. package/src/plugins/builtin/correlation/settings.ts +1 -2
  190. package/src/plugins/builtin/debug.tsx +99 -70
  191. package/src/plugins/builtin/earnings/earnings-cache.test.ts +2 -2
  192. package/src/plugins/builtin/earnings/earnings-cache.ts +12 -3
  193. package/src/plugins/builtin/earnings/earnings-data.test.ts +42 -11
  194. package/src/plugins/builtin/earnings/earnings-data.ts +70 -5
  195. package/src/plugins/builtin/earnings/index.tsx +187 -94
  196. package/src/plugins/builtin/econ/calendar-source.ts +9 -116
  197. package/src/plugins/builtin/econ/fred-client.test.ts +1 -14
  198. package/src/plugins/builtin/econ/fred-series-map.ts +1 -0
  199. package/src/plugins/builtin/econ/index.tsx +236 -421
  200. package/src/plugins/builtin/econ/types.ts +1 -0
  201. package/src/plugins/builtin/fx-matrix/index.tsx +43 -45
  202. package/src/plugins/builtin/fx-matrix/pairs.ts +1 -0
  203. package/src/plugins/builtin/help.test.tsx +120 -60
  204. package/src/plugins/builtin/help.tsx +266 -82
  205. package/src/plugins/builtin/holders/index.test.ts +45 -0
  206. package/src/plugins/builtin/holders/index.tsx +949 -0
  207. package/src/plugins/builtin/insider/index.tsx +207 -253
  208. package/src/plugins/builtin/insider/insider-data.test.ts +1 -11
  209. package/src/plugins/builtin/insider/insider-data.ts +1 -0
  210. package/src/plugins/builtin/layout-manager.tsx +12 -40
  211. package/src/plugins/builtin/market-movers/index.tsx +70 -144
  212. package/src/plugins/builtin/market-movers/screener.test.ts +0 -18
  213. package/src/plugins/builtin/news-wire/breaking-notifications.test.ts +98 -0
  214. package/src/plugins/builtin/news-wire/breaking-notifications.ts +110 -0
  215. package/src/plugins/builtin/news-wire/breaking-pane.tsx +157 -0
  216. package/src/plugins/builtin/news-wire/categories.ts +114 -0
  217. package/src/plugins/builtin/news-wire/default-feeds.ts +3 -0
  218. package/src/plugins/builtin/news-wire/digest-store.ts +44 -0
  219. package/src/plugins/builtin/news-wire/feed-config.test.ts +90 -0
  220. package/src/plugins/builtin/news-wire/feed-config.ts +216 -0
  221. package/src/plugins/builtin/news-wire/feed-pane.tsx +21 -0
  222. package/src/plugins/builtin/news-wire/hash.ts +9 -0
  223. package/src/plugins/builtin/news-wire/index.tsx +92 -0
  224. package/src/plugins/builtin/news-wire/industry-pane.tsx +140 -0
  225. package/src/plugins/builtin/news-wire/news-detail-view.tsx +256 -0
  226. package/src/plugins/builtin/news-wire/news-footer.ts +28 -0
  227. package/src/plugins/builtin/news-wire/news-preset-pane.tsx +94 -0
  228. package/src/plugins/builtin/news-wire/news-query-presets.ts +40 -0
  229. package/src/plugins/builtin/news-wire/news-table.test.tsx +206 -0
  230. package/src/plugins/builtin/news-wire/news-table.tsx +296 -0
  231. package/src/plugins/builtin/news-wire/persisted-articles.ts +92 -0
  232. package/src/plugins/builtin/news-wire/read-state.test.ts +30 -0
  233. package/src/plugins/builtin/news-wire/read-state.ts +76 -0
  234. package/src/plugins/builtin/news-wire/rss-parser.test.ts +184 -0
  235. package/src/plugins/builtin/news-wire/rss-parser.ts +213 -0
  236. package/src/plugins/builtin/news-wire/rss-source.test.ts +159 -0
  237. package/src/plugins/builtin/news-wire/rss-source.ts +181 -0
  238. package/src/plugins/builtin/news-wire/top-pane.tsx +21 -0
  239. package/src/plugins/builtin/news.tsx +98 -25
  240. package/src/plugins/builtin/notes-files.ts +51 -10
  241. package/src/plugins/builtin/notes.tsx +332 -131
  242. package/src/plugins/builtin/options.test.tsx +185 -0
  243. package/src/plugins/builtin/options.tsx +346 -206
  244. package/src/plugins/builtin/plugin-groups.ts +92 -0
  245. package/src/plugins/builtin/portfolio-list/header.tsx +72 -37
  246. package/src/plugins/builtin/portfolio-list/index.tsx +25 -42
  247. package/src/plugins/builtin/portfolio-list/metrics.ts +1 -1
  248. package/src/plugins/builtin/portfolio-list/pane.tsx +246 -164
  249. package/src/plugins/builtin/portfolio-list/settings.ts +19 -51
  250. package/src/plugins/builtin/portfolio-list/summary.tsx +21 -16
  251. package/src/plugins/builtin/portfolio-list/table.tsx +66 -25
  252. package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.test.tsx +1 -1
  253. package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.ts +1 -1
  254. package/src/plugins/builtin/portfolio-list.test.ts +60 -1
  255. package/src/plugins/builtin/portfolio-list.test.tsx +122 -55
  256. package/src/plugins/builtin/quote-monitor.test.tsx +2 -27
  257. package/src/plugins/builtin/research/index.tsx +724 -0
  258. package/src/plugins/builtin/sec.tsx +67 -19
  259. package/src/plugins/builtin/sectors/index.tsx +42 -80
  260. package/src/plugins/builtin/sectors/sector-data.ts +1 -0
  261. package/src/plugins/builtin/ticker-detail/chart-tab.tsx +25 -39
  262. package/src/plugins/builtin/ticker-detail/financials-tab.tsx +108 -58
  263. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +383 -271
  264. package/src/plugins/builtin/ticker-detail/pane.tsx +135 -49
  265. package/src/plugins/builtin/ticker-detail/quote-monitor.tsx +43 -27
  266. package/src/plugins/builtin/ticker-detail/settings.ts +3 -6
  267. package/src/plugins/builtin/ticker-detail-chart-tab-switch.test.tsx +16 -10
  268. package/src/plugins/builtin/ticker-detail.test.tsx +156 -33
  269. package/src/plugins/builtin/ticker-surface.ts +68 -0
  270. package/src/plugins/builtin/world-indices/index.tsx +44 -82
  271. package/src/plugins/builtin/world-indices/indices.ts +1 -0
  272. package/src/plugins/builtin/yahoo.ts +55 -1
  273. package/src/plugins/builtin/yield-curve/index.tsx +87 -145
  274. package/src/plugins/builtin/yield-curve/treasury-data.ts +6 -77
  275. package/src/plugins/catalog-ui.test.ts +24 -0
  276. package/src/plugins/catalog-ui.ts +44 -0
  277. package/src/plugins/catalog.ts +2 -50
  278. package/src/plugins/ibkr/__snapshots__/trade-tab.test.tsx.snap +5 -5
  279. package/src/plugins/ibkr/account-cache.test.ts +15 -14
  280. package/src/plugins/ibkr/account-cache.ts +15 -120
  281. package/src/plugins/ibkr/broker-adapter.ts +67 -0
  282. package/src/plugins/ibkr/config.test.ts +0 -18
  283. package/src/plugins/ibkr/dialogs.tsx +14 -56
  284. package/src/plugins/ibkr/flex.test.ts +57 -2
  285. package/src/plugins/ibkr/flex.ts +116 -12
  286. package/src/plugins/ibkr/gateway-helpers.ts +8 -3
  287. package/src/plugins/ibkr/gateway-service-native.ts +2381 -0
  288. package/src/plugins/ibkr/gateway-service.test.ts +47 -9
  289. package/src/plugins/ibkr/gateway-service.ts +264 -2235
  290. package/src/plugins/ibkr/gateway-types.ts +27 -0
  291. package/src/plugins/ibkr/index.test.tsx +3 -11
  292. package/src/plugins/ibkr/index.tsx +1 -0
  293. package/src/plugins/ibkr/price-normalization.test.ts +1 -6
  294. package/src/plugins/ibkr/price-normalization.ts +1 -0
  295. package/src/plugins/ibkr/trade-badge.tsx +6 -5
  296. package/src/plugins/ibkr/trade-tab.test.tsx +2 -2
  297. package/src/plugins/ibkr/trade-tab.tsx +164 -117
  298. package/src/plugins/ibkr/trading-pane.tsx +117 -83
  299. package/src/plugins/pane-manager.test.ts +3 -1
  300. package/src/plugins/pane-manager.ts +93 -19
  301. package/src/plugins/plugin-runtime.test.tsx +176 -74
  302. package/src/plugins/plugin-runtime.tsx +228 -10
  303. package/src/plugins/prediction-markets/cache.ts +49 -0
  304. package/src/plugins/prediction-markets/categories.ts +0 -10
  305. package/src/plugins/prediction-markets/chart.test.tsx +1 -1
  306. package/src/plugins/prediction-markets/chart.tsx +21 -20
  307. package/src/plugins/prediction-markets/cli.ts +1 -0
  308. package/src/plugins/prediction-markets/controller-data.ts +135 -89
  309. package/src/plugins/prediction-markets/controller.ts +37 -108
  310. package/src/plugins/prediction-markets/detail/book.tsx +23 -22
  311. package/src/plugins/prediction-markets/detail/outcomes.tsx +33 -32
  312. package/src/plugins/prediction-markets/detail/overview.tsx +17 -12
  313. package/src/plugins/prediction-markets/detail/pane.tsx +59 -45
  314. package/src/plugins/prediction-markets/detail/rules.tsx +22 -7
  315. package/src/plugins/prediction-markets/detail/shared.tsx +3 -2
  316. package/src/plugins/prediction-markets/detail/trades.tsx +12 -11
  317. package/src/plugins/prediction-markets/detail.test.tsx +1 -1
  318. package/src/plugins/prediction-markets/keyboard.ts +1 -0
  319. package/src/plugins/prediction-markets/launch.test.ts +1 -1
  320. package/src/plugins/prediction-markets/launch.ts +1 -1
  321. package/src/plugins/prediction-markets/metrics.ts +25 -12
  322. package/src/plugins/prediction-markets/pane.test.tsx +16 -7
  323. package/src/plugins/prediction-markets/pane.tsx +180 -82
  324. package/src/plugins/prediction-markets/plugin.test.tsx +37 -45
  325. package/src/plugins/prediction-markets/services/fetch.ts +27 -10
  326. package/src/plugins/prediction-markets/services/kalshi-adapter.ts +20 -3
  327. package/src/plugins/prediction-markets/services/polymarket-adapter.ts +29 -11
  328. package/src/plugins/prediction-markets/settings.ts +0 -5
  329. package/src/plugins/prediction-markets/test-helpers.tsx +19 -44
  330. package/src/plugins/registry.test.ts +307 -0
  331. package/src/plugins/registry.ts +293 -82
  332. package/src/react/input.ts +53 -0
  333. package/src/react/plugins/plugin-slot.tsx +21 -0
  334. package/src/react/use-raf-callback.ts +44 -0
  335. package/src/renderers/electrobun/bun/application-menu-click.test.ts +41 -0
  336. package/src/renderers/electrobun/bun/application-menu-click.ts +55 -0
  337. package/src/renderers/electrobun/bun/application-menu.ts +118 -0
  338. package/src/renderers/electrobun/bun/context-menu-click.test.ts +67 -0
  339. package/src/renderers/electrobun/bun/context-menu-click.ts +61 -0
  340. package/src/renderers/electrobun/bun/context-menu-normalize.test.ts +52 -0
  341. package/src/renderers/electrobun/bun/context-menu-normalize.ts +67 -0
  342. package/src/renderers/electrobun/bun/core-capabilities.ts +328 -0
  343. package/src/renderers/electrobun/bun/desktop-workspace.test.ts +63 -0
  344. package/src/renderers/electrobun/bun/desktop-workspace.ts +162 -0
  345. package/src/renderers/electrobun/bun/index.ts +1149 -0
  346. package/src/renderers/electrobun/bun/window-frame.test.ts +40 -0
  347. package/src/renderers/electrobun/bun/window-frame.ts +46 -0
  348. package/src/renderers/electrobun/shared/protocol.ts +72 -0
  349. package/src/renderers/electrobun/view/ai-host.ts +93 -0
  350. package/src/renderers/electrobun/view/app-services.ts +348 -0
  351. package/src/renderers/electrobun/view/application-menu-bridge.ts +12 -0
  352. package/src/renderers/electrobun/view/backend-rpc.ts +173 -0
  353. package/src/renderers/electrobun/view/broker-remote-client.ts +154 -0
  354. package/src/renderers/electrobun/view/config-host.ts +35 -0
  355. package/src/renderers/electrobun/view/context-menu.test.ts +140 -0
  356. package/src/renderers/electrobun/view/context-menu.ts +164 -0
  357. package/src/renderers/electrobun/view/data-table.tsx +773 -0
  358. package/src/renderers/electrobun/view/desktop-controls.tsx +651 -0
  359. package/src/renderers/electrobun/view/desktop-window-bridge.ts +59 -0
  360. package/src/renderers/electrobun/view/desktop-window-snapshot.test.ts +57 -0
  361. package/src/renderers/electrobun/view/desktop-window-snapshot.ts +41 -0
  362. package/src/renderers/electrobun/view/dialog-host.tsx +62 -0
  363. package/src/renderers/electrobun/view/http-fetch.ts +113 -0
  364. package/src/renderers/electrobun/view/input-host.tsx +65 -0
  365. package/src/renderers/electrobun/view/key-event.test.ts +34 -0
  366. package/src/renderers/electrobun/view/key-event.ts +134 -0
  367. package/src/renderers/electrobun/view/main.tsx +104 -0
  368. package/src/renderers/electrobun/view/native-renderer.ts +110 -0
  369. package/src/renderers/electrobun/view/native-stubs/chart-kitty-support.ts +9 -0
  370. package/src/renderers/electrobun/view/native-stubs/chart-surface-manager.ts +25 -0
  371. package/src/renderers/electrobun/view/native-stubs/chart-surface-sync.ts +1 -0
  372. package/src/renderers/electrobun/view/notes-files.ts +56 -0
  373. package/src/renderers/electrobun/view/resource-store.test.ts +44 -0
  374. package/src/renderers/electrobun/view/resource-store.ts +106 -0
  375. package/src/renderers/electrobun/view/rpc-codec.ts +42 -0
  376. package/src/renderers/electrobun/view/scrollbar-activity.ts +23 -0
  377. package/src/renderers/electrobun/view/toast-host.tsx +65 -0
  378. package/src/renderers/electrobun/view/ui-host.tsx +1490 -0
  379. package/src/renderers/electrobun/view/update-host.ts +38 -0
  380. package/src/renderers/opentui/dialog-host.test.tsx +44 -0
  381. package/src/renderers/opentui/dialog-host.tsx +56 -0
  382. package/src/renderers/opentui/host.tsx +134 -0
  383. package/src/renderers/opentui/input-host.tsx +24 -0
  384. package/src/renderers/opentui/start.tsx +71 -0
  385. package/src/renderers/opentui/test-utils.tsx +76 -0
  386. package/src/renderers/opentui/toast-host.tsx +10 -0
  387. package/src/renderers/opentui/ui-host.tsx +97 -0
  388. package/src/sources/gloomberb-cloud.test.ts +288 -17
  389. package/src/sources/gloomberb-cloud.ts +176 -9
  390. package/src/sources/provider-errors.ts +1 -0
  391. package/src/sources/provider-router.test.ts +220 -81
  392. package/src/sources/provider-router.ts +294 -111
  393. package/src/sources/sec-edgar.ts +19 -6
  394. package/src/sources/yahoo-finance.test.ts +0 -5
  395. package/src/sources/yahoo-finance.ts +420 -11
  396. package/src/state/app-activity.ts +7 -7
  397. package/src/state/app-bootstrap.ts +114 -40
  398. package/src/state/app-context-selector.test.tsx +70 -1
  399. package/src/state/app-context.test.ts +71 -3
  400. package/src/state/app-context.test.tsx +14 -7
  401. package/src/state/app-context.tsx +205 -869
  402. package/src/state/config-save-scheduler.ts +35 -0
  403. package/src/state/persist-scheduler.test.ts +67 -0
  404. package/src/state/persist-scheduler.ts +69 -0
  405. package/src/state/selectors-ui.ts +55 -0
  406. package/src/state/selectors.ts +15 -71
  407. package/src/state/session-persistence.test.ts +1 -1
  408. package/src/state/ticker-refresh-queue.ts +52 -3
  409. package/src/state/use-inline-tickers.ts +18 -16
  410. package/src/state/use-quote-streaming.test.ts +0 -6
  411. package/src/state/use-quote-streaming.test.tsx +1 -1
  412. package/src/state/use-quote-streaming.ts +3 -2
  413. package/src/state/use-session-snapshot.ts +32 -17
  414. package/src/test-support/plugin-runtime.ts +149 -0
  415. package/src/theme/color-utils.ts +1 -0
  416. package/src/theme/colors.ts +16 -0
  417. package/src/types/broker.ts +16 -1
  418. package/src/types/capability-route-source.ts +23 -0
  419. package/src/types/config.test.ts +23 -0
  420. package/src/types/config.ts +145 -10
  421. package/src/types/context-menu.ts +95 -0
  422. package/src/types/data-provider.ts +34 -3
  423. package/src/types/desktop-menu.ts +13 -0
  424. package/src/types/desktop-window.ts +29 -0
  425. package/src/types/financials.ts +123 -2
  426. package/src/types/instrument.ts +1 -0
  427. package/src/types/news-source.ts +9 -0
  428. package/src/types/persistence.ts +1 -0
  429. package/src/types/plugin.ts +32 -8
  430. package/src/ui/ascii-font.ts +85 -0
  431. package/src/ui/context-menu.test.ts +94 -0
  432. package/src/ui/context-menu.tsx +433 -0
  433. package/src/ui/dialog.tsx +55 -0
  434. package/src/ui/host.tsx +361 -0
  435. package/src/ui/index.tsx +126 -0
  436. package/src/ui/toast.tsx +40 -0
  437. package/src/updater.test.ts +69 -0
  438. package/src/updater.ts +61 -18
  439. package/src/utils/api-client.test.ts +99 -9
  440. package/src/utils/api-client.ts +378 -18
  441. package/src/utils/back-navigation.ts +6 -0
  442. package/src/utils/cli-output.ts +1 -0
  443. package/src/utils/currency-units.test.ts +1 -6
  444. package/src/utils/currency-units.ts +1 -0
  445. package/src/utils/debug-log.ts +68 -0
  446. package/src/utils/dom-screenshot.ts +326 -0
  447. package/src/utils/exchange-rate-map.ts +1 -0
  448. package/src/utils/exchanges.ts +214 -0
  449. package/src/utils/format.test.ts +7 -0
  450. package/src/utils/format.ts +1 -0
  451. package/src/utils/fuzzy-search.ts +1 -0
  452. package/src/utils/html-entities.test.ts +8 -0
  453. package/src/utils/html-entities.ts +32 -0
  454. package/src/utils/http-transport.ts +11 -0
  455. package/src/utils/main-thread-monitor.ts +127 -0
  456. package/src/utils/perf-marks.ts +54 -0
  457. package/src/utils/quote-freshness.ts +27 -50
  458. package/src/utils/quote-resolution.test.ts +5 -5
  459. package/src/utils/quote-resolution.ts +0 -1
  460. package/src/utils/quote-time.ts +1 -5
  461. package/src/utils/sec.test.ts +0 -12
  462. package/src/utils/selection-clipboard.ts +14 -6
  463. package/src/utils/shortcut-labels.test.ts +20 -0
  464. package/src/utils/shortcut-labels.ts +67 -0
  465. package/src/utils/slugify.ts +1 -0
  466. package/src/utils/terminal-input-reset.ts +1 -0
  467. package/src/utils/text-wrap.ts +82 -0
  468. package/src/utils/throttled-fetch.test.ts +19 -0
  469. package/src/utils/throttled-fetch.ts +11 -9
  470. package/src/utils/ticker-list.test.ts +1 -7
  471. package/src/utils/ticker-list.ts +1 -0
  472. package/src/utils/timestamp.ts +29 -0
  473. package/src/utils/tweet-text.test.ts +13 -0
  474. package/src/utils/tweet-text.ts +18 -0
  475. package/src/utils/url.ts +11 -0
  476. package/src/version.ts +1 -1
  477. package/src/components/chart/chart-control-hint.tsx +0 -39
  478. package/src/components/detail-feed-view.test.tsx +0 -180
  479. package/src/components/detail-feed-view.tsx +0 -531
  480. package/src/components/layout/pixel-mouse-mode.test.ts +0 -122
  481. package/src/components/layout/pixel-mouse.test.ts +0 -40
  482. package/src/components/spinner.tsx +0 -2
  483. package/src/components/tab-bar.tsx +0 -17
  484. package/src/components/ui/multi-select-chips.tsx +0 -69
  485. package/src/plugins/builtin/ai/index.test.tsx +0 -139
  486. package/src/plugins/builtin/ask-ai.tsx +0 -9
  487. package/src/plugins/builtin/correlation/plugin.test.ts +0 -44
  488. package/src/plugins/builtin/correlation/settings.test.ts +0 -34
  489. package/src/plugins/builtin/econ/calendar-source.test.ts +0 -219
  490. package/src/plugins/builtin/econ/fred-client.ts +0 -79
  491. package/src/plugins/builtin/fred-settings.ts +0 -10
  492. package/src/plugins/builtin/options-availability.test.tsx +0 -179
  493. package/src/plugins/builtin/options-availability.ts +0 -158
  494. package/src/plugins/builtin/portfolio-list/metrics.test.ts +0 -61
  495. package/src/plugins/builtin/yield-curve/treasury-data.test.ts +0 -212
  496. package/src/plugins/prediction-markets/status.ts +0 -24
  497. package/src/plugins/prediction-markets/table.tsx +0 -87
  498. package/src/utils/quote-time.test.ts +0 -22
  499. package/src/utils/selection-clipboard.test.ts +0 -43
  500. package/src/utils/terminal-input-reset.test.ts +0 -50
@@ -1,6 +1,7 @@
1
- import { TextAttributes, type ScrollBoxRenderable } from "@opentui/core";
1
+ import { Box, ScrollBox, Text } from "../../../ui";
2
+ import { TextAttributes, type ScrollBoxRenderable } from "../../../ui";
2
3
  import type { RefObject } from "react";
3
- import { Spinner, TabBar } from "../../../components";
4
+ import { Spinner, Tabs } from "../../../components";
4
5
  import { EmptyState } from "../../../components/ui/status";
5
6
  import { colors } from "../../../theme/colors";
6
7
  import { padTo } from "../../../utils/format";
@@ -35,30 +36,30 @@ interface MetricCell {
35
36
 
36
37
  function MetricLabelRow({ metrics }: { metrics: MetricCell[] }) {
37
38
  return (
38
- <box flexDirection="row" height={1}>
39
+ <Box flexDirection="row" height={1}>
39
40
  {metrics.map((metric) => (
40
- <box key={metric.label} width={metric.width + 1}>
41
- <text fg={colors.textDim}>{padTo(metric.label, metric.width)}</text>
42
- </box>
41
+ <Box key={metric.label} width={metric.width + 1}>
42
+ <Text fg={colors.textDim}>{padTo(metric.label, metric.width)}</Text>
43
+ </Box>
43
44
  ))}
44
- </box>
45
+ </Box>
45
46
  );
46
47
  }
47
48
 
48
49
  function MetricValueRow({ metrics }: { metrics: MetricCell[] }) {
49
50
  return (
50
- <box flexDirection="row" height={1}>
51
+ <Box flexDirection="row" height={1}>
51
52
  {metrics.map((metric) => (
52
- <box key={metric.label} width={metric.width + 1}>
53
- <text
53
+ <Box key={metric.label} width={metric.width + 1}>
54
+ <Text
54
55
  fg={metric.color ?? colors.textBright}
55
56
  attributes={TextAttributes.BOLD}
56
57
  >
57
58
  {padTo(metric.value, metric.width)}
58
- </text>
59
- </box>
59
+ </Text>
60
+ </Box>
60
61
  ))}
61
- </box>
62
+ </Box>
62
63
  );
63
64
  }
64
65
 
@@ -97,12 +98,12 @@ export function PredictionMarketDetailPane({
97
98
  }) {
98
99
  if (!selectedSummary) {
99
100
  return (
100
- <box flexGrow={1} justifyContent="center">
101
+ <Box flexGrow={1} justifyContent="center">
101
102
  <EmptyState
102
103
  title="Select a market."
103
104
  hint="Use the table on the left to inspect live prediction market detail."
104
105
  />
105
- </box>
106
+ </Box>
106
107
  );
107
108
  }
108
109
 
@@ -188,50 +189,53 @@ export function PredictionMarketDetailPane({
188
189
  const headerHeight = detailSubtitle.length > 0 ? 3 : 2;
189
190
  const detailLoading = detailLoadCount > 0 && !detail;
190
191
  const titleColor = focused ? colors.textBright : colors.text;
192
+ const detailTextWidth = Math.max(detailWidth, 12);
191
193
 
192
194
  return (
193
195
  <>
194
- <box flexDirection="column" height={headerHeight} paddingBottom={1}>
195
- <box flexDirection="row" height={1}>
196
- <text fg={titleColor} attributes={TextAttributes.BOLD}>
197
- {detailTitle}
198
- </text>
199
- </box>
196
+ <Box flexDirection="column" height={headerHeight} paddingBottom={1}>
197
+ <Box flexDirection="row" height={1}>
198
+ <Text fg={titleColor} attributes={TextAttributes.BOLD}>
199
+ {truncatePredictionText(detailTitle, detailTextWidth)}
200
+ </Text>
201
+ </Box>
200
202
  {detailSubtitle.length > 0 && (
201
- <box flexDirection="row" height={1}>
202
- <text fg={colors.textDim}>{detailSubtitle}</text>
203
- </box>
203
+ <Box flexDirection="row" height={1}>
204
+ <Text fg={colors.textDim}>
205
+ {truncatePredictionText(detailSubtitle, detailTextWidth)}
206
+ </Text>
207
+ </Box>
204
208
  )}
205
- </box>
209
+ </Box>
206
210
 
207
- <box flexDirection="column" height={4} paddingBottom={1}>
211
+ <Box flexDirection="column" height={4} paddingBottom={1}>
208
212
  <MetricLabelRow metrics={primaryMetrics} />
209
213
  <MetricValueRow metrics={primaryMetrics} />
210
214
  <MetricLabelRow metrics={secondaryMetrics} />
211
215
  <MetricValueRow metrics={secondaryMetrics} />
212
- </box>
216
+ </Box>
213
217
 
214
218
  {relatedSiblings.length > 0 && (
215
- <box flexDirection="row" gap={1} height={1} paddingBottom={1}>
216
- <text fg={colors.textDim}>Related:</text>
219
+ <Box flexDirection="row" gap={1} height={1} paddingBottom={1}>
220
+ <Text fg={colors.textDim}>Related:</Text>
217
221
  {relatedSiblings.map((sibling) => (
218
- <box
222
+ <Box
219
223
  key={sibling.key}
220
224
  onMouseDown={(event) => {
221
225
  event.preventDefault();
222
226
  onSelectMarket(sibling.key);
223
227
  }}
224
228
  >
225
- <text fg={colors.text}>
229
+ <Text fg={colors.text}>
226
230
  {`${truncatePredictionText(sibling.label, 10)} ${formatPredictionProbability(sibling.yesPrice)}`}
227
- </text>
228
- </box>
231
+ </Text>
232
+ </Box>
229
233
  ))}
230
- </box>
234
+ </Box>
231
235
  )}
232
236
 
233
- <box paddingBottom={1}>
234
- <TabBar
237
+ <Box paddingBottom={1}>
238
+ <Tabs
235
239
  tabs={DETAIL_TABS.map((tab) => ({
236
240
  label: tab.label,
237
241
  value: tab.value,
@@ -240,19 +244,26 @@ export function PredictionMarketDetailPane({
240
244
  onSelect={(value) => onDetailTabChange(value as PredictionDetailTab)}
241
245
  compact
242
246
  />
243
- </box>
247
+ </Box>
244
248
 
245
249
  {detailError && !detail && (
246
- <box paddingBottom={1}>
247
- <text fg={colors.negative}>{detailError}</text>
248
- </box>
250
+ <Box paddingBottom={1}>
251
+ <Text
252
+ fg={colors.negative}
253
+ width={detailTextWidth}
254
+ wrapMode="word"
255
+ wrapText
256
+ >
257
+ {detailError}
258
+ </Text>
259
+ </Box>
249
260
  )}
250
261
 
251
- <scrollbox ref={scrollRef} flexGrow={1} scrollY>
262
+ <ScrollBox ref={scrollRef} flexGrow={1} scrollY>
252
263
  {detailLoading && (
253
- <box height={1} paddingBottom={1}>
264
+ <Box height={1} paddingBottom={1}>
254
265
  <Spinner label="Loading market detail..." />
255
- </box>
266
+ </Box>
256
267
  )}
257
268
  {detailTab === "overview" && (
258
269
  <PredictionMarketOverviewView
@@ -279,9 +290,12 @@ export function PredictionMarketDetailPane({
279
290
  )}
280
291
 
281
292
  {detailTab === "rules" && (
282
- <PredictionMarketRulesView rules={detail?.rules ?? []} />
293
+ <PredictionMarketRulesView
294
+ detailWidth={detailTextWidth}
295
+ rules={detail?.rules ?? []}
296
+ />
283
297
  )}
284
- </scrollbox>
298
+ </ScrollBox>
285
299
  </>
286
300
  );
287
301
  }
@@ -1,16 +1,31 @@
1
+ import { Box, Text } from "../../../ui";
1
2
  import { colors } from "../../../theme/colors";
2
3
 
3
- export function PredictionMarketRulesView({ rules }: { rules: string[] }) {
4
+ export function PredictionMarketRulesView({
5
+ rules,
6
+ detailWidth,
7
+ }: {
8
+ rules: string[];
9
+ detailWidth: number;
10
+ }) {
11
+ const textWidth = Math.max(detailWidth, 12);
12
+
4
13
  return (
5
- <box flexDirection="column" gap={1}>
14
+ <Box flexDirection="column" gap={1}>
6
15
  {rules.map((rule, index) => (
7
- <box key={`${index}:${rule.slice(0, 24)}`} flexDirection="column">
8
- <text fg={colors.text}>{rule}</text>
9
- </box>
16
+ <Box
17
+ key={`${index}:${rule.slice(0, 24)}`}
18
+ flexDirection="column"
19
+ width={textWidth}
20
+ >
21
+ <Text fg={colors.text} width={textWidth} wrapMode="word" wrapText>
22
+ {rule}
23
+ </Text>
24
+ </Box>
10
25
  ))}
11
26
  {rules.length === 0 && (
12
- <text fg={colors.textDim}>No rule text returned by the venue.</text>
27
+ <Text fg={colors.textDim}>No rule text returned by the venue.</Text>
13
28
  )}
14
- </box>
29
+ </Box>
15
30
  );
16
31
  }
@@ -1,3 +1,4 @@
1
+ import { Box } from "../../../ui";
1
2
  import { colors } from "../../../theme/colors";
2
3
  import { ExternalLinkText } from "../../../components/ui/external-link";
3
4
 
@@ -18,12 +19,12 @@ export function SummaryLink({
18
19
  maxLength: number;
19
20
  }) {
20
21
  return (
21
- <box height={1}>
22
+ <Box height={1}>
22
23
  <ExternalLinkText
23
24
  url={url}
24
25
  label={truncatePredictionText(url, maxLength)}
25
26
  color={colors.textBright}
26
27
  />
27
- </box>
28
+ </Box>
28
29
  );
29
30
  }
@@ -1,3 +1,4 @@
1
+ import { Box, Text } from "../../../ui";
1
2
  import { colors } from "../../../theme/colors";
2
3
  import { formatNumber, padTo } from "../../../utils/format";
3
4
  import { formatPredictionProbability } from "../metrics";
@@ -9,22 +10,22 @@ export function PredictionMarketTradesView({
9
10
  trades: PredictionTrade[];
10
11
  }) {
11
12
  return (
12
- <box flexDirection="column">
13
- <box height={1}>
14
- <text
13
+ <Box flexDirection="column">
14
+ <Box height={1}>
15
+ <Text
15
16
  fg={colors.textDim}
16
- >{`${padTo("TIME", 16)} ${padTo("SIDE", 6)} ${padTo("OUT", 4)} ${padTo("PRICE", 8)} ${padTo("SIZE", 10)}`}</text>
17
- </box>
17
+ >{`${padTo("TIME", 16)} ${padTo("SIDE", 6)} ${padTo("OUT", 4)} ${padTo("PRICE", 8)} ${padTo("SIZE", 10)}`}</Text>
18
+ </Box>
18
19
  {trades.slice(0, 30).map((trade) => (
19
- <box key={trade.id} height={1}>
20
- <text fg={trade.side === "buy" ? colors.positive : colors.negative}>
20
+ <Box key={trade.id} height={1}>
21
+ <Text fg={trade.side === "buy" ? colors.positive : colors.negative}>
21
22
  {`${padTo(new Date(trade.timestamp).toLocaleTimeString("en-US", { hour12: false }), 16)} ${padTo(trade.side.toUpperCase(), 6)} ${padTo(trade.outcome.toUpperCase(), 4)} ${padTo(formatPredictionProbability(trade.price), 8)} ${padTo(formatNumber(trade.size, 0), 10, "right")}`}
22
- </text>
23
- </box>
23
+ </Text>
24
+ </Box>
24
25
  ))}
25
26
  {trades.length === 0 && (
26
- <text fg={colors.textDim}>No recent trades.</text>
27
+ <Text fg={colors.textDim}>No recent trades.</Text>
27
28
  )}
28
- </box>
29
+ </Box>
29
30
  );
30
31
  }
@@ -1,5 +1,5 @@
1
1
  import { afterEach, describe, expect, test } from "bun:test";
2
- import { testRender } from "@opentui/react/test-utils";
2
+ import { testRender } from "../../renderers/opentui/test-utils";
3
3
  import {
4
4
  ChartHarness,
5
5
  GroupedDetailHarness,
@@ -1,3 +1,4 @@
1
+
1
2
  export type PredictionKeyboardCommand =
2
3
  | "escape"
3
4
  | "search"
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, test } from "bun:test";
2
2
  import { createDefaultConfig } from "../../types/config";
3
- import type { AppSessionSnapshot } from "../../state/session-persistence";
3
+ import type { AppSessionSnapshot } from "../../core/state/session-persistence";
4
4
  import {
5
5
  applyPredictionLaunchIntentToConfig,
6
6
  applyPredictionLaunchIntentToSessionSnapshot,
@@ -8,7 +8,7 @@ import {
8
8
  type SavedLayout,
9
9
  } from "../../types/config";
10
10
  import type { CliLaunchRequest, PaneDef } from "../../types/plugin";
11
- import type { AppSessionSnapshot } from "../../state/session-persistence";
11
+ import type { AppSessionSnapshot } from "../../core/state/session-persistence";
12
12
  import { PREDICTION_CATEGORY_OPTIONS, type PredictionCategoryId } from "./categories";
13
13
  import { BROWSE_TABS } from "./navigation";
14
14
  import type { PredictionBrowseTab, PredictionVenueScope } from "./types";
@@ -212,22 +212,35 @@ export function sortPredictionMarkets(
212
212
  sortPreference: PredictionSortPreference,
213
213
  ): PredictionListRow[] {
214
214
  if (!sortPreference.columnId) return [...markets];
215
+ const columnId = sortPreference.columnId;
216
+ const direction = sortPreference.direction === "asc" ? 1 : -1;
215
217
 
216
- return [...markets].sort((left, right) => {
217
- const leftValue = getMarketSortValue(left, sortPreference.columnId!);
218
- const rightValue = getMarketSortValue(right, sortPreference.columnId!);
218
+ return markets
219
+ .map((market, index) => ({
220
+ index,
221
+ market,
222
+ sortValue: getMarketSortValue(market, columnId),
223
+ }))
224
+ .sort((left, right) => {
225
+ const leftValue = left.sortValue;
226
+ const rightValue = right.sortValue;
219
227
 
220
- if (leftValue == null && rightValue == null) return 0;
221
- if (leftValue == null) return 1;
222
- if (rightValue == null) return -1;
228
+ if (leftValue == null && rightValue == null) {
229
+ return left.index - right.index;
230
+ }
231
+ if (leftValue == null) return 1;
232
+ if (rightValue == null) return -1;
223
233
 
224
- const comparison =
225
- typeof leftValue === "string" && typeof rightValue === "string"
226
- ? leftValue.localeCompare(rightValue)
227
- : Number(leftValue) - Number(rightValue);
234
+ const comparison =
235
+ typeof leftValue === "string" && typeof rightValue === "string"
236
+ ? leftValue.localeCompare(rightValue)
237
+ : Number(leftValue) - Number(rightValue);
228
238
 
229
- return sortPreference.direction === "asc" ? comparison : -comparison;
230
- });
239
+ return comparison === 0
240
+ ? left.index - right.index
241
+ : comparison * direction;
242
+ })
243
+ .map((entry) => entry.market);
231
244
  }
232
245
 
233
246
  export function getPredictionColumnValue(
@@ -1,6 +1,6 @@
1
1
  import { afterEach, describe, expect, test } from "bun:test";
2
2
  import { act } from "react";
3
- import { testRender } from "@opentui/react/test-utils";
3
+ import { testRender } from "../../renderers/opentui/test-utils";
4
4
  import {
5
5
  Harness,
6
6
  MemoryPersistence,
@@ -83,7 +83,7 @@ describe("prediction markets pane interactions", () => {
83
83
  expect(frame).toContain("Will the Fed cut rates?");
84
84
  });
85
85
 
86
- test("shows a venue warning instead of a raw fetch error when one catalog fails", async () => {
86
+ test("keeps fallback venue markets visible when one catalog fails", async () => {
87
87
  attachPredictionMarketsPersistence(new MemoryPersistence());
88
88
 
89
89
  globalThis.fetch = (async (input: Request | string | URL) => {
@@ -128,9 +128,6 @@ describe("prediction markets pane interactions", () => {
128
128
 
129
129
  const frame = testSetup.captureCharFrame();
130
130
  expect(frame).toContain("Will the Fed cut rates?");
131
- expect(frame).toContain(
132
- "Polymarket unavailable right now; showing Kalshi markets.",
133
- );
134
131
  expect(frame).not.toContain("Was there a typo in the url or port?");
135
132
  });
136
133
 
@@ -266,7 +263,7 @@ describe("prediction markets pane interactions", () => {
266
263
  const frame = testSetup.captureCharFrame();
267
264
  expect(frame).toContain("Will the Fed cut rates?");
268
265
  expect(frame).not.toContain("Kalshi primary rule");
269
- expect(frame).not.toContain("<- Back");
266
+ expect(frame).not.toContain("\u2190 Back");
270
267
  });
271
268
 
272
269
  test("keeps a clicked header sort instead of snapping back to the default sort", async () => {
@@ -511,6 +508,18 @@ describe("prediction markets pane interactions", () => {
511
508
  ]?.selectedDetailMarketKey,
512
509
  ).toBe("kalshi:KXFED-27APR-T4.50");
513
510
 
511
+ await emitKeypress(testSetup, { name: "escape", sequence: "\u001b" });
512
+ await flushFrames(testSetup);
513
+
514
+ const escapeFrame = testSetup.captureCharFrame();
515
+ expect(escapeFrame).toContain("\u2190 Back");
516
+ expect(escapeFrame).toContain("Rule 2");
517
+ expect(
518
+ harnessStateRef.current?.paneState[TEST_PANE_ID]?.pluginState?.[
519
+ "prediction-markets"
520
+ ]?.selectedDetailMarketKey,
521
+ ).toBe("kalshi:KXFED-27APR-T4.50");
522
+
514
523
  await emitKeypress(testSetup, { name: "backspace", sequence: "\u007f" });
515
524
  await flushFrames(testSetup);
516
525
 
@@ -541,7 +550,7 @@ describe("prediction markets pane interactions", () => {
541
550
  await flushFrames(testSetup);
542
551
 
543
552
  const detailFrame = testSetup.captureCharFrame();
544
- expect(detailFrame).toContain("<- Back");
553
+ expect(detailFrame).toContain("\u2190 Back");
545
554
  expect(detailFrame).toContain("Kalshi primary rule");
546
555
  expect(
547
556
  harnessStateRef.current?.paneState[TEST_PANE_ID]?.pluginState?.[