gloomberb 0.11.2 → 0.12.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 (531) hide show
  1. package/README.md +7 -4
  2. package/README.zh-CN.md +4 -2
  3. package/package.json +28 -1
  4. package/src/api-client/auth.ts +10 -0
  5. package/src/api-client/data.ts +108 -1
  6. package/src/api-client/index.test.ts +127 -0
  7. package/src/api-client/index.ts +134 -1
  8. package/src/api-client/normalizers.ts +39 -0
  9. package/src/api-client/paths.ts +85 -1
  10. package/src/api-client/types.ts +220 -0
  11. package/src/app/pane-runtime/index.tsx +15 -12
  12. package/src/app/pane-runtime/layout-placement.test.ts +27 -0
  13. package/src/app/pane-runtime/layout-placement.ts +13 -0
  14. package/src/app/pane-runtime/plugin-bindings.ts +62 -0
  15. package/src/app.tsx +60 -18
  16. package/src/architecture/browser-entry-graph.test.ts +96 -0
  17. package/src/brokers/profile-form.test.ts +25 -27
  18. package/src/brokers/test-broker.ts +68 -0
  19. package/src/capabilities/chart-series.ts +3 -2
  20. package/src/cli/commands/market.ts +2 -2
  21. package/src/cli/commands/plugins.ts +39 -11
  22. package/src/cli/desktop-pane-shot.ts +285 -16
  23. package/src/cli/index.ts +1 -1
  24. package/src/cli/pane-functions/capabilities.ts +61 -31
  25. package/src/cli/pane-functions/catalog.ts +16 -4
  26. package/src/cli/pane-functions/discovery.ts +1 -0
  27. package/src/cli/pane-functions/headless.test.ts +247 -0
  28. package/src/cli/pane-functions/headless.ts +423 -0
  29. package/src/cli/pane-functions/index.test.ts +9 -0
  30. package/src/cli/pane-functions/index.ts +6 -2
  31. package/src/cli/pane-functions/options.ts +27 -2
  32. package/src/cli/pane-functions/report.ts +4 -0
  33. package/src/cli/pane-functions/resolver.ts +26 -8
  34. package/src/cli/pane-functions/screenshot.test.ts +76 -0
  35. package/src/cli/pane-functions/screenshot.ts +236 -20
  36. package/src/cli/registry.ts +0 -22
  37. package/src/cli/restore-plugins.ts +34 -0
  38. package/src/cli/result.ts +7 -10
  39. package/src/components/chart/{static/chart → composite}/axis-overlays.test.tsx +3 -3
  40. package/src/components/chart/{static/chart → composite}/axis-overlays.tsx +6 -11
  41. package/src/components/chart/{static/chart → composite}/bitmap.ts +17 -3
  42. package/src/components/chart/composite/composite-chart.test.tsx +68 -20
  43. package/src/components/chart/composite/composite-chart.tsx +301 -204
  44. package/src/components/chart/composite/format.ts +8 -2
  45. package/src/components/chart/composite/interactions.test.ts +68 -102
  46. package/src/components/chart/composite/interactions.ts +214 -264
  47. package/src/components/chart/composite/price-axis-labels.test.ts +21 -0
  48. package/src/components/chart/{price-axis-labels.tsx → composite/price-axis-labels.tsx} +9 -4
  49. package/src/components/chart/composite/scene.ts +57 -3
  50. package/src/components/chart/composite/text-renderer.ts +3 -2
  51. package/src/components/chart/composite/time-scale.ts +138 -57
  52. package/src/components/chart/composite/types.ts +25 -1
  53. package/src/components/chart/core/data.ts +9 -13
  54. package/src/components/chart/core/date-window.ts +7 -1
  55. package/src/components/chart/core/pointer.ts +3 -0
  56. package/src/components/chart/core/types.ts +0 -45
  57. package/src/components/chart/native/chart-rasterizer.ts +0 -1
  58. package/src/components/chart/native/kitty/index.test.ts +0 -188
  59. package/src/components/chart/native/raster/crosshair.ts +6 -5
  60. package/src/components/chart/native/raster/primitives.ts +0 -23
  61. package/src/components/chart/native/renderer-selection.ts +81 -31
  62. package/src/components/chart/static/{chart/surface.test.tsx → chart-surface.test.tsx} +28 -80
  63. package/src/components/chart/static/chart-surface.tsx +150 -0
  64. package/src/components/chart/static/index.ts +8 -0
  65. package/src/components/chart/static/multi-line-chart-surface.tsx +51 -166
  66. package/src/components/chart/static/scatter-chart-surface.test.ts +30 -0
  67. package/src/components/chart/static/scatter-chart-surface.tsx +135 -34
  68. package/src/components/chart/static/series.ts +49 -0
  69. package/src/components/command-bar/action-runtime.ts +1 -3
  70. package/src/components/command-bar/assist/model.test.ts +17 -2
  71. package/src/components/command-bar/assist/model.ts +40 -9
  72. package/src/components/command-bar/commands/direct/index.ts +1 -1
  73. package/src/components/command-bar/commands/direct/runtime.ts +1 -1
  74. package/src/components/command-bar/commands/registry.ts +0 -11
  75. package/src/components/command-bar/helpers.test.ts +2 -2
  76. package/src/components/command-bar/helpers.ts +2 -6
  77. package/src/components/command-bar/keyboard-shortcuts.ts +0 -11
  78. package/src/components/command-bar/list/badge.test.ts +54 -0
  79. package/src/components/command-bar/list/badge.ts +62 -0
  80. package/src/components/command-bar/list/model.test.ts +103 -0
  81. package/src/components/command-bar/list/model.ts +61 -4
  82. package/src/components/command-bar/list/view.test.tsx +113 -0
  83. package/src/components/command-bar/list/view.tsx +147 -131
  84. package/src/components/command-bar/pane-templates/workflow.ts +1 -1
  85. package/src/components/command-bar/panel/body.tsx +51 -65
  86. package/src/components/command-bar/panel/index.tsx +48 -13
  87. package/src/components/command-bar/panel/layout.test.ts +139 -0
  88. package/src/components/command-bar/panel/layout.ts +65 -28
  89. package/src/components/command-bar/panel/native-surface.ts +24 -0
  90. package/src/components/command-bar/panel/prompt-binding.ts +54 -0
  91. package/src/components/command-bar/panel/runtime.ts +6 -3
  92. package/src/components/command-bar/panel/state.ts +41 -7
  93. package/src/components/command-bar/panel/types.ts +7 -1
  94. package/src/components/command-bar/routes/root/provider-search.ts +34 -15
  95. package/src/components/command-bar/routes/root/results.test.ts +161 -0
  96. package/src/components/command-bar/routes/root/results.ts +30 -28
  97. package/src/components/command-bar/routes/root/runtime.ts +37 -20
  98. package/src/components/command-bar/routes/root/search-providers.test.tsx +224 -0
  99. package/src/components/command-bar/routes/root/search-providers.ts +205 -0
  100. package/src/components/command-bar/routes/root/selection.ts +1 -12
  101. package/src/components/command-bar/routes/root/shortcuts.test.ts +8 -8
  102. package/src/components/command-bar/routes/ticker-search/actions.ts +10 -9
  103. package/src/components/command-bar/routes/ticker-search/results.test.ts +54 -1
  104. package/src/components/command-bar/routes/ticker-search/results.ts +60 -19
  105. package/src/components/command-bar/routes/ticker-search/route.ts +1 -0
  106. package/src/components/command-bar/routing/actions.ts +1 -81
  107. package/src/components/command-bar/routing/launch-request.ts +1 -1
  108. package/src/components/command-bar/routing/list-state.ts +6 -21
  109. package/src/components/command-bar/selection-runtime.ts +1 -1
  110. package/src/components/command-bar/surface/assist.test.tsx +14 -10
  111. package/src/components/command-bar/surface/environment.ts +8 -1
  112. package/src/components/command-bar/surface/index.test.tsx +84 -68
  113. package/src/components/command-bar/surface/index.tsx +33 -36
  114. package/src/components/command-bar/surface/test-harness.tsx +15 -3
  115. package/src/components/command-bar/theme-picker.tsx +43 -15
  116. package/src/components/command-bar/view-model.test.ts +53 -8
  117. package/src/components/command-bar/view-model.ts +43 -14
  118. package/src/components/command-bar/workflow/tickers.ts +1 -0
  119. package/src/components/command-bar/workflow/types.ts +1 -1
  120. package/src/components/data-table/export.test.ts +21 -0
  121. package/src/components/data-table/export.ts +21 -0
  122. package/src/components/data-table/view.test.tsx +45 -6
  123. package/src/components/data-table/view.tsx +55 -9
  124. package/src/components/index.ts +10 -2
  125. package/src/components/input-search-bar.test.tsx +36 -2
  126. package/src/components/input-search-bar.tsx +18 -1
  127. package/src/components/layout/header.test.tsx +57 -10
  128. package/src/components/layout/header.tsx +292 -149
  129. package/src/components/layout/market-summary.test.ts +81 -0
  130. package/src/components/layout/market-summary.ts +109 -0
  131. package/src/components/layout/shell/chrome.ts +96 -1
  132. package/src/components/layout/shell/index.tsx +11 -3
  133. package/src/components/layout/shell/menu.ts +9 -0
  134. package/src/components/layout/shell/pane/actions.ts +29 -0
  135. package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
  136. package/src/components/layout/shell/shortcuts.ts +3 -0
  137. package/src/components/layout/status-bar.test.tsx +9 -8
  138. package/src/components/layout/status-bar.tsx +85 -54
  139. package/src/components/onboarding/onboarding-frame.tsx +2 -26
  140. package/src/components/price-sparkline/index.ts +61 -34
  141. package/src/components/price-sparkline/view.tsx +1 -1
  142. package/src/components/sign-in-gate.test.tsx +128 -0
  143. package/src/components/sign-in-gate.tsx +149 -0
  144. package/src/components/speedometer-gauge/desktop.tsx +2 -12
  145. package/src/components/ticker/badge/list.tsx +17 -3
  146. package/src/components/ticker/list-table-view.tsx +6 -3
  147. package/src/components/ui/data-table/index.tsx +22 -4
  148. package/src/components/ui/data-table/opentui/index.tsx +165 -120
  149. package/src/components/ui/frame.tsx +28 -0
  150. package/src/components/ui/index.ts +3 -0
  151. package/src/components/ui/native-select.tsx +20 -10
  152. package/src/components/ui/select-button.tsx +138 -0
  153. package/src/components/ui/status.tsx +2 -2
  154. package/src/components/ui/table-layout.ts +1 -1
  155. package/src/components/use-double-click-activation.ts +7 -3
  156. package/src/core/app-services.ts +3 -3
  157. package/src/data/config/chart-settings.ts +4 -4
  158. package/src/data/config/layout.ts +12 -6
  159. package/src/data/config/store/index.test.ts +97 -0
  160. package/src/data/config/store/migrations.ts +41 -0
  161. package/src/data/config/store/normalize.ts +5 -3
  162. package/src/i18n/es.ts +6 -2
  163. package/src/i18n/ja.ts +6 -2
  164. package/src/i18n/ko.ts +6 -2
  165. package/src/i18n/zh-cn.ts +6 -2
  166. package/src/i18n/zh-tw.ts +6 -2
  167. package/src/layout-marketplace/gallery-terminal.tsx +126 -96
  168. package/src/layout-marketplace/gallery.test.tsx +65 -1
  169. package/src/layout-marketplace/pane-imagery.ts +1 -0
  170. package/src/layout-marketplace/payload.test.ts +17 -0
  171. package/src/layout-marketplace/payload.ts +40 -5
  172. package/src/market-data/coordinator/index.ts +3 -2
  173. package/src/market-data/coordinator/quotes.ts +173 -66
  174. package/src/market-data/coordinator/subscriptions.test.ts +215 -37
  175. package/src/pane-settings.ts +1 -1
  176. package/src/plugins/builtin/analytics/pane-model.ts +1 -1
  177. package/src/plugins/builtin/analytics/view.tsx +1 -1
  178. package/src/plugins/builtin/broker-manager/index.test.tsx +3 -2
  179. package/src/plugins/builtin/broker-manager/model.test.ts +2 -2
  180. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +16 -2
  181. package/src/plugins/builtin/chart-composer/cli-options.ts +12 -9
  182. package/src/plugins/builtin/chart-composer/pane.tsx +22 -8
  183. package/src/plugins/builtin/chart-composer/settings.ts +2 -13
  184. package/src/plugins/builtin/chat/content/composer.tsx +8 -4
  185. package/src/plugins/builtin/cloud/auth-dialog.tsx +14 -228
  186. package/src/plugins/builtin/cloud/auth-form.tsx +277 -0
  187. package/src/plugins/builtin/cloud/plugin.tsx +1 -0
  188. package/src/plugins/builtin/composite-plugins.ts +16 -1
  189. package/src/plugins/builtin/correlation/relationship/model.ts +1 -1
  190. package/src/plugins/builtin/correlation/relationship/pane.tsx +2 -6
  191. package/src/plugins/builtin/correlation/relationship/view-model.ts +2 -2
  192. package/src/plugins/builtin/credit-conditions/index.test.tsx +12 -6
  193. package/src/plugins/builtin/credit-conditions/index.tsx +2 -21
  194. package/src/plugins/builtin/dividend-yield/index.tsx +1 -0
  195. package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
  196. package/src/plugins/builtin/earnings/index.tsx +1 -0
  197. package/src/plugins/builtin/earnings-calls/data.ts +223 -0
  198. package/src/plugins/builtin/earnings-calls/format.ts +49 -0
  199. package/src/plugins/builtin/earnings-calls/index.tsx +69 -0
  200. package/src/plugins/builtin/earnings-calls/pane.tsx +756 -0
  201. package/src/plugins/builtin/earnings-calls/prose.test.ts +36 -0
  202. package/src/plugins/builtin/earnings-calls/prose.ts +59 -0
  203. package/src/plugins/builtin/earnings-calls/transcript-view.tsx +334 -0
  204. package/src/plugins/builtin/econ/detail-view.tsx +1 -2
  205. package/src/plugins/builtin/econ/fred-series-map.ts +16 -1
  206. package/src/plugins/builtin/econ/index.tsx +3 -1
  207. package/src/plugins/builtin/econ-statistics/cache.ts +4 -0
  208. package/src/plugins/builtin/econ-statistics/client.ts +92 -0
  209. package/src/plugins/builtin/econ-statistics/defs.ts +66 -0
  210. package/src/plugins/builtin/econ-statistics/detail.tsx +154 -0
  211. package/src/plugins/builtin/econ-statistics/headless.test.ts +54 -0
  212. package/src/plugins/builtin/econ-statistics/headless.ts +171 -0
  213. package/src/plugins/builtin/econ-statistics/index.tsx +54 -0
  214. package/src/plugins/builtin/econ-statistics/pane.tsx +356 -0
  215. package/src/plugins/builtin/econ-statistics/stats.ts +522 -0
  216. package/src/plugins/builtin/econ-statistics/transform.test.ts +86 -0
  217. package/src/plugins/builtin/econ-statistics/transform.ts +85 -0
  218. package/src/plugins/builtin/econ-statistics/trend.ts +57 -0
  219. package/src/plugins/builtin/econ-statistics/view.ts +90 -0
  220. package/src/plugins/builtin/fear-greed/charts.tsx +7 -18
  221. package/src/plugins/builtin/fear-greed/data.ts +6 -1
  222. package/src/plugins/builtin/futures/index.tsx +1 -0
  223. package/src/plugins/builtin/fx-matrix/index.tsx +1 -0
  224. package/src/plugins/builtin/help/index.tsx +4 -0
  225. package/src/plugins/builtin/holders/index.tsx +1 -0
  226. package/src/plugins/builtin/insider/index.tsx +1 -0
  227. package/src/plugins/builtin/ipo-calendar/index.tsx +1 -0
  228. package/src/plugins/builtin/kelly-sizer/pane.tsx +1 -1
  229. package/src/plugins/builtin/kelly-sizer/sections.tsx +2 -3
  230. package/src/plugins/builtin/market-halts/index.tsx +1 -0
  231. package/src/plugins/builtin/market-movers/index.tsx +1 -0
  232. package/src/plugins/builtin/market-valuation/align.ts +122 -0
  233. package/src/plugins/builtin/market-valuation/cache.ts +32 -0
  234. package/src/plugins/builtin/market-valuation/chart-projection.ts +217 -0
  235. package/src/plugins/builtin/market-valuation/chart-series.ts +89 -0
  236. package/src/plugins/builtin/market-valuation/client.test.ts +141 -0
  237. package/src/plugins/builtin/market-valuation/client.ts +116 -0
  238. package/src/plugins/builtin/market-valuation/defs.ts +251 -0
  239. package/src/plugins/builtin/market-valuation/detail.tsx +159 -0
  240. package/src/plugins/builtin/market-valuation/headless.test.ts +58 -0
  241. package/src/plugins/builtin/market-valuation/headless.ts +170 -0
  242. package/src/plugins/builtin/market-valuation/index.tsx +64 -0
  243. package/src/plugins/builtin/market-valuation/indicators.ts +422 -0
  244. package/src/plugins/builtin/market-valuation/model.test.ts +350 -0
  245. package/src/plugins/builtin/market-valuation/model.ts +8 -0
  246. package/src/plugins/builtin/market-valuation/pane.test.tsx +247 -0
  247. package/src/plugins/builtin/market-valuation/pane.tsx +344 -0
  248. package/src/plugins/builtin/market-valuation/series.ts +12 -0
  249. package/src/plugins/builtin/market-valuation/settings.ts +38 -0
  250. package/src/plugins/builtin/market-valuation/sources.ts +130 -0
  251. package/src/plugins/builtin/market-valuation/trend.ts +89 -0
  252. package/src/plugins/builtin/market-valuation/view.ts +137 -0
  253. package/src/plugins/builtin/market-valuation/zone-scale/desktop.tsx +123 -0
  254. package/src/plugins/builtin/market-valuation/zone-scale/index.tsx +12 -0
  255. package/src/plugins/builtin/market-valuation/zone-scale/model.ts +60 -0
  256. package/src/plugins/builtin/market-valuation/zone-scale/terminal.tsx +94 -0
  257. package/src/plugins/builtin/news/wire/breaking/filters.test.ts +99 -0
  258. package/src/plugins/builtin/news/wire/breaking/filters.ts +110 -0
  259. package/src/plugins/builtin/news/wire/breaking/notifications.test.ts +157 -53
  260. package/src/plugins/builtin/news/wire/breaking/notifications.ts +91 -25
  261. package/src/plugins/builtin/news/wire/index.tsx +31 -44
  262. package/src/plugins/builtin/options/analytics.test.ts +83 -0
  263. package/src/plugins/builtin/options/analytics.ts +110 -0
  264. package/src/plugins/builtin/options/index.tsx +18 -2
  265. package/src/plugins/builtin/options/table.ts +121 -51
  266. package/src/plugins/builtin/options/types.ts +22 -15
  267. package/src/plugins/builtin/options/view.test.tsx +53 -1
  268. package/src/plugins/builtin/options/view.tsx +92 -41
  269. package/src/plugins/builtin/plugin-marketplace/feed.ts +80 -0
  270. package/src/plugins/builtin/plugin-marketplace/index.tsx +31 -0
  271. package/src/plugins/builtin/plugin-marketplace/model.test.ts +200 -0
  272. package/src/plugins/builtin/plugin-marketplace/model.ts +204 -0
  273. package/src/plugins/builtin/plugin-marketplace/pane.tsx +385 -0
  274. package/src/plugins/builtin/plugin-marketplace/store.ts +46 -0
  275. package/src/plugins/builtin/plugin-module.test.ts +2 -4
  276. package/src/plugins/builtin/plugin-module.ts +9 -8
  277. package/src/plugins/builtin/portfolio-list/index.tsx +1 -0
  278. package/src/plugins/builtin/research/index.tsx +4 -0
  279. package/src/plugins/builtin/research-search/command-bar-search.test.ts +81 -0
  280. package/src/plugins/builtin/research-search/command-bar-search.ts +175 -0
  281. package/src/plugins/builtin/research-search/data.ts +109 -0
  282. package/src/plugins/builtin/research-search/document-view.test.tsx +75 -0
  283. package/src/plugins/builtin/research-search/document-view.tsx +254 -0
  284. package/src/plugins/builtin/research-search/filter-bar.tsx +62 -0
  285. package/src/plugins/builtin/research-search/focus-handoff.ts +47 -0
  286. package/src/plugins/builtin/research-search/index.tsx +85 -0
  287. package/src/plugins/builtin/research-search/model.test.ts +40 -0
  288. package/src/plugins/builtin/research-search/model.ts +291 -0
  289. package/src/plugins/builtin/research-search/pane.test.tsx +241 -0
  290. package/src/plugins/builtin/research-search/pane.tsx +678 -0
  291. package/src/plugins/builtin/research-search/saved-view.tsx +171 -0
  292. package/src/plugins/builtin/research-search/snippet-text.tsx +35 -0
  293. package/src/plugins/builtin/research-search/snippet.test.ts +78 -0
  294. package/src/plugins/builtin/research-search/snippet.ts +134 -0
  295. package/src/plugins/builtin/scanner/flow-model.test.ts +11 -1
  296. package/src/plugins/builtin/scanner/flow-model.ts +29 -1
  297. package/src/plugins/builtin/scanner/flow-pane.tsx +78 -22
  298. package/src/plugins/builtin/scanner/index.ts +2 -0
  299. package/src/plugins/builtin/sec/index.tsx +1 -0
  300. package/src/plugins/builtin/sectors/index.tsx +1 -0
  301. package/src/plugins/builtin/shared/series-cache.ts +93 -0
  302. package/src/plugins/builtin/short-interest/index.tsx +1 -0
  303. package/src/plugins/builtin/short-interest/pane.tsx +1 -1
  304. package/src/plugins/builtin/thirteenf/index.tsx +1 -0
  305. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +3 -5
  306. package/src/plugins/builtin/ticker-detail/index.tsx +2 -14
  307. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +1 -1
  308. package/src/plugins/builtin/treasury-auctions/index.tsx +1 -0
  309. package/src/plugins/builtin/volatility/index.tsx +1 -1
  310. package/src/plugins/builtin/world-indices/index.tsx +1 -0
  311. package/src/plugins/builtin/world-venue-map/index.ts +27 -0
  312. package/src/plugins/builtin/world-venue-map/map.desktop.test.tsx +106 -0
  313. package/src/plugins/builtin/world-venue-map/map.tsx +481 -0
  314. package/src/plugins/builtin/world-venue-map/model.test.ts +85 -0
  315. package/src/plugins/builtin/world-venue-map/model.ts +268 -0
  316. package/src/plugins/builtin/world-venue-map/pane.tsx +300 -0
  317. package/src/plugins/builtin/world-venue-map/world-outlines.ts +2 -0
  318. package/src/plugins/builtin/yield-curve/index.tsx +1 -1
  319. package/src/plugins/bundle.test.ts +109 -0
  320. package/src/plugins/bundle.ts +139 -0
  321. package/src/plugins/catalog-backend.ts +6 -44
  322. package/src/plugins/catalog-browser.test.ts +1 -0
  323. package/src/plugins/catalog-browser.ts +12 -0
  324. package/src/plugins/catalog-manifest.test.ts +43 -0
  325. package/src/plugins/catalog-manifest.ts +92 -0
  326. package/src/plugins/catalog-ui.ts +20 -4
  327. package/src/plugins/catalog.ts +9 -2
  328. package/src/plugins/current-target.ts +28 -0
  329. package/src/plugins/host-contract.ts +37 -0
  330. package/src/plugins/host-link.ts +158 -0
  331. package/src/plugins/host-modules.ts +61 -0
  332. package/src/plugins/loader.ts +41 -22
  333. package/src/plugins/ownership.ts +1 -0
  334. package/src/plugins/prediction-markets/index.tsx +1 -0
  335. package/src/plugins/registry/context.ts +3 -0
  336. package/src/plugins/registry/contributions.ts +27 -0
  337. package/src/plugins/registry/index.test.ts +25 -0
  338. package/src/plugins/registry/index.ts +8 -0
  339. package/src/plugins/registry/pane-settings.test.ts +53 -0
  340. package/src/plugins/registry/pane-settings.ts +29 -3
  341. package/src/plugins/seed.test.ts +83 -0
  342. package/src/plugins/seed.ts +80 -0
  343. package/src/public/broker.ts +31 -0
  344. package/src/public/market-data.ts +9 -0
  345. package/src/public/pane-hooks.ts +57 -0
  346. package/src/public/public-api.test.ts +285 -0
  347. package/src/public/react.ts +62 -0
  348. package/src/public/test-support.ts +23 -0
  349. package/src/public/time-series.ts +8 -0
  350. package/src/public/utils.ts +77 -0
  351. package/src/remote/layout-helpers.ts +6 -8
  352. package/src/remote/resources.ts +2 -0
  353. package/src/remote/schema.ts +1 -0
  354. package/src/remote/semantic-tree.test.tsx +105 -0
  355. package/src/remote/semantic-tree.tsx +53 -35
  356. package/src/renderers/browser/main.tsx +6 -0
  357. package/src/renderers/browser/ui-host.tsx +11 -0
  358. package/src/renderers/cloudflare/worker.test.ts +19 -0
  359. package/src/renderers/cloudflare/worker.ts +1 -1
  360. package/src/renderers/electrobun/bun/desktop/host-requests.ts +9 -2
  361. package/src/renderers/electrobun/bun/external-plugins.ts +140 -0
  362. package/src/renderers/electrobun/bun/index.ts +6 -2
  363. package/src/renderers/electrobun/shared/protocol.ts +25 -0
  364. package/src/renderers/electrobun/view/build-assets.ts +0 -1
  365. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +51 -1
  366. package/src/renderers/electrobun/view/external-plugins.test.ts +45 -0
  367. package/src/renderers/electrobun/view/external-plugins.ts +63 -0
  368. package/src/renderers/electrobun/view/host/ascii-text.test.ts +48 -0
  369. package/src/renderers/electrobun/view/host/ascii-text.ts +11 -1
  370. package/src/renderers/electrobun/view/host/chart-surface.tsx +38 -32
  371. package/src/renderers/electrobun/view/host/mouse.ts +16 -2
  372. package/src/renderers/electrobun/view/main.tsx +27 -2
  373. package/src/renderers/electrobun/view/styles.css +5 -0
  374. package/src/renderers/electrobun/view/toast-host.tsx +18 -0
  375. package/src/renderers/electrobun/view/ui-host.tsx +3 -0
  376. package/src/renderers/opentui/chart-surface.tsx +5 -1
  377. package/src/renderers/opentui/host.tsx +20 -2
  378. package/src/renderers/opentui/interaction-performance.test.ts +79 -0
  379. package/src/renderers/opentui/interaction-performance.ts +188 -0
  380. package/src/renderers/opentui/start.tsx +26 -1
  381. package/src/renderers/opentui/test-utils.tsx +13 -0
  382. package/src/renderers/opentui/toast-host.tsx +26 -2
  383. package/src/renderers/opentui/ui-host.tsx +1 -0
  384. package/src/sources/provider-router/search.test.ts +191 -0
  385. package/src/sources/provider-router/search.ts +92 -39
  386. package/src/state/app/context/index.tsx +2 -0
  387. package/src/state/app/context/selector.test.tsx +33 -1
  388. package/src/state/hooks/quote-streaming.test.tsx +93 -0
  389. package/src/state/hooks/quote-streaming.ts +106 -30
  390. package/src/state/pane-table-export-registry.ts +50 -0
  391. package/src/theme/color-utils.ts +1 -1
  392. package/src/theme/colors.ts +50 -0
  393. package/src/theme/themes.test.ts +15 -2
  394. package/src/theme/themes.ts +19 -1
  395. package/src/tickers/search/index.test.ts +26 -0
  396. package/src/tickers/search/index.ts +42 -17
  397. package/src/tickers/search/ranking.ts +14 -0
  398. package/src/time-series/chart-data.test.ts +216 -0
  399. package/src/time-series/range.ts +12 -0
  400. package/src/time-series/resolution.test.ts +25 -26
  401. package/src/time-series/resolution.ts +44 -25
  402. package/src/time-series/resolve.ts +7 -0
  403. package/src/time-series/spec.ts +5 -5
  404. package/src/time-series/types.ts +3 -1
  405. package/src/time-series/use-chart-resolution.ts +5 -0
  406. package/src/types/config.ts +123 -3
  407. package/src/types/data-provider.ts +11 -0
  408. package/src/types/headless.ts +161 -0
  409. package/src/types/plugin.ts +104 -20
  410. package/src/ui/host.tsx +19 -1
  411. package/src/ui/index.tsx +1 -0
  412. package/src/ui/toast.tsx +8 -4
  413. package/src/utils/csv.test.ts +13 -0
  414. package/src/utils/csv.ts +37 -0
  415. package/src/utils/format.ts +35 -0
  416. package/src/utils/fuzzy-search.test.ts +94 -0
  417. package/src/utils/fuzzy-search.ts +81 -22
  418. package/src/utils/save-text-file.test.ts +24 -0
  419. package/src/utils/save-text-file.ts +36 -0
  420. package/src/utils/shortcut-labels.ts +14 -0
  421. package/src/components/chart/core/axis-format.ts +0 -157
  422. package/src/components/chart/core/index.test.ts +0 -1075
  423. package/src/components/chart/core/projection.ts +0 -357
  424. package/src/components/chart/core/render-utils.ts +0 -4
  425. package/src/components/chart/core/renderer.ts +0 -244
  426. package/src/components/chart/core/scene.ts +0 -180
  427. package/src/components/chart/core/time-axis.ts +0 -330
  428. package/src/components/chart/cursor-motion.ts +0 -47
  429. package/src/components/chart/indicators/types.ts +0 -26
  430. package/src/components/chart/market-session.ts +0 -176
  431. package/src/components/chart/multi-line/renderer.test.ts +0 -83
  432. package/src/components/chart/multi-line/renderer.ts +0 -161
  433. package/src/components/chart/multi-line/scene.ts +0 -188
  434. package/src/components/chart/native/raster/layout.ts +0 -129
  435. package/src/components/chart/native/raster/price-chart.ts +0 -244
  436. package/src/components/chart/rendering/chart-draw.ts +0 -270
  437. package/src/components/chart/rendering/geometry.ts +0 -95
  438. package/src/components/chart/rendering/pixel-buffer.ts +0 -267
  439. package/src/components/chart/scatter-chart-renderer.test.ts +0 -31
  440. package/src/components/chart/scatter-chart-renderer.ts +0 -223
  441. package/src/components/chart/static/chart/surface.tsx +0 -358
  442. package/src/components/command-bar/routes/root/article-results.test.ts +0 -83
  443. package/src/components/command-bar/routes/root/article-results.ts +0 -55
  444. package/src/plugins/builtin/news/wire/article-search.test.ts +0 -72
  445. package/src/plugins/builtin/news/wire/article-search.ts +0 -140
  446. package/src/plugins/builtin/substack/api/auth.ts +0 -185
  447. package/src/plugins/builtin/substack/api/cache.ts +0 -82
  448. package/src/plugins/builtin/substack/api/loaders.ts +0 -170
  449. package/src/plugins/builtin/substack/api/store.ts +0 -186
  450. package/src/plugins/builtin/substack/api/types.ts +0 -42
  451. package/src/plugins/builtin/substack/api.test.ts +0 -298
  452. package/src/plugins/builtin/substack/article-detail.tsx +0 -397
  453. package/src/plugins/builtin/substack/article-stack.tsx +0 -133
  454. package/src/plugins/builtin/substack/content.ts +0 -478
  455. package/src/plugins/builtin/substack/feed-tabs.tsx +0 -44
  456. package/src/plugins/builtin/substack/index.tsx +0 -49
  457. package/src/plugins/builtin/substack/login-view.tsx +0 -126
  458. package/src/plugins/builtin/substack/model.test.ts +0 -155
  459. package/src/plugins/builtin/substack/normalize.ts +0 -343
  460. package/src/plugins/builtin/substack/pane-footer.ts +0 -52
  461. package/src/plugins/builtin/substack/pane-state.ts +0 -170
  462. package/src/plugins/builtin/substack/pane.tsx +0 -547
  463. package/src/plugins/builtin/substack/read-state.ts +0 -30
  464. package/src/plugins/builtin/substack/table.ts +0 -111
  465. package/src/plugins/builtin/substack/types.ts +0 -71
  466. package/src/plugins/builtin/substack/utils.ts +0 -131
  467. package/src/plugins/builtin/ticker-detail/data-panes/provider-search.tsx +0 -229
  468. package/src/plugins/ibkr/account-cache.test.ts +0 -102
  469. package/src/plugins/ibkr/account-cache.ts +0 -24
  470. package/src/plugins/ibkr/broker-adapter.ts +0 -286
  471. package/src/plugins/ibkr/config.test.ts +0 -79
  472. package/src/plugins/ibkr/config.ts +0 -250
  473. package/src/plugins/ibkr/dialogs.tsx +0 -40
  474. package/src/plugins/ibkr/flex/index.test.ts +0 -264
  475. package/src/plugins/ibkr/flex/index.ts +0 -367
  476. package/src/plugins/ibkr/flex/statement-client.ts +0 -235
  477. package/src/plugins/ibkr/fundamental-parser.ts +0 -149
  478. package/src/plugins/ibkr/gateway/account-loaders.ts +0 -266
  479. package/src/plugins/ibkr/gateway/account-summary.ts +0 -164
  480. package/src/plugins/ibkr/gateway/client-lock.ts +0 -154
  481. package/src/plugins/ibkr/gateway/connection-events.ts +0 -114
  482. package/src/plugins/ibkr/gateway/connection.ts +0 -152
  483. package/src/plugins/ibkr/gateway/contracts.ts +0 -132
  484. package/src/plugins/ibkr/gateway/fundamentals.ts +0 -83
  485. package/src/plugins/ibkr/gateway/helpers.ts +0 -33
  486. package/src/plugins/ibkr/gateway/history-requests.ts +0 -158
  487. package/src/plugins/ibkr/gateway/history.ts +0 -120
  488. package/src/plugins/ibkr/gateway/instrument-lookup.ts +0 -117
  489. package/src/plugins/ibkr/gateway/market-data.ts +0 -345
  490. package/src/plugins/ibkr/gateway/order-execution.ts +0 -218
  491. package/src/plugins/ibkr/gateway/orders.ts +0 -63
  492. package/src/plugins/ibkr/gateway/price-normalization.test.ts +0 -44
  493. package/src/plugins/ibkr/gateway/price-normalization.ts +0 -50
  494. package/src/plugins/ibkr/gateway/quote-stream-controller.ts +0 -203
  495. package/src/plugins/ibkr/gateway/quote-stream.ts +0 -171
  496. package/src/plugins/ibkr/gateway/selection.ts +0 -51
  497. package/src/plugins/ibkr/gateway/service/index.test.ts +0 -675
  498. package/src/plugins/ibkr/gateway/service/index.ts +0 -429
  499. package/src/plugins/ibkr/gateway/service/lifecycle.ts +0 -191
  500. package/src/plugins/ibkr/gateway/service/manager.ts +0 -52
  501. package/src/plugins/ibkr/gateway/service/market-data.ts +0 -48
  502. package/src/plugins/ibkr/gateway/service/native-loader.ts +0 -12
  503. package/src/plugins/ibkr/gateway/service/native.ts +0 -543
  504. package/src/plugins/ibkr/gateway/timeouts.ts +0 -13
  505. package/src/plugins/ibkr/gateway/types.ts +0 -27
  506. package/src/plugins/ibkr/index.test.tsx +0 -162
  507. package/src/plugins/ibkr/index.tsx +0 -159
  508. package/src/plugins/ibkr/instance-selection.test.ts +0 -120
  509. package/src/plugins/ibkr/instance-selection.ts +0 -36
  510. package/src/plugins/ibkr/portfolio-performance.test.ts +0 -32
  511. package/src/plugins/ibkr/portfolio-performance.ts +0 -129
  512. package/src/plugins/ibkr/trade/badge.tsx +0 -28
  513. package/src/plugins/ibkr/trade/dialogs.tsx +0 -44
  514. package/src/plugins/ibkr/trade/preview-panel.tsx +0 -130
  515. package/src/plugins/ibkr/trade/tab/actions.tsx +0 -443
  516. package/src/plugins/ibkr/trade/tab/field-editors.tsx +0 -79
  517. package/src/plugins/ibkr/trade/tab/footer.ts +0 -53
  518. package/src/plugins/ibkr/trade/tab/header.tsx +0 -167
  519. package/src/plugins/ibkr/trade/tab/index.test.tsx +0 -178
  520. package/src/plugins/ibkr/trade/tab/index.tsx +0 -298
  521. package/src/plugins/ibkr/trade/tab/model.ts +0 -159
  522. package/src/plugins/ibkr/trade/tab/order-request.ts +0 -64
  523. package/src/plugins/ibkr/trade/tab/shortcuts.ts +0 -89
  524. package/src/plugins/ibkr/trade/tab/state-sync.ts +0 -86
  525. package/src/plugins/ibkr/trade/ticket-panel.tsx +0 -301
  526. package/src/plugins/ibkr/trade/utils.test.ts +0 -175
  527. package/src/plugins/ibkr/trade/utils.ts +0 -144
  528. package/src/plugins/ibkr/trading/pane-view.tsx +0 -237
  529. package/src/plugins/ibkr/trading/pane.tsx +0 -332
  530. package/src/plugins/ibkr/trading/state.test.ts +0 -109
  531. package/src/plugins/ibkr/trading/state.ts +0 -335
