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
@@ -0,0 +1,25 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { isDialogDismissKey, shouldFocusDialogContainer } from "./dialog-host";
3
+
4
+ describe("desktop dialog focus and dismissal", () => {
5
+ test("dismisses on Escape, including legacy WebView key names", () => {
6
+ expect(isDialogDismissKey({ key: "Escape", isComposing: false })).toBe(true);
7
+ expect(isDialogDismissKey({ key: "Esc", isComposing: false })).toBe(true);
8
+ expect(isDialogDismissKey({ key: "Enter", isComposing: false })).toBe(false);
9
+ expect(isDialogDismissKey({ key: "Escape", isComposing: true })).toBe(false);
10
+ });
11
+
12
+ test("preserves focus already owned by dialog content", () => {
13
+ const child = {} as Element;
14
+ const outside = {} as Element;
15
+ const dialog = {
16
+ contains(element: Node | null) {
17
+ return element === child;
18
+ },
19
+ };
20
+
21
+ expect(shouldFocusDialogContainer(dialog, child)).toBe(false);
22
+ expect(shouldFocusDialogContainer(dialog, outside)).toBe(true);
23
+ expect(shouldFocusDialogContainer(dialog, null)).toBe(true);
24
+ });
25
+ });
@@ -1,6 +1,6 @@
1
1
  /// <reference lib="dom" />
2
2
  /** @jsxImportSource react */
3
- import { useCallback, useMemo, useState, type ReactNode } from "react";
3
+ import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react";
4
4
  import { DialogHostProvider, type DialogApi } from "../../../ui/dialog";
5
5
  import { blendHex, colors } from "../../../theme/colors";
6
6
  import { useThemeColors } from "../../../theme/theme-context";
@@ -8,33 +8,86 @@ import { useThemeColors } from "../../../theme/theme-context";
8
8
  interface DialogState {
9
9
  id: string;
10
10
  content: ReactNode | ((context: { dialogId: string; dismiss(): void; resolve(value: unknown): void }) => ReactNode);
11
+ closeOnClickOutside: boolean;
12
+ returnFocus: HTMLElement | null;
11
13
  resolve(value: unknown): void;
12
14
  }
13
15
 
14
16
  let nextDialogId = 1;
15
17
 
