gloomberb 0.10.4 → 0.11.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 (555) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +30 -3
  3. package/README.zh-CN.md +1 -1
  4. package/package.json +5 -5
  5. package/src/api-client/auth.ts +34 -0
  6. package/src/api-client/data.ts +25 -0
  7. package/src/api-client/index.test.ts +81 -0
  8. package/src/api-client/index.ts +37 -0
  9. package/src/api-client/request.test.ts +30 -0
  10. package/src/api-client/request.ts +44 -20
  11. package/src/api-client/socket-health.test.ts +79 -0
  12. package/src/api-client/socket.ts +108 -5
  13. package/src/api-client/types.ts +139 -0
  14. package/src/app/global-shortcuts.test.tsx +144 -9
  15. package/src/app/global-shortcuts.ts +33 -11
  16. package/src/app/runtime/broker-import.ts +39 -5
  17. package/src/app/runtime/startup.ts +7 -1
  18. package/src/app/runtime/update.ts +26 -1
  19. package/src/app.tsx +36 -19
  20. package/src/brokers/broker-ticker-sync.ts +3 -8
  21. package/src/brokers/sync-broker-instance.test.ts +59 -0
  22. package/src/brokers/sync-broker-instance.ts +37 -13
  23. package/src/capabilities/chart-series.ts +318 -0
  24. package/src/capabilities/factories.ts +46 -0
  25. package/src/capabilities/index.ts +1 -0
  26. package/src/capabilities/registry.test.ts +135 -0
  27. package/src/capabilities/registry.ts +31 -4
  28. package/src/capabilities/types.ts +52 -0
  29. package/src/cli/commands/overview.ts +2 -2
  30. package/src/cli/desktop-pane-shot.ts +2 -0
  31. package/src/cli/entry.ts +11 -1
  32. package/src/cli/native-smoke.ts +5 -0
  33. package/src/cli/pane-functions/catalog.ts +4 -0
  34. package/src/cli/pane-functions/data.ts +6 -1
  35. package/src/cli/pane-functions/discovery.ts +76 -10
  36. package/src/cli/pane-functions/report.test.ts +76 -0
  37. package/src/cli/pane-functions/report.ts +7 -1
  38. package/src/cli/pane-functions/resolver.ts +4 -1
  39. package/src/cli/pane-functions/screenshot.test.ts +56 -3
  40. package/src/cli/pane-functions/screenshot.ts +68 -5
  41. package/src/components/chart/composite/composite-chart.test.tsx +54 -0
  42. package/src/components/chart/composite/composite-chart.tsx +92 -15
  43. package/src/components/chart/composite/downsample.test.ts +136 -0
  44. package/src/components/chart/composite/downsample.ts +208 -0
  45. package/src/components/chart/composite/panel-series.test.ts +66 -0
  46. package/src/components/chart/composite/panel-series.ts +101 -0
  47. package/src/components/chart/multi-line/renderer.ts +1 -0
  48. package/src/components/chart/native/kitty/index.test.ts +25 -3
  49. package/src/components/chart/native/kitty/support.ts +37 -3
  50. package/src/components/chart/native/use-chart-text-fallback.ts +19 -0
  51. package/src/components/chart/static/chart/surface.tsx +43 -12
  52. package/src/components/chart/static/multi-line-chart-surface.tsx +41 -5
  53. package/src/components/command-bar/commands/direct/index.ts +18 -0
  54. package/src/components/command-bar/commands/registry.ts +14 -0
  55. package/src/components/command-bar/routes/root/article-results.test.ts +83 -0
  56. package/src/components/command-bar/routes/root/article-results.ts +55 -0
  57. package/src/components/command-bar/routes/root/command-items.ts +3 -0
  58. package/src/components/command-bar/routes/root/provider-search.ts +7 -3
  59. package/src/components/command-bar/routes/root/results.ts +38 -11
  60. package/src/components/command-bar/routes/root/runtime.ts +6 -2
  61. package/src/components/command-bar/routes/ticker-search/actions.ts +1 -1
  62. package/src/components/command-bar/routes/ticker-search/results.test.ts +34 -0
  63. package/src/components/command-bar/routes/ticker-search/results.ts +4 -6
  64. package/src/components/command-bar/routes/ticker-search/route.ts +2 -2
  65. package/src/components/command-bar/routing/list-state.ts +2 -1
  66. package/src/components/command-bar/surface/assist.test.tsx +13 -6
  67. package/src/components/command-bar/surface/index.test.tsx +65 -0
  68. package/src/components/command-bar/surface/index.tsx +35 -0
  69. package/src/components/command-bar/view-model.test.ts +43 -0
  70. package/src/components/command-bar/view-model.ts +41 -4
  71. package/src/components/command-bar/workflow/collection-actions.test.ts +94 -0
  72. package/src/components/command-bar/workflow/collection-actions.ts +14 -3
  73. package/src/components/command-bar/workflow/ops.test.ts +136 -0
  74. package/src/components/command-bar/workflow/ops.ts +71 -1
  75. package/src/components/index.ts +1 -1
  76. package/src/components/layout/header.test.tsx +35 -0
  77. package/src/components/layout/header.tsx +101 -9
  78. package/src/components/layout/pane/cycle-order.test.ts +0 -1
  79. package/src/components/layout/pane/footer/index.test.tsx +19 -19
  80. package/src/components/layout/pane/header.tsx +2 -13
  81. package/src/components/layout/shell/index.tsx +3 -0
  82. package/src/components/layout/status-bar.test.tsx +36 -1
  83. package/src/components/layout/status-bar.tsx +48 -2
  84. package/src/components/onboarding/account-step/chooser-panel.tsx +27 -50
  85. package/src/components/onboarding/account-step/form-panel.tsx +85 -29
  86. package/src/components/onboarding/account-step/index.tsx +10 -1
  87. package/src/components/onboarding/account-step/model.ts +3 -3
  88. package/src/components/onboarding/account-step/qr-panel.tsx +68 -0
  89. package/src/components/onboarding/account-step/signed-in-panel.tsx +4 -7
  90. package/src/components/onboarding/onboarding-frame.tsx +517 -0
  91. package/src/components/onboarding/onboarding-steps.tsx +0 -264
  92. package/src/components/onboarding/onboarding-wizard.test.tsx +626 -200
  93. package/src/components/onboarding/onboarding-wizard.tsx +848 -266
  94. package/src/components/onboarding/portfolio-step/broker-fields-panel.tsx +81 -51
  95. package/src/components/onboarding/portfolio-step/broker-setup-panel.tsx +14 -20
  96. package/src/components/onboarding/portfolio-step/broker-sync-panel.tsx +8 -11
  97. package/src/components/onboarding/portfolio-step/choose-panel.tsx +18 -22
  98. package/src/components/onboarding/portfolio-step/index.tsx +3 -0
  99. package/src/components/onboarding/portfolio-step/types.ts +3 -0
  100. package/src/components/onboarding/wizard-account.ts +38 -3
  101. package/src/components/onboarding/wizard-broker-sync.ts +45 -67
  102. package/src/components/onboarding/wizard-model.ts +18 -2
  103. package/src/components/ui/data-table/opentui/index.tsx +29 -9
  104. package/src/components/ui/data-table/types.ts +2 -0
  105. package/src/components/ui/external-link.tsx +4 -2
  106. package/src/components/ui/loading.tsx +1 -1
  107. package/src/components/ui/status.tsx +76 -4
  108. package/src/components/ui/table-layout.test.ts +24 -0
  109. package/src/components/ui/table-layout.ts +90 -23
  110. package/src/components/ui/tabs.tsx +86 -4
  111. package/src/core/app-services.ts +14 -3
  112. package/src/core/connection-health.test.ts +143 -0
  113. package/src/core/connection-health.ts +295 -0
  114. package/src/core/state/app/layout-reducer.ts +34 -0
  115. package/src/core/state/app/layout.ts +21 -0
  116. package/src/core/state/app/state.ts +10 -0
  117. package/src/core/state/app/types.ts +7 -1
  118. package/src/data/config/chart-settings.ts +11 -2
  119. package/src/data/config/store/index.test.ts +83 -9
  120. package/src/data/config/store/migrations.ts +16 -0
  121. package/src/data/config/store/normalize.ts +59 -1
  122. package/src/data/fred-series.ts +8 -2
  123. package/src/i18n/es.ts +126 -0
  124. package/src/i18n/ja.ts +126 -0
  125. package/src/i18n/ko.ts +126 -0
  126. package/src/i18n/zh-cn.ts +126 -0
  127. package/src/i18n/zh-tw.ts +126 -0
  128. package/src/market-data/coordinator/chart.ts +3 -2
  129. package/src/market-data/hooks.tsx +3 -2
  130. package/src/market-data/market/status.test.ts +12 -0
  131. package/src/market-data/market/status.ts +34 -17
  132. package/src/news/aggregator.test.ts +39 -0
  133. package/src/news/aggregator.ts +78 -22
  134. package/src/news/hooks.ts +4 -0
  135. package/src/notifications/app-notifier.test.ts +28 -6
  136. package/src/notifications/app-notifier.ts +24 -5
  137. package/src/plugins/broker-sync/broker-sync.test.ts +130 -0
  138. package/src/plugins/broker-sync/native-loader.ts +10 -0
  139. package/src/plugins/broker-sync/normalize.ts +271 -0
  140. package/src/plugins/broker-sync/public.ts +122 -0
  141. package/src/plugins/broker-sync/robinhood-native.ts +367 -0
  142. package/src/plugins/broker-sync/robinhood.ts +114 -0
  143. package/src/plugins/broker-sync/simplefin-native.ts +201 -0
  144. package/src/plugins/broker-sync/simplefin.ts +70 -0
  145. package/src/plugins/builtin/account-management/footer.ts +8 -19
  146. package/src/plugins/builtin/account-management/pane.tsx +13 -8
  147. package/src/plugins/builtin/ai/pi/host.ts +6 -5
  148. package/src/plugins/builtin/ai/providers.ts +1 -1
  149. package/src/plugins/builtin/ai/screener/footer.ts +0 -7
  150. package/src/plugins/builtin/ai/screener/pane.test.tsx +2 -1
  151. package/src/plugins/builtin/ai/screener/pane.tsx +3 -1
  152. package/src/plugins/builtin/ai/screener/results-view.tsx +8 -0
  153. package/src/plugins/builtin/ai/screener/runner.ts +7 -3
  154. package/src/plugins/builtin/ai/workspace/pane.tsx +13 -2
  155. package/src/plugins/builtin/alerts/alert-engine.ts +13 -0
  156. package/src/plugins/builtin/alerts/constants.ts +2 -1
  157. package/src/plugins/builtin/alerts/index.tsx +67 -36
  158. package/src/plugins/builtin/alerts/pane.tsx +17 -65
  159. package/src/plugins/builtin/analytics/index.tsx +24 -11
  160. package/src/plugins/builtin/analytics/pane-model.ts +48 -14
  161. package/src/plugins/builtin/analytics/portfolio-selection.ts +19 -0
  162. package/src/plugins/builtin/analytics/view.tsx +5 -3
  163. package/src/plugins/builtin/broker-manager/detail.tsx +7 -2
  164. package/src/plugins/builtin/broker-manager/footer.ts +4 -9
  165. package/src/plugins/builtin/broker-manager/index.tsx +1 -6
  166. package/src/plugins/builtin/buildout/detail/ui.tsx +15 -4
  167. package/src/plugins/builtin/buildout/pane/header.tsx +1 -0
  168. package/src/plugins/builtin/changelog/index.tsx +60 -45
  169. package/src/plugins/builtin/chart-composer/catalog-inventory.test.ts +97 -0
  170. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +366 -0
  171. package/src/plugins/builtin/chart-composer/chart-spec.ts +21 -1
  172. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +307 -0
  173. package/src/plugins/builtin/chart-composer/editor/controller.ts +1 -1
  174. package/src/plugins/builtin/chart-composer/index.tsx +45 -5
  175. package/src/plugins/builtin/chart-composer/pane.tsx +7 -88
  176. package/src/plugins/builtin/chart-composer/presets.test.ts +48 -0
  177. package/src/plugins/builtin/chart-composer/presets.ts +69 -3
  178. package/src/plugins/builtin/chart-composer/quick-add.tsx +8 -5
  179. package/src/plugins/builtin/chart-composer/semantic.ts +18 -14
  180. package/src/plugins/builtin/chart-composer/series-catalog.test.ts +39 -0
  181. package/src/plugins/builtin/chart-composer/series-catalog.ts +104 -3
  182. package/src/plugins/builtin/chart-composer/shortcuts.test.ts +10 -10
  183. package/src/plugins/builtin/chart-composer/shortcuts.ts +4 -12
  184. package/src/plugins/builtin/chart-composer/use-series-catalog.ts +230 -23
  185. package/src/plugins/builtin/chat/chat.test.tsx +22 -0
  186. package/src/plugins/builtin/chat/content/composer.tsx +6 -4
  187. package/src/plugins/builtin/chat/content/index.tsx +7 -0
  188. package/src/plugins/builtin/chat/content/snapshot.ts +9 -2
  189. package/src/plugins/builtin/chat/content/transcript.tsx +10 -0
  190. package/src/plugins/builtin/chat/controller/channel-actions.ts +11 -1
  191. package/src/plugins/builtin/chat/controller/channels.ts +13 -3
  192. package/src/plugins/builtin/chat/controller/index.ts +61 -7
  193. package/src/plugins/builtin/chat/controller/lifecycle.ts +3 -0
  194. package/src/plugins/builtin/chat/controller/message-loading.ts +5 -1
  195. package/src/plugins/builtin/chat/controller/messages.ts +3 -1
  196. package/src/plugins/builtin/chat/controller/notifications.ts +32 -11
  197. package/src/plugins/builtin/chat/controller/state.ts +7 -0
  198. package/src/plugins/builtin/chat/controller/view.ts +1 -0
  199. package/src/plugins/builtin/chat/controller.test.ts +117 -28
  200. package/src/plugins/builtin/cloud/auth-commands.ts +18 -0
  201. package/src/plugins/builtin/cloud/device-signin-dialog.tsx +243 -0
  202. package/src/plugins/builtin/cloud/device-signin.test.ts +217 -0
  203. package/src/plugins/builtin/cloud/device-signin.ts +240 -0
  204. package/src/plugins/builtin/cloud/plugin.tsx +7 -1
  205. package/src/plugins/builtin/cloud-tweets/table.tsx +57 -10
  206. package/src/plugins/builtin/composite-plugins.ts +39 -5
  207. package/src/plugins/builtin/congress-trades/detail.tsx +2 -4
  208. package/src/plugins/builtin/congress-trades/footer.ts +7 -16
  209. package/src/plugins/builtin/congress-trades/pane.tsx +12 -20
  210. package/src/plugins/builtin/connections/index.ts +53 -0
  211. package/src/plugins/builtin/connections/pane.test.tsx +79 -0
  212. package/src/plugins/builtin/connections/pane.tsx +258 -0
  213. package/src/plugins/builtin/correlation/index.test.tsx +2 -1
  214. package/src/plugins/builtin/correlation/index.tsx +58 -19
  215. package/src/plugins/builtin/correlation/matrix/model.ts +18 -7
  216. package/src/plugins/builtin/correlation/relationship/pane.tsx +43 -19
  217. package/src/plugins/builtin/correlation/settings.ts +1 -1
  218. package/src/plugins/builtin/credit-conditions/client.test.ts +80 -0
  219. package/src/plugins/builtin/credit-conditions/client.ts +105 -0
  220. package/src/plugins/builtin/credit-conditions/index.test.tsx +111 -0
  221. package/src/plugins/builtin/credit-conditions/index.tsx +227 -0
  222. package/src/plugins/builtin/credit-conditions/model.test.ts +43 -0
  223. package/src/plugins/builtin/credit-conditions/model.ts +64 -0
  224. package/src/plugins/builtin/debug/index.tsx +11 -6
  225. package/src/plugins/builtin/dividend-yield/client.test.ts +58 -0
  226. package/src/plugins/builtin/dividend-yield/client.ts +252 -0
  227. package/src/plugins/builtin/dividend-yield/index.tsx +61 -0
  228. package/src/plugins/builtin/dividend-yield/model.ts +83 -0
  229. package/src/plugins/builtin/dividend-yield/pane.tsx +305 -0
  230. package/src/plugins/builtin/dividend-yield/types.ts +22 -0
  231. package/src/plugins/builtin/earnings/data/cache.test.ts +10 -7
  232. package/src/plugins/builtin/earnings/data/cache.ts +23 -9
  233. package/src/plugins/builtin/earnings/index.tsx +57 -6
  234. package/src/plugins/builtin/econ/calendar-model.test.ts +37 -3
  235. package/src/plugins/builtin/econ/calendar-model.ts +32 -26
  236. package/src/plugins/builtin/econ/fred-cache.test.ts +8 -1
  237. package/src/plugins/builtin/econ/fred-series-map.ts +18 -0
  238. package/src/plugins/builtin/econ/index.test.tsx +105 -0
  239. package/src/plugins/builtin/econ/index.tsx +95 -52
  240. package/src/plugins/builtin/fear-greed/cache.test.ts +18 -3
  241. package/src/plugins/builtin/fear-greed/cache.ts +18 -6
  242. package/src/plugins/builtin/fear-greed/charts.tsx +9 -2
  243. package/src/plugins/builtin/fear-greed/format.ts +0 -9
  244. package/src/plugins/builtin/fear-greed/pane.tsx +21 -21
  245. package/src/plugins/builtin/futures/contracts.ts +100 -0
  246. package/src/plugins/builtin/futures/index.tsx +298 -0
  247. package/src/plugins/builtin/futures/model.test.ts +129 -0
  248. package/src/plugins/builtin/futures/model.ts +136 -0
  249. package/src/plugins/builtin/futures/pane.test.tsx +239 -0
  250. package/src/plugins/builtin/futures/table.test.ts +155 -0
  251. package/src/plugins/builtin/futures/table.tsx +241 -0
  252. package/src/plugins/builtin/fx-matrix/index.tsx +160 -134
  253. package/src/plugins/builtin/fx-matrix/pairs.ts +12 -1
  254. package/src/plugins/builtin/fx-matrix/pane.test.tsx +109 -0
  255. package/src/plugins/builtin/help/components.tsx +1 -30
  256. package/src/plugins/builtin/help/index.tsx +5 -25
  257. package/src/plugins/builtin/holders/pane.tsx +17 -12
  258. package/src/plugins/builtin/holders/treemap.tsx +4 -2
  259. package/src/plugins/builtin/insider/index.tsx +20 -10
  260. package/src/plugins/builtin/ipo-calendar/cache.ts +101 -0
  261. package/src/plugins/builtin/ipo-calendar/client.test.ts +31 -0
  262. package/src/plugins/builtin/ipo-calendar/client.ts +296 -0
  263. package/src/plugins/builtin/ipo-calendar/index.tsx +65 -0
  264. package/src/plugins/builtin/ipo-calendar/model.ts +145 -0
  265. package/src/plugins/builtin/ipo-calendar/pane.tsx +328 -0
  266. package/src/plugins/builtin/ipo-calendar/types.ts +19 -0
  267. package/src/plugins/builtin/kelly-sizer/index.test.tsx +3 -3
  268. package/src/plugins/builtin/kelly-sizer/model.ts +8 -1
  269. package/src/plugins/builtin/kelly-sizer/pane.tsx +33 -15
  270. package/src/plugins/builtin/kelly-sizer/view.tsx +12 -5
  271. package/src/plugins/builtin/market-halts/client.ts +114 -0
  272. package/src/plugins/builtin/market-halts/index.tsx +29 -0
  273. package/src/plugins/builtin/market-halts/model.test.ts +187 -0
  274. package/src/plugins/builtin/market-halts/model.ts +291 -0
  275. package/src/plugins/builtin/market-halts/pane.tsx +256 -0
  276. package/src/plugins/builtin/market-heatmap/data.test.ts +1 -0
  277. package/src/plugins/builtin/market-heatmap/data.ts +12 -3
  278. package/src/plugins/builtin/market-heatmap/index.tsx +131 -46
  279. package/src/plugins/builtin/market-movers/index.tsx +145 -63
  280. package/src/plugins/builtin/market-movers/model.ts +19 -3
  281. package/src/plugins/builtin/market-movers/screener.test.ts +108 -0
  282. package/src/plugins/builtin/market-movers/screener.ts +168 -10
  283. package/src/plugins/builtin/news/index.tsx +78 -99
  284. package/src/plugins/builtin/news/wire/article-search.test.ts +72 -0
  285. package/src/plugins/builtin/news/wire/article-search.ts +140 -0
  286. package/src/plugins/builtin/news/wire/breaking/pane.tsx +18 -8
  287. package/src/plugins/builtin/news/wire/categories.ts +35 -0
  288. package/src/plugins/builtin/news/wire/default-feeds.ts +47 -1
  289. package/src/plugins/builtin/news/wire/feed-config.test.ts +10 -5
  290. package/src/plugins/builtin/news/wire/index.tsx +41 -4
  291. package/src/plugins/builtin/news/wire/industry-pane.tsx +38 -19
  292. package/src/plugins/builtin/news/wire/news/detail-view.tsx +10 -5
  293. package/src/plugins/builtin/news/wire/news/footer.ts +1 -0
  294. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +15 -6
  295. package/src/plugins/builtin/news/wire/news/query-presets.ts +16 -2
  296. package/src/plugins/builtin/news/wire/news/table.test.tsx +54 -0
  297. package/src/plugins/builtin/news/wire/news/table.tsx +111 -17
  298. package/src/plugins/builtin/notes/files.test.ts +18 -0
  299. package/src/plugins/builtin/notes/files.ts +10 -4
  300. package/src/plugins/builtin/notes/index.tsx +4 -1
  301. package/src/plugins/builtin/notes/quick-notes-pane.test.tsx +182 -0
  302. package/src/plugins/builtin/notes/quick-notes-pane.tsx +74 -31
  303. package/src/plugins/builtin/notes/ticker-notes-tab.test.tsx +380 -0
  304. package/src/plugins/builtin/notes/ticker-notes-tab.tsx +97 -40
  305. package/src/plugins/builtin/options/calc-seed.test.ts +118 -0
  306. package/src/plugins/builtin/options/calc-seed.ts +52 -0
  307. package/src/plugins/builtin/options/footer.ts +4 -1
  308. package/src/plugins/builtin/options/index.tsx +22 -1
  309. package/src/plugins/builtin/options/live-quotes.ts +8 -0
  310. package/src/plugins/builtin/options/table.ts +3 -1
  311. package/src/plugins/builtin/options/view.test.tsx +5 -1
  312. package/src/plugins/builtin/options/view.tsx +112 -17
  313. package/src/plugins/builtin/options-calculator/index.tsx +35 -0
  314. package/src/plugins/builtin/options-calculator/model.test.ts +172 -0
  315. package/src/plugins/builtin/options-calculator/model.ts +273 -0
  316. package/src/plugins/builtin/options-calculator/pane.test.tsx +138 -0
  317. package/src/plugins/builtin/options-calculator/pane.tsx +218 -0
  318. package/src/plugins/builtin/plugin-module.test.ts +16 -2
  319. package/src/plugins/builtin/plugin-module.ts +5 -1
  320. package/src/plugins/builtin/polls/client.ts +57 -0
  321. package/src/plugins/builtin/polls/hooks.ts +20 -0
  322. package/src/plugins/builtin/polls/index.tsx +35 -0
  323. package/src/plugins/builtin/polls/normalize.test.ts +230 -0
  324. package/src/plugins/builtin/polls/normalize.ts +319 -0
  325. package/src/plugins/builtin/polls/pane.tsx +722 -0
  326. package/src/plugins/builtin/polls/types.ts +77 -0
  327. package/src/plugins/builtin/portfolio-list/header.tsx +15 -4
  328. package/src/plugins/builtin/portfolio-list/pane/index.tsx +7 -3
  329. package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +1 -1
  330. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -0
  331. package/src/plugins/builtin/portfolio-list/summary/index.tsx +2 -1
  332. package/src/plugins/builtin/research/analyst-pane.tsx +40 -67
  333. package/src/plugins/builtin/research/corporate-actions-pane.tsx +25 -19
  334. package/src/plugins/builtin/research/index.test.ts +6 -25
  335. package/src/plugins/builtin/research/index.tsx +11 -5
  336. package/src/plugins/builtin/research/relative-valuation-pane.tsx +86 -26
  337. package/src/plugins/builtin/scanner/denied.tsx +31 -0
  338. package/src/plugins/builtin/scanner/feed.ts +94 -0
  339. package/src/plugins/builtin/scanner/flow-model.test.ts +85 -0
  340. package/src/plugins/builtin/scanner/flow-model.ts +133 -0
  341. package/src/plugins/builtin/scanner/flow-pane.tsx +197 -0
  342. package/src/plugins/builtin/scanner/hilo-bars.tsx +121 -0
  343. package/src/plugins/builtin/scanner/hilo-model.test.ts +44 -0
  344. package/src/plugins/builtin/scanner/hilo-model.ts +75 -0
  345. package/src/plugins/builtin/scanner/hilo-pane.tsx +165 -0
  346. package/src/plugins/builtin/scanner/index.ts +109 -0
  347. package/src/plugins/builtin/scanner/waiting.tsx +11 -0
  348. package/src/plugins/builtin/sec/index.tsx +11 -7
  349. package/src/plugins/builtin/sectors/index.tsx +105 -81
  350. package/src/plugins/builtin/sectors/move-bar.tsx +43 -0
  351. package/src/plugins/builtin/sectors/sector-data.ts +18 -0
  352. package/src/plugins/builtin/sectors/sector-model.ts +13 -15
  353. package/src/plugins/builtin/shared/auto-refresh.ts +51 -0
  354. package/src/plugins/builtin/shared/cloud-upgrade.ts +29 -5
  355. package/src/plugins/builtin/shared/pane-footer.ts +16 -3
  356. package/src/plugins/builtin/shared/screener-live-quotes.test.ts +91 -0
  357. package/src/plugins/builtin/shared/screener-live-quotes.ts +134 -0
  358. package/src/plugins/builtin/shared/table-pane.ts +1 -5
  359. package/src/plugins/builtin/shared/use-quote-board.test.tsx +211 -0
  360. package/src/plugins/builtin/shared/use-quote-board.ts +195 -0
  361. package/src/plugins/builtin/short-interest/client.ts +124 -0
  362. package/src/plugins/builtin/short-interest/index.tsx +62 -0
  363. package/src/plugins/builtin/short-interest/model.ts +113 -0
  364. package/src/plugins/builtin/short-interest/pane.tsx +252 -0
  365. package/src/plugins/builtin/short-interest/types.ts +9 -0
  366. package/src/plugins/builtin/substack/article-detail.tsx +15 -1
  367. package/src/plugins/builtin/substack/pane-footer.ts +3 -22
  368. package/src/plugins/builtin/substack/pane.tsx +1 -2
  369. package/src/plugins/builtin/thirteenf/api.ts +4 -1
  370. package/src/plugins/builtin/thirteenf/model.ts +2 -0
  371. package/src/plugins/builtin/thirteenf/pane.tsx +6 -2
  372. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +10 -4
  373. package/src/plugins/builtin/ticker-detail/data-panes/provider-search.tsx +67 -11
  374. package/src/plugins/builtin/ticker-detail/financials/tab.tsx +15 -8
  375. package/src/plugins/builtin/ticker-detail/index.tsx +5 -21
  376. package/src/plugins/builtin/ticker-detail/overview/components.tsx +72 -32
  377. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +0 -10
  378. package/src/plugins/builtin/ticker-detail/pane.tsx +4 -1
  379. package/src/plugins/builtin/ticker-detail/quote-monitor/card.test.tsx +70 -0
  380. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +34 -26
  381. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +96 -49
  382. package/src/plugins/builtin/ticker-detail/research-tabs.ts +32 -0
  383. package/src/plugins/builtin/ticker-research-backend-plugin.ts +2 -9
  384. package/src/plugins/builtin/ticker-research-plugin.ts +6 -0
  385. package/src/plugins/builtin/treasury-auctions/cache.test.ts +173 -0
  386. package/src/plugins/builtin/treasury-auctions/cache.ts +104 -0
  387. package/src/plugins/builtin/treasury-auctions/client.test.ts +181 -0
  388. package/src/plugins/builtin/treasury-auctions/client.ts +151 -0
  389. package/src/plugins/builtin/treasury-auctions/index.tsx +90 -0
  390. package/src/plugins/builtin/treasury-auctions/model.test.ts +147 -0
  391. package/src/plugins/builtin/treasury-auctions/model.ts +188 -0
  392. package/src/plugins/builtin/treasury-auctions/pane.test.tsx +170 -0
  393. package/src/plugins/builtin/treasury-auctions/pane.tsx +417 -0
  394. package/src/plugins/builtin/treasury-auctions/types.ts +59 -0
  395. package/src/plugins/builtin/tv/pane.tsx +67 -7
  396. package/src/plugins/builtin/tv/youtube-embed.test.ts +84 -0
  397. package/src/plugins/builtin/tv/youtube-embed.ts +173 -0
  398. package/src/plugins/builtin/tv/youtube-stream.ts +27 -2
  399. package/src/plugins/builtin/volatility/client.ts +87 -0
  400. package/src/plugins/builtin/volatility/index.test.tsx +107 -0
  401. package/src/plugins/builtin/volatility/index.tsx +193 -0
  402. package/src/plugins/builtin/volatility/model.test.ts +66 -0
  403. package/src/plugins/builtin/volatility/model.ts +113 -0
  404. package/src/plugins/builtin/world-indices/footer.ts +43 -23
  405. package/src/plugins/builtin/world-indices/index.tsx +44 -93
  406. package/src/plugins/builtin/world-indices/indices.ts +11 -2
  407. package/src/plugins/builtin/world-indices/model.ts +17 -24
  408. package/src/plugins/builtin/world-indices/pane.test.tsx +116 -0
  409. package/src/plugins/builtin/world-indices/table.tsx +82 -37
  410. package/src/plugins/builtin/yield-curve/index.tsx +16 -7
  411. package/src/plugins/builtin/yield-curve/treasury-data.test.ts +25 -0
  412. package/src/plugins/builtin/yield-curve/treasury-data.ts +16 -0
  413. package/src/plugins/catalog-backend.ts +8 -0
  414. package/src/plugins/catalog-ui.ts +8 -0
  415. package/src/plugins/event-bus.ts +1 -0
  416. package/src/plugins/ibkr/gateway/account-loaders.ts +1 -1
  417. package/src/plugins/ibkr/trade/tab/footer.ts +9 -23
  418. package/src/plugins/ibkr/trade/tab/index.tsx +1 -5
  419. package/src/plugins/ibkr/trading/pane-view.tsx +149 -87
  420. package/src/plugins/ibkr/trading/pane.tsx +2 -2
  421. package/src/plugins/ownership.ts +3 -0
  422. package/src/plugins/pane-manager/floating.ts +8 -2
  423. package/src/plugins/pane-manager.test.ts +13 -0
  424. package/src/plugins/prediction-markets/backend-plugin.ts +2 -0
  425. package/src/plugins/prediction-markets/capability.test.ts +281 -0
  426. package/src/plugins/prediction-markets/capability.ts +171 -0
  427. package/src/plugins/prediction-markets/chart.tsx +18 -26
  428. package/src/plugins/prediction-markets/columns.ts +3 -2
  429. package/src/plugins/prediction-markets/controller/catalog.ts +15 -12
  430. package/src/plugins/prediction-markets/controller/detail.ts +4 -0
  431. package/src/plugins/prediction-markets/controller/index.ts +0 -15
  432. package/src/plugins/prediction-markets/detail/book.tsx +3 -23
  433. package/src/plugins/prediction-markets/detail/overview.tsx +0 -18
  434. package/src/plugins/prediction-markets/detail/pane.tsx +36 -12
  435. package/src/plugins/prediction-markets/detail.test.tsx +1 -1
  436. package/src/plugins/prediction-markets/index.tsx +2 -0
  437. package/src/plugins/prediction-markets/metrics.ts +28 -18
  438. package/src/plugins/prediction-markets/navigation.ts +1 -0
  439. package/src/plugins/prediction-markets/pane.test.tsx +1 -1
  440. package/src/plugins/prediction-markets/pane.tsx +0 -1
  441. package/src/plugins/prediction-markets/services/fetch.ts +3 -2
  442. package/src/plugins/prediction-markets/services/kalshi/adapter.ts +82 -23
  443. package/src/plugins/prediction-markets/services/kalshi/normalize.ts +3 -1
  444. package/src/plugins/prediction-markets/services/kalshi/types.ts +14 -0
  445. package/src/plugins/prediction-markets/services/polymarket/adapter.ts +26 -14
  446. package/src/plugins/prediction-markets/services/polymarket/detail.ts +67 -18
  447. package/src/plugins/prediction-markets/test-helpers.tsx +0 -1
  448. package/src/plugins/prediction-markets/types.ts +3 -9
  449. package/src/plugins/registry/context.ts +6 -1
  450. package/src/plugins/registry/index.test.ts +2 -0
  451. package/src/plugins/registry/index.ts +53 -3
  452. package/src/plugins/runtime/context.tsx +4 -2
  453. package/src/plugins/runtime/index.tsx +9 -0
  454. package/src/remote/controller.test.ts +17 -4
  455. package/src/renderers/electrobun/bun/application-menu/index.ts +1 -1
  456. package/src/renderers/electrobun/bun/core-capabilities.ts +2 -2
  457. package/src/renderers/electrobun/bun/desktop/capability-bridge.test.ts +64 -0
  458. package/src/renderers/electrobun/bun/desktop/capability-bridge.ts +41 -7
  459. package/src/renderers/electrobun/bun/desktop/host-requests.ts +5 -2
  460. package/src/renderers/electrobun/bun/desktop/http-fetch.test.ts +33 -0
  461. package/src/renderers/electrobun/bun/desktop/http-fetch.ts +53 -6
  462. package/src/renderers/electrobun/bun/desktop/initialization.test.ts +27 -0
  463. package/src/renderers/electrobun/bun/desktop/initialization.ts +6 -1
  464. package/src/renderers/electrobun/bun/index.ts +1 -0
  465. package/src/renderers/electrobun/shared/protocol.ts +2 -0
  466. package/src/renderers/electrobun/view/app-services.ts +25 -2
  467. package/src/renderers/electrobun/view/build-assets.ts +5 -0
  468. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +186 -65
  469. package/src/renderers/electrobun/view/cli-pane-shot-health.test.ts +25 -0
  470. package/src/renderers/electrobun/view/cli-pane-shot-health.ts +31 -0
  471. package/src/renderers/electrobun/view/data-table/index.tsx +10 -11
  472. package/src/renderers/electrobun/view/data-table/row.tsx +2 -0
  473. package/src/renderers/electrobun/view/desktop/controls.test.tsx +68 -0
  474. package/src/renderers/electrobun/view/desktop/controls.tsx +25 -1
  475. package/src/renderers/electrobun/view/host/box.test.tsx +80 -0
  476. package/src/renderers/electrobun/view/host/media-surface.tsx +70 -3
  477. package/src/renderers/electrobun/view/host/tabs.tsx +97 -5
  478. package/src/renderers/electrobun/view/host/text.tsx +5 -2
  479. package/src/renderers/electrobun/view/input-host.test.tsx +140 -0
  480. package/src/renderers/electrobun/view/input-host.tsx +4 -2
  481. package/src/renderers/electrobun/view/native-stubs/backend-rpc.ts +19 -1
  482. package/src/renderers/electrobun/view/native-stubs/broker-sync-native-loader.ts +7 -0
  483. package/src/renderers/electrobun/view/native-stubs/chart/kitty-support.ts +10 -0
  484. package/src/renderers/electrobun/view/remote/capability-invoker.test.ts +46 -0
  485. package/src/renderers/electrobun/view/remote/capability-invoker.ts +34 -13
  486. package/src/renderers/electrobun/view/remote/connection-health-backend.ts +19 -0
  487. package/src/renderers/electrobun/view/remote/connection-health.test.ts +44 -0
  488. package/src/renderers/electrobun/view/remote/connection-health.ts +49 -0
  489. package/src/renderers/electrobun/view/styles.css +5 -0
  490. package/src/renderers/opentui/dialog-host.tsx +279 -80
  491. package/src/renderers/opentui/host.tsx +15 -1
  492. package/src/renderers/opentui/terminal-media.test.ts +102 -0
  493. package/src/renderers/opentui/terminal-media.ts +142 -0
  494. package/src/renderers/opentui/test-utils.tsx +9 -6
  495. package/src/renderers/opentui/toast-host.tsx +136 -7
  496. package/src/renderers/opentui/ui-host.tsx +19 -3
  497. package/src/sources/provider-router/connection-health.test.ts +28 -0
  498. package/src/sources/provider-router/connection-health.ts +47 -0
  499. package/src/sources/provider-router/history.test.ts +53 -4
  500. package/src/sources/provider-router/history.ts +119 -14
  501. package/src/sources/provider-router/index.ts +12 -1
  502. package/src/sources/yahoo-finance/history.ts +2 -9
  503. package/src/sources/yahoo-finance/http.ts +1 -1
  504. package/src/sources/yahoo-finance/symbols.ts +1 -1
  505. package/src/sources/yahoo-finance/types.ts +19 -0
  506. package/src/state/app/context/index.test.tsx +101 -0
  507. package/src/state/app/context/index.tsx +10 -0
  508. package/src/state/config-save-scheduler.ts +1 -6
  509. package/src/state/persist-scheduler.test.ts +31 -0
  510. package/src/state/persist-scheduler.ts +21 -8
  511. package/src/sync/auth-transition.test.ts +36 -0
  512. package/src/sync/auth-transition.ts +22 -0
  513. package/src/sync/core-contributors.test.ts +58 -1
  514. package/src/sync/core-contributors.ts +39 -2
  515. package/src/sync/react.ts +9 -0
  516. package/src/test-support/plugin-runtime.ts +3 -0
  517. package/src/theme/font-scale.test.ts +14 -0
  518. package/src/theme/font-scale.ts +69 -0
  519. package/src/tickers/search/index.test.ts +284 -2
  520. package/src/tickers/search/index.ts +55 -18
  521. package/src/tickers/search/ranking.ts +143 -18
  522. package/src/tickers/search/types.ts +1 -0
  523. package/src/time-series/alignment.ts +15 -11
  524. package/src/time-series/field-catalog.ts +10 -0
  525. package/src/time-series/fundamentals.ts +19 -11
  526. package/src/time-series/hooks.ts +8 -2
  527. package/src/time-series/parsed-history-cache.ts +35 -0
  528. package/src/time-series/resolution.ts +10 -0
  529. package/src/time-series/resolve.test.ts +332 -3
  530. package/src/time-series/resolve.ts +225 -25
  531. package/src/time-series/spec-studies.test.ts +23 -0
  532. package/src/time-series/spec.ts +21 -2
  533. package/src/time-series/transforms.test.ts +76 -1
  534. package/src/time-series/transforms.ts +39 -8
  535. package/src/time-series/types.ts +9 -2
  536. package/src/time-series/use-chart-resolution.test.tsx +105 -1
  537. package/src/time-series/use-chart-resolution.ts +67 -13
  538. package/src/types/config.ts +30 -27
  539. package/src/types/media.ts +2 -0
  540. package/src/types/plugin.ts +8 -1
  541. package/src/ui/host.tsx +4 -0
  542. package/src/ui/pointer-drag.ts +12 -0
  543. package/src/ui/qr.test.ts +79 -0
  544. package/src/ui/qr.ts +82 -0
  545. package/src/updater/github-releases.test.ts +3 -3
  546. package/src/updater/github-releases.ts +1 -1
  547. package/src/updater.test.ts +117 -16
  548. package/src/updater.ts +32 -5
  549. package/src/utils/exchanges.ts +1 -0
  550. package/src/utils/external-url.test.ts +32 -0
  551. package/src/utils/external-url.ts +23 -0
  552. package/src/utils/sort-values.ts +31 -0
  553. package/src/utils/zoned-date-time.ts +67 -0
  554. package/src/components/onboarding/wizard-keyboard.ts +0 -306
  555. package/src/plugins/builtin/chart-composer/date-window-dialog.tsx +0 -120