@@ -0,0 +1,140 @@
1
+ import { readdir, stat } from "fs/promises";
2
+ import { existsSync } from "fs";
3
+ import { join } from "path";
4
+
5
+ import type { DesktopExternalPluginBundle } from "../shared/protocol";
6
+ import { bundleExternalPlugin, pluginBundleCacheDir } from "../../../plugins/bundle";
7
+ import { linkHostPackages } from "../../../plugins/host-link";
8
+ import { getPluginsDir, resolvePluginEntry } from "../../../plugins/loader";
9
+ import type { GloomPlugin } from "../../../types/plugin";
10
+ import { debugLog } from "../../../utils/debug-log";
11
+
12
+ const log = debugLog.createLogger("desktop-plugins");
13
+
14
+ /**
15
+ * Prepares external plugins for the desktop view.
16
+ *
17
+ * The view is a browser context and cannot read `~/.gloomberb/plugins`, so the
18
+ * Bun process does both halves here: it imports each plugin natively to read
19
+ * its metadata, and compiles it to an ES module the view can evaluate.
20
+ *
21
+ * Bundling is cached against the newest mtime in the plugin directory. Without
22
+ * that, every window open would recompile every plugin, which is slow enough to
23
+ * be visible at startup.
24
+ */
25
+
26
+ interface CacheEntry {
27
+ mtimeMs: number;
28
+ code: string;
29
+ }
30
+
31
+ const bundleCache = new Map<string, CacheEntry>();
32
+
33
+ async function newestMtime(dir: string): Promise<number> {
34
+ let newest = 0;
35
+ const walk = async (current: string, depth: number): Promise<void> => {
36
+ if (depth > 6) return;
37
+ for (const entry of await readdir(current, { withFileTypes: true })) {
38
+ if (entry.name === "node_modules" || entry.name === ".git") continue;
39
+ const full = join(current, entry.name);
40
+ if (entry.isDirectory()) {
41
+ await walk(full, depth + 1);
42
+ continue;
43
+ }
44
+ const info = await stat(full);
45
+ if (info.mtimeMs > newest) newest = info.mtimeMs;
46
+ }
47
+ };
48
+ await walk(dir, 0);
49
+ return newest;
50
+ }
51
+
52
+ async function readPluginMetadata(entryFile: string): Promise<GloomPlugin | null> {
53
+ try {
54
+ const mod = await import(entryFile);
55
+ const plugin: GloomPlugin = mod.default ?? mod.plugin;
56
+ return plugin?.id && plugin?.name ? plugin : null;
57
+ } catch (error) {
58
+ log.error(`Metadata read failed for ${entryFile}: ${error}`);
59
+ return null;
60
+ }
61
+ }
62
+
63
+ export async function collectExternalPluginBundles(): Promise<DesktopExternalPluginBundle[]> {
64
+ const pluginsDir = getPluginsDir();
65
+ if (!existsSync(pluginsDir)) return [];
66
+
67
+ const outDir = pluginBundleCacheDir(join(pluginsDir, ".cache"));
68
+ const bundles: DesktopExternalPluginBundle[] = [];
69
+
70
+ for (const entry of await readdir(pluginsDir, { withFileTypes: true })) {
71
+ if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
72
+ const pluginDir = join(pluginsDir, entry.name);
73
+
74
+ const entryFile = await resolvePluginEntry(pluginDir);
75
+ if (!entryFile) continue;
76
+
77
+ linkHostPackages(pluginDir);
78
+
79
+ const plugin = await readPluginMetadata(entryFile);
80
+ const base = {
81
+ id: plugin?.id ?? entry.name,
82
+ name: plugin?.name ?? entry.name,
83
+ version: plugin?.version ?? "0.0.0",
84
+ path: pluginDir,
85
+ ...(plugin?.targets ? { targets: plugin.targets } : {}),
86
+ };
87
+
88
+ if (!plugin) {
89
+ bundles.push({ ...base, error: "Plugin did not export a valid GloomPlugin." });
90
+ continue;
91
+ }
92
+
93
+ // Skip compiling something the desktop cannot run anyway; the marketplace
94
+ // still lists it, explaining why it is inert.
95
+ if (plugin.targets && !plugin.targets.includes("desktop")) {
96
+ bundles.push({ ...base, error: `${plugin.name} does not support the desktop app.` });
97
+ continue;
98
+ }
99
+
100
+ try {
101
+ const mtimeMs = await newestMtime(pluginDir);
102
+ const cached = bundleCache.get(pluginDir);
103
+ if (cached && cached.mtimeMs === mtimeMs) {
104
+ bundles.push({ ...base, code: cached.code });
105
+ continue;
106
+ }
107
+
108
+ const result = await bundleExternalPlugin(pluginDir, join(outDir, entry.name));
109
+ const code = await Bun.file(result.outputPath).text();
110
+ bundleCache.set(pluginDir, { mtimeMs, code });
111
+ log.info(`Bundled ${plugin.id} (${Math.round(code.length / 1024)}KB, shared: ${result.shared.join(", ")})`);
112
+ bundles.push({ ...base, code });
113
+ } catch (error) {
114
+ const message = error instanceof Error ? error.message : String(error);
115
+ log.error(`Bundling ${plugin.id} failed: ${message}`);
116
+ bundles.push({ ...base, error: message });
117
+ }
118
+ }
119
+
120
+ return bundles;
121
+ }
122
+
123
+ /**
124
+ * Installs a plugin on behalf of the desktop view, which cannot run git or bun
125
+ * itself. Errors are returned rather than thrown so the marketplace can show
126
+ * them next to the plugin instead of surfacing an RPC failure.
127
+ *
128
+ * The bundle cache is cleared so the next `plugins.listExternal` compiles the
129
+ * newly installed plugin rather than serving a stale set.
130
+ */
131
+ export async function installExternalPlugin(ref: string): Promise<{ ok: boolean; error?: string }> {
132
+ try {
133
+ const { installPlugin } = await import("../../../cli/commands/plugins");
134
+ await installPlugin(ref, { quiet: true });
135
+ bundleCache.clear();
136
+ return { ok: true };
137
+ } catch (error) {
138
+ return { ok: false, error: error instanceof Error ? error.message : String(error) };
139
+ }
140
+ }
@@ -25,7 +25,6 @@ import type { DesktopWorkspace } from "./desktop/workspace";
25
25
  import { buildDesktopApplicationMenu } from "./application-menu";