18
+ export function isDialogDismissKey(event: Pick<KeyboardEvent, "key" | "isComposing">): boolean {
19
+ return !event.isComposing && (event.key === "Escape" || event.key === "Esc");
20
+ }
21
+
22
+ export function shouldFocusDialogContainer(
23
+ dialog: Pick<HTMLElement, "contains">,
24
+ activeElement: Element | null,
25
+ ): boolean {
26
+ return activeElement === null || !dialog.contains(activeElement);
27
+ }
28
+
16
29
  export function WebDialogHostProvider({ children }: { children: ReactNode }) {
17
30
  useThemeColors();
18
31
  const [dialogState, setDialogState] = useState<DialogState | null>(null);
32
+ const dialogStateRef = useRef<DialogState | null>(null);
33
+ const dialogElementRef = useRef<HTMLDivElement | null>(null);
19
34
  const dialogBorder = blendHex(colors.border, colors.borderFocused, 0.18);
20
35
  const dialogBg = blendHex(colors.panel, colors.bg, 0.12);
21
36
 
22
37
  const close = useCallback((value?: unknown) => {
23
- setDialogState((current) => {
24
- current?.resolve(value);
25
- return null;
38
+ const current = dialogStateRef.current;
39
+ if (!current) return;
40
+ dialogStateRef.current = null;
41
+ setDialogState(null);
42
+ current.resolve(value);
43
+ queueMicrotask(() => {
44
+ if (current.returnFocus?.isConnected) {
45
+ current.returnFocus.focus({ preventScroll: true });
46
+ }
26
47
  });
27
48
  }, []);
28
49
 
29
- const open = useCallback((options: Record<string, unknown>) => (
30
- new Promise<unknown>((resolve) => {
31
- setDialogState({
50
+ const open = useCallback(function openDialog<T>(options: Record<string, unknown>): Promise<T> {
51
+ return new Promise<T>((resolve) => {
52
+ const activeElement = document.activeElement;
53
+ const next: DialogState = {
32
54
  id: `web-dialog-${nextDialogId++}`,
33
55
  content: options.content as DialogState["content"],
34
- resolve,
35
- });
36
- })
37
- ), []);
56
+ closeOnClickOutside: options.closeOnClickOutside === true,
57
+ returnFocus: activeElement instanceof HTMLElement ? activeElement : null,
58
+ resolve: (value) => resolve(value as T),
59
+ };
60
+ dialogStateRef.current?.resolve(undefined);
61
+ dialogStateRef.current = next;
62
+ setDialogState(next);
63
+ });
64
+ }, []);
65
+
66
+ useEffect(() => {
67
+ if (!dialogState) return;
68
+ const frame = requestAnimationFrame(() => {
69
+ const dialogElement = dialogElementRef.current;
70
+ if (
71
+ dialogElement
72
+ && shouldFocusDialogContainer(dialogElement, document.activeElement)
73
+ ) {
74
+ dialogElement.focus({ preventScroll: true });
75
+ }
76
+ });
77
+ return () => cancelAnimationFrame(frame);
78
+ }, [dialogState?.id]);
79
+
80
+ useEffect(() => {
81
+ if (!dialogState) return;
82
+ const dismissOnEscape = (event: KeyboardEvent) => {
83
+ if (!isDialogDismissKey(event)) return;
84
+ event.preventDefault();
85
+ event.stopPropagation();
86
+ close(undefined);
87
+ };
88
+ window.addEventListener("keydown", dismissOnEscape, true);
89
+ return () => window.removeEventListener("keydown", dismissOnEscape, true);
90
+ }, [close, dialogState?.id]);
38
91
 
39
92
  const api = useMemo<DialogApi>(() => ({
40
93
  alert: open,
@@ -48,10 +101,20 @@ export function WebDialogHostProvider({ children }: { children: ReactNode }) {
48
101
  <div
49
102
  className="gloom-dialog-backdrop"
50
103
  onMouseDown={(event) => {
51
- if (event.target === event.currentTarget) close(undefined);
104
+ if (
105
+ dialogState.closeOnClickOutside
106
+ && event.target === event.currentTarget
107
+ ) {
108
+ close(undefined);
109
+ }
52
110
  }}
53
111
  >
54
112
  <div
113
+ ref={dialogElementRef}
114
+ role="dialog"
115
+ aria-modal="true"
116
+ aria-label="Dialog"
117
+ tabIndex={-1}
55
118
  className="gloom-dialog"
56
119
  style={{
57
120
  borderColor: dialogBorder,
@@ -130,6 +130,10 @@ export const WebBox = forwardRef<HTMLDivElement, Record<string, unknown> & { chi
130
130
  document.addEventListener("mouseup", handleDocumentUp);
131
131
  };
132
132
 
133
+ const handleMouseDownCapture = (event: MouseEvent) => {
134
+ callMouseHandler(propsRef.current.onMouseDownCapture, event, "down");
135
+ };
136
+
133
137
  const handleWheel = (event: ReactWheelEvent<HTMLDivElement>) => {
134
138
  callMouseHandler(propsRef.current.onMouseScroll, event, "scroll");
135
139
  };
@@ -148,6 +152,7 @@ export const WebBox = forwardRef<HTMLDivElement, Record<string, unknown> & { chi
148
152
  {...cleanDomProps(props)}
149
153
  data-gloom-hover-bg={hoverBackgroundColor ? "true" : undefined}
150
154
  ref={elementRef}
155
+ onMouseDownCapture={typeof props.onMouseDownCapture === "function" ? handleMouseDownCapture : undefined}
151
156
  onMouseDown={handleMouseDown}
152
157
  onMouseOver={handleMouseOver}
153
158
  onMouseMove={(event) => scheduleFrameMouseHandler(event, "move")}
@@ -2,6 +2,7 @@
2
2
  /** @jsxImportSource react */
3
3
  import {
4
4
  forwardRef,
5
+ memo,
5
6
  useEffect,
6
7
  useRef,
7
8
  type CSSProperties,
@@ -11,7 +12,7 @@ import {
11
12
  import type { BitmapSurface, BoxRenderable, ChartCrosshairOverlay } from "../../../../ui/host";
12
13
  import { WebBox } from "./box";
13
14
 
14
- function CanvasBitmap({ bitmap }: { bitmap: BitmapSurface }) {
15
+ const CanvasBitmap = memo(function CanvasBitmap({ bitmap }: { bitmap: BitmapSurface }) {
15
16
  const canvasRef = useRef<HTMLCanvasElement | null>(null);
16
17
 
17
18
  useEffect(() => {
@@ -19,10 +20,10 @@ function CanvasBitmap({ bitmap }: { bitmap: BitmapSurface }) {
19
20
  if (!canvas) return;
20
21
  const context = canvas.getContext("2d");
21
22
  if (!context) return;
22
- const pixels = bitmap.pixels instanceof Uint8ClampedArray
23
- ? bitmap.pixels
24
- : new Uint8ClampedArray(bitmap.pixels.buffer, bitmap.pixels.byteOffset, bitmap.pixels.byteLength);
25
- context.putImageData(new ImageData(new Uint8ClampedArray(pixels), bitmap.width, bitmap.height), 0, 0);
23
+ const pixels = bitmap.pixels.buffer instanceof ArrayBuffer
24
+ ? new Uint8ClampedArray(bitmap.pixels.buffer, bitmap.pixels.byteOffset, bitmap.pixels.byteLength)
25
+ : new Uint8ClampedArray(bitmap.pixels);
26
+ context.putImageData(new ImageData(pixels, bitmap.width, bitmap.height), 0, 0);
26
27
  }, [bitmap]);
27
28
 
28
29
  return (
@@ -37,9 +38,9 @@ function CanvasBitmap({ bitmap }: { bitmap: BitmapSurface }) {
37
38
  }}
38
39
  />
39
40
  );
40
- }
41
+ });
41
42
 
42
- function BoxLayer({ bitmap, index }: { bitmap: BitmapSurface; index: number }) {
43
+ const BoxLayer = memo(function BoxLayer({ bitmap, index }: { bitmap: BitmapSurface; index: number }) {
43
44
  return (
44
45
  <div
45
46
  style={{
@@ -52,7 +53,7 @@ function BoxLayer({ bitmap, index }: { bitmap: BitmapSurface; index: number }) {
52
53
  <CanvasBitmap bitmap={bitmap} />
53
54
  </div>
54
55
  );
55
- }
56
+ });
56
57
 
57
58
  function ChartCrosshair({
58
59
  bitmap,
@@ -132,7 +133,7 @@ export const WebChartSurface = forwardRef<BoxRenderable, Record<string, unknown>
132
133
  >
133
134
  {layers.length > 0
134
135
  ? layers.map((layer, index) => (
135
- <BoxLayer key={`${layer.width}x${layer.height}:${index}`} index={index} bitmap={layer} />
136
+ <BoxLayer key={`layer:${index}`} index={index} bitmap={layer} />
136
137
  ))
137
138
  : children as ReactNode}
138
139
  <ChartCrosshair bitmap={baseLayer} crosshair={crosshair} />
@@ -89,13 +89,11 @@ function useLatestRef<T>(value: T): RefObject<T> {
89
89
 
90
90
  function useSyncedEditableElement<T extends HTMLInputElement | HTMLTextAreaElement>({
91
91
  elementRef,
92
- propsRef,
93
92
  valueRef,
94
93
  handleValueChange,
95
94
  active,
96
95
  }: {
97
96
  elementRef: RefObject<T | null>;
98
- propsRef: RefObject<Record<string, unknown>>;
99
97
  valueRef: RefObject<string>;
100
98
  handleValueChange: (nextValue: string) => void;
101
99
  active: boolean;
@@ -122,21 +120,6 @@ function useSyncedEditableElement<T extends HTMLInputElement | HTMLTextAreaEleme
122
120
  };
123
121
  }, [active, syncElementValue]);
124
122
 
125
- useEffect(() => {
126
- if (!active) return;
127
- const commitBeforeOutsideMouseDown = (event: globalThis.MouseEvent) => {
128
- const element = elementRef.current;
129
- const target = event.target;
130
- if (!element || (target instanceof Node && element.contains(target))) return;
131
- const nextValue = syncElementValue();
132
- callTextHandler(propsRef.current.onBlur, nextValue);
133
- };
134
- document.addEventListener("mousedown", commitBeforeOutsideMouseDown, true);
135
- return () => {
136
- document.removeEventListener("mousedown", commitBeforeOutsideMouseDown, true);
137
- };
138
- }, [active, elementRef, propsRef, syncElementValue]);
139
-
140
123
  return syncElementValue;
141
124
  }
142
125
 
@@ -223,6 +206,8 @@ export const WebInput = forwardRef<InputRenderable, Record<string, unknown>>(fun
223
206
  useEffect(() => {
224
207
  if (props.focused === true) {
225
208
  elementRef.current?.focus();
209
+ } else if (props.focused === false && document.activeElement === elementRef.current) {
210
+ elementRef.current?.blur();
226
211
  }
227
212
  }, [props.focused]);
228
213
 
@@ -236,6 +221,7 @@ export const WebInput = forwardRef<InputRenderable, Record<string, unknown>>(fun
236
221
  },
237
222
  setCursorOffset: applyCursorOffset,
238
223
  focus: () => elementRef.current?.focus(),
224
+ blur: () => elementRef.current?.blur(),
239
225
  }), [applyCursorOffset, cursorOffset, setValue, valueRef]);
240
226
 
241
227
  const handleValueChange = useCallback((nextValue: string) => {
@@ -247,13 +233,18 @@ export const WebInput = forwardRef<InputRenderable, Record<string, unknown>>(fun
247
233
  }, [propsRef, setValue]);
248
234
  const syncElementValue = useSyncedEditableElement({
249
235
  elementRef,
250
- propsRef,
251
236
  valueRef,
252
237
  handleValueChange,
253
238
  active: props.focused === true || domFocused,
254
239
  });
255
240
 
256
241
  const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
242
+ if ((event.key === "Escape" || event.key === "Esc") && typeof propsRef.current.onEscape === "function") {
243
+ event.preventDefault();
244
+ event.stopPropagation();
245
+ (propsRef.current.onEscape as () => void)();
246
+ return;
247
+ }
257
248
  if (event.key === "Enter" && typeof propsRef.current.onSubmit === "function") {
258
249
  event.preventDefault();
259
250
  (propsRef.current.onSubmit as (value: string) => void)(syncElementValue());
@@ -361,7 +352,6 @@ export const WebTextarea = forwardRef<TextareaRenderable, Record<string, unknown
361
352
  }, [propsRef, setValue]);
362
353
  const syncElementValue = useSyncedEditableElement({
363
354
  elementRef,
364
- propsRef,
365
355
  valueRef,
366
356
  handleValueChange,
367
357
  active: props.focused === true || domFocused,
@@ -0,0 +1,170 @@
1
+ /// <reference lib="dom" />
2
+ /** @jsxImportSource react */
3
+ import Hls from "hls.js";
4
+ import { forwardRef, useEffect, useImperativeHandle, useRef, useState, type CSSProperties, type ReactNode } from "react";
5
+ import type { MediaSurfaceHandle, MediaSurfaceProps } from "../../../../ui/host";
6
+ import { cleanDomProps, commonStyle } from "./style";
7
+
8
+ export const WebMediaSurface = forwardRef<HTMLVideoElement, MediaSurfaceProps>(function WebMediaSurface(rawProps, forwardedRef) {
9
+ const {
10
+ children,
11
+ src,
12
+ title,
13
+ poster,
14
+ autoPlay = false,
15
+ muted = false,
16
+ mediaHandleRef,
17
+ onPlaybackStateChange,
18
+ onMutedChange,
19
+ onError,
20
+ ...props
21
+ } = rawProps as MediaSurfaceProps & {
22
+ children?: ReactNode;
23
+ src?: string;
24
+ title?: string;
25
+ poster?: string;
26
+ autoPlay?: boolean;
27
+ muted?: boolean;
28
+ mediaHandleRef?: MediaSurfaceProps["mediaHandleRef"];
29
+ onPlaybackStateChange?: (state: "idle" | "loading" | "playing" | "paused" | "error") => void;
30
+ onMutedChange?: (muted: boolean) => void;
31
+ onError?: (message: string) => void;
32
+ };
33
+ const videoRef = useRef<HTMLVideoElement | null>(null);
34
+ const [failed, setFailed] = useState(false);
35
+ const mediaSrc = typeof src === "string" ? src.trim() : "";
36
+ const baseStyle = commonStyle(props);
37
+
38
+ useImperativeHandle(forwardedRef, () => videoRef.current as HTMLVideoElement, []);
39
+ useImperativeHandle(mediaHandleRef, (): MediaSurfaceHandle => ({
40
+ async play() {
41
+ if (videoRef.current) await videoRef.current.play();
42
+ },
43
+ pause() {
44
+ videoRef.current?.pause();
45
+ },
46
+ async toggle() {
47
+ const video = videoRef.current;
48
+ if (!video) return;
49
+ if (video.paused) {
50
+ await video.play();
51
+ } else {
52
+ video.pause();
53
+ }
54
+ },
55
+ toggleMuted() {
56
+ const video = videoRef.current;
57
+ if (!video) return muted;
58
+ video.muted = !video.muted;
59
+ return video.muted;
60
+ },
61
+ }), [muted]);
62
+
63
+ useEffect(() => {
64
+ const video = videoRef.current;
65
+ if (!video || !mediaSrc) {
66
+ onPlaybackStateChange?.("idle");
67
+ return;
68
+ }
69
+
70
+ setFailed(false);
71
+ onPlaybackStateChange?.("loading");
72
+ let hls: Hls | null = null;
73
+
74
+ let active = true;
75
+ const fail = (message: string) => {
76
+ if (!active) return;
77
+ setFailed(true);
78
+ onPlaybackStateChange?.("error");
79
+ onError?.(message);
80
+ };
81
+ const handlePlaying = () => onPlaybackStateChange?.("playing");
82
+ const handlePause = () => onPlaybackStateChange?.("paused");
83
+ const handleWaiting = () => onPlaybackStateChange?.("loading");
84
+ const handleVolumeChange = () => onMutedChange?.(video.muted);
85
+ const handleError = () => fail("The live stream could not be played.");
86
+ video.addEventListener("playing", handlePlaying);
87
+ video.addEventListener("pause", handlePause);
88
+ video.addEventListener("waiting", handleWaiting);
89
+ video.addEventListener("volumechange", handleVolumeChange);
90
+ video.addEventListener("error", handleError);
91
+ video.defaultMuted = muted;
92
+ video.muted = muted;
93
+ onMutedChange?.(video.muted);
94
+
95
+ const startPlayback = () => {
96
+ if (!autoPlay) return;
97
+ void video.play().catch(() => {
98
+ if (active) onPlaybackStateChange?.("paused");
99
+ });
100
+ };
101
+
102
+ if (video.canPlayType("application/vnd.apple.mpegurl")) {
103
+ video.src = mediaSrc;
104
+ video.load();
105
+ startPlayback();
106
+ } else if (Hls.isSupported()) {
107
+ hls = new Hls({
108
+ backBufferLength: 30,
109
+ enableWorker: true,
110
+ lowLatencyMode: true,
111
+ });
112
+ hls.on(Hls.Events.ERROR, (_event, data) => {
113
+ if (!data.fatal) return;
114
+ fail(data.details ? `Stream playback failed: ${data.details}` : "Stream playback failed.");
115
+ });
116
+ hls.on(Hls.Events.MANIFEST_PARSED, startPlayback);
117
+ hls.loadSource(mediaSrc);
118
+ hls.attachMedia(video);
119
+ } else {
120
+ fail("HLS playback is unavailable in this desktop runtime.");
121
+ }
122
+
123
+ return () => {
124
+ active = false;
125
+ hls?.destroy();
126
+ video.removeEventListener("playing", handlePlaying);
127
+ video.removeEventListener("pause", handlePause);
128
+ video.removeEventListener("waiting", handleWaiting);
129
+ video.removeEventListener("volumechange", handleVolumeChange);
130
+ video.removeEventListener("error", handleError);
131
+ video.pause();
132
+ video.removeAttribute("src");
133
+ video.load();
134
+ };
135
+ }, [autoPlay, mediaSrc, onError, onMutedChange, onPlaybackStateChange]);
136
+
137
+ return (
138
+ <div
139
+ {...cleanDomProps(props)}
140
+ style={{
141
+ ...baseStyle,
142
+ position: "relative",
143
+ overflow: "hidden",
144
+ background: "#050505",
145
+ ...(props.style as CSSProperties | undefined),
146
+ }}
147
+ >
148
+ {mediaSrc && !failed ? (
149
+ <video
150
+ key={mediaSrc}
151
+ ref={videoRef}
152
+ aria-label={title || "Live TV stream"}
153
+ title={title}
154
+ poster={poster}
155
+ controls
156
+ playsInline
157
+ autoPlay={autoPlay}
158
+ muted={muted}
159
+ style={{
160
+ width: "100%",
161
+ height: "100%",
162
+ display: "block",
163
+ objectFit: "contain",
164
+ background: "#050505",
165
+ }}
166
+ />
167
+ ) : children as ReactNode}
168
+ </div>
169
+ );
170
+ });
@@ -60,6 +60,7 @@ export function cellMouseEvent(event: MouseLikeEvent, type?: string) {
60
60
  ctrl: event.ctrlKey || event.metaKey,
61
61
  },
62
62
  scroll: resolveScroll(event),
63
+ isDefaultPrevented: () => event.defaultPrevented,
63
64
  preventDefault: () => event.preventDefault(),
64
65
  stopPropagation: () => event.stopPropagation(),
65
66
  };
@@ -102,7 +102,7 @@ export function cleanDomProps(props: Record<string, unknown>): Record<string, un
102
102
  "border", "borderStyle", "borderColor", "overflow", "selectable", "visible", "name", "color",
103
103
  "focused", "focusedBackgroundColor", "textColor", "focusedTextColor", "placeholderColor",
104
104
  "cursorColor", "selectionBg", "selectionFg", "showCursor", "keyBindings", "wrapText", "wrapMode",
105
- "initialValue", "value", "onInput", "onChange", "onSubmit", "onCursorChange", "onMouse",
105
+ "initialValue", "value", "onInput", "onChange", "onSubmit", "onEscape", "onCursorChange", "onMouse",
106
106
  "scrollX", "scrollY", "focusable", "bitmap", "bitmaps", "crosshair", "text", "font",
107
107
  "hoverBackgroundColor",
108
108
  ]) {
@@ -17,6 +17,7 @@ export function WebTabs({
17
17
  activeValue,
18
18
  onSelect,
19
19
  compact = false,
20
+ dense = false,
20
21
  variant = "underline",
21
22
  closeMode = "always",
22
23
  addLabel = "+",
@@ -29,7 +30,7 @@ export function WebTabs({
29
30
  const showUnderline = variant === "underline" && !compact;
30
31
  const listHeight = showUnderline ? 28 : compact ? WEB_CELL_HEIGHT : "100%";
31
32
  const tabFontSize = compact || showUnderline ? 12 : 13;
32
- const tabPaddingInline = showUnderline ? 10 : 8;
33
+ const tabPaddingInline = dense ? 5 : showUnderline ? 10 : 8;
33
34
  const tabPaddingBlock = variant === "bare" || variant === "pill" ? 2 : 0;
34
35
 
35
36
  useEffect(() => {
@@ -74,14 +75,14 @@ export function WebTabs({
74
75
  display: "flex",
75
76
  flexDirection: "row",
76
77
  alignItems: "stretch",
77
- gap: 4,
78
+ gap: dense ? 2 : 4,
78
79
  width: "100%",
79
80
  height: listHeight,
80
81
  minInlineSize: 0,
81
82
  flexShrink: 0,
82
83
  overflowX: "auto",
83
84
  overflowY: "hidden",
84
- paddingInline: variant === "underline" ? 0 : 4,
85
+ paddingInline: variant === "underline" || dense ? 0 : 4,
85
86
  paddingBlock: tabPaddingBlock,
86
87
  marginBottom: showUnderline ? 4 : 0,
87
88
  boxSizing: "border-box",
@@ -2,10 +2,16 @@
2
2
  /** @jsxImportSource react */
3
3
  import { createRoot } from "react-dom/client";
4
4
  import { App } from "../../../app";
5
+ import { applyLanguageFromConfig } from "../../../i18n";
5
6
  import { UiHostProvider } from "../../../ui/host";
6
7
  import { debugLog } from "../../../utils/debug-log";
7
8
  import { measurePerfAsync } from "../../../utils/perf-marks";
8
- import { backendRequest, initElectrobunBackend, setElectrobunRemoteRequestHandler } from "./backend-rpc";
9
+ import {
10
+ backendRequest,
11
+ initElectrobunBackend,
12
+ requestMainWindowRemoteControl,
13
+ setElectrobunRemoteRequestHandler,
14
+ } from "./backend-rpc";
9
15
  import { installElectrobunAiHost } from "./ai-host";
10
16
  import { installElectrobunBrokerRemoteClient } from "./broker-remote-client";
11
17
  import { installElectrobunConfigStoreHost } from "./config-host";
@@ -82,18 +88,19 @@ async function boot() {
82
88
  installElectrobunCloudApiFetchTransport();
83
89
  installElectrobunUpdateHost();
84
90
  const init = await measurePerfAsync("startup.electrobun.backend-init", () => backendInitPromise);
85
- await installElectrobunAiHost();
91
+ installElectrobunAiHost();
86
92
  const desktopSnapshot = init.windowKind === "detached" && init.paneId && init.desktopSnapshot
87
93
  ? prepareDetachedSnapshot(init.desktopSnapshot, init.paneId)
88
94
  : init.desktopSnapshot;
89
95
  const config = desktopSnapshot?.config ?? init.config;
96
+ applyLanguageFromConfig(config);
90
97
  const desktopWindowBridge = createDesktopWindowBridge(init.windowKind, init.paneId);
91
98
  const desktopApplicationMenuBridge = createApplicationMenuBridge();
92
99
  const desktopDeepLinkBridge = createDesktopDeepLinkBridge();
93
100
  const webUiHost = createWebUiHost(init.desktopPlatform);
94
101
  const remoteControlAdapter = init.windowKind === "main"
95
102
  ? { registerHandler: setElectrobunRemoteRequestHandler }
96
- : undefined;
103
+ : { handleRequest: requestMainWindowRemoteControl };
97
104
  measurePerfAsync("startup.electrobun.root-render", async () => {
98
105
  root.render(
99
106
  <ElectrobunErrorBoundary>
@@ -455,3 +455,20 @@ body.gloom-dragging [data-gloom-role="chart-surface"] {
455
455
  0 18px 48px color-mix(in srgb, var(--gloom-bg) 46%, transparent),
456
456
  inset 0 1px 0 color-mix(in srgb, var(--gloom-text-bright) 5%, transparent);
457
457
  }
458
+ .gloom-popover-anchor {
459
+ display: inline-flex;
460
+ min-width: 0;
461
+ outline: none;
462
+ }
463
+ .gloom-popover {
464
+ position: fixed;
465
+ z-index: 10001;
466
+ max-height: min(420px, calc(100vh - 20px));
467
+ overflow: auto;
468
+ border: 1px solid;
469
+ border-radius: 8px;
470
+ padding: 8px;
471
+ box-shadow:
472
+ 0 14px 34px color-mix(in srgb, var(--gloom-bg) 42%, transparent),
473
+ inset 0 1px 0 color-mix(in srgb, var(--gloom-text-bright) 5%, transparent);
474
+ }
@@ -15,9 +15,11 @@ import {
15
15
  WebSegmentedControl,
16
16
  WebTextField,
17
17
  } from "./desktop/controls";
18
+ import { WebPopover } from "./desktop/popover";
18
19
  import { WebBox } from "./host/box";
19
20
  import { WebChartSurface } from "./host/chart-surface";
20
21
  import { WebInput, WebTextarea } from "./host/input";
22
+ import { WebMediaSurface } from "./host/media-surface";
21
23
  import {
22
24
  NATIVE_CONTEXT_MENU_SUPPORTED,
23
25
  showDesktopContextMenu,
@@ -84,6 +86,7 @@ export function createWebUiHost(desktopPlatform?: string): UiHost {
84
86
  Textarea: WebTextarea,
85
87
  Button: WebButton,
86
88
  Checkbox: WebCheckbox,
89
+ Popover: WebPopover,
87
90
  TextField: WebTextField,
88
91
  MessageComposer: WebMessageComposer,
89
92
  ListView: WebListView,
@@ -124,6 +127,7 @@ export function createWebUiHost(desktopPlatform?: string): UiHost {
124
127
  </div>
125
128
  );
126
129
  },
130
+ MediaSurface: WebMediaSurface,
127
131
  SpinnerMark: ({ color, ...props }) => (
128
132
  <span
129
133
  {...cleanDomProps(props)}
@@ -198,4 +202,7 @@ export const webRendererHost: RendererHost = {
198
202
  }).catch(() => {});
199
203
  },
200
204
  showContextMenu: showDesktopContextMenu,
205
+ resolveLiveStream(request) {
206
+ return backendRequest("media.resolveLiveStream", request);
207
+ },
201
208
  };