gloomberb 0.9.9 → 0.10.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 (519) hide show
  1. package/README.md +41 -3
  2. package/README.zh-CN.md +294 -0
  3. package/package.json +10 -4
  4. package/patches/@opentui%2Fcore@0.3.2.patch +26 -69
  5. package/src/api-client/auth.ts +2 -0
  6. package/src/api-client/index.test.ts +4 -0
  7. package/src/api-client/types.ts +3 -0
  8. package/src/app/pane-runtime/pane-settings-runtime.tsx +1 -0
  9. package/src/app/pane-template-dialog-wizard.test.tsx +36 -0
  10. package/src/app/pane-template-dialog-wizard.tsx +10 -3
  11. package/src/app/runtime/desktop-deeplink.test.ts +15 -0
  12. package/src/app/runtime/desktop-deeplink.ts +21 -6
  13. package/src/app.tsx +40 -33
  14. package/src/capabilities/service-capabilities.ts +7 -2
  15. package/src/cli/commands/automation.test.ts +202 -0
  16. package/src/cli/commands/automation.ts +153 -36
  17. package/src/cli/commands/market.ts +1 -14
  18. package/src/cli/commands/system.ts +4 -1
  19. package/src/cli/context.ts +1 -1
  20. package/src/cli/desktop-pane-shot.ts +65 -4
  21. package/src/cli/entry.ts +1 -1
  22. package/src/cli/pane-functions/capabilities.ts +532 -0
  23. package/src/cli/pane-functions/catalog.ts +39 -4
  24. package/src/cli/pane-functions/data.test.ts +17 -0
  25. package/src/cli/pane-functions/data.ts +55 -13
  26. package/src/cli/pane-functions/discovery.ts +36 -9
  27. package/src/cli/pane-functions/index.test.ts +104 -1
  28. package/src/cli/pane-functions/index.ts +64 -5
  29. package/src/cli/pane-functions/options.ts +21 -6
  30. package/src/cli/pane-functions/report.ts +735 -72
  31. package/src/cli/pane-functions/resolver.ts +70 -6
  32. package/src/cli/pane-functions/screenshot.test.ts +431 -0
  33. package/src/cli/pane-functions/screenshot.ts +952 -8
  34. package/src/components/chart/composite/column-layout.ts +215 -0
  35. package/src/components/chart/composite/composite-chart.test.tsx +630 -0
  36. package/src/components/chart/composite/composite-chart.tsx +1148 -0
  37. package/src/components/chart/composite/format.test.ts +137 -0
  38. package/src/components/chart/composite/format.ts +135 -0
  39. package/src/components/chart/composite/index.ts +38 -0
  40. package/src/components/chart/composite/interactions.test.ts +109 -0
  41. package/src/components/chart/composite/interactions.ts +247 -0
  42. package/src/components/chart/composite/price-series.test.ts +39 -0
  43. package/src/components/chart/composite/price-series.ts +78 -0
  44. package/src/components/chart/composite/rasterizer.ts +359 -0
  45. package/src/components/chart/composite/renderers.test.ts +610 -0
  46. package/src/components/chart/composite/scene.test.ts +464 -0
  47. package/src/components/chart/composite/scene.ts +553 -0
  48. package/src/components/chart/composite/text-renderer.ts +285 -0
  49. package/src/components/chart/composite/time-scale.test.ts +88 -0
  50. package/src/components/chart/composite/time-scale.ts +217 -0
  51. package/src/components/chart/composite/types.ts +141 -0
  52. package/src/components/chart/core/data.ts +81 -5
  53. package/src/components/chart/core/index.test.ts +128 -43
  54. package/src/components/chart/core/resolution.ts +0 -6
  55. package/src/components/chart/native/chart-rasterizer.ts +1 -3
  56. package/src/components/chart/native/kitty/index.test.ts +99 -120
  57. package/src/components/chart/native/kitty/manager.ts +3 -0
  58. package/src/components/chart/native/kitty/protocol.ts +4 -1
  59. package/src/components/chart/native/raster/crosshair.ts +79 -46
  60. package/src/components/chart/native/raster/types.ts +2 -11
  61. package/src/components/chart/native/surface/manager.ts +48 -3
  62. package/src/components/chart/price-axis-labels.tsx +18 -4
  63. package/src/components/command-bar/commands/direct/index.ts +25 -0
  64. package/src/components/command-bar/commands/registry.ts +9 -0
  65. package/src/components/command-bar/helpers.test.ts +4 -1
  66. package/src/components/command-bar/list/view.tsx +18 -6
  67. package/src/components/command-bar/multi-select-picker.tsx +6 -5
  68. package/src/components/command-bar/pane-settings.test.ts +141 -0
  69. package/src/components/command-bar/pane-settings.ts +61 -1
  70. package/src/components/command-bar/panel/body.tsx +7 -6
  71. package/src/components/command-bar/routes/root/command-items.ts +4 -4
  72. package/src/components/command-bar/routes/root/results.ts +10 -1
  73. package/src/components/command-bar/routes/root/selection.test.ts +54 -0
  74. package/src/components/command-bar/routes/root/selection.ts +13 -0
  75. package/src/components/command-bar/routing/actions.ts +18 -2
  76. package/src/components/command-bar/routing/list-state.ts +2 -1
  77. package/src/components/command-bar/surface/index.test.tsx +36 -4
  78. package/src/components/command-bar/surface/pane.test.tsx +93 -0
  79. package/src/components/command-bar/theme-picker.tsx +2 -1
  80. package/src/components/command-bar/view-model.ts +19 -20
  81. package/src/components/command-bar/workflow/body.tsx +2 -1
  82. package/src/components/command-bar/workflow/field-row.tsx +3 -2
  83. package/src/components/command-bar/workflow/fields.ts +4 -2
  84. package/src/components/command-bar/workflow/ops.test.ts +161 -0
  85. package/src/components/command-bar/workflow/ops.ts +20 -50
  86. package/src/components/command-bar/workflow/route-actions.test.ts +34 -0
  87. package/src/components/command-bar/workflow/route-actions.ts +4 -0
  88. package/src/components/command-bar/workflow/types.ts +1 -0
  89. package/src/components/feed-data-table/stack-view.tsx +6 -5
  90. package/src/components/index.ts +13 -1
  91. package/src/components/layout/detached-pane-shell.tsx +2 -1
  92. package/src/components/layout/floating-pane.tsx +3 -0
  93. package/src/components/layout/header.tsx +15 -8
  94. package/src/components/layout/pane/footer/index.test.tsx +22 -3
  95. package/src/components/layout/pane/footer/index.tsx +3 -1
  96. package/src/components/layout/pane/footer/model.ts +9 -2
  97. package/src/components/layout/pane/header.tsx +4 -6
  98. package/src/components/layout/pane/index.tsx +3 -0
  99. package/src/components/layout/pane/sidebar.test.tsx +100 -0
  100. package/src/components/layout/pane/sidebar.tsx +239 -0
  101. package/src/components/layout/pane/title.ts +4 -3
  102. package/src/components/layout/shell/action-menu-overlay.tsx +5 -3
  103. package/src/components/layout/shell/drag/runtime.ts +2 -0
  104. package/src/components/layout/shell/menu.test.ts +19 -0
  105. package/src/components/layout/shell/menu.ts +8 -5
  106. package/src/components/layout/shell/native/pointer-runtime.ts +4 -1
  107. package/src/components/layout/shell/pane/layers.tsx +2 -2
  108. package/src/components/layout/status-bar.tsx +8 -7
  109. package/src/components/layout/window-controls.tsx +4 -3
  110. package/src/components/metric-treemap/index.tsx +3 -2
  111. package/src/components/onboarding/onboarding-steps.tsx +30 -29
  112. package/src/components/onboarding/onboarding-wizard.tsx +5 -4
  113. package/src/components/onboarding/portfolio-step/broker-fields-panel.tsx +15 -12
  114. package/src/components/onboarding/portfolio-step/broker-setup-panel.tsx +17 -16
  115. package/src/components/onboarding/portfolio-step/broker-sync-panel.tsx +6 -5
  116. package/src/components/onboarding/portfolio-step/choose-panel.tsx +5 -4
  117. package/src/components/pane-settings-dialog/desktop.tsx +25 -3
  118. package/src/components/pane-settings-dialog/field-dialogs.tsx +5 -4
  119. package/src/components/pane-settings-dialog/tui.tsx +11 -9
  120. package/src/components/pane-settings-dialog/value.ts +8 -5
  121. package/src/components/pane-settings-dialog.test.tsx +235 -0
  122. package/src/components/pane-settings-dialog.tsx +24 -0
  123. package/src/components/pane-template-wizard.tsx +6 -2
  124. package/src/components/ticker/list-table-view.tsx +3 -2
  125. package/src/components/ui/button.tsx +3 -1
  126. package/src/components/ui/choice-dialog.tsx +6 -1
  127. package/src/components/ui/confirm-dialog.tsx +3 -2
  128. package/src/components/ui/fields.tsx +10 -1
  129. package/src/components/ui/frame.tsx +4 -1
  130. package/src/components/ui/index.ts +5 -0
  131. package/src/components/ui/inline-quick-add.tsx +104 -0
  132. package/src/components/ui/list-view.tsx +5 -4
  133. package/src/components/ui/loading.tsx +2 -1
  134. package/src/components/ui/multi-select/dialog.tsx +188 -44
  135. package/src/components/ui/popover.tsx +10 -0
  136. package/src/components/ui/shortcut-hint.tsx +2 -0
  137. package/src/components/ui/status.tsx +4 -3
  138. package/src/components/ui/tabs.tsx +19 -8
  139. package/src/components/ui/toggle.tsx +44 -3
  140. package/src/components/ui/ui.test.tsx +67 -5
  141. package/src/core/app-services.ts +4 -6
  142. package/src/core/state/session-persistence.ts +3 -2
  143. package/src/data/config/chart-settings.ts +712 -0
  144. package/src/data/config/layout.ts +24 -5
  145. package/src/data/config/store/index.test.ts +377 -14
  146. package/src/data/config/store/normalize.ts +79 -65
  147. package/src/data/fred-series.ts +137 -0
  148. package/src/i18n/index.test.ts +114 -0
  149. package/src/i18n/index.ts +147 -0
  150. package/src/i18n/ja.ts +1013 -0
  151. package/src/i18n/ko.ts +1011 -0
  152. package/src/i18n/languages.test.ts +31 -0
  153. package/src/i18n/languages.ts +72 -0
  154. package/src/i18n/zh-cn.ts +1013 -0
  155. package/src/i18n/zh-tw.ts +1013 -0
  156. package/src/plugins/builtin/account-management/footer.ts +3 -2
  157. package/src/plugins/builtin/account-management/form-components.tsx +3 -2
  158. package/src/plugins/builtin/account-management/keyboard.ts +6 -0
  159. package/src/plugins/builtin/account-management/model.ts +25 -21
  160. package/src/plugins/builtin/account-management/navigation.test.ts +27 -0
  161. package/src/plugins/builtin/account-management/navigation.ts +24 -0
  162. package/src/plugins/builtin/account-management/pane.tsx +120 -85
  163. package/src/plugins/builtin/account-management/password-dialog.tsx +10 -9
  164. package/src/plugins/builtin/ai/agent-history.ts +139 -0
  165. package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +133 -51
  166. package/src/plugins/builtin/ai/ask-ai.test.tsx +83 -22
  167. package/src/plugins/builtin/ai/index.test.ts +302 -0
  168. package/src/plugins/builtin/ai/index.tsx +374 -40
  169. package/src/plugins/builtin/ai/pane-settings.test.ts +155 -0
  170. package/src/plugins/builtin/ai/pane-settings.ts +319 -0
  171. package/src/plugins/builtin/ai/pi/credential-store.test.ts +80 -0
  172. package/src/plugins/builtin/ai/pi/credential-store.ts +138 -0
  173. package/src/plugins/builtin/ai/pi/file-store.ts +138 -0
  174. package/src/plugins/builtin/ai/pi/host.test.ts +500 -0
  175. package/src/plugins/builtin/ai/pi/host.ts +564 -0
  176. package/src/plugins/builtin/ai/pi/index.ts +5 -0
  177. package/src/plugins/builtin/ai/pi/models-store.test.ts +44 -0
  178. package/src/plugins/builtin/ai/pi/models-store.ts +141 -0
  179. package/src/plugins/builtin/ai/pi/providers.ts +68 -0
  180. package/src/plugins/builtin/ai/pi/runtime.test.ts +240 -0
  181. package/src/plugins/builtin/ai/pi/runtime.ts +635 -0
  182. package/src/plugins/builtin/ai/providers.test.ts +68 -0
  183. package/src/plugins/builtin/ai/providers.ts +163 -38
  184. package/src/plugins/builtin/ai/runner-selection.test.ts +174 -0
  185. package/src/plugins/builtin/ai/runner-selection.ts +193 -0
  186. package/src/plugins/builtin/ai/runner-selector.tsx +153 -0
  187. package/src/plugins/builtin/ai/runner.test.ts +198 -0
  188. package/src/plugins/builtin/ai/runner.ts +276 -73
  189. package/src/plugins/builtin/ai/screener/contract.test.ts +26 -73
  190. package/src/plugins/builtin/ai/screener/contract.ts +14 -52
  191. package/src/plugins/builtin/ai/screener/editor-runtime.ts +55 -5
  192. package/src/plugins/builtin/ai/screener/editor.tsx +42 -36
  193. package/src/plugins/builtin/ai/screener/footer.ts +82 -67
  194. package/src/plugins/builtin/ai/screener/keyboard.ts +27 -11
  195. package/src/plugins/builtin/ai/screener/model.test.ts +24 -0
  196. package/src/plugins/builtin/ai/screener/model.ts +9 -3
  197. package/src/plugins/builtin/ai/screener/pane.test.tsx +142 -55
  198. package/src/plugins/builtin/ai/screener/pane.tsx +95 -97
  199. package/src/plugins/builtin/ai/screener/results-view.tsx +8 -41
  200. package/src/plugins/builtin/ai/screener/runner.ts +41 -25
  201. package/src/plugins/builtin/ai/screener/tabs-bar.tsx +2 -2
  202. package/src/plugins/builtin/ai/use-runtime-providers.ts +28 -0
  203. package/src/plugins/builtin/ai/workspace/model.test.ts +227 -0
  204. package/src/plugins/builtin/ai/workspace/model.ts +308 -0
  205. package/src/plugins/builtin/ai/workspace/pane.test.tsx +197 -0
  206. package/src/plugins/builtin/ai/workspace/pane.tsx +896 -0
  207. package/src/plugins/builtin/analytics/index.test.tsx +3 -3
  208. package/src/plugins/builtin/analytics/index.tsx +3 -8
  209. package/src/plugins/builtin/broker-manager/detail.tsx +25 -24
  210. package/src/plugins/builtin/broker-manager/index.test.tsx +1 -1
  211. package/src/plugins/builtin/broker-manager/index.tsx +9 -11
  212. package/src/plugins/builtin/broker-manager/model.ts +11 -10
  213. package/src/plugins/builtin/broker-manager/pane-actions.tsx +22 -21
  214. package/src/plugins/builtin/broker-manager/table.ts +9 -8
  215. package/src/plugins/builtin/changelog/index.tsx +3 -8
  216. package/src/plugins/builtin/chart-composer/chart-spec.ts +89 -0
  217. package/src/plugins/builtin/chart-composer/cli-options.ts +98 -0
  218. package/src/plugins/builtin/chart-composer/date-window-dialog.tsx +120 -0
  219. package/src/plugins/builtin/chart-composer/editor.test.tsx +332 -0
  220. package/src/plugins/builtin/chart-composer/editor.tsx +1107 -0
  221. package/src/plugins/builtin/chart-composer/index.tsx +228 -0
  222. package/src/plugins/builtin/chart-composer/pane.tsx +645 -0
  223. package/src/plugins/builtin/chart-composer/presets.test.ts +561 -0
  224. package/src/plugins/builtin/chart-composer/presets.ts +759 -0
  225. package/src/plugins/builtin/chart-composer/quick-add.test.tsx +245 -0
  226. package/src/plugins/builtin/chart-composer/quick-add.tsx +409 -0
  227. package/src/plugins/builtin/chart-composer/semantic.ts +86 -0
  228. package/src/plugins/builtin/chart-composer/series-catalog.test.ts +65 -0
  229. package/src/plugins/builtin/chart-composer/series-catalog.ts +295 -0
  230. package/src/plugins/builtin/chart-composer/settings.test.ts +163 -0
  231. package/src/plugins/builtin/chart-composer/settings.ts +397 -0
  232. package/src/plugins/builtin/chart-composer/shortcuts.test.ts +44 -0
  233. package/src/plugins/builtin/chart-composer/shortcuts.ts +35 -0
  234. package/src/plugins/builtin/chart-composer/use-series-catalog.ts +103 -0
  235. package/src/plugins/builtin/chat/channel-labels.ts +5 -4
  236. package/src/plugins/builtin/chat/channels.ts +12 -11
  237. package/src/plugins/builtin/chat/chat.test.tsx +32 -0
  238. package/src/plugins/builtin/chat/content/composer-runtime.ts +6 -1
  239. package/src/plugins/builtin/chat/content/composer.tsx +9 -8
  240. package/src/plugins/builtin/chat/content/index.tsx +36 -1
  241. package/src/plugins/builtin/chat/content/new-dm-dialog.tsx +8 -7
  242. package/src/plugins/builtin/chat/content/transcript.tsx +9 -3
  243. package/src/plugins/builtin/chat/controller/utils.ts +10 -9
  244. package/src/plugins/builtin/chat/message/desktop.tsx +17 -10
  245. package/src/plugins/builtin/chat/message/profile-popover.test.tsx +122 -3
  246. package/src/plugins/builtin/chat/message/profile-popover.tsx +38 -1
  247. package/src/plugins/builtin/chat/message/terminal.tsx +19 -10
  248. package/src/plugins/builtin/chat/pane-state.test.ts +31 -0
  249. package/src/plugins/builtin/chat/pane-state.ts +30 -0
  250. package/src/plugins/builtin/chat/pane.tsx +27 -4
  251. package/src/plugins/builtin/chat/profile-popover.ts +62 -5
  252. package/src/plugins/builtin/chat/sidebar.test.tsx +4 -7
  253. package/src/plugins/builtin/chat/sidebar.tsx +107 -168
  254. package/src/plugins/builtin/chat/test-harness.tsx +6 -0
  255. package/src/plugins/builtin/cloud/auth-actions.tsx +5 -4
  256. package/src/plugins/builtin/cloud/plugin.tsx +150 -127
  257. package/src/plugins/builtin/composite-plugins.ts +71 -0
  258. package/src/plugins/builtin/correlation/index.test.tsx +4 -4
  259. package/src/plugins/builtin/correlation/index.tsx +3 -8
  260. package/src/plugins/builtin/earnings/index.tsx +37 -21
  261. package/src/plugins/builtin/earnings/model.test.ts +52 -0
  262. package/src/plugins/builtin/earnings/model.ts +32 -0
  263. package/src/plugins/builtin/econ/detail-view.tsx +17 -4
  264. package/src/plugins/builtin/econ/fred-cache.test.ts +57 -11
  265. package/src/plugins/builtin/econ/index.tsx +16 -20
  266. package/src/plugins/builtin/fear-greed/index.tsx +2 -8
  267. package/src/plugins/builtin/fx-matrix/index.tsx +3 -8
  268. package/src/plugins/builtin/help/components.tsx +5 -4
  269. package/src/plugins/builtin/help/index.tsx +14 -17
  270. package/src/plugins/builtin/holders/index.tsx +2 -8
  271. package/src/plugins/builtin/insider/index.tsx +2 -7
  272. package/src/plugins/builtin/kelly-sizer/index.test.tsx +2 -2
  273. package/src/plugins/builtin/kelly-sizer/index.tsx +3 -7
  274. package/src/plugins/builtin/kelly-sizer/test-support.tsx +3 -3
  275. package/src/plugins/builtin/layout-manager/index.tsx +3 -7
  276. package/src/plugins/builtin/market-heatmap/index.tsx +3 -8
  277. package/src/plugins/builtin/market-movers/index.tsx +3 -8
  278. package/src/plugins/builtin/news/index.tsx +12 -16
  279. package/src/plugins/builtin/news/wire/breaking/pane.test.tsx +3 -3
  280. package/src/plugins/builtin/news/wire/index.tsx +83 -73
  281. package/src/plugins/builtin/options/index.tsx +2 -8
  282. package/src/plugins/builtin/plugin-module.test.ts +89 -0
  283. package/src/plugins/builtin/plugin-module.ts +116 -0
  284. package/src/plugins/builtin/portfolio-list/header.tsx +4 -3
  285. package/src/plugins/builtin/portfolio-list/index.test.ts +17 -0
  286. package/src/plugins/builtin/portfolio-list/index.tsx +3 -5
  287. package/src/plugins/builtin/portfolio-list/pane/index.test.tsx +3 -3
  288. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +53 -53
  289. package/src/plugins/builtin/portfolio-list/settings.ts +5 -3
  290. package/src/plugins/builtin/portfolio-list/summary/index.tsx +4 -3
  291. package/src/plugins/builtin/research/index.tsx +2 -8
  292. package/src/plugins/builtin/sec/index.tsx +2 -8
  293. package/src/plugins/builtin/sectors/index.tsx +3 -8
  294. package/src/plugins/builtin/thirteenf/index.tsx +3 -8
  295. package/src/plugins/builtin/thirteenf/pane.test.tsx +1 -1
  296. package/src/plugins/builtin/ticker-detail/chart-tab-switch.test.tsx +49 -23
  297. package/src/plugins/builtin/ticker-detail/index.test.tsx +15 -136
  298. package/src/plugins/builtin/ticker-detail/index.tsx +2 -93
  299. package/src/plugins/builtin/ticker-detail/overview/components.tsx +6 -5
  300. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +41 -17
  301. package/src/plugins/builtin/ticker-detail/pane.tsx +4 -3
  302. package/src/plugins/builtin/ticker-detail/quote-monitor/index.test.tsx +1 -1
  303. package/src/plugins/builtin/ticker-detail/settings.ts +3 -50
  304. package/src/plugins/builtin/ticker-research-backend-plugin.ts +17 -0
  305. package/src/plugins/builtin/ticker-research-plugin.ts +27 -0
  306. package/src/plugins/builtin/tv/channels.ts +27 -0
  307. package/src/plugins/builtin/tv/index.tsx +40 -0
  308. package/src/plugins/builtin/tv/pane.tsx +286 -0
  309. package/src/plugins/builtin/tv/youtube-stream.ts +100 -0
  310. package/src/plugins/builtin/world-indices/index.tsx +3 -8
  311. package/src/plugins/builtin/yield-curve/index.tsx +8 -8
  312. package/src/plugins/catalog-backend.test.ts +21 -0
  313. package/src/plugins/catalog-backend.ts +42 -0
  314. package/src/plugins/catalog-ui.ts +6 -14
  315. package/src/plugins/ibkr/instance-selection.test.ts +0 -1
  316. package/src/plugins/ownership.ts +143 -0
  317. package/src/plugins/prediction-markets/backend-plugin.ts +19 -0
  318. package/src/plugins/prediction-markets/chart.test.tsx +7 -5
  319. package/src/plugins/prediction-markets/chart.tsx +25 -7
  320. package/src/plugins/registry/index.test.ts +51 -0
  321. package/src/plugins/registry/index.ts +8 -0
  322. package/src/plugins/registry/pane-settings.ts +1 -1
  323. package/src/plugins/runtime/index.test.tsx +2 -2
  324. package/src/remote/app-host.tsx +16 -3
  325. package/src/remote/controller.test.ts +102 -1
  326. package/src/remote/controller.ts +122 -0
  327. package/src/remote/types.ts +11 -0
  328. package/src/renderers/electrobun/bun/core-capabilities.ts +123 -27
  329. package/src/renderers/electrobun/bun/desktop/backend-requests.ts +5 -1
  330. package/src/renderers/electrobun/bun/desktop/initialization.ts +5 -1
  331. package/src/renderers/electrobun/bun/desktop/media.ts +15 -0
  332. package/src/renderers/electrobun/bun/desktop/window-style.ts +4 -1
  333. package/src/renderers/electrobun/bun/desktop/workspace/index.test.ts +32 -1
  334. package/src/renderers/electrobun/bun/desktop/workspace/index.ts +10 -0
  335. package/src/renderers/electrobun/bun/index.ts +5 -0
  336. package/src/renderers/electrobun/bun/tui-entry.ts +1 -6
  337. package/src/renderers/electrobun/view/ai-host.ts +124 -19
  338. package/src/renderers/electrobun/view/backend-rpc.ts +4 -0
  339. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +151 -39
  340. package/src/renderers/electrobun/view/desktop/popover.tsx +166 -0
  341. package/src/renderers/electrobun/view/dialog-host.test.ts +25 -0
  342. package/src/renderers/electrobun/view/dialog-host.tsx +75 -12
  343. package/src/renderers/electrobun/view/host/box.tsx +5 -0
  344. package/src/renderers/electrobun/view/host/chart-surface.tsx +10 -9
  345. package/src/renderers/electrobun/view/host/input.tsx +9 -19
  346. package/src/renderers/electrobun/view/host/media-surface.tsx +170 -0
  347. package/src/renderers/electrobun/view/host/mouse.ts +1 -0
  348. package/src/renderers/electrobun/view/host/style.ts +1 -1
  349. package/src/renderers/electrobun/view/host/tabs.tsx +4 -3
  350. package/src/renderers/electrobun/view/main.tsx +10 -3
  351. package/src/renderers/electrobun/view/styles.css +17 -0
  352. package/src/renderers/electrobun/view/ui-host.tsx +7 -0
  353. package/src/renderers/opentui/chart-surface.test.tsx +52 -6
  354. package/src/renderers/opentui/chart-surface.tsx +95 -11
  355. package/src/renderers/opentui/dialog-host.tsx +36 -5
  356. package/src/renderers/opentui/host.tsx +39 -0
  357. package/src/renderers/opentui/start.tsx +30 -0
  358. package/src/renderers/opentui/ui-host.tsx +2 -0
  359. package/src/sources/provider-router/index.test.ts +43 -0
  360. package/src/sources/provider-router/index.ts +4 -0
  361. package/src/sources/provider-router/supplemental.ts +46 -0
  362. package/src/sources/sec-edgar.test.ts +203 -2
  363. package/src/sources/sec-edgar.ts +70 -15
  364. package/src/state/app/context/index.tsx +13 -1
  365. package/src/state/app/context/selector.test.tsx +62 -1
  366. package/src/state/session-persistence.test.ts +13 -2
  367. package/src/sync/controller.test.ts +190 -3
  368. package/src/sync/controller.ts +76 -45
  369. package/src/sync/core-contributors.test.ts +121 -0
  370. package/src/sync/core-contributors.ts +104 -27
  371. package/src/sync/react.ts +6 -8
  372. package/src/sync/types.ts +3 -0
  373. package/src/time-series/alignment.ts +158 -0
  374. package/src/time-series/economic.ts +49 -0
  375. package/src/time-series/field-catalog.ts +306 -0
  376. package/src/time-series/fundamentals.test.ts +505 -0
  377. package/src/time-series/fundamentals.ts +842 -0
  378. package/src/time-series/hooks.ts +45 -0
  379. package/src/time-series/index.ts +14 -0
  380. package/src/time-series/live-quotes.test.ts +219 -0
  381. package/src/time-series/live-quotes.ts +203 -0
  382. package/src/time-series/market-resolution.ts +53 -0
  383. package/src/time-series/market.ts +125 -0
  384. package/src/time-series/reporting.ts +98 -0
  385. package/src/time-series/resolve.test.ts +1281 -0
  386. package/src/time-series/resolve.ts +783 -0
  387. package/src/time-series/spec-studies.test.ts +400 -0
  388. package/src/time-series/spec.ts +487 -0
  389. package/src/time-series/studies.ts +563 -0
  390. package/src/time-series/transforms.test.ts +105 -0
  391. package/src/time-series/transforms.ts +152 -0
  392. package/src/time-series/types.ts +162 -0
  393. package/src/time-series/use-chart-resolution.ts +166 -0
  394. package/src/types/config.ts +9 -11
  395. package/src/types/desktop-window.ts +1 -0
  396. package/src/types/financials.ts +4 -0
  397. package/src/types/media.ts +17 -0
  398. package/src/types/plugin.ts +32 -1
  399. package/src/ui/context-menu.tsx +15 -3
  400. package/src/ui/dialog.tsx +14 -2
  401. package/src/ui/host.tsx +40 -1
  402. package/src/ui/index.tsx +9 -0
  403. package/src/utils/exchanges.test.ts +13 -1
  404. package/src/utils/exchanges.ts +10 -0
  405. package/src/utils/financial-statements.test.ts +138 -0
  406. package/src/utils/financial-statements.ts +115 -8
  407. package/src/utils/format.test.ts +11 -1
  408. package/src/utils/format.ts +7 -0
  409. package/src/version.ts +1 -1
  410. package/src/components/chart/bar-chart-renderer.test.ts +0 -233
  411. package/src/components/chart/bar-chart-renderer.ts +0 -445
  412. package/src/components/chart/comparison/data.test.ts +0 -159
  413. package/src/components/chart/comparison/data.ts +0 -302
  414. package/src/components/chart/comparison/renderer.test.ts +0 -187
  415. package/src/components/chart/comparison/renderer.ts +0 -282
  416. package/src/components/chart/comparison/scene.ts +0 -178
  417. package/src/components/chart/comparison-stock-chart/axis-width.ts +0 -99
  418. package/src/components/chart/comparison-stock-chart/canvas.ts +0 -148
  419. package/src/components/chart/comparison-stock-chart/controls.ts +0 -252
  420. package/src/components/chart/comparison-stock-chart/helpers.ts +0 -101
  421. package/src/components/chart/comparison-stock-chart/interactions.ts +0 -208
  422. package/src/components/chart/comparison-stock-chart/layout-metrics.ts +0 -62
  423. package/src/components/chart/comparison-stock-chart/layout.tsx +0 -155
  424. package/src/components/chart/comparison-stock-chart/legend.tsx +0 -74
  425. package/src/components/chart/comparison-stock-chart/native-surfaces.ts +0 -89
  426. package/src/components/chart/comparison-stock-chart/render-data.ts +0 -227
  427. package/src/components/chart/comparison-stock-chart/render-output.ts +0 -140
  428. package/src/components/chart/comparison-stock-chart/selection-runtime.ts +0 -128
  429. package/src/components/chart/comparison-stock-chart/sources.ts +0 -51
  430. package/src/components/chart/comparison-stock-chart/surface-runtime.ts +0 -159
  431. package/src/components/chart/comparison-stock-chart/toolbar.tsx +0 -117
  432. package/src/components/chart/comparison-stock-chart/types.ts +0 -22
  433. package/src/components/chart/comparison-stock-chart/view.tsx +0 -572
  434. package/src/components/chart/comparison-stock-chart/viewport-runtime.ts +0 -239
  435. package/src/components/chart/comparison-stock-chart.tsx +0 -24
  436. package/src/components/chart/core/axis-measure.ts +0 -33
  437. package/src/components/chart/core/controller.test.ts +0 -157
  438. package/src/components/chart/core/controller.ts +0 -349
  439. package/src/components/chart/core/indicator-selector.tsx +0 -75
  440. package/src/components/chart/core/keyboard.ts +0 -24
  441. package/src/components/chart/core/pane-settings.test.ts +0 -52
  442. package/src/components/chart/core/pane-settings.ts +0 -141
  443. package/src/components/chart/core/scroll.test.ts +0 -64
  444. package/src/components/chart/core/scroll.ts +0 -82
  445. package/src/components/chart/core/window-viewport.test.ts +0 -69
  446. package/src/components/chart/core/window-viewport.ts +0 -162
  447. package/src/components/chart/display-cursor.ts +0 -153
  448. package/src/components/chart/indicators/bands.test.ts +0 -36
  449. package/src/components/chart/indicators/bands.ts +0 -29
  450. package/src/components/chart/indicators/moving-averages.test.ts +0 -39
  451. package/src/components/chart/indicators/moving-averages.ts +0 -42
  452. package/src/components/chart/indicators/options.test.ts +0 -32
  453. package/src/components/chart/indicators/options.ts +0 -107
  454. package/src/components/chart/indicators/oscillators.test.ts +0 -58
  455. package/src/components/chart/indicators/oscillators.ts +0 -92
  456. package/src/components/chart/native/canvas-bitmaps.ts +0 -140
  457. package/src/components/chart/native/raster/comparison-chart.ts +0 -162
  458. package/src/components/chart/native/surface/hooks.ts +0 -144
  459. package/src/components/chart/native/surface/rendering.ts +0 -211
  460. package/src/components/chart/static/bar-chart-surface.test.tsx +0 -101
  461. package/src/components/chart/static/bar-chart-surface.tsx +0 -292
  462. package/src/components/chart/stock-chart/auto.ts +0 -216
  463. package/src/components/chart/stock-chart/axis-width.ts +0 -146
  464. package/src/components/chart/stock-chart/bitmaps.ts +0 -22
  465. package/src/components/chart/stock-chart/canvas.ts +0 -202
  466. package/src/components/chart/stock-chart/controls.tsx +0 -149
  467. package/src/components/chart/stock-chart/cursor.ts +0 -60
  468. package/src/components/chart/stock-chart/data-runtime.ts +0 -128
  469. package/src/components/chart/stock-chart/footer.test.ts +0 -65
  470. package/src/components/chart/stock-chart/footer.ts +0 -303
  471. package/src/components/chart/stock-chart/geometry.ts +0 -160
  472. package/src/components/chart/stock-chart/index.test.ts +0 -532
  473. package/src/components/chart/stock-chart/index.tsx +0 -29
  474. package/src/components/chart/stock-chart/indicators.ts +0 -226
  475. package/src/components/chart/stock-chart/interaction-runtime.ts +0 -44
  476. package/src/components/chart/stock-chart/interactions.ts +0 -300
  477. package/src/components/chart/stock-chart/keyboard.ts +0 -373
  478. package/src/components/chart/stock-chart/layout-metrics.ts +0 -62
  479. package/src/components/chart/stock-chart/layout.tsx +0 -177
  480. package/src/components/chart/stock-chart/native-surfaces.ts +0 -121
  481. package/src/components/chart/stock-chart/presentation.ts +0 -67
  482. package/src/components/chart/stock-chart/renderer-switch.test.tsx +0 -263
  483. package/src/components/chart/stock-chart/rendering/bounds.ts +0 -116
  484. package/src/components/chart/stock-chart/rendering/candidates.ts +0 -145
  485. package/src/components/chart/stock-chart/rendering/data.ts +0 -370
  486. package/src/components/chart/stock-chart/rendering/invalidation.ts +0 -68
  487. package/src/components/chart/stock-chart/rendering/output.ts +0 -304
  488. package/src/components/chart/stock-chart/rendering/planning.ts +0 -122
  489. package/src/components/chart/stock-chart/rendering/projection.test.tsx +0 -98
  490. package/src/components/chart/stock-chart/rendering/projection.ts +0 -112
  491. package/src/components/chart/stock-chart/rendering/state.test.ts +0 -70
  492. package/src/components/chart/stock-chart/rendering/state.ts +0 -283
  493. package/src/components/chart/stock-chart/requests.ts +0 -337
  494. package/src/components/chart/stock-chart/resolution-support.ts +0 -96
  495. package/src/components/chart/stock-chart/resolved/runtime.ts +0 -289
  496. package/src/components/chart/stock-chart/resolved.tsx +0 -359
  497. package/src/components/chart/stock-chart/settings.ts +0 -81
  498. package/src/components/chart/stock-chart/surface-runtime.ts +0 -62
  499. package/src/components/chart/stock-chart/types.ts +0 -25
  500. package/src/components/chart/stock-chart/view.tsx +0 -155
  501. package/src/components/chart/stock-chart/viewport/effects.ts +0 -293
  502. package/src/components/chart/stock-chart/viewport/index.ts +0 -144
  503. package/src/components/chart/stock-chart/viewport/runtime.ts +0 -375
  504. package/src/components/chart/time-axis-label.tsx +0 -175
  505. package/src/plugins/builtin/ai/gloomberb-cli.ts +0 -71
  506. package/src/plugins/builtin/ai/screener/action-bar.tsx +0 -58
  507. package/src/plugins/builtin/comparison-chart/index.test.tsx +0 -848
  508. package/src/plugins/builtin/comparison-chart/index.tsx +0 -194
  509. package/src/plugins/builtin/econ/fred-cache.ts +0 -106
  510. package/src/plugins/builtin/plugin-groups.ts +0 -130
  511. package/src/plugins/builtin/ticker-detail/chart-pane.tsx +0 -91
  512. package/src/plugins/builtin/ticker-detail/chart-tab.tsx +0 -100
  513. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/content.tsx +0 -296
  514. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/data.ts +0 -62
  515. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/model.ts +0 -608
  516. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/settings.ts +0 -65
  517. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/types.ts +0 -58
  518. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph.tsx +0 -138
  519. package/src/plugins/builtin/ticker-detail/data-panes.test.ts +0 -220