@@ -0,0 +1,281 @@
1
+ import { afterEach, expect, test } from "bun:test";
2
+ import { setHttpFetchTransport } from "../../utils/http-transport";
3
+ import {
4
+ predictionChartSeriesCapability,
5
+ predictionHistoryPoints,
6
+ predictionSeriesId,
7
+ } from "./capability";
8
+ import {
9
+ attachPredictionMarketsPersistence,
10
+ resetPredictionMarketsPersistence,
11
+ } from "./services/fetch";
12
+ import { MemoryPersistence } from "./test-helpers";
13
+ import type { PredictionMarketSummary } from "./types";
14
+
15
+ function json(value: unknown) {
16
+ return new Response(JSON.stringify(value), { status: 200 });
17
+ }
18
+
19
+ function identity(overrides: Partial<PredictionMarketSummary>): PredictionMarketSummary {
20
+ return {
21
+ venue: "polymarket",
22
+ marketId: "market-1",
23
+ eventId: "event-1",
24
+ ...overrides,
25
+ } as PredictionMarketSummary;
26
+ }
27
+
28
+ afterEach(() => {
29
+ setHttpFetchTransport(null);
30
+ resetPredictionMarketsPersistence();
31
+ });
32
+
33
+ test("prediction chart history restores persisted dates, drops invalid rows, and sorts observations", () => {
34
+ const points = predictionHistoryPoints([
35
+ { date: "2026-02-02T00:00:00Z" as unknown as Date, close: 0.6 },
36
+ { date: new Date("invalid"), close: 0.9 },
37
+ { date: "2026-02-01T00:00:00Z" as unknown as Date, close: 0.4, open: 0.3 },
38
+ ], "polymarket");
39
+
40
+ expect(points.map((point) => [point.date.toISOString(), point.value])).toEqual([
41
+ ["2026-02-01T00:00:00.000Z", 0.4],
42
+ ["2026-02-02T00:00:00.000Z", 0.6],
43
+ ]);
44
+ expect(points[0]).toMatchObject({ open: 0.3, provenance: { providerId: "polymarket", quality: "reported" } });
45
+ });
46
+
47
+ test("stable prediction IDs retain provider lookup identity", () => {
48
+ expect(predictionSeriesId(identity({ venue: "kalshi", eventTicker: "EVT-1", marketId: "MKT-1" })))
49
+ .toBe("kalshi/EVT-1/MKT-1");
50
+ expect(predictionSeriesId(identity({ venue: "polymarket", eventId: "123", marketId: "456" })))
51
+ .toBe("polymarket/123/456");
52
+ expect(predictionSeriesId(identity({ venue: "polymarket", eventId: undefined }))).toBeNull();
53
+ });
54
+
55
+ test("catalog limit bounds provider search hydration", async () => {
56
+ const requested: string[] = [];
57
+ setHttpFetchTransport(async (url) => {
58
+ requested.push(url);
59
+ if (url.includes("public-search")) {
60
+ return json({ events: Array.from({ length: 5 }, (_, index) => ({ id: `event-${index + 1}` })) });
61
+ }
62
+ if (url.includes("gamma-api.polymarket.com/events/event-")) {
63
+ const eventId = url.split("/").at(-1)!;
64
+ return json({
65
+ id: eventId,
66
+ title: `Rates ${eventId}`,
67
+ markets: [{
68
+ id: `market-${eventId}`,
69
+ question: `Rates ${eventId}`,
70
+ active: true,
71
+ closed: false,
72
+ outcomes: ["Yes", "No"],
73
+ outcomePrices: ["0.5", "0.5"],
74
+ clobTokenIds: [`yes-${eventId}`, `no-${eventId}`],
75
+ }],
76
+ });
77
+ }
78
+ if (url.includes("api.elections.kalshi.com/trade-api/v2/events")) {
79
+ return json({ events: [] });
80
+ }
81
+ throw new Error(`Unexpected URL ${url}`);
82
+ });
83
+
84
+ const items = await predictionChartSeriesCapability.provider.search!({ query: "rates", limit: 2 });
85
+
86
+ expect(items).toHaveLength(2);
87
+ expect(requested.filter((url) => url.includes("gamma-api.polymarket.com/events/event-"))).toHaveLength(2);
88
+ expect(requested.some((url) => url.includes("api.elections.kalshi.com") && url.includes("limit=20"))).toBe(true);
89
+ });
90
+
91
+ test("polymarket resolution reloads the event and follows a rotated YES token", async () => {
92
+ const requested: string[] = [];
93
+ setHttpFetchTransport(async (url) => {
94
+ requested.push(url);
95
+ if (url.endsWith("/events/event-1")) {
96
+ return json({
97
+ id: "event-1",
98
+ title: "Current event",
99
+ markets: [{
100
+ id: "market-1",
101
+ question: "Current market title",
102
+ active: true,
103
+ closed: false,
104
+ outcomes: ["Yes", "No"],
105
+ outcomePrices: ["0.7", "0.3"],
106
+ clobTokenIds: ["rotated-yes", "rotated-no"],
107
+ }],
108
+ });
109
+ }
110
+ if (url.includes("prices-history")) return json({ history: [{ t: 1_700_000_000, p: 0.7 }] });
111
+ throw new Error(`Unexpected URL ${url}`);
112
+ });
113
+
114
+ const resolved = await predictionChartSeriesCapability.provider.resolve({
115
+ seriesId: "polymarket/event-1/market-1",
116
+ viewport: { range: "1M", resolution: "auto" },
117
+ });
118
+
119
+ expect(resolved.label).toBe("Current market title");
120
+ expect(resolved.points[0]?.value).toBe(0.7);
121
+ expect(requested.some((url) => url.includes("market=rotated-yes"))).toBe(true);
122
+ });
123
+
124
+ test("polymarket pans request and cache their bounded history windows", async () => {
125
+ attachPredictionMarketsPersistence(new MemoryPersistence());
126
+ const historyRequests: string[] = [];
127
+ setHttpFetchTransport(async (url) => {
128
+ if (url.endsWith("/events/event-1")) {
129
+ return json({
130
+ id: "event-1",
131
+ title: "Current event",
132
+ markets: [{
133
+ id: "market-1",
134
+ question: "Current market title",
135
+ active: true,
136
+ closed: false,
137
+ outcomes: ["Yes", "No"],
138
+ outcomePrices: ["0.7", "0.3"],
139
+ clobTokenIds: ["yes-token", "no-token"],
140
+ }],
141
+ });
142
+ }
143
+ if (url.includes("prices-history")) {
144
+ historyRequests.push(url);
145
+ const start = Number(new URL(url).searchParams.get("startTs"));
146
+ return json({ history: [{ t: start + 1, p: 0.7 }] });
147
+ }
148
+ throw new Error(`Unexpected URL ${url}`);
149
+ });
150
+ const viewport = (start: string, end: string) => ({
151
+ range: "1W" as const,
152
+ resolution: "auto" as const,
153
+ dateWindow: { start, end },
154
+ });
155
+ const january = viewport("2026-01-01T00:00:00.000Z", "2026-01-08T00:00:00.000Z");
156
+ const february = viewport("2026-02-01T00:00:00.000Z", "2026-02-08T00:00:00.000Z");
157
+
158
+ await predictionChartSeriesCapability.provider.resolve({
159
+ seriesId: "polymarket/event-1/market-1",
160
+ viewport: january,
161
+ });
162
+ const panned = await predictionChartSeriesCapability.provider.resolve({
163
+ seriesId: "polymarket/event-1/market-1",
164
+ viewport: february,
165
+ });
166
+ await predictionChartSeriesCapability.provider.resolve({
167
+ seriesId: "polymarket/event-1/market-1",
168
+ viewport: january,
169
+ });
170
+
171
+ expect(historyRequests).toHaveLength(2);
172
+ expect(historyRequests.map((url) => {
173
+ const params = new URL(url).searchParams;
174
+ return [params.get("startTs"), params.get("endTs"), params.get("interval")];
175
+ })).toEqual([
176
+ [String(Date.parse(january.dateWindow.start) / 1000), String(Date.parse(january.dateWindow.end) / 1000), null],
177
+ [String(Date.parse(february.dateWindow.start) / 1000), String(Date.parse(february.dateWindow.end) / 1000), null],
178
+ ]);
179
+ expect(panned.points[0]?.date.toISOString()).toBe("2026-02-01T00:00:01.000Z");
180
+ });
181
+
182
+ test("aborting catalog search stops Polymarket event hydration without caching it", async () => {
183
+ attachPredictionMarketsPersistence(new MemoryPersistence());
184
+ let searchRequests = 0;
185
+ let eventRequests = 0;
186
+ let markHydrationStarted: (() => void) | undefined;
187
+ const hydrationStarted = new Promise<void>((resolve) => {
188
+ markHydrationStarted = resolve;
189
+ });
190
+ setHttpFetchTransport(async (url, init) => {
191
+ if (url.includes("public-search")) {
192
+ searchRequests += 1;
193
+ return json({ events: [{ id: "event-1" }] });
194
+ }
195
+ if (url.endsWith("/events/event-1")) {
196
+ eventRequests += 1;
197
+ if (eventRequests === 1) {
198
+ markHydrationStarted?.();
199
+ return await new Promise<Response>((_resolve, reject) => {
200
+ init?.signal?.addEventListener("abort", () => reject(init.signal?.reason), { once: true });
201
+ });
202
+ }
203
+ return json({
204
+ id: "event-1",
205
+ title: "Rates",
206
+ markets: [{
207
+ id: "market-1",
208
+ question: "Rates",
209
+ active: true,
210
+ closed: false,
211
+ outcomes: ["Yes", "No"],
212
+ outcomePrices: ["0.5", "0.5"],
213
+ clobTokenIds: ["yes", "no"],
214
+ }],
215
+ });
216
+ }
217
+ if (url.includes("api.elections.kalshi.com")) return json({ events: [] });
218
+ throw new Error(`Unexpected URL ${url}`);
219
+ });
220
+
221
+ const controller = new AbortController();
222
+ const pending = predictionChartSeriesCapability.provider.search!({
223
+ query: "rates",
224
+ limit: 1,
225
+ signal: controller.signal,
226
+ });
227
+ await hydrationStarted;
228
+ controller.abort();
229
+ await expect(pending).rejects.toMatchObject({ name: "AbortError" });
230
+
231
+ const retried = await predictionChartSeriesCapability.provider.search!({ query: "rates", limit: 1 });
232
+ expect(retried).toHaveLength(1);
233
+ expect(searchRequests).toBe(2);
234
+ expect(eventRequests).toBe(2);
235
+ });
236
+
237
+ test("kalshi resolution reloads the event before requesting current market history", async () => {
238
+ const requested: string[] = [];
239
+ setHttpFetchTransport(async (url) => {
240
+ requested.push(url);
241
+ if (url.endsWith("/events/EVT-1")) {
242
+ return json({
243
+ event: { title: "Current event", event_ticker: "EVT-1", series_ticker: "SERIES-NEW" },
244
+ markets: [{
245
+ ticker: "MKT-1",
246
+ event_ticker: "EVT-1",
247
+ title: "Current Kalshi market",
248
+ status: "active",
249
+ market_type: "binary",
250
+ last_price_dollars: "0.55",
251
+ }],
252
+ });
253
+ }
254
+ if (url.includes("/candlesticks")) {
255
+ return json({ candlesticks: [{ end_period_ts: 1_700_000_000, price: { close_dollars: "0.55" } }] });
256
+ }
257
+ throw new Error(`Unexpected URL ${url}`);
258
+ });
259
+
260
+ const resolved = await predictionChartSeriesCapability.provider.resolve({
261
+ seriesId: "kalshi/EVT-1/MKT-1",
262
+ viewport: {
263
+ range: "1W",
264
+ resolution: "auto",
265
+ dateWindow: { start: "2026-01-01T00:00:00.000Z", end: "2026-01-08T00:00:00.000Z" },
266
+ },
267
+ });
268
+
269
+ expect(resolved.label).toBe("Current Kalshi market");
270
+ expect(requested.some((url) => url.includes("/series/SERIES-NEW/markets/MKT-1/candlesticks"))).toBe(true);
271
+ expect(requested.some((url) => url.includes("start_ts=1767225600") && url.includes("end_ts=1767830400"))).toBe(true);
272
+ });
273
+
274
+ test("resolution reports an actionable error when a persisted market disappears", async () => {
275
+ setHttpFetchTransport(async () => json({ id: "event-1", title: "Current event", markets: [] }));
276
+
277
+ await expect(predictionChartSeriesCapability.provider.resolve({
278
+ seriesId: "polymarket/event-1/missing",
279
+ viewport: { range: "1M", resolution: "auto" },
280
+ })).rejects.toThrow("no longer resolvable. Remove or replace this chart series");
281
+ });
@@ -0,0 +1,171 @@
1
+ import { chartSeriesProvider, type ChartSeriesCatalogItem, type ChartSeriesResolveRequest } from "../../capabilities";
2
+ import { debugLog } from "../../utils/debug-log";
3
+ import type { ResolvedSeries, TimeSeriesPoint } from "../../time-series/types";
4
+ import {
5
+ loadKalshiCatalog,
6
+ loadKalshiHistory,
7
+ resolveKalshiChartSummary,
8
+ } from "./services/kalshi/adapter";
9
+ import { loadPolymarketCatalog } from "./services/polymarket/adapter";
10
+ import {
11
+ loadPolymarketHistory,
12
+ resolvePolymarketChartSummary,
13
+ } from "./services/polymarket/detail";
14
+ import type { PredictionHistoryPoint, PredictionHistoryRange, PredictionMarketSummary, PredictionVenue } from "./types";
15
+
16
+ export const PREDICTION_CHART_SERIES_CAPABILITY_ID = "prediction-markets.series";
17
+ const predictionSeriesLog = debugLog.createLogger("prediction-markets.series");
18
+
19
+ interface PredictionSeriesIdentity {
20
+ venue: PredictionVenue;
21
+ eventId: string;
22
+ marketId: string;
23
+ }
24
+
25
+ export function predictionSeriesId(summary: PredictionMarketSummary): string | null {
26
+ const eventId = summary.venue === "kalshi" ? summary.eventTicker : summary.eventId;
27
+ if (!eventId?.trim() || !summary.marketId.trim()) return null;
28
+ const id = `${summary.venue}/${encodeURIComponent(eventId)}/${encodeURIComponent(summary.marketId)}`;
29
+ return id.length <= 240 ? id : null;
30
+ }
31
+
32
+ function parsePredictionSeriesId(seriesId: string): PredictionSeriesIdentity {
33
+ const parts = seriesId.split("/");
34
+ if (parts.length !== 3 || (parts[0] !== "kalshi" && parts[0] !== "polymarket")) {
35
+ throw new Error(`Prediction series ID ${seriesId} is invalid. Remove and add the series again.`);
36
+ }
37
+ try {
38
+ const eventId = decodeURIComponent(parts[1] ?? "");
39
+ const marketId = decodeURIComponent(parts[2] ?? "");
40
+ if (!eventId || !marketId) throw new Error("missing identity");
41
+ return { venue: parts[0], eventId, marketId };
42
+ } catch {
43
+ throw new Error(`Prediction series ID ${seriesId} is invalid. Remove and add the series again.`);
44
+ }
45
+ }
46
+
47
+ function historyRange(request: ChartSeriesResolveRequest): PredictionHistoryRange {
48
+ if (request.viewport.dateWindow) {
49
+ const span = Date.parse(request.viewport.dateWindow.end) - Date.parse(request.viewport.dateWindow.start);
50
+ if (span <= 24 * 60 * 60_000) return "1D";
51
+ if (span <= 7 * 24 * 60 * 60_000) return "1W";
52
+ if (span <= 31 * 24 * 60 * 60_000) return "1M";
53
+ return "ALL";
54
+ }
55
+ switch (request.viewport.range) {
56
+ case "1D": return "1D";
57
+ case "1W": return "1W";
58
+ case "1M": return "1M";
59
+ default: return "ALL";
60
+ }
61
+ }
62
+
63
+ export function predictionHistoryPoints(points: PredictionHistoryPoint[], venue: string): TimeSeriesPoint[] {
64
+ return points.flatMap((point) => {
65
+ const date = point.date instanceof Date ? new Date(point.date) : new Date(point.date as unknown as string);
66
+ if (!Number.isFinite(date.getTime()) || !Number.isFinite(point.close)) return [];
67
+ return [{
68
+ date,
69
+ observedAt: new Date(date),
70
+ value: point.close,
71
+ open: point.open,
72
+ high: point.high,
73
+ low: point.low,
74
+ close: point.close,
75
+ volume: point.volume,
76
+ provenance: { providerId: venue, quality: "reported" as const },
77
+ }];
78
+ }).sort((left, right) => left.date.getTime() - right.date.getTime());
79
+ }
80
+
81
+ function catalogItem(summary: PredictionMarketSummary): ChartSeriesCatalogItem | null {
82
+ const seriesId = predictionSeriesId(summary);
83
+ if (!seriesId) return null;
84
+ return {
85
+ seriesId,
86
+ label: summary.title.slice(0, 160),
87
+ description: `${summary.venue === "kalshi" ? "Kalshi" : "Polymarket"} · YES probability`,
88
+ detail: summary.venue === "kalshi" ? "Kalshi" : "Polymarket",
89
+ style: "area",
90
+ };
91
+ }
92
+
93
+ async function catalog(
94
+ query = "",
95
+ limit = 8,
96
+ signal?: AbortSignal,
97
+ ): Promise<ChartSeriesCatalogItem[]> {
98
+ const providers = [
99
+ { id: "polymarket", load: () => loadPolymarketCatalog(query, "all", { limit, signal }) },
100
+ { id: "kalshi", load: () => loadKalshiCatalog(query, "all", { limit, signal }) },
101
+ ] as const;
102
+ const results = await Promise.allSettled(providers.map((provider) => provider.load()));
103
+ if (signal?.aborted) throw signal.reason ?? new DOMException("Aborted", "AbortError");
104
+ const failures = results.flatMap((result, index) => {
105
+ if (result.status === "fulfilled") return [];
106
+ const error = result.reason instanceof Error ? result.reason.message : String(result.reason);
107
+ predictionSeriesLog.warn("Prediction chart catalog provider failed", {
108
+ provider: providers[index]?.id,
109
+ error,
110
+ });
111
+ return [`${providers[index]?.id}: ${error}`];
112
+ });
113
+ if (failures.length === providers.length) {
114
+ throw new Error(`Prediction chart catalog failed (${failures.join("; ")}).`);
115
+ }
116
+ return results
117
+ .flatMap((result) => result.status === "fulfilled" ? result.value : [])
118
+ .sort((left, right) => (right.volume24h ?? 0) - (left.volume24h ?? 0))
119
+ .flatMap((summary) => {
120
+ const item = catalogItem(summary);
121
+ return item ? [item] : [];
122
+ })
123
+ .slice(0, limit);
124
+ }
125
+
126
+ async function resolveCurrentSummary(identity: PredictionSeriesIdentity, signal?: AbortSignal) {
127
+ return identity.venue === "kalshi"
128
+ ? resolveKalshiChartSummary(identity.eventId, identity.marketId, signal)
129
+ : resolvePolymarketChartSummary(identity.eventId, identity.marketId, signal);
130
+ }
131
+
132
+ export const predictionChartSeriesCapability = chartSeriesProvider({
133
+ id: PREDICTION_CHART_SERIES_CAPABILITY_ID,
134
+ name: "Prediction Markets",
135
+ provider: {
136
+ catalog: (request) => catalog(request.query, request.limit, request.signal),
137
+ search: (request) => catalog(request.query, request.limit, request.signal),
138
+ async resolve(request): Promise<ResolvedSeries> {
139
+ const identity = parsePredictionSeriesId(request.seriesId);
140
+ const summary = await resolveCurrentSummary(identity, request.signal);
141
+ const range = historyRange(request);
142
+ const dateWindow = request.viewport.dateWindow;
143
+ const historyOptions = {
144
+ ...(dateWindow ? {
145
+ start: new Date(dateWindow.start),
146
+ end: new Date(dateWindow.end),
147
+ } : {}),
148
+ signal: request.signal,
149
+ strict: true,
150
+ };
151
+ const history = summary.venue === "kalshi"
152
+ ? await loadKalshiHistory(summary, range, historyOptions)
153
+ : await loadPolymarketHistory(summary, range, historyOptions);
154
+ return {
155
+ id: request.seriesId,
156
+ label: summary.title.slice(0, 160),
157
+ color: "#4dabf7",
158
+ unit: "probability",
159
+ unitGroup: "probability",
160
+ nativeFrequency: "auto",
161
+ dataShape: "scalar",
162
+ style: "area",
163
+ transform: "raw",
164
+ axis: "left",
165
+ panelId: "main",
166
+ interpolation: "none",
167
+ points: predictionHistoryPoints(history, summary.venue),
168
+ };
169
+ },
170
+ },
171
+ });
@@ -1,11 +1,10 @@
1
1
  import { Box, Text } from "../../ui";
