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
@@ -3,28 +3,103 @@ import {
3
3
  type AiRunnerEvent,
4
4
  } from "../../../capabilities";
5
5
  import {
6
- getAiProviderDefinitions,
7
- __setDetectedProvidersForTests,
8
- } from "../../../plugins/builtin/ai/providers";
9
- import { AiRunCancelledError, setAiRunHost } from "../../../plugins/builtin/ai/runner";
6
+ AiRunCancelledError,
7
+ installAiRunHost,
8
+ type AiAuthProgressEvent,
9
+ type AiRunHost,
10
+ type AiRuntimeAuthType,
11
+ type AiRuntimeCatalog,
12
+ } from "../../../plugins/builtin/ai/runner";
13
+ import { debugLog } from "../../../utils/debug-log";
10
14
  import { backendRequest, onCapabilityEvent } from "./backend-rpc";
11
15
 
16
+ const AI_STARTUP_READINESS_TIMEOUT_MS = 5_000;
17
+ const aiHostLog = debugLog.createLogger("electrobun-ai-host");
12
18
  let nextRunId = 1;
13
19
 
14
- export async function installElectrobunAiHost(): Promise<void> {
15
- const availability = await backendRequest<Record<string, boolean>>("capability.invoke", {
20
+ function connectProvider(
21
+ providerId: string,
22
+ authType?: AiRuntimeAuthType,
23
+ onAuthEvent?: (event: AiAuthProgressEvent) => void,
24
+ ): Promise<AiRuntimeCatalog> {
25
+ const subscriptionId = `ai-login:${nextRunId++}`;
26
+ let settled = false;
27
+ let disposeMessages: () => void = () => {};
28
+ let resolveDone: (catalog: AiRuntimeCatalog) => void = () => {};
29
+ let rejectDone: (error: unknown) => void = () => {};
30
+ const cleanup = () => {
31
+ disposeMessages();
32
+ void backendRequest("capability.unsubscribe", { subscriptionId }).catch(() => {});
33
+ };
34
+ const settle = (callback: () => void) => {
35
+ if (settled) return;
36
+ settled = true;
37
+ cleanup();
38
+ callback();
39
+ };
40
+ const done = new Promise<AiRuntimeCatalog>((resolve, reject) => {
41
+ resolveDone = resolve;
42
+ rejectDone = reject;
43
+ });
44
+ disposeMessages = onCapabilityEvent(subscriptionId, (message) => {
45
+ const event = message.event as AiRunnerEvent;
46
+ if (event.kind === "account-connected") {
47
+ settle(() => resolveDone(event.catalog as AiRuntimeCatalog));
48
+ } else if (event.kind === "account-auth") {
49
+ onAuthEvent?.(event.event);
50
+ } else if (event.kind === "account-error") {
51
+ settle(() => rejectDone(new Error(event.error)));
52
+ }
53
+ });
54
+ void backendRequest("capability.subscribe", {
55
+ subscriptionId,
16
56
  capabilityId: AI_RUNNER_CAPABILITY_ID,
17
- operationId: "getProviderAvailability",
18
- payload: {},
57
+ operationId: "connectProvider",
58
+ payload: { providerId, authType },
59
+ }).catch((error) => {
60
+ settle(() => rejectDone(error));
61
+ }).finally(() => {
62
+ if (settled) void backendRequest("capability.unsubscribe", { subscriptionId }).catch(() => {});
19
63
  });
20
- const providers = getAiProviderDefinitions().map((definition) => ({
21
- ...definition,
22
- available: availability[definition.id] ?? false,
23
- }));
64
+ return done;
65
+ }
24
66
 
25
- __setDetectedProvidersForTests(providers);
26
- setAiRunHost({
27
- run({ provider, prompt, cwd, onChunk }) {
67
+ export function installElectrobunAiHost(): void {
68
+ const host: AiRunHost = {
69
+ getCatalog() {
70
+ return backendRequest("capability.invoke", {
71
+ capabilityId: AI_RUNNER_CAPABILITY_ID,
72
+ operationId: "getCatalog",
73
+ payload: {},
74
+ });
75
+ },
76
+ connect(providerId, authType, onAuthEvent) {
77
+ return connectProvider(providerId, authType, onAuthEvent);
78
+ },
79
+ disconnect(providerId) {
80
+ return backendRequest("capability.invoke", {
81
+ capabilityId: AI_RUNNER_CAPABILITY_ID,
82
+ operationId: "disconnectProvider",
83
+ payload: { providerId },
84
+ });
85
+ },
86
+ checkStatus(providerId) {
87
+ return backendRequest("capability.invoke", {
88
+ capabilityId: AI_RUNNER_CAPABILITY_ID,
89
+ operationId: "checkProviderStatus",
90
+ payload: { providerId },
91
+ });
92
+ },
93
+ run({
94
+ providerId,
95
+ prompt,
96
+ messages,
97
+ agentMessages,
98
+ modelId,
99
+ onChunk,
100
+ onAgentMessages,
101
+ outputMode,
102
+ }) {
28
103
  const subscriptionId = `ai-run:${nextRunId++}`;
29
104
  let disposed = false;
30
105
  let settled = false;
@@ -58,7 +133,10 @@ export async function installElectrobunAiHost(): Promise<void> {
58
133
  onChunk?.(event.output);
59
134
  break;
60
135
  case "done":
61
- settle(() => resolveDone(event.output));
136
+ settle(() => {
137
+ if (event.agentMessages) onAgentMessages?.(event.agentMessages);
138
+ resolveDone(event.output);
139
+ });
62
140
  break;
63
141
  case "cancelled":
64
142
  settle(() => rejectDone(new AiRunCancelledError()));
@@ -66,21 +144,35 @@ export async function installElectrobunAiHost(): Promise<void> {
66
144
  case "error":
67
145
  settle(() => rejectDone(new Error(event.error)));
68
146
  break;
147
+ case "account-connected":
148
+ case "account-auth":
149
+ case "account-error":
150
+ break;
69
151
  }
70
152
  });
71
153
 
72
- void backendRequest("capability.subscribe", {
154
+ const subscribePromise = backendRequest("capability.subscribe", {
73
155
  subscriptionId,
74
156
  capabilityId: AI_RUNNER_CAPABILITY_ID,
75
157
  operationId: "run",
76
158
  payload: {
77
- providerId: provider.id,
159
+ providerId,
78
160
  prompt,
79
- cwd,
161
+ messages,
162
+ agentMessages,
163
+ modelId,
164
+ outputMode,
80
165
  },
81
166
  }).catch((error) => {
82
167
  settle(() => rejectDone(error));
168
+ }).finally(() => {
169
+ // Cancellation can race the async subscribe. Unsubscribe again after it
170
+ // settles so a late backend subscription cannot outlive this run.
171
+ if (disposed) {
172
+ void backendRequest("capability.unsubscribe", { subscriptionId }).catch(() => {});
173
+ }
83
174
  });
175
+ void subscribePromise;
84
176
 
85
177
  return {
86
178
  done,
@@ -89,5 +181,18 @@ export async function installElectrobunAiHost(): Promise<void> {
89
181
  },
90
182
  };
91
183
  },
184
+ };
185
+ void installAiRunHost(host, {
186
+ catalogTimeoutMs: AI_STARTUP_READINESS_TIMEOUT_MS,
187
+ timeoutMessage: "In-app AI provider discovery timed out during desktop startup",
188
+ onCatalogError(error) {
189
+ aiHostLog.warn("In-app AI provider discovery could not finish during desktop startup", {
190
+ error: error instanceof Error ? error.message : String(error),
191
+ });
192
+ },
193
+ }).catch((error) => {
194
+ aiHostLog.warn("In-app AI providers could not be initialized", {
195
+ error: error instanceof Error ? error.message : String(error),
196
+ });
92
197
  });
93
198
  }
@@ -164,6 +164,10 @@ export async function backendRequest<T = unknown>(method: string, payload: unkno
164
164
  return decodeRpcValue<T>(result);
165
165
  }
166
166
 
167
+ export function requestMainWindowRemoteControl(request: RemoteControlRequest): Promise<RemoteControlResponse> {
168
+ return backendRequest("remote.forward", { request });
169
+ }
170
+
167
171
  export async function initElectrobunBackend(payload?: { kind?: "main" | "detached"; paneId?: string }): Promise<ElectrobunBackendInit> {
168
172
  initSnapshot = await backendRequest<ElectrobunBackendInit>("init", payload ?? null);
169
173
  return initSnapshot;
@@ -14,16 +14,30 @@ import { webUiHost } from "./ui-host";
14
14
  import { getLoadablePlugins } from "../../../plugins/catalog";
15
15
  import { setSharedMarketDataForTests } from "../../../plugins/registry";
16
16
  import { PluginRenderProvider, type PluginRuntimeAccess } from "../../../plugins/runtime";
17
+ import {
18
+ RemoteUiRegistryProvider,
19
+ useRemoteUiRegistry,
20
+ } from "../../../remote/semantic-tree";
21
+ import type { RemoteUiNodeSnapshot } from "../../../remote/types";
17
22
  import { FloatingPaneWrapper } from "../../../components/layout/floating-pane";
18
23
  import { PaneContent } from "../../../components/layout/pane/content";
19
24
  import { resolvePaneBodyFrame } from "../../../components/layout/pane/sizing";
20
25
  import { getPaneDisplayTitle } from "../../../components/layout/pane/title";
26
+ import { subtractTimeRange } from "../../../components/chart/core/date-window";
27
+ import {
28
+ getPresetResolution,
29
+ normalizeChartResolutionSupport,
30
+ TIME_RANGE_ORDER,
31
+ } from "../../../components/chart/core/resolution";
32
+ import type { TimeRange } from "../../../components/chart/core/types";
21
33
  import type { AppConfig } from "../../../types/config";
22
34
  import type { CachedFinancialsTarget, DataProvider, QuoteSubscriptionTarget } from "../../../types/data-provider";
23
- import type { OptionsChain, TickerFinancials } from "../../../types/financials";
35
+ import type { OptionsChain, PricePoint, TickerFinancials } from "../../../types/financials";
24
36
  import type { TickerRecord } from "../../../types/ticker";
25
37
  import type { AppState, PaneRuntimeState } from "../../../core/state/app/state";
26
38
  import type { PaneDef } from "../../../types/plugin";
39
+ import { canonicalTickerKey, parsePublicTickerKey } from "../../../utils/exchanges";
40
+ import { hydrateFredSeries, type FredSeriesCacheEntry } from "../../../data/fred-series";
27
41
 
28
42
  interface CliPaneShotPayload {
29
43
  config: AppConfig;
@@ -33,6 +47,7 @@ interface CliPaneShotPayload {
33
47
  tickers: TickerRecord[];
34
48
  financials: Array<[string, TickerFinancials]>;
35
49
  optionsChains: Array<[string, OptionsChain]>;
50
+ fredSeries: Array<[string, FredSeriesCacheEntry]>;
36
51
  paneState: Record<string, PaneRuntimeState>;
37
52
  }
38
53
 
@@ -42,10 +57,14 @@ declare global {
42
57
  __GLOOM_CLI_SHOT_READY__?: boolean;
43
58
  __GLOOM_CLI_SHOT_PENDING__?: number;
44
59
  __GLOOM_CLI_SHOT_ERROR__?: string;
60
+ __GLOOM_CLI_SHOT_SEMANTIC_UI__?: RemoteUiNodeSnapshot[];
45
61
  }
46
62
  }
47
63
 
48
- const SHOT_READY_STABLE_FRAMES = 2;
64
+ // Effects that request pane data start after the first paint. Waiting only two
65
+ // frames can capture the shell before those requests register, leaving a
66
+ // loading chart or stale performance table in an otherwise valid PNG.
67
+ const SHOT_READY_STABLE_FRAMES = 10;
49
68
  const SHOT_LOADING_TEXT_PATTERN = /\b(Loading|Rendering pane)\b/i;
50
69
  const TRACKED_RESPONSE_METHODS = new Set<PropertyKey>([
51
70
  "arrayBuffer",
@@ -58,6 +77,12 @@ const TRACKED_RESPONSE_METHODS = new Set<PropertyKey>([
58
77
  let pendingShotWork = 0;
59
78
  let didInstallShotFetchTracker = false;
60
79
  let shotDataProvider: DataProvider | null = null;
80
+ const SHOT_CHART_RESOLUTION_SUPPORT = normalizeChartResolutionSupport(
81
+ TIME_RANGE_ORDER.map((maxRange) => ({
82
+ resolution: getPresetResolution(maxRange),
83
+ maxRange,
84
+ })),
85
+ );
61
86
 
62
87
  const rendererHost: RendererHost = {
63
88
  requestExit() {},
@@ -73,6 +98,22 @@ function normalizeSymbol(value: string): string {
73
98
  return value.trim().replace(/^\$/, "").toUpperCase();
74
99
  }
75
100
 
101
+ function clipShotPriceHistory(points: PricePoint[], range: TimeRange): PricePoint[] {
102
+ if (range === "ALL" || points.length === 0) return points;
103
+ const dated = points.flatMap((point) => {
104
+ const date = point.date instanceof Date ? point.date : new Date(point.date);
105
+ return Number.isFinite(date.getTime()) ? [{ point, date }] : [];
106
+ });
107
+ const end = dated.reduce<Date | null>((latest, entry) => (
108
+ !latest || entry.date.getTime() > latest.getTime() ? entry.date : latest
109
+ ), null);
110
+ if (!end) return [];
111
+ const start = subtractTimeRange(end, range);
112
+ return dated
113
+ .filter(({ date }) => date.getTime() >= start.getTime() && date.getTime() <= end.getTime())
114
+ .map(({ point }) => point);
115
+ }
116
+
76
117
  function updatePendingShotWork(next: number): void {
77
118
  pendingShotWork = Math.max(0, next);
78
119
  window.__GLOOM_CLI_SHOT_PENDING__ = pendingShotWork;
@@ -112,6 +153,24 @@ function isShotLoadingTextVisible(): boolean {
112
153
  return SHOT_LOADING_TEXT_PATTERN.test(document.body.textContent ?? "");
113
154
  }
114
155
 
156
+ function hasUnresolvedChartData(): boolean {
157
+ return (window.__GLOOM_CLI_SHOT_SEMANTIC_UI__ ?? []).some((node) => (
158
+ node.role === "chart-data"
159
+ && (
160
+ node.metadata?.kind === "stock-price"
161
+ || node.metadata?.kind === "price-comparison"
162
+ || node.metadata?.kind === "chart-composer"
163
+ )
164
+ && (
165
+ node.metadata.loading === true
166
+ || (node.metadata.kind !== "chart-composer" && (
167
+ typeof node.metadata.projectedPointCount !== "number"
168
+ || node.metadata.projectedPointCount <= 0
169
+ ))
170
+ )
171
+ ));
172
+ }
173
+
115
174
  function waitForShotReadiness(): () => void {
116
175
  let cancelled = false;
117
176
  let mutationVersion = 0;
@@ -131,7 +190,12 @@ function waitForShotReadiness(): () => void {
131
190
  if (cancelled || window.__GLOOM_CLI_SHOT_READY__) return;
132
191
  const changedSinceLastFrame = mutationVersion !== lastSeenMutationVersion;
133
192
  lastSeenMutationVersion = mutationVersion;
134
- if (pendingShotWork === 0 && !isShotLoadingTextVisible() && !changedSinceLastFrame) {
193
+ if (
194
+ pendingShotWork === 0
195
+ && !isShotLoadingTextVisible()
196
+ && !hasUnresolvedChartData()
197
+ && !changedSinceLastFrame
198
+ ) {
135
199
  stableFrames += 1;
136
200
  } else {
137
201
  stableFrames = 0;
@@ -153,11 +217,22 @@ function waitForShotReadiness(): () => void {
153
217
  }
154
218
 
155
219
  function createShotDataProvider(payload: CliPaneShotPayload): DataProvider {
156
- const financials = new Map(payload.financials.map(([symbol, data]) => [normalizeSymbol(symbol), data]));
157
- const optionsChains = new Map((payload.optionsChains ?? []).map(([symbol, data]) => [normalizeSymbol(symbol), data]));
220
+ const financials = new Map<string, TickerFinancials>();
221
+ for (const [key, data] of payload.financials) {
222
+ const instrument = parsePublicTickerKey(key);
223
+ financials.set(canonicalTickerKey(instrument.symbol, instrument.exchange), data);
224
+ if (!instrument.exchange) financials.set(normalizeSymbol(instrument.symbol), data);
225
+ }
226
+ const optionsChains = new Map<string, OptionsChain>();
227
+ for (const [key, data] of payload.optionsChains ?? []) {
228
+ const instrument = parsePublicTickerKey(key);
229
+ optionsChains.set(canonicalTickerKey(instrument.symbol, instrument.exchange), data);
230
+ if (!instrument.exchange) optionsChains.set(normalizeSymbol(instrument.symbol), data);
231
+ }
158
232
 
159
- const getFinancials = (symbol: string) => {
160
- const data = financials.get(normalizeSymbol(symbol));
233
+ const getFinancials = (symbol: string, exchange?: string) => {
234
+ const data = financials.get(canonicalTickerKey(symbol, exchange))
235
+ ?? financials.get(normalizeSymbol(symbol));
161
236
  if (!data) throw new Error(`No screenshot market data available for ${symbol}.`);
162
237
  return data;
163
238
  };
@@ -165,18 +240,20 @@ function createShotDataProvider(payload: CliPaneShotPayload): DataProvider {
165
240
  return {
166
241
  id: "cli-shot",
167
242
  name: "CLI screenshot data",
168
- getTickerFinancials(ticker) {
169
- return trackShotWork(Promise.resolve().then(() => getFinancials(ticker)));
243
+ getTickerFinancials(ticker, exchange) {
244
+ return trackShotWork(Promise.resolve().then(() => getFinancials(ticker, exchange)));
170
245
  },
171
246
  getTickerFinancialsBatch(targets: CachedFinancialsTarget[]) {
172
247
  return resolveShotWork(targets.map((target) => ({
173
248
  target,
174
- financials: financials.get(normalizeSymbol(target.symbol)) ?? null,
249
+ financials: financials.get(canonicalTickerKey(target.symbol, target.exchange))
250
+ ?? financials.get(normalizeSymbol(target.symbol))
251
+ ?? null,
175
252
  })));
176
253
  },
177
- getQuote(ticker) {
254
+ getQuote(ticker, exchange) {
178
255
  return trackShotWork(Promise.resolve().then(() => {
179
- const quote = getFinancials(ticker).quote;
256
+ const quote = getFinancials(ticker, exchange).quote;
180
257
  if (!quote) throw new Error(`No screenshot quote data available for ${ticker}.`);
181
258
  return quote;
182
259
  }));
@@ -184,15 +261,17 @@ function createShotDataProvider(payload: CliPaneShotPayload): DataProvider {
184
261
  getQuotesBatch(targets: QuoteSubscriptionTarget[]) {
185
262
  return resolveShotWork(targets.map((target) => ({
186
263
  target,
187
- quote: financials.get(normalizeSymbol(target.symbol))?.quote ?? null,
264
+ quote: (financials.get(canonicalTickerKey(target.symbol, target.exchange))
265
+ ?? financials.get(normalizeSymbol(target.symbol)))?.quote ?? null,
188
266
  })));
189
267
  },
190
268
  getExchangeRate() {
191
269
  return resolveShotWork(1);
192
270
  },
193
- getOptionsChain(ticker) {
271
+ getOptionsChain(ticker, exchange) {
194
272
  return trackShotWork(Promise.resolve().then(() => {
195
- const chain = optionsChains.get(normalizeSymbol(ticker));
273
+ const chain = optionsChains.get(canonicalTickerKey(ticker, exchange))
274
+ ?? optionsChains.get(normalizeSymbol(ticker));
196
275
  if (!chain) throw new Error(`No screenshot options data available for ${ticker}.`);
197
276
  return chain;
198
277
  }));
@@ -213,11 +292,18 @@ function createShotDataProvider(payload: CliPaneShotPayload): DataProvider {
213
292
  getArticleSummary() {
214
293
  return resolveShotWork(null);
215
294
  },
216
- getPriceHistory(ticker) {
217
- return trackShotWork(Promise.resolve().then(() => getFinancials(ticker).priceHistory ?? []));
295
+ getPriceHistory(ticker, exchange, range) {
296
+ return trackShotWork(Promise.resolve().then(() => (
297
+ clipShotPriceHistory(getFinancials(ticker, exchange).priceHistory ?? [], range)
298
+ )));
218
299
  },
219
- getPriceHistoryForResolution(ticker) {
220
- return trackShotWork(Promise.resolve().then(() => getFinancials(ticker).priceHistory ?? []));
300
+ getPriceHistoryForResolution(ticker, exchange, bufferRange) {
301
+ return trackShotWork(Promise.resolve().then(() => (
302
+ clipShotPriceHistory(getFinancials(ticker, exchange).priceHistory ?? [], bufferRange)
303
+ )));
304
+ },
305
+ getChartResolutionSupport() {
306
+ return resolveShotWork(SHOT_CHART_RESOLUTION_SUPPORT);
221
307
  },
222
308
  subscribeQuotes() {
223
309
  return () => {};
@@ -230,8 +316,14 @@ function installShotMarketData(payload: CliPaneShotPayload): void {
230
316
  shotDataProvider = provider;
231
317
  const coordinator = new MarketDataCoordinator(provider);
232
318
  coordinator.primeCachedFinancials(payload.tickers.flatMap((ticker) => {
233
- const financials = payload.financials.find(([symbol]) => normalizeSymbol(symbol) === normalizeSymbol(ticker.metadata.ticker))?.[1];
234
319
  const instrument = instrumentFromTicker(ticker, ticker.metadata.ticker);
320
+ const instrumentKey = instrument
321
+ ? canonicalTickerKey(instrument.symbol, instrument.exchange)
322
+ : normalizeSymbol(ticker.metadata.ticker);
323
+ const financials = payload.financials.find(([key]) => {
324
+ const candidate = parsePublicTickerKey(key);
325
+ return canonicalTickerKey(candidate.symbol, candidate.exchange) === instrumentKey;
326
+ })?.[1];
235
327
  return instrument && financials ? [{ instrument, financials }] : [];
236
328
  }));
237
329
  setSharedMarketDataForTests(provider);
@@ -315,6 +407,22 @@ function HydratePayload({
315
407
  return children;
316
408
  }
317
409
 
410
+ function CaptureShotSemanticUi() {
411
+ const registry = useRemoteUiRegistry();
412
+
413
+ useEffect(() => {
414
+ let frame = 0;
415
+ const capture = () => {
416
+ window.__GLOOM_CLI_SHOT_SEMANTIC_UI__ = registry?.snapshot() ?? [];
417
+ frame = requestAnimationFrame(capture);
418
+ };
419
+ capture();
420
+ return () => cancelAnimationFrame(frame);
421
+ }, [registry]);
422
+
423
+ return null;
424
+ }
425
+
318
426
  function ShotPane({ payload }: { payload: CliPaneShotPayload }) {
319
427
  const instance = payload.config.layout.instances.find((entry) => entry.instanceId === payload.paneId);
320
428
  if (!instance) throw new Error(`Pane instance ${payload.paneId} is missing from the screenshot layout.`);
@@ -375,31 +483,35 @@ function render() {
375
483
  const payload = window.__GLOOM_CLI_SHOT_PAYLOAD__;
376
484
  if (!payload) throw new Error("Missing CLI pane screenshot payload.");
377
485
  installShotFetchTracker();
486
+ hydrateFredSeries(payload.fredSeries ?? []);
378
487
  installShotMarketData(payload);
379
488
 
380
489
  const rootElement = document.getElementById("root");
381
490
  if (!rootElement) throw new Error("Missing root element.");
382
491
 
383
492
  createRoot(rootElement).render(
384
- <UiHostProvider ui={webUiHost} renderer={rendererHost} nativeRenderer={webNativeRenderer}>
385
- <WebInputHostProvider>
386
- <WebToastHostProvider>
387
- <WebDialogHostProvider>
388
- <AppProvider config={payload.config} desktopSnapshot={{
389
- config: payload.config,
390
- paneState: payload.paneState,
391
- focusedPaneId: payload.paneId,
392
- activePanel: "right",
393
- statusBarVisible: false,
394
- }}>
395
- <HydratePayload payload={payload}>
396
- <ShotPane payload={payload} />
397
- </HydratePayload>
398
- </AppProvider>
399
- </WebDialogHostProvider>
400
- </WebToastHostProvider>
401
- </WebInputHostProvider>
402
- </UiHostProvider>,
493
+ <RemoteUiRegistryProvider>
494
+ <CaptureShotSemanticUi />
495
+ <UiHostProvider ui={webUiHost} renderer={rendererHost} nativeRenderer={webNativeRenderer}>
496
+ <WebInputHostProvider>
497
+ <WebToastHostProvider>
498
+ <WebDialogHostProvider>
499
+ <AppProvider config={payload.config} desktopSnapshot={{
500
+ config: payload.config,
501
+ paneState: payload.paneState,
502
+ focusedPaneId: payload.paneId,
503
+ activePanel: "right",
504
+ statusBarVisible: false,
505
+ }}>
506
+ <HydratePayload payload={payload}>
507
+ <ShotPane payload={payload} />
508
+ </HydratePayload>
509
+ </AppProvider>
510
+ </WebDialogHostProvider>
511
+ </WebToastHostProvider>
512
+ </WebInputHostProvider>
513
+ </UiHostProvider>
514
+ </RemoteUiRegistryProvider>,
403
515
  );
404
516
  }
405
517
 
@@ -0,0 +1,166 @@
1
+ /// <reference lib="dom" />
2
+ /** @jsxImportSource react */
3
+
4
+ import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
5
+ import { createPortal } from "react-dom";
6
+ import type { HostPopoverProps } from "../../../../ui";
7
+ import { blendHex, colors } from "../../../../theme/colors";
8
+ import { useThemeColors } from "../../../../theme/theme-context";
9
+
10
+ const VIEWPORT_MARGIN = 10;
11
+ const POPOVER_GAP = 6;
12
+
13
+ interface PopoverPosition {
14
+ left: number;
15
+ top: number;
16
+ visible: boolean;
17
+ }
18
+
19
+ function cssSize(value: number | string | undefined): number | string | undefined {
20
+ return typeof value === "number" ? `${value}px` : value;
21
+ }
22
+
23
+ export function WebPopover({
24
+ open,
25
+ onOpenChange,
26
+ trigger,
27
+ children,
28
+ anchorPoint,
29
+ placement = "bottom-start",
30
+ minWidth = 280,
31
+ maxWidth = "min(420px, calc(100vw - 20px))",
32
+ label,
33
+ }: HostPopoverProps) {
34
+ useThemeColors();
35
+ const anchorRef = useRef<HTMLDivElement | null>(null);
36
+ const popoverRef = useRef<HTMLDivElement | null>(null);
37
+ const [position, setPosition] = useState<PopoverPosition>({ left: 0, top: 0, visible: false });
38
+
39
+ const updatePosition = useCallback(() => {
40
+ const anchor = anchorRef.current;
41
+ const popover = popoverRef.current;
42
+ if (!anchor || !popover) return;
43
+
44
+ const popoverRect = popover.getBoundingClientRect();
45
+ const triggerRect = anchor.getBoundingClientRect();
46
+ // Keyboard-driven popovers can intentionally omit a visible trigger. In
47
+ // that case the anchor wrapper still inherits a line-height, so its DOM
48
+ // rect is not truly empty even though there is nothing useful to anchor to.
49
+ // Center these unanchored menus; explicit pointer/footer coordinates still
50
+ // take precedence below.
51
+ if (!anchorPoint && (trigger == null || (triggerRect.width === 0 && triggerRect.height === 0))) {
52
+ setPosition({
53
+ left: Math.max(
54
+ VIEWPORT_MARGIN,
55
+ Math.min(
56
+ (window.innerWidth - popoverRect.width) / 2,
57
+ window.innerWidth - popoverRect.width - VIEWPORT_MARGIN,
58
+ ),
59
+ ),
60
+ top: Math.max(
61
+ VIEWPORT_MARGIN,
62
+ Math.min(
63
+ (window.innerHeight - popoverRect.height) / 2,
64
+ window.innerHeight - popoverRect.height - VIEWPORT_MARGIN,
65
+ ),
66
+ ),
67
+ visible: true,
68
+ });
69
+ return;
70
+ }
71
+ const anchorRect = anchorPoint
72
+ ? {
73
+ left: anchorPoint.x,
74
+ right: anchorPoint.x,
75
+ top: anchorPoint.y,
76
+ bottom: anchorPoint.y,
77
+ }
78
+ : triggerRect;
79
+ const preferredLeft = placement === "bottom-end"
80
+ ? anchorRect.right - popoverRect.width
81
+ : anchorRect.left;
82
+ const left = Math.max(
83
+ VIEWPORT_MARGIN,
84
+ Math.min(preferredLeft, window.innerWidth - popoverRect.width - VIEWPORT_MARGIN),
85
+ );
86
+ const below = anchorRect.bottom + POPOVER_GAP;
87
+ const above = anchorRect.top - popoverRect.height - POPOVER_GAP;
88
+ const top = below + popoverRect.height <= window.innerHeight - VIEWPORT_MARGIN
89
+ ? below
90
+ : Math.max(VIEWPORT_MARGIN, above);
91
+ setPosition({ left, top, visible: true });
92
+ }, [anchorPoint, placement, trigger]);
93
+
94
+ useLayoutEffect(() => {
95
+ if (!open) {
96
+ setPosition((current) => current.visible ? { ...current, visible: false } : current);
97
+ return;
98
+ }
99
+ updatePosition();
100
+ const frame = window.requestAnimationFrame(updatePosition);
101
+ window.addEventListener("resize", updatePosition);
102
+ document.addEventListener("scroll", updatePosition, true);
103
+ return () => {
104
+ window.cancelAnimationFrame(frame);
105
+ window.removeEventListener("resize", updatePosition);
106
+ document.removeEventListener("scroll", updatePosition, true);
107
+ };
108
+ }, [open, updatePosition]);
109
+
110
+ useEffect(() => {
111
+ if (!open) return;
112
+ const focusFrame = window.requestAnimationFrame(() => popoverRef.current?.focus({ preventScroll: true }));
113
+ const handleOutsideMouseDown = (event: MouseEvent) => {
114
+ const target = event.target;
115
+ if (!(target instanceof Node)) return;
116
+ if (anchorRef.current?.contains(target) || popoverRef.current?.contains(target)) return;
117
+ onOpenChange(false);
118
+ };
119
+ const handleKeyDown = (event: KeyboardEvent) => {
120
+ if (event.key !== "Escape") return;
121
+ event.preventDefault();
122
+ event.stopPropagation();
123
+ onOpenChange(false);
124
+ queueMicrotask(() => anchorRef.current?.focus({ preventScroll: true }));
125
+ };
126
+ document.addEventListener("mousedown", handleOutsideMouseDown, true);
127
+ document.addEventListener("keydown", handleKeyDown, true);
128
+ return () => {
129
+ window.cancelAnimationFrame(focusFrame);
130
+ document.removeEventListener("mousedown", handleOutsideMouseDown, true);
131
+ document.removeEventListener("keydown", handleKeyDown, true);
132
+ };
133
+ }, [onOpenChange, open]);
134
+
135
+ return (
136
+ <>
137
+ <div ref={anchorRef} tabIndex={-1} className="gloom-popover-anchor">
138
+ {trigger}
139
+ </div>
140
+ {open && createPortal(
141
+ <div
142
+ ref={popoverRef}
143
+ className="gloom-popover"
144
+ role="dialog"
145
+ aria-label={label}
146
+ tabIndex={-1}
147
+ onMouseDown={(event) => event.stopPropagation()}
148
+ onClick={(event) => event.stopPropagation()}
149
+ style={{
150
+ left: position.left,
151
+ top: position.top,
152
+ visibility: position.visible ? "visible" : "hidden",
153
+ minWidth: cssSize(minWidth),
154
+ maxWidth: cssSize(maxWidth),
155
+ borderColor: blendHex(colors.border, colors.borderFocused, 0.18),
156
+ background: blendHex(colors.panel, colors.bg, 0.08),
157
+ color: colors.text,
158
+ }}
159
+ >
160
+ {children}
161
+ </div>,
162
+ document.body,
163
+ )}
164
+ </>
165
+ );
166
+ }