@@ -1,9 +1,10 @@
1
1
  import { afterEach, describe, expect, test } from "bun:test";
2
2
  import { createTestRenderer } from "@opentui/core/testing";
3
- import { act } from "react";
3
+ import { act, useState } from "react";
4
4
  import { ChartSurface, Text } from "../../ui";
5
5
  import { getNativeSurfaceManager } from "../../components/chart/native/surface/manager";
6
6
  import type { ChartRendererPreference } from "../../components/chart/core/types";
7
+ import type { CellRect } from "../../components/chart/native/chart-rasterizer";
7
8
  import { AppProvider } from "../../state/app/context";
8
9
  import { createDefaultConfig } from "../../types/config";
9
10
  import { createOpenTuiTestRoot } from "./test-utils";
@@ -23,11 +24,15 @@ function setNativeRendererReady(): void {
23
24
  (testSetup!.renderer as { _resolution: unknown })._resolution = { width: 800, height: 400 };
24
25
  }
25
26
 
26
- function surfaceCount(): number {
27
+ function surfaces(): Map<string, { snapshot: { rect: CellRect; imageZIndex?: number } }> {
27
28
  const manager = getNativeSurfaceManager(testSetup!.renderer as never) as unknown as {
28
- surfaces: Map<string, unknown>;
29
+ surfaces: Map<string, { snapshot: { rect: CellRect; imageZIndex?: number } }>;
29
30
  };
30
- return manager.surfaces.size;
31
+ return manager.surfaces;
32
+ }
33
+
34
+ function surfaceCount(): number {
35
+ return surfaces().size;
31
36
  }
32
37
 
33
38
  async function flushFrames(): Promise<void> {
@@ -39,13 +44,24 @@ async function flushFrames(): Promise<void> {
39
44
  });
40
45
  }