26
26
  import { applicationMenuCommand } from "./application-menu/click";
27
27
  import { registerElectrobunCoreCapabilities } from "./core-capabilities";
28
- import { setNativeIbkrGatewayModuleLoader } from "../../../plugins/ibkr/gateway/service";
29
28
  import {
30
29
  runElectrobunDesktopUpdate,
31
30
  } from "./desktop/update";
@@ -37,6 +36,7 @@ import {
37
36
  } from "./window/frame";
38
37
  import { MAIN_WINDOW_RPC_KEY } from "./window/focus";
39
38
  import { handleHttpFetch } from "./desktop/http-fetch";
39
+ import { collectExternalPluginBundles, installExternalPlugin } from "./external-plugins";
40
40
  import { handleDesktopPluginStateRequest } from "./desktop/plugin-state";
41
41
  import { scheduleDesktopRelaunch } from "./desktop/relaunch";
42
42
  import {
@@ -72,7 +72,6 @@ console.info = (...args) => console.error(...args);
72
72
  console.warn = (...args) => console.error(...args);
73
73
 
74
74
  setConfigStoreHost(nodeConfigStoreHost);
75
- setNativeIbkrGatewayModuleLoader(() => import("../../../plugins/ibkr/gateway/service/native"));
76
75
 
77
76
  let currentConfig: AppConfig | null = null;
78
77
  let services: AppServices | null = null;
@@ -468,6 +467,10 @@ async function handleBackendRequest(
468
467
  case "pluginState.setMany":
469
468
  case "pluginState.delete":
470
469
  return handleDesktopPluginStateRequest(requireServices().persistence.pluginState, request);
470
+ case "plugins.listExternal":
471
+ return collectExternalPluginBundles();
472
+ case "plugins.install":
473
+ return installExternalPlugin(request.payload.ref);
471
474
  case "host.restart":
472
475
  case "host.exit":
473
476
  case "host.windowControl":
@@ -476,6 +479,7 @@ async function handleBackendRequest(
476
479
  case "host.focusWindow":
477
480
  case "host.copyPngImage":
478
481
  case "host.readText":
482
+ case "host.saveTextFile":
479
483
  case "host.notify":
480
484
  case "host.showContextMenu":
481
485
  return handleDesktopHostRequest({
@@ -83,6 +83,25 @@ export interface DesktopCapabilitySubscribeRequest extends DesktopCapabilityInvo
83
83
  subscriptionId: string;
84
84
  }
85
85
 
86
+ /**
87
+ * One external plugin, compiled for the view.
88
+ *
89
+ * The Bun process owns the filesystem, so it reads and bundles the plugin and
90
+ * hands the view executable module text. `error` is carried rather than thrown
91
+ * so a single broken plugin surfaces in the marketplace instead of taking down
92
+ * the renderer.
93
+ */
94
+ export interface DesktopExternalPluginBundle {
95
+ id: string;
96
+ name: string;
97
+ version: string;
98
+ path: string;
99
+ /** ES module source, absent when `error` is set. */
100
+ code?: string;
101
+ targets?: readonly ("cli" | "tui" | "desktop" | "web")[];
102
+ error?: string;
103
+ }
104
+
86
105
  export interface DesktopBackendRequestMap {
87
106
  init: {
88
107
  request: { kind?: "main" | "detached"; paneId?: string };
@@ -111,6 +130,8 @@ export interface DesktopBackendRequestMap {
111
130
  "pluginState.set": { request: DesktopPluginStateSetEntry; response: null };
112
131
  "pluginState.setMany": { request: { entries: DesktopPluginStateSetEntry[] }; response: null };
113
132
  "pluginState.delete": { request: { pluginId: string; key: string }; response: null };
133
+ "plugins.listExternal": { request: null; response: DesktopExternalPluginBundle[] };
134
+ "plugins.install": { request: { ref: string }; response: { ok: boolean; error?: string } };
114
135
  "host.restart": { request: DesktopRestartMessage; response: null };
115
136
  "host.exit": { request: null; response: null };
116
137
  "host.windowControl": { request: { action: DesktopWindowControlAction }; response: null };
@@ -119,6 +140,10 @@ export interface DesktopBackendRequestMap {
119
140
  "host.focusWindow": { request: null; response: null };
120
141
  "host.copyPngImage": { request: { pngBase64: string }; response: null };
121
142
  "host.readText": { request: null; response: string };
143
+ "host.saveTextFile": {
144
+ request: { name: string; text: string; mimeType: string };
145
+ response: string;
146
+ };
122
147
  "host.notify": {
123
148
  request: { title?: string; body?: string; subtitle?: string; sound?: string };
124
149
  response: null;
@@ -23,7 +23,6 @@ const COMMON_ALIAS_RULES: AliasRule[] = [
23
23
  ["./kitty/support", "components/chart/native/renderer-selection.ts", "native-stubs/chart/kitty-support.ts"],
24
24
  ["native/surface/manager", "native-stubs/chart/surface-manager.ts"],
25
25
  ["native/surface/sync", "native-stubs/chart/surface-sync.ts"],
26
- ["./native-loader", "plugins/ibkr/gateway/service/index.ts", "native-stubs/ibkr-native-loader.ts"],
27
26
  ["./native-loader", "plugins/broker-sync/robinhood.ts", "native-stubs/broker-sync-native-loader.ts"],
28
27
  ["./native-loader", "plugins/broker-sync/simplefin.ts", "native-stubs/broker-sync-native-loader.ts"],
29
28
  ];
@@ -41,9 +41,14 @@ import type { AppState, PaneRuntimeState } from "../../../core/state/app/state";
41
41
  import type { PaneDef } from "../../../types/plugin";
42
42
  import { canonicalTickerKey, parsePublicTickerKey } from "../../../utils/exchanges";
43
43
  import { hydrateFredSeries, type FredSeriesCacheEntry } from "../../../data/fred-series";
44
+ import { hydrateValuationSeries } from "../../../plugins/builtin/market-valuation/cache";
45
+ import { statsCache } from "../../../plugins/builtin/econ-statistics/cache";
46
+ import type { DatedObservation } from "../../../plugins/builtin/market-valuation/series";
44
47
  import { clipPriceHistoryToRange } from "../../../time-series/history-window";
45
48
  import type { ResolvedSeries } from "../../../time-series/types";
46
49
  import { chartSeriesSourceKey } from "../../../capabilities";
50
+ import { apiClient, setCloudApiFetchTransport } from "../../../api-client";
51
+ import { createGloomberbCloudProvider } from "../../../sources/gloomberb-cloud";
47
52
 
48
53
  interface CliPaneShotPayload {
49
54
  config: AppConfig;
@@ -54,6 +59,8 @@ interface CliPaneShotPayload {
54
59
  financials: Array<[string, TickerFinancials]>;
55
60
  optionsChains: Array<[string, OptionsChain]>;
56
61
  fredSeries: Array<[string, FredSeriesCacheEntry]>;
62
+ valuationSeries: Array<[string, DatedObservation[]]>;
63
+ statSeries: Array<[string, DatedObservation[]]>;
57
64
  capabilitySeries: Array<[string, ResolvedSeries]>;
58
65
  paneState: Record<string, PaneRuntimeState>;
59
66
  }
@@ -77,6 +84,7 @@ const SHOT_READY_STABLE_FRAMES = 10;
77
84
  // content contains the word "loading" (a changelog release note, a news
78
85
  // headline) wait forever and time out.
79
86
  const SHOT_LOADING_SELECTOR = "[data-gloom-status=\"loading\"]";
87
+ const SHOT_API_PROXY_PREFIX = "/__gloom_cli_api__";
80
88
  const TRACKED_RESPONSE_METHODS = new Set<PropertyKey>([
81
89
  "arrayBuffer",
82
90
  "blob",
@@ -141,6 +149,29 @@ function installShotFetchTracker(): void {
141
149
  )) as typeof fetch;
142
150
  }
143
151
 
152
+ function installShotCloudApiTransport(): void {
153
+ apiClient.setCookieSessionMode(true);
154
+ setCloudApiFetchTransport((url, init = {}) => {
155
+ const upstream = new URL(url);
156
+ const proxyUrl = new URL(`${SHOT_API_PROXY_PREFIX}${upstream.pathname}`, window.location.origin);
157
+ proxyUrl.search = upstream.search;
158
+ const headers = new Headers(init.headers);
159
+ // Bun owns the real session cookie. The browser only talks to the local
160
+ // same-origin proxy and never receives or serializes the credential.
161
+ headers.delete("Cookie");
162
+ headers.delete("Origin");
163
+ return window.fetch(proxyUrl, {
164
+ ...init,
165
+ headers,
166
+ credentials: "same-origin",
167
+ });
168
+ });
169
+ }
170
+
171
+ async function restoreShotCloudSession(): Promise<void> {
172
+ await apiClient.getSession().catch(() => null);
173
+ }
174
+
144
175
  function resolveShotWork<T>(value: T): Promise<T> {
145
176
  return trackShotWork(Promise.resolve(value));
146
177
  }
@@ -249,7 +280,7 @@ function createShotDataProvider(payload: CliPaneShotPayload): DataProvider {
249
280
  return data;
250
281
  };
251
282
 
252
- return {
283
+ const injectedProvider: DataProvider = {
253
284
  id: "cli-shot",
254
285
  name: "CLI screenshot data",
255
286
  getTickerFinancials(ticker, exchange) {
@@ -330,6 +361,19 @@ function createShotDataProvider(payload: CliPaneShotPayload): DataProvider {
330
361
  return () => {};
331
362
  },
332
363
  };
364
+
365
+ // Keep deterministic payload data for the mapped screenshot capabilities,
366
+ // then fall through to the authenticated cloud provider for operations such
367
+ // as SEC filings that live panes request through market-data hooks.
368
+ const cloudProvider = createGloomberbCloudProvider();
369
+ return new Proxy(injectedProvider, {
370
+ get(target, property, receiver) {
371
+ const local = Reflect.get(target, property, receiver);
372
+ if (local !== undefined) return local;
373
+ const cloud = Reflect.get(cloudProvider, property, cloudProvider);
374
+ return typeof cloud === "function" ? cloud.bind(cloudProvider) : cloud;
375
+ },
376
+ });
333
377
  }
334
378
 
335
379
  function installShotMarketData(payload: CliPaneShotPayload): void {
@@ -586,11 +630,17 @@ async function render() {
586
630
  if (!payload) throw new Error("Missing CLI pane screenshot payload.");
587
631
  revivePayloadDates(payload);
588
632
  installShotFetchTracker();
633
+ installShotCloudApiTransport();
589
634
  hydrateFredSeries(payload.fredSeries ?? []);
635
+ hydrateValuationSeries(payload.valuationSeries ?? []);
636
+ statsCache.hydrate(payload.statSeries ?? []);
590
637
  installShotMarketData(payload);
591
638
  // Panes contributed from an async setup() only exist once every plugin has
592
639
  // finished registering, so the tree cannot mount before that resolves.
593
640
  await installShotPluginRegistry(payload);
641
+ // Plugin setup hydrates its in-memory persistence and may reset apiClient.
642
+ // Restore the proxied cloud session only after registration is complete.
643
+ await restoreShotCloudSession();
594
644
 
595
645
  const rootElement = document.getElementById("root");
596
646
  if (!rootElement) throw new Error("Missing root element.");
@@ -0,0 +1,45 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import { loadDesktopExternalPlugins } from "./external-plugins";
4
+ import type { DesktopExternalPluginBundle } from "../shared/protocol";
5
+
6
+ /**
7
+ * The guarantee under test is containment: a plugin that fails to compile, ships
8
+ * no bundle, or exports the wrong shape must surface as a broken entry the
9
+ * marketplace can explain, never as an exception that stops the desktop app from
10
+ * starting. Every case here is one a user can hit by installing a bad plugin.
11
+ */
12
+ function bundle(overrides: Partial<DesktopExternalPluginBundle>): DesktopExternalPluginBundle {
13
+ return { id: "x", name: "X", version: "1.0.0", path: "/tmp/x", ...overrides };
14
+ }
15
+
16
+ describe("loadDesktopExternalPlugins", () => {
17
+ test("does nothing when there is nothing to load", async () => {
18
+ expect(await loadDesktopExternalPlugins([])).toEqual([]);
19
+ });
20
+
21
+ test("carries a compile error through instead of throwing", async () => {
22
+ const [entry] = await loadDesktopExternalPlugins([
23
+ bundle({ id: "broken", error: "Could not resolve ./nope" }),
24
+ ]);
25
+
26
+ expect(entry?.error).toBe("Could not resolve ./nope");
27
+ expect(entry?.plugin.id).toBe("broken");
28
+ });
29
+
30
+ test("reports a bundle that arrived without code", async () => {
31
+ const [entry] = await loadDesktopExternalPlugins([bundle({ id: "empty" })]);
32
+
33
+ expect(entry?.error).toContain("no bundle");
34
+ });
35
+
36
+ test("keeps loading the rest after one plugin fails", async () => {
37
+ const entries = await loadDesktopExternalPlugins([
38
+ bundle({ id: "first", error: "boom" }),
39
+ bundle({ id: "second", error: "also boom" }),
40
+ ]);
41
+
42
+ expect(entries.map((entry) => entry.plugin.id)).toEqual(["first", "second"]);
43
+ expect(entries.every((entry) => entry.error)).toBe(true);
44
+ });
45
+ });
@@ -0,0 +1,63 @@
1
+ import type { DesktopExternalPluginBundle } from "../shared/protocol";
2
+ import { installPluginHostModules } from "../../../plugins/host-modules";
3
+ import type { LoadedExternalPlugin } from "../../../plugins/loader";
4
+ import type { GloomPlugin } from "../../../types/plugin";
5
+ import { debugLog } from "../../../utils/debug-log";
6
+
7
+ const log = debugLog.createLogger("desktop-plugins");
8
+
9
+ /**
10
+ * Evaluates the plugin bundles the Bun process compiled for this view.
11
+ *
12
+ * The host's shared modules have to be published before any bundle is imported:
13
+ * a compiled plugin's `react` and `gloomberb/*` imports read from that registry
14
+ * rather than carrying their own copies (see `plugins/bundle.ts`).
15
+ *
16
+ * Bundles are imported from blob URLs. The desktop view has no origin to fetch
17
+ * from and no filesystem, and writing them to a served directory would mean
18
+ * managing a cache the renderer cannot clean up reliably.
19
+ */
20
+ export async function loadDesktopExternalPlugins(
21
+ bundles: readonly DesktopExternalPluginBundle[],
22
+ ): Promise<LoadedExternalPlugin[]> {
23
+ if (bundles.length === 0) return [];
24
+
25
+ await installPluginHostModules();
26
+
27
+ const loaded: LoadedExternalPlugin[] = [];
28
+
29
+ for (const bundle of bundles) {
30
+ const fallback = {
31
+ plugin: { id: bundle.id, name: bundle.name, version: bundle.version } as GloomPlugin,
32
+ path: bundle.path,
33
+ };
34
+
35
+ if (bundle.error || !bundle.code) {
36
+ loaded.push({ ...fallback, error: bundle.error ?? "Plugin produced no bundle." });
37
+ continue;
38
+ }
39
+
40
+ let objectUrl: string | null = null;
41
+ try {
42
+ objectUrl = URL.createObjectURL(new Blob([bundle.code], { type: "text/javascript" }));
43
+ const mod = await import(/* @vite-ignore */ objectUrl);
44
+ const plugin: GloomPlugin = mod.default ?? mod.plugin;
45
+ if (!plugin?.id || !plugin?.name) {
46
+ loaded.push({ ...fallback, error: "Bundle did not export a valid GloomPlugin." });
47
+ continue;
48
+ }
49
+ log.info(`Loaded external plugin: ${plugin.id} v${plugin.version ?? "0.0.0"}`);
50
+ loaded.push({ plugin, path: bundle.path });
51
+ } catch (error) {
52
+ const message = error instanceof Error ? error.message : String(error);
53
+ log.error(`Evaluating ${bundle.id} failed: ${message}`);
54
+ loaded.push({ ...fallback, error: message });
55
+ } finally {
56
+ // The module graph keeps its own reference once imported, so the URL can
57
+ // be released immediately; leaving it would leak for the session.
58
+ if (objectUrl) URL.revokeObjectURL(objectUrl);
59
+ }
60
+ }
61
+
62
+ return loaded;
63
+ }
@@ -0,0 +1,48 @@
1
+ import { afterEach, describe, expect, test } from "bun:test";
2
+ import { webAsciiTextWordmarkVariant } from "./ascii-text";
3
+
4
+ const globalWithNavigator = globalThis as { navigator?: unknown };
5
+ const originalNavigator = Object.getOwnPropertyDescriptor(globalThis, "navigator");
6
+
7
+ function stubNavigator(platform: string): void {
8
+ Object.defineProperty(globalThis, "navigator", {
9
+ value: { platform, userAgent: platform },
10
+ configurable: true,
11
+ writable: true,
12
+ });
13
+ }
14
+
15
+ afterEach(() => {
16
+ if (originalNavigator) Object.defineProperty(globalThis, "navigator", originalNavigator);
17
+ else delete globalWithNavigator.navigator;
18
+ });
19
+
20
+ /**
21
+ * The block-glyph wordmark only renders on macOS fonts, so every other platform
22
+ * gets the `/ \ | _` fallback. The DOM host is shared by Electrobun, which knows
23
+ * its `process.platform`, and the hosted web build, which does not and passes a
24
+ * sentinel instead. Reading that sentinel as "not macOS" is what shipped the
25
+ * wrong wordmark to Mac browsers.
26
+ */
27
+ describe("web wordmark variant", () => {
28
+ test("trusts a real platform from the desktop host over the navigator", () => {
29
+ stubNavigator("MacIntel");
30
+ expect(webAsciiTextWordmarkVariant("Gloomberb", "wordmark", "darwin")).toBe("legacy");
31
+ expect(webAsciiTextWordmarkVariant("Gloomberb", "wordmark", "win32")).toBe("compat");
32
+ expect(webAsciiTextWordmarkVariant("Gloomberb", "wordmark", "linux")).toBe("compat");
33
+ });
34
+
35
+ test("falls back to the navigator when the host names no OS", () => {
36
+ stubNavigator("MacIntel");
37
+ expect(webAsciiTextWordmarkVariant("Gloomberb", "wordmark", "browser")).toBe("legacy");
38
+ expect(webAsciiTextWordmarkVariant("Gloomberb", "wordmark", "")).toBe("legacy");
39
+ stubNavigator("Win32");
40
+ expect(webAsciiTextWordmarkVariant("Gloomberb", "wordmark", "browser")).toBe("compat");
41
+ });
42
+
43
+ test("leaves anything that is not the wordmark alone", () => {
44
+ stubNavigator("MacIntel");
45
+ expect(webAsciiTextWordmarkVariant("Gloomberb", "tiny", "darwin")).toBeNull();
46
+ expect(webAsciiTextWordmarkVariant("Portfolio", "wordmark", "darwin")).toBeNull();
47
+ });
48
+ });
@@ -25,8 +25,18 @@ function currentDesktopPlatform(): string {
25
25
  ].filter(Boolean).join(" ");
26
26
  }
27
27
 
28
+ /**
29
+ * A platform string worth trusting. The DOM host is shared by Electrobun, which
30
+ * passes a real `process.platform`, and the browser build, which passes the
31
+ * sentinel `"browser"` because a web page has no OS of its own. Only the former
32
+ * answers the question we are asking, so anything that does not name an OS
33
+ * falls through to sniffing the navigator.
34
+ */
35
+ const OS_PLATFORM = /(darwin|mac|iphone|ipad|ipod|win|linux|android|freebsd|openbsd|netbsd|aix|sunos|cros|x11)/i;
36
+
28
37
  function isMacDesktopPlatform(desktopPlatform?: string): boolean {
29
- const raw = desktopPlatform?.trim() || currentDesktopPlatform();
38
+ const hint = desktopPlatform?.trim();
39
+ const raw = hint && OS_PLATFORM.test(hint) ? hint : currentDesktopPlatform();
30
40
  return /(darwin|mac|iphone|ipad|ipod)/i.test(raw);
31
41
  }
32
42
 
@@ -145,9 +145,11 @@ function ChartCrosshair({
145
145
  }) {
146
146
  if (!bitmap || !crosshair) return null;
147
147
  const x = bitmap.width <= 1 ? 0 : (crosshair.pixelX / (bitmap.width - 1)) * 100;
148
- const y = bitmap.height <= 1 ? 0 : (crosshair.pixelY / (bitmap.height - 1)) * 100;
148
+ const y = crosshair.pixelY === null
149
+ ? null
150
+ : bitmap.height <= 1 ? 0 : (crosshair.pixelY / (bitmap.height - 1)) * 100;
149
151
  const clampedX = Math.max(0, Math.min(100, x));
150
- const clampedY = Math.max(0, Math.min(100, y));
152
+ const clampedY = y === null ? null : Math.max(0, Math.min(100, y));
151
153
  return (
152
154
  <>
153
155
  <div
@@ -164,36 +166,40 @@ function ChartCrosshair({
164
166
  zIndex: 10,
165
167
  }}
166
168
  />
167
- <div
168
- style={{
169
- position: "absolute",
170
- left: 0,
171
- right: 0,
172
- top: `${clampedY}%`,
173
- height: 1,
174
- backgroundColor: crosshair.color,
175
- opacity: 0.78,
176
- transform: "translateY(-0.5px)",
177
- pointerEvents: "none",
178
- zIndex: 10,
179
- }}
180
- />
181
- <div
182
- style={{
183
- position: "absolute",
184
- left: `${clampedX}%`,
185
- top: `${clampedY}%`,
186
- width: 7,
187
- height: 7,
188
- borderRadius: 7,
189
- border: `1px solid ${crosshair.color}`,
190
- backgroundColor: `color-mix(in srgb, ${crosshair.color} 16%, transparent)`,
191
- boxSizing: "border-box",
192
- transform: "translate(-50%, -50%)",
193
- pointerEvents: "none",
194
- zIndex: 11,
195
- }}
196
- />
169
+ {clampedY === null ? null : (
170
+ <>
171
+ <div
172
+ style={{
173
+ position: "absolute",
174
+ left: 0,
175
+ right: 0,
176
+ top: `${clampedY}%`,
177
+ height: 1,
178
+ backgroundColor: crosshair.color,
179
+ opacity: 0.78,
180
+ transform: "translateY(-0.5px)",
181
+ pointerEvents: "none",
182
+ zIndex: 10,
183
+ }}
184
+ />
185
+ <div
186
+ style={{
187
+ position: "absolute",
188
+ left: `${clampedX}%`,
189
+ top: `${clampedY}%`,
190
+ width: 7,
191
+ height: 7,
192
+ borderRadius: 7,
193
+ border: `1px solid ${crosshair.color}`,
194
+ backgroundColor: `color-mix(in srgb, ${crosshair.color} 16%, transparent)`,
195
+ boxSizing: "border-box",
196
+ transform: "translate(-50%, -50%)",
197
+ pointerEvents: "none",
198
+ zIndex: 11,
199
+ }}
200
+ />
201
+ </>
202
+ )}
197
203
  </>
198
204
  );
199
205
  }
@@ -27,16 +27,30 @@ export function cancelWebFrame(id: number): void {
27
27
  clearTimeout(id as unknown as ReturnType<typeof setTimeout>);
28
28
  }
29
29
 
30
+ // Line and page deltas come from wheel mice; trackpads report pixels.
31
+ const WHEEL_LINE_PIXELS = 16;
32
+ const WHEEL_PAGE_PIXELS = 400;
33
+
34
+ function wheelPixels(delta: number, deltaMode: number): number {
35
+ if (deltaMode === 1) return delta * WHEEL_LINE_PIXELS;
36
+ if (deltaMode === 2) return delta * WHEEL_PAGE_PIXELS;
37
+ return delta;
38
+ }
39
+
30
40
  function resolveScroll(event: MouseLikeEvent) {
31
41
  if (!("deltaX" in event) && !("deltaY" in event)) return undefined;
32
42
  const wheel = event as WheelEvent;
33
- const useHorizontal = Math.abs(wheel.deltaX) > Math.abs(wheel.deltaY);
34
- const delta = useHorizontal ? wheel.deltaX : wheel.deltaY;
43
+ const deltaX = wheelPixels(wheel.deltaX, wheel.deltaMode);
44
+ const deltaY = wheelPixels(wheel.deltaY, wheel.deltaMode);
45
+ const useHorizontal = Math.abs(deltaX) > Math.abs(deltaY);
46
+ const delta = useHorizontal ? deltaX : deltaY;
35
47
  return {
36
48
  direction: useHorizontal
37
49
  ? (delta > 0 ? "right" : "left")
38
50
  : (delta > 0 ? "down" : "up"),
39
51
  delta: Math.abs(delta),
52
+ deltaX,
53
+ deltaY,
40
54
  };
41
55
  }
42
56