2
2
  import { useMemo } from "react";
3
- import { TextAttributes } from "../../ui";
3
+ import { Tabs } from "../../components";
4
4
  import {
5
5
  CompositeChart,
6
6
  pricePointsToResolvedSeries,
7
7
  } from "../../components/chart/composite";
8
- import type { ChartMouseEvent } from "../../components/chart/core/pointer";
9
8
  import { EmptyState } from "../../components/ui/status";
10
9
  import { colors } from "../../theme/colors";
11
10
  import { formatNumber, formatPercentRaw } from "../../utils/format";
@@ -40,38 +39,27 @@ function toPricePoints(points: PredictionHistoryPoint[]): PricePoint[] {
40
39
  });
41
40
  }
42
41
 
42
+ const RANGE_TABS = RANGES.map((entry) => ({ label: entry, value: entry }));
43
+
44
+ /** Shared Tabs so the range picker keeps keyboard navigation, not just clicks. */
43
45
  function PredictionRangeTabs({
44
46
  activeRange,
47
+ focused,
45
48
  onRangeSelect,
46
49
  }: {
47
50
  activeRange: PredictionHistoryRange;
51
+ focused: boolean;
48
52
  onRangeSelect: (range: PredictionHistoryRange) => void;
49
53
  }) {
50
54
  return (
51
- <Box flexDirection="row" gap={1}>
52
- {RANGES.map((entry) => {
53
- const active = entry === activeRange;
54
- return (
55
- <Box
56
- key={entry}
57
- onMouseDown={(event: ChartMouseEvent) => {
58
- event.preventDefault?.();
59
- onRangeSelect(entry);
60
- }}
61
- cursor="pointer"
62
- data-gloom-interactive="true"
63
- data-gloom-label={`${entry} prediction history`}
64
- >
65
- <Text
66
- fg={active ? colors.textBright : colors.textDim}
67
- attributes={active ? TextAttributes.BOLD : 0}
68
- >
69
- {entry}
70
- </Text>
71
- </Box>
72
- );
73
- })}
74
- </Box>
55
+ <Tabs
56
+ tabs={RANGE_TABS}
57
+ activeValue={activeRange}
58
+ onSelect={(value) => onRangeSelect(value as PredictionHistoryRange)}
59
+ compact
60
+ variant="bare"
61
+ focused={focused}
62
+ />
75
63
  );
76
64
  }