41
46
 
42
- function Harness({ preference }: { preference: ChartRendererPreference }) {
47
+ type TestCrosshair = { pixelX: number; pixelY: number; color: string } | null;
48
+ let setHarnessCrosshair: ((crosshair: TestCrosshair) => void) | null = null;
49
+
50
+ function Harness({
51
+ preference,
52
+ initialCrosshair = null,
53
+ }: {
54
+ preference: ChartRendererPreference;
55
+ initialCrosshair?: TestCrosshair;
56
+ }) {
43
57
  const config = createDefaultConfig(`/tmp/gloomberb-chart-surface-${preference}`);
44
58
  config.chartPreferences.renderer = preference;
59
+ const [crosshair, setCrosshair] = useState<TestCrosshair>(initialCrosshair);
60
+ setHarnessCrosshair = setCrosshair;
45
61
 
46
62
  return (
47
63
  <AppProvider config={config}>
48
- <ChartSurface width={20} height={4} flexDirection="column" bitmaps={[bitmap]}>
64
+ <ChartSurface width={20} height={4} flexDirection="column" bitmaps={[bitmap]} crosshair={crosshair}>
49
65
  <Text>fallback chart</Text>
50
66
  </ChartSurface>
51
67
  </AppProvider>
@@ -98,4 +114,34 @@ describe("OpenTuiChartSurface", () => {
98
114
  expect(testSetup.captureCharFrame()).not.toContain("fallback chart");
99
115
  expect(surfaceCount()).toBe(1);
100
116
  });
117
+
118
+ test("overlays the crosshair as thin strips above the plot and drops them with the cursor", async () => {
119
+ actEnvironment.IS_REACT_ACT_ENVIRONMENT = true;
120
+ testSetup = await createTestRenderer({ width: 40, height: 12 });
121
+ setNativeRendererReady();
122
+ root = createOpenTuiTestRoot(testSetup.renderer);
123
+
124
+ // Cursor at the middle of a 4x4 source bitmap, scaled onto a 20x4 cell plot.
125
+ act(() => {
126
+ root!.render(<Harness preference="kitty" initialCrosshair={{ pixelX: 2, pixelY: 2, color: "#ffcc00" }} />);
127
+ });
128
+ await flushFrames();
129
+
130
+ const entries = surfaces();
131
+ expect(entries.size).toBe(3);
132
+ const vertical = [...entries].find(([id]) => id.endsWith(":crosshair-v"))![1].snapshot;
133
+ const horizontal = [...entries].find(([id]) => id.endsWith(":crosshair-h"))![1].snapshot;
134
+ // Both strips stay inside the plot and cover only the cursor's cells.
135
+ expect(vertical.rect).toEqual({ x: 12, y: 0, width: 3, height: 4 });
136
+ expect(horizontal.rect).toEqual({ x: 0, y: 2, width: 20, height: 1 });
137
+ expect(vertical.imageZIndex).toBe(1);
138
+ expect(horizontal.imageZIndex).toBe(1);
139
+
140
+ act(() => {
141
+ setHarnessCrosshair!(null);
142
+ });
143
+ await flushFrames();
144
+
145
+ expect(surfaceCount()).toBe(1);
146
+ });
101
147
  });
@@ -2,9 +2,11 @@ import { createElement, forwardRef, useCallback, useEffect, useMemo, useRef, use
2
2
  import {
3
3
  computeBitmapSize,
4
4
  intersectCellRects,
5
+ renderCrosshairStrips,
5
6
  type CellRect,
6
7
  type NativeChartBitmap,
7
8
  } from "../../components/chart/native/chart-rasterizer";
9
+ import { scaleLocalPixelCoordinate } from "../../components/chart/core/pointer";
8
10
  import type { ChartRendererPreference } from "../../components/chart/core/types";
9
11
  import { useResolvedChartRendererState } from "../../components/chart/native/renderer-selection";
10
12
  import { getNativeSurfaceManager } from "../../components/chart/native/surface/manager";
@@ -15,6 +17,7 @@ import {
15
17
  } from "../../components/chart/native/surface/visibility";
16
18
  import { useOptionalAppSelector, useOptionalPaneInstanceId } from "../../state/app/context";
17
19
  import { useNativeRenderer, type BoxRenderable, type ChartSurfaceProps } from "../../ui";
20
+ import type { ChartCrosshairOverlay } from "../../ui/host";
18
21
 
19
22
  interface NativeRenderableNode extends BoxRenderable, NativeSurfaceRenderableNode {
20
23
  x: number;
@@ -33,6 +36,9 @@ interface SurfaceTarget {
33
36
 
34
37
  let nextChartSurfaceId = 1;
35
38
 
39
+ // Crosshair strips must land above the plot they annotate; plots use the kitty default.
40
+ const CROSSHAIR_Z_INDEX = 1;
41
+
36
42
  function assignRef(ref: ForwardedRef<unknown>, value: unknown) {
37
43
  if (typeof ref === "function") {
38
44
  ref(value);
@@ -60,21 +66,22 @@ function sameTarget(left: SurfaceTarget | null, right: SurfaceTarget | null): bo
60
66
  && sameRect(left.visibleRect, right.visibleRect);
61
67
  }
62
68
 
63
- function hashBitmap(bitmap: NativeChartBitmap): string {
64
- let hash = 0x811c9dc5;
65
- for (let index = 0; index < bitmap.pixels.length; index += 1) {
66
- hash ^= bitmap.pixels[index]!;
67
- hash = Math.imul(hash, 0x01000193);
68
- }
69
- return (hash >>> 0).toString(16);
70
- }
69
+ // ponytail: identity token instead of hashing every pixel — renderers always
70
+ // build a new bitmap object per raster, and hashing megabytes per crosshair
71
+ // move was the dominant cost. Mutating a bitmap in place would need a hash.
72
+ const bitmapTokens = new WeakMap<NativeChartBitmap, string>();
73
+ let nextBitmapToken = 1;
71
74
 
72
75
  function bitmapKey(bitmap: NativeChartBitmap): string {
73
- return `${bitmap.width}x${bitmap.height}:${hashBitmap(bitmap)}`;
76
+ const existing = bitmapTokens.get(bitmap);
77
+ if (existing) return existing;
78
+ const token = `${bitmap.width}x${bitmap.height}:${nextBitmapToken++}`;
79
+ bitmapTokens.set(bitmap, token);
80
+ return token;
74
81
  }
75
82
 
76
83
  export const OpenTuiChartSurface = forwardRef<unknown, ChartSurfaceProps>(function OpenTuiChartSurface(
77
- { children, bitmap, bitmaps, crosshair: _crosshair, nativeBitmapsEnabled = true, ...props },
84
+ { children, bitmap, bitmaps, crosshair, nativeBitmapsEnabled = true, ...props },
78
85
  forwardedRef,
79
86
  ) {
80
87
  const renderer = useNativeRenderer();
@@ -90,6 +97,7 @@ export const OpenTuiChartSurface = forwardRef<unknown, ChartSurfaceProps>(functi
90
97
  const renderableRef = useRef<NativeRenderableNode | null>(null);
91
98
  const [target, setTarget] = useState<SurfaceTarget | null>(null);
92
99
  const nativeBitmap = (bitmaps?.[0] ?? bitmap ?? null) as NativeChartBitmap | null;
100
+ const nativeCrosshair = (crosshair ?? null) as ChartCrosshairOverlay | null;
93
101
  const nativeBitmapKey = useMemo(() => (nativeBitmap ? bitmapKey(nativeBitmap) : null), [nativeBitmap]);
94
102
 
95
103
  const setRenderableRef = useCallback((node: unknown) => {
@@ -145,11 +153,17 @@ export const OpenTuiChartSurface = forwardRef<unknown, ChartSurfaceProps>(functi
145
153
  };
146
154
  }, [nativeBitmap, nativeBitmapKey, nativeSurfacesEnabled, renderer]);
147
155
 
156
+ const crosshairSurfaceIds = useMemo(
157
+ () => [`${surfaceId}:crosshair-v`, `${surfaceId}:crosshair-h`] as const,
158
+ [surfaceId],
159
+ );
160
+
148
161
  useEffect(() => {
149
162
  return () => {
150
163
  nativeSurfaceManager.removeSurface(surfaceId);
164
+ for (const id of crosshairSurfaceIds) nativeSurfaceManager.removeSurface(id);
151
165
  };
152
- }, [nativeSurfaceManager, surfaceId]);
166
+ }, [crosshairSurfaceIds, nativeSurfaceManager, surfaceId]);
153
167
 
154
168
  useEffect(() => {
155
169
  if (!nativeSurfacesEnabled || !target?.visibleRect || !nativeBitmap) {
@@ -168,6 +182,76 @@ export const OpenTuiChartSurface = forwardRef<unknown, ChartSurfaceProps>(functi
168
182
  renderer.requestRender();
169
183
  }, [nativeBitmap, nativeSurfaceManager, nativeSurfacesEnabled, paneId, renderer, surfaceId, target]);
170
184
 
185
+ // Crosshair moves only re-encode two thin strips, leaving the plot image resident.
186
+ useEffect(() => {
187
+ const removeAll = () => {
188
+ for (const id of crosshairSurfaceIds) nativeSurfaceManager.removeSurface(id);
189
+ };
190
+ const visibleRect = target?.visibleRect;
191
+ if (!nativeSurfacesEnabled || !nativeCrosshair || !nativeBitmap || !target || !visibleRect || !renderer.resolution) {
192
+ removeAll();
193
+ return;
194
+ }
195
+
196
+ const { rect } = target;
197
+ const size = computeBitmapSize(rect, renderer.resolution, renderer.terminalWidth, renderer.terminalHeight);
198
+ const strips = renderCrosshairStrips({
199
+ pixelX: scaleLocalPixelCoordinate(nativeCrosshair.pixelX, nativeBitmap.width, size.pixelWidth) ?? 0,
200
+ pixelY: scaleLocalPixelCoordinate(nativeCrosshair.pixelY, nativeBitmap.height, size.pixelHeight) ?? 0,
201
+ pixelWidth: size.pixelWidth,
202
+ pixelHeight: size.pixelHeight,
203
+ cols: rect.width,
204
+ rows: rect.height,
205
+ cellWidth: size.cellWidth,
206
+ cellHeight: size.cellHeight,
207
+ color: nativeCrosshair.color,
208
+ markers: nativeCrosshair.markers?.map((marker) => ({
209
+ pixelY: scaleLocalPixelCoordinate(marker.pixelY, nativeBitmap.height, size.pixelHeight) ?? 0,
210
+ color: marker.color,
211
+ })),
212
+ });
213
+ if (strips.length === 0) {
214
+ removeAll();
215
+ return;
216
+ }
217
+
218
+ strips.forEach((strip, index) => {
219
+ const id = crosshairSurfaceIds[index];
220
+ if (!id) return;
221
+ const stripRect: CellRect = {
222
+ x: rect.x + strip.rect.x,
223
+ y: rect.y + strip.rect.y,
224
+ width: strip.rect.width,
225
+ height: strip.rect.height,
226
+ };
227
+ const stripVisibleRect = intersectCellRects(stripRect, visibleRect);
228
+ if (!stripVisibleRect) {
229
+ nativeSurfaceManager.removeSurface(id);
230
+ return;
231
+ }
232
+ nativeSurfaceManager.upsertSurface({
233
+ id,
234
+ paneId: paneId ?? "__global__",
235
+ rect: stripRect,
236
+ visibleRect: stripVisibleRect,
237
+ bitmap: strip.bitmap,
238
+ bitmapKey: `${strip.key}@${stripRect.x}:${stripRect.y}:${stripRect.width}x${stripRect.height}`,
239
+ imageZIndex: CROSSHAIR_Z_INDEX,
240
+ });
241
+ });
242
+ renderer.requestRender();
243
+ }, [
244
+ crosshairSurfaceIds,
245
+ nativeCrosshair,
246
+ nativeBitmap,
247
+ nativeSurfaceManager,
248
+ nativeSurfacesEnabled,
249
+ paneId,
250
+ renderer,
251
+ surfaceId,
252
+ target,
253
+ ]);
254
+
171
255
  const showFallback = !nativeSurfacesEnabled || !target || !nativeBitmap;
172
256
  return createElement("box" as any, { ...props, ref: setRenderableRef }, showFallback ? children as ReactNode : null);
173
257
  });
@@ -13,17 +13,48 @@ function renderDialogContent(content: unknown, context: Record<string, unknown>)
13
13
  : content as ReactNode;
14
14
  }
15
15
 
16
+ function normalizeDialogId(value: unknown): string | undefined {
17
+ return typeof value === "string" || typeof value === "number" ? String(value) : undefined;
18
+ }
19
+
20
+ function OpenTuiDialogContentBridge({
21
+ dialog,
22
+ dialogId,
23
+ children,
24
+ }: {
25
+ dialog: DialogApi;
26
+ dialogId: string | undefined;
27
+ children: ReactNode;
28
+ }) {
29
+ const isTopmost = useOpenTuiDialogState(
30
+ (state) => normalizeDialogId(state.topDialog?.id) === dialogId,
31
+ );
32
+ return (
33
+ <DialogHostProvider
34
+ dialog={dialog}
35
+ isOpen={true}
36
+ dialogId={dialogId}
37
+ keyboardEnabled={isTopmost}
38
+ >
39
+ {children}
40
+ </DialogHostProvider>
41
+ );
42
+ }
43
+
16
44
  function OpenTuiDialogBridge({ children }: { children: ReactNode }) {
17
45
  const openTuiDialog = useOpenTuiDialog();
18
46
  const isOpen = useOpenTuiDialogState((state) => state.isOpen);
19
47
  const dialog = useMemo<DialogApi>(() => {
20
48
  const wrapOptions = (options: Record<string, unknown>) => ({
21
49
  ...options,
22
- content: (context: Record<string, unknown>) => (
23
- <DialogHostProvider dialog={dialog} isOpen={true}>
24
- {renderDialogContent(options.content, context)}
25
- </DialogHostProvider>
26
- ),
50
+ content: (context: Record<string, unknown>) => {
51
+ const dialogId = normalizeDialogId(context.dialogId);
52
+ return (
53
+ <OpenTuiDialogContentBridge dialog={dialog} dialogId={dialogId}>
54
+ {renderDialogContent(options.content, { ...context, dialogId })}
55
+ </OpenTuiDialogContentBridge>
56
+ );
57
+ },
27
58
  });
28
59
 
29
60
  return {
@@ -82,6 +82,9 @@ function installResolutionEventBridge(renderer: CliRenderer): void {
82
82
  export async function createOpenTuiHost(): Promise<OpenTuiHost> {
83
83
  resetTerminalInputState();
84
84
 
85
+ // Mouse motion is reported per pixel; our @opentui/core patch coalesces moves
86
+ // to one per fastest frame (~16ms) so pointer-tracked UI like the chart
87
+ // crosshair follows the cursor instead of stepping at 10fps.
85
88
  const renderer = await createCliRenderer({
86
89
  exitOnCtrlC: true,
87
90
  backgroundColor: colors.bg,
@@ -128,6 +131,42 @@ export async function createOpenTuiHost(): Promise<OpenTuiHost> {
128
131
  notify() {
129
132
  // The app-level notifier still owns toast/desktop notification behavior.
130
133
  },
134
+ async playTerminalMedia(url, title, options) {
135
+ const mpv = Bun.which("mpv");
136
+ if (!mpv) {
137
+ throw new Error("mpv is required for terminal TV playback. Install mpv and try again.");
138
+ }
139
+
140
+ renderer.suspend();
141
+ try {
142
+ const proc = Bun.spawn([
143
+ mpv,
144
+ "--no-config",
145
+ "--profile=sw-fast",
146
+ "--vo=kitty",
147
+ "--vo-kitty-auto-multiplexer-passthrough=yes",
148
+ "--ytdl=no",
149
+ `--mute=${options?.muted === false ? "no" : "yes"}`,
150
+ ...(title ? [`--title=${title}`] : []),
151
+ "--",
152
+ url,
153
+ ], {
154
+ stdin: "inherit",
155
+ stdout: "inherit",
156
+ stderr: "pipe",
157
+ });
158
+ const stderrPromise = new Response(proc.stderr).text();
159
+ const exitCode = await proc.exited;
160
+ const stderr = await stderrPromise;
161
+ if (exitCode !== 0) {
162
+ const detail = stderr.trim().split("\n").slice(-3).join(" ");
163
+ throw new Error(detail || `mpv exited with status ${exitCode}`);
164
+ }
165
+ } finally {
166
+ renderer.resume();
167
+ renderer.requestRender();
168
+ }
169
+ },
131
170
  };
132
171
 
133
172
  const nativeRenderer: NativeRendererHost = {
@@ -3,6 +3,7 @@ import { existsSync, mkdirSync } from "fs";
3
3
  import { App } from "../../app";
4
4
  import { dispatchCli } from "../../cli/index";
5
5
  import { getDataDir, initDataDir, setConfigStoreHost } from "../../data/config/store";
6
+ import { applyLanguageFromConfig } from "../../i18n";
6
7
  import * as nodeConfigStoreHost from "../../data/config/store/node";
7
8
  import { loadExternalPlugins } from "../../plugins/loader";
8
9
  import { OpenTuiInputHostProvider } from "./input-host";
@@ -19,6 +20,12 @@ import { measurePerfAsync } from "../../utils/perf-marks";
19
20
  import type { CliLaunchRequest } from "../../types/plugin";
20
21
  import type { RemoteControlAdapter } from "../../remote/app-host";
21
22
  import { startRemoteControlServer, type RemoteControlServer } from "../../remote/server";
23
+ import { createPiAiHost } from "../../plugins/builtin/ai/pi";
24
+ import {
25
+ installAiRunHost,
26
+ } from "../../plugins/builtin/ai/runner";
27
+
28
+ const AI_STARTUP_READINESS_TIMEOUT_MS = 5_000;
22
29
 
23
30
  export interface StartOpenTuiAppOptions {
24
31
  externalPlugins?: Awaited<ReturnType<typeof loadExternalPlugins>>;
@@ -95,6 +102,29 @@ export async function startOpenTuiApp(options: StartOpenTuiAppOptions = {}): Pro
95
102
  }
96
103
 
97
104
  const config = await measurePerfAsync("startup.opentui.init-data-dir", () => initDataDir(dataDir));
105
+ applyLanguageFromConfig(config);
106
+ try {
107
+ const aiHost = createPiAiHost({
108
+ appKind: "tui",
109
+ dataDir: config.dataDir,
110
+ });
111
+ await measurePerfAsync(
112
+ "startup.opentui.ai-catalog",
113
+ () => installAiRunHost(aiHost, {
114
+ catalogTimeoutMs: AI_STARTUP_READINESS_TIMEOUT_MS,
115
+ timeoutMessage: "In-app AI provider discovery timed out during startup",
116
+ onCatalogError(error) {
117
+ appLog.warn("In-app AI provider discovery could not finish during startup", {
118
+ error: error instanceof Error ? error.message : String(error),
119
+ });
120
+ },
121
+ }),
122
+ );
123
+ } catch (error) {
124
+ appLog.warn("In-app AI providers could not be initialized", {
125
+ error: error instanceof Error ? error.message : String(error),
126
+ });
127
+ }
98
128
  host = await measurePerfAsync("startup.opentui.create-host", () => createOpenTuiHost());
99
129
  host.renderer.once("destroy", finishProcessExit);
100
130
 
@@ -61,6 +61,7 @@ const OpenTuiScrollBox = createOpenTuiPrimitive("scrollbox");
61
61
  const OpenTuiInput = createOpenTuiPrimitive("input");
62
62
  const OpenTuiTextarea = createOpenTuiPrimitive("textarea");
63
63
  const OpenTuiSpinnerMark = createOpenTuiPrimitive("spinner");
64
+ const OpenTuiMediaSurface = createOpenTuiPrimitive("box");
64
65
 
65
66
  const OpenTuiText = forwardRef<unknown, TextProps>(function OpenTuiText({ children, ...props }, ref) {
66
67
  const textProps = stripTextProps(props);
@@ -103,6 +104,7 @@ export const openTuiUiHost: UiHost = {
103
104
  Textarea: OpenTuiTextarea as UiHost["Textarea"],
104
105
  ChartSurface: OpenTuiChartSurface,
105
106
  ImageSurface: OpenTuiImageSurface,
107
+ MediaSurface: OpenTuiMediaSurface as UiHost["MediaSurface"],
106
108
  SpinnerMark: OpenTuiSpinnerMark as UiHost["SpinnerMark"],
107
109
  AsciiText: ({ text, font = "tiny", color, fg, bg, backgroundColor, selectable = false, ...props }) => {
108
110
  const resolvedColor = color ?? fg;
@@ -209,6 +209,49 @@ describe("AssetDataRouter", () => {
209
209
  expect(holders.holders[0]?.name).toBe("Vanguard Group Inc");
210
210
  });
211
211
 
212
+ test("routes earnings calendars through providers and fills missing symbols from fallback", async () => {
213
+ const cloudCalls: string[][] = [];
214
+ const yahooCalls: string[][] = [];
215
+ const event = (symbol: string, date: string) => ({
216
+ symbol,
217
+ name: symbol,
218
+ earningsDate: new Date(date),
219
+ epsEstimate: null,
220
+ epsActual: null,
221
+ revenueEstimate: null,
222
+ revenueActual: null,
223
+ surprise: null,
224
+ timing: "" as const,
225
+ });
226
+ const cloudProvider: DataProvider = {
227
+ ...fallbackProvider,
228
+ id: "cloud",
229
+ name: "Cloud",
230
+ priority: 100,
231
+ async getEarningsCalendar(symbols) {
232
+ cloudCalls.push(symbols);
233
+ return [event("AAPL", "2026-07-30T20:00:00Z")];
234
+ },
235
+ };
236
+ const yahooProvider: DataProvider = {
237
+ ...fallbackProvider,
238
+ id: "yahoo",
239
+ name: "Yahoo",
240
+ priority: 1000,
241
+ async getEarningsCalendar(symbols) {
242
+ yahooCalls.push(symbols);
243
+ return [event("MSFT", "2026-07-29T20:00:00Z")];
244
+ },
245
+ };
246
+
247
+ const router = new AssetDataRouter(yahooProvider, [cloudProvider]);
248
+ const events = await router.getEarningsCalendar(["msft", " AAPL ", "aapl"]);
249
+
250
+ expect(cloudCalls).toEqual([["MSFT", "AAPL"]]);
251
+ expect(yahooCalls).toEqual([["MSFT"]]);
252
+ expect(events.map((entry) => entry.symbol)).toEqual(["MSFT", "AAPL"]);
253
+ });
254
+
212
255
  test("falls back to later analyst providers when rating targets are missing", async () => {
213
256
  const cloudProvider: DataProvider = {
214
257
  ...fallbackProvider,
@@ -215,6 +215,10 @@ export class AssetDataRouter implements DataProvider {
215
215
  return this.supplementalRoutes.getCorporateActions(ticker, exchange, context);
216
216
  }
217
217
 
218
+ async getEarningsCalendar(symbols: string[], context?: MarketDataRequestContext) {
219
+ return this.supplementalRoutes.getEarningsCalendar(symbols, context);
220
+ }
221
+
218
222
  async getSecFilings(ticker: string, count = 15, exchange?: string, context?: MarketDataRequestContext): Promise<SecFilingItem[]> {
219
223
  return this.documentRoutes.getSecFilings(ticker, count, exchange, context);
220
224
  }
@@ -1,5 +1,6 @@
1
1
  import type {
2
2
  DataProvider,
3
+ EarningsEvent,
3
4
  MarketDataRequestContext,
4
5
  } from "../../types/data-provider";
5
6
  import type {
@@ -120,6 +121,51 @@ export class ProviderRouterSupplementalRoutes {
120
121
  throw new Error(`No corporate actions provider available for ${ticker}`);
121
122
  }
122
123
 
124
+ async getEarningsCalendar(
125
+ symbols: string[],
126
+ context?: MarketDataRequestContext,
127
+ ): Promise<EarningsEvent[]> {
128
+ const remaining = new Set(
129
+ symbols
130
+ .map((symbol) => symbol.trim().toUpperCase())
131
+ .filter(Boolean),
132
+ );
133
+ if (remaining.size === 0) return [];
134
+
135
+ const eventsBySymbol = new Map<string, EarningsEvent>();
136
+ let supported = false;
137
+ let succeeded = false;
138
+ let lastError: unknown = null;
139
+
140
+ for (const provider of this.deps.providersInPriorityOrder()) {
141
+ if (!provider.getEarningsCalendar || remaining.size === 0) continue;
142
+ supported = true;
143
+ try {
144
+ const events = await provider.getEarningsCalendar([...remaining], context);
145
+ succeeded = true;
146
+ for (const event of events) {
147
+ const symbol = event.symbol.trim().toUpperCase();
148
+ if (!remaining.has(symbol)) continue;
149
+ eventsBySymbol.set(symbol, event);
150
+ remaining.delete(symbol);
151
+ }
152
+ } catch (error) {
153
+ lastError = error;
154
+ if (shouldLogProviderError(error)) {
155
+ this.deps.logProviderError(`${provider.id} failed: ${error}`);
156
+ }
157
+ }
158
+ }
159
+
160
+ if (eventsBySymbol.size > 0 || succeeded) {
161
+ return [...eventsBySymbol.values()]
162
+ .sort((a, b) => a.earningsDate.getTime() - b.earningsDate.getTime());
163
+ }
164
+ if (lastError) throw lastError;
165
+ if (!supported) throw new Error("No earnings calendar provider available");
166
+ return [];
167
+ }
168
+
123
169
  async getOptionsChain(ticker: string, exchange?: string, expirationDate?: number, context?: MarketDataRequestContext): Promise<OptionsChain> {
124
170
  const entityKey = this.deps.getEntityKey(ticker, context?.instrument);
125
171
  const variantKeys = [