77
65
 
@@ -80,6 +68,7 @@ export function PredictionMarketChart({
80
68
  width,
81
69
  height,
82
70
  loading = false,
71
+ focused = false,
83
72
  range,
84
73
  onRangeSelect,
85
74
  }: {
@@ -87,6 +76,7 @@ export function PredictionMarketChart({
87
76
  width: number;
88
77
  height: number;
89
78
  loading?: boolean;
79
+ focused?: boolean;
90
80
  range: PredictionHistoryRange;
91
81
  onRangeSelect: (range: PredictionHistoryRange) => void;
92
82
  }) {
@@ -98,6 +88,7 @@ export function PredictionMarketChart({
98
88
  <Box flexDirection="row" height={1}>
99
89
  <PredictionRangeTabs
100
90
  activeRange={range}
91
+ focused={focused}
101
92
  onRangeSelect={onRangeSelect}
102
93
  />
103
94
  </Box>
@@ -135,6 +126,7 @@ export function PredictionMarketChart({
135
126
  <Box flexDirection="row" height={1}>
136
127
  <PredictionRangeTabs
137
128
  activeRange={range}
129
+ focused={focused}
138
130
  onRangeSelect={onRangeSelect}
139
131
  />
140
132
  <Box flexGrow={1} />
@@ -53,8 +53,10 @@ export const PREDICTION_COLUMN_DEFS: PredictionColumnDef[] = [
53
53
  },
54
54
  {
55
55
  id: "open_interest",
56
+ // One cell wider than the value needs: OI is right aligned and ENDS is left
57
+ // aligned, so without the slack the two headers read as one "OI ENDS" word.
56
58
  label: "OI",
57
- width: 10,
59
+ width: 11,
58
60
  align: "right",
59
61
  description: "Open interest.",
60
62
  },
@@ -125,7 +127,6 @@ export const DEFAULT_PREDICTION_COLUMN_IDS = [
125
127
  "vol_24h",
126
128
  "open_interest",
127
129
  "ends",
128
- "status",
129
130
  ];
130
131
 
131
132
  export const PREDICTION_COLUMNS_BY_ID = new Map(
@@ -11,6 +11,7 @@ import {
11
11
  formatPredictionLoadError,
12
12
  getPredictionCatalogStatus,
13
13
  } from "./status";
14
+ import { useAutoRefresh } from "../../builtin/shared/auto-refresh";
14
15
  import { getCachedPredictionResource } from "../services/fetch";
15
16
  import { loadKalshiCatalog } from "../services/kalshi/adapter";
16
17
  import { loadPolymarketCatalog } from "../services/polymarket/adapter";
@@ -43,6 +44,8 @@ export function usePredictionCatalogData({
43
44
  Record<string, string | null>
44
45
  >({});
45
46
  const [debouncedSearchQuery, setDebouncedSearchQuery] = useState(searchQuery);
47
+ const [polymarketLoadedAt, setPolymarketLoadedAt] = useState<number | null>(null);
48
+ const [kalshiLoadedAt, setKalshiLoadedAt] = useState<number | null>(null);
46
49
  const activeCatalogRef = useRef<PredictionCatalogCache>({});
47
50
 
48
51
  const normalizedCatalogQuery = debouncedSearchQuery.trim().toLowerCase();
@@ -198,6 +201,7 @@ export function usePredictionCatalogData({
198
201
  ),
199
202
  );
200
203
  } finally {
204
+ setPolymarketLoadedAt(Date.now());
201
205
  if (showPending) {
202
206
  setCatalogPending((current) =>
203
207
  updatePredictionPendingCounts(current, cacheKey, -1),
@@ -248,6 +252,7 @@ export function usePredictionCatalogData({
248
252
  ),
249
253
  );
250
254
  } finally {
255
+ setKalshiLoadedAt(Date.now());
251
256
  if (showPending) {
252
257
  setCatalogPending((current) =>
253
258
  updatePredictionPendingCounts(current, cacheKey, -1),
@@ -269,6 +274,8 @@ export function usePredictionCatalogData({
269
274
  return () => clearTimeout(timeoutId);
270
275
  }, [searchQuery]);
271
276
 
277
+ // Reloads follow the one refresh cadence the user configured, instead of two
278
+ // hardcoded intervals nobody can change.
272
279
  useEffect(() => {
273
280
  if (!includePolymarket) return;
274
281
  void loadPolymarket(
@@ -276,14 +283,6 @@ export function usePredictionCatalogData({
276
283
  debouncedSearchQuery,
277
284
  categoryId,
278
285
  );
279
- const intervalId = setInterval(() => {
280
- void loadPolymarket(
281
- polymarketCatalogKey,
282
- debouncedSearchQuery,
283
- categoryId,
284
- );
285
- }, 30_000);
286
- return () => clearInterval(intervalId);
287
286
  }, [
288
287
  categoryId,
289
288
  debouncedSearchQuery,
@@ -292,13 +291,13 @@ export function usePredictionCatalogData({
292
291
  polymarketCatalogKey,
293
292
  ]);
294
293
 
294
+ useAutoRefresh(includePolymarket ? polymarketLoadedAt : null, useCallback(() => {
295
+ void loadPolymarket(polymarketCatalogKey, debouncedSearchQuery, categoryId);
296
+ }, [categoryId, debouncedSearchQuery, loadPolymarket, polymarketCatalogKey]));
297
+
295
298
  useEffect(() => {
296
299
  if (!includeKalshi) return;
297
300
  void loadKalshi(kalshiCatalogKey, debouncedSearchQuery, categoryId);
298
- const intervalId = setInterval(() => {
299
- void loadKalshi(kalshiCatalogKey, debouncedSearchQuery, categoryId);
300
- }, 20_000);
301
- return () => clearInterval(intervalId);
302
301
  }, [
303
302
  categoryId,
304
303
  debouncedSearchQuery,
@@ -307,6 +306,10 @@ export function usePredictionCatalogData({
307
306
  loadKalshi,
308
307
  ]);
309
308
 
309
+ useAutoRefresh(includeKalshi ? kalshiLoadedAt : null, useCallback(() => {
310
+ void loadKalshi(kalshiCatalogKey, debouncedSearchQuery, categoryId);
311
+ }, [categoryId, debouncedSearchQuery, kalshiCatalogKey, loadKalshi]));
312
+
310
313
  return {
311
314
  allMarkets,
312
315
  catalogLoadCount,
@@ -167,7 +167,10 @@ export function usePredictionDetailData({
167
167
  }, [focused, loadSelectedDetail, selectedSummaryKey, selectedSummaryVenue]);
168
168
 
169
169
  useEffect(() => {
170
+ // Same focus gate as the Kalshi polling above: an unfocused pane must not keep
171
+ // a live socket open.
170
172
  if (
173
+ !focused ||
171
174
  selectedSummaryVenue !== "polymarket" ||
172
175
  (!selectedYesTokenId && !selectedNoTokenId) ||
173
176
  !selectedSummaryKey
@@ -220,6 +223,7 @@ export function usePredictionDetailData({
220
223
  },
221
224
  );
222
225
  }, [
226
+ focused,
223
227
  selectedNoTokenId,
224
228
  selectedSummaryKey,
225
229
  selectedSummaryVenue,