gloomberb 0.11.3 → 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 (489) hide show
  1. package/README.md +6 -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 +103 -1
  6. package/src/api-client/index.test.ts +127 -0
  7. package/src/api-client/index.ts +129 -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 +196 -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/cli/commands/plugins.ts +39 -11
  20. package/src/cli/desktop-pane-shot.ts +285 -16
  21. package/src/cli/index.ts +1 -1
  22. package/src/cli/pane-functions/capabilities.ts +58 -28
  23. package/src/cli/pane-functions/catalog.ts +16 -4
  24. package/src/cli/pane-functions/discovery.ts +1 -0
  25. package/src/cli/pane-functions/headless.test.ts +247 -0
  26. package/src/cli/pane-functions/headless.ts +423 -0
  27. package/src/cli/pane-functions/index.test.ts +9 -0
  28. package/src/cli/pane-functions/index.ts +6 -2
  29. package/src/cli/pane-functions/options.ts +27 -2
  30. package/src/cli/pane-functions/report.ts +4 -0
  31. package/src/cli/pane-functions/resolver.ts +26 -8
  32. package/src/cli/pane-functions/screenshot.test.ts +76 -0
  33. package/src/cli/pane-functions/screenshot.ts +236 -20
  34. package/src/cli/restore-plugins.ts +34 -0
  35. package/src/components/chart/{static/chart → composite}/axis-overlays.test.tsx +3 -3
  36. package/src/components/chart/{static/chart → composite}/axis-overlays.tsx +6 -11
  37. package/src/components/chart/{static/chart → composite}/bitmap.ts +17 -3
  38. package/src/components/chart/composite/composite-chart.test.tsx +68 -20
  39. package/src/components/chart/composite/composite-chart.tsx +301 -204
  40. package/src/components/chart/composite/format.ts +8 -2
  41. package/src/components/chart/composite/interactions.test.ts +68 -102
  42. package/src/components/chart/composite/interactions.ts +214 -264
  43. package/src/components/chart/composite/price-axis-labels.test.ts +21 -0
  44. package/src/components/chart/{price-axis-labels.tsx → composite/price-axis-labels.tsx} +9 -4
  45. package/src/components/chart/composite/scene.ts +57 -3
  46. package/src/components/chart/composite/text-renderer.ts +3 -2
  47. package/src/components/chart/composite/time-scale.ts +138 -57
  48. package/src/components/chart/composite/types.ts +25 -1
  49. package/src/components/chart/core/data.ts +9 -13
  50. package/src/components/chart/core/date-window.ts +7 -1
  51. package/src/components/chart/core/pointer.ts +3 -0
  52. package/src/components/chart/core/types.ts +0 -45
  53. package/src/components/chart/native/chart-rasterizer.ts +0 -1
  54. package/src/components/chart/native/kitty/index.test.ts +0 -188
  55. package/src/components/chart/native/raster/crosshair.ts +6 -5
  56. package/src/components/chart/native/raster/primitives.ts +0 -23
  57. package/src/components/chart/native/renderer-selection.ts +81 -31
  58. package/src/components/chart/static/{chart/surface.test.tsx → chart-surface.test.tsx} +28 -80
  59. package/src/components/chart/static/chart-surface.tsx +150 -0
  60. package/src/components/chart/static/index.ts +8 -0
  61. package/src/components/chart/static/multi-line-chart-surface.tsx +51 -166
  62. package/src/components/chart/static/scatter-chart-surface.test.ts +30 -0
  63. package/src/components/chart/static/scatter-chart-surface.tsx +135 -34
  64. package/src/components/chart/static/series.ts +49 -0
  65. package/src/components/command-bar/action-runtime.ts +1 -3
  66. package/src/components/command-bar/assist/model.test.ts +17 -2
  67. package/src/components/command-bar/assist/model.ts +40 -9
  68. package/src/components/command-bar/commands/direct/index.ts +1 -1
  69. package/src/components/command-bar/commands/direct/runtime.ts +1 -1
  70. package/src/components/command-bar/commands/registry.ts +0 -11
  71. package/src/components/command-bar/helpers.test.ts +2 -2
  72. package/src/components/command-bar/helpers.ts +2 -6
  73. package/src/components/command-bar/keyboard-shortcuts.ts +0 -11
  74. package/src/components/command-bar/list/badge.test.ts +54 -0
  75. package/src/components/command-bar/list/badge.ts +62 -0
  76. package/src/components/command-bar/list/model.test.ts +103 -0
  77. package/src/components/command-bar/list/model.ts +61 -4
  78. package/src/components/command-bar/list/view.test.tsx +113 -0
  79. package/src/components/command-bar/list/view.tsx +147 -131
  80. package/src/components/command-bar/pane-templates/workflow.ts +1 -1
  81. package/src/components/command-bar/panel/body.tsx +51 -65
  82. package/src/components/command-bar/panel/index.tsx +48 -13
  83. package/src/components/command-bar/panel/layout.test.ts +139 -0
  84. package/src/components/command-bar/panel/layout.ts +65 -28
  85. package/src/components/command-bar/panel/native-surface.ts +24 -0
  86. package/src/components/command-bar/panel/prompt-binding.ts +54 -0
  87. package/src/components/command-bar/panel/runtime.ts +6 -3
  88. package/src/components/command-bar/panel/state.ts +41 -7
  89. package/src/components/command-bar/panel/types.ts +7 -1
  90. package/src/components/command-bar/routes/root/provider-search.ts +34 -15
  91. package/src/components/command-bar/routes/root/results.test.ts +161 -0
  92. package/src/components/command-bar/routes/root/results.ts +30 -28
  93. package/src/components/command-bar/routes/root/runtime.ts +37 -20
  94. package/src/components/command-bar/routes/root/search-providers.test.tsx +224 -0
  95. package/src/components/command-bar/routes/root/search-providers.ts +205 -0
  96. package/src/components/command-bar/routes/root/selection.ts +1 -12
  97. package/src/components/command-bar/routes/root/shortcuts.test.ts +8 -8
  98. package/src/components/command-bar/routes/ticker-search/actions.ts +10 -9
  99. package/src/components/command-bar/routes/ticker-search/results.test.ts +54 -1
  100. package/src/components/command-bar/routes/ticker-search/results.ts +60 -19
  101. package/src/components/command-bar/routes/ticker-search/route.ts +1 -0
  102. package/src/components/command-bar/routing/actions.ts +1 -81
  103. package/src/components/command-bar/routing/launch-request.ts +1 -1
  104. package/src/components/command-bar/routing/list-state.ts +6 -21
  105. package/src/components/command-bar/selection-runtime.ts +1 -1
  106. package/src/components/command-bar/surface/assist.test.tsx +14 -10
  107. package/src/components/command-bar/surface/environment.ts +8 -1
  108. package/src/components/command-bar/surface/index.test.tsx +84 -68
  109. package/src/components/command-bar/surface/index.tsx +33 -36
  110. package/src/components/command-bar/surface/test-harness.tsx +15 -3
  111. package/src/components/command-bar/theme-picker.tsx +43 -15
  112. package/src/components/command-bar/view-model.test.ts +53 -8
  113. package/src/components/command-bar/view-model.ts +43 -14
  114. package/src/components/command-bar/workflow/tickers.ts +1 -0
  115. package/src/components/command-bar/workflow/types.ts +1 -1
  116. package/src/components/data-table/view.test.tsx +45 -6
  117. package/src/components/data-table/view.tsx +55 -9
  118. package/src/components/index.ts +10 -2
  119. package/src/components/input-search-bar.test.tsx +36 -2
  120. package/src/components/input-search-bar.tsx +18 -1
  121. package/src/components/layout/header.test.tsx +57 -10
  122. package/src/components/layout/header.tsx +292 -149
  123. package/src/components/layout/market-summary.test.ts +81 -0
  124. package/src/components/layout/market-summary.ts +109 -0
  125. package/src/components/layout/shell/chrome.ts +96 -1
  126. package/src/components/layout/shell/index.tsx +11 -3
  127. package/src/components/layout/shell/menu.ts +9 -0
  128. package/src/components/layout/shell/pane/actions.ts +29 -0
  129. package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
  130. package/src/components/layout/shell/shortcuts.ts +3 -0
  131. package/src/components/layout/status-bar.test.tsx +9 -8
  132. package/src/components/layout/status-bar.tsx +85 -54
  133. package/src/components/onboarding/onboarding-frame.tsx +2 -26
  134. package/src/components/price-sparkline/index.ts +61 -34
  135. package/src/components/price-sparkline/view.tsx +1 -1
  136. package/src/components/sign-in-gate.test.tsx +128 -0
  137. package/src/components/sign-in-gate.tsx +149 -0
  138. package/src/components/speedometer-gauge/desktop.tsx +2 -12
  139. package/src/components/ticker/badge/list.tsx +17 -3
  140. package/src/components/ticker/list-table-view.tsx +6 -3
  141. package/src/components/ui/data-table/index.tsx +3 -2
  142. package/src/components/ui/data-table/opentui/index.tsx +165 -120
  143. package/src/components/ui/frame.tsx +28 -0
  144. package/src/components/ui/index.ts +3 -0
  145. package/src/components/ui/native-select.tsx +20 -10
  146. package/src/components/ui/select-button.tsx +138 -0
  147. package/src/components/ui/status.tsx +2 -2
  148. package/src/components/ui/table-layout.ts +1 -1
  149. package/src/components/use-double-click-activation.ts +7 -3
  150. package/src/core/app-services.ts +3 -3
  151. package/src/data/config/layout.ts +12 -6
  152. package/src/data/config/store/index.test.ts +97 -0
  153. package/src/data/config/store/migrations.ts +41 -0
  154. package/src/data/config/store/normalize.ts +5 -3
  155. package/src/i18n/es.ts +6 -2
  156. package/src/i18n/ja.ts +6 -2
  157. package/src/i18n/ko.ts +6 -2
  158. package/src/i18n/zh-cn.ts +6 -2
  159. package/src/i18n/zh-tw.ts +6 -2
  160. package/src/layout-marketplace/pane-imagery.ts +1 -0
  161. package/src/layout-marketplace/payload.test.ts +17 -0
  162. package/src/layout-marketplace/payload.ts +40 -5
  163. package/src/market-data/coordinator/index.ts +3 -2
  164. package/src/market-data/coordinator/quotes.ts +173 -66
  165. package/src/market-data/coordinator/subscriptions.test.ts +215 -37
  166. package/src/pane-settings.ts +1 -1
  167. package/src/plugins/builtin/analytics/pane-model.ts +1 -1
  168. package/src/plugins/builtin/analytics/view.tsx +1 -1
  169. package/src/plugins/builtin/broker-manager/index.test.tsx +3 -2
  170. package/src/plugins/builtin/broker-manager/model.test.ts +2 -2
  171. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +16 -2
  172. package/src/plugins/builtin/chart-composer/pane.tsx +22 -8
  173. package/src/plugins/builtin/chat/content/composer.tsx +8 -4
  174. package/src/plugins/builtin/cloud/auth-dialog.tsx +14 -228
  175. package/src/plugins/builtin/cloud/auth-form.tsx +277 -0
  176. package/src/plugins/builtin/cloud/plugin.tsx +1 -0
  177. package/src/plugins/builtin/composite-plugins.ts +14 -1
  178. package/src/plugins/builtin/correlation/relationship/model.ts +1 -1
  179. package/src/plugins/builtin/correlation/relationship/pane.tsx +2 -6
  180. package/src/plugins/builtin/correlation/relationship/view-model.ts +2 -2
  181. package/src/plugins/builtin/credit-conditions/index.test.tsx +12 -6
  182. package/src/plugins/builtin/credit-conditions/index.tsx +2 -21
  183. package/src/plugins/builtin/dividend-yield/index.tsx +1 -0
  184. package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
  185. package/src/plugins/builtin/earnings-calls/data.ts +223 -0
  186. package/src/plugins/builtin/earnings-calls/format.ts +49 -0
  187. package/src/plugins/builtin/earnings-calls/index.tsx +69 -0
  188. package/src/plugins/builtin/earnings-calls/pane.tsx +756 -0
  189. package/src/plugins/builtin/earnings-calls/prose.test.ts +36 -0
  190. package/src/plugins/builtin/earnings-calls/prose.ts +59 -0
  191. package/src/plugins/builtin/earnings-calls/transcript-view.tsx +334 -0
  192. package/src/plugins/builtin/econ/detail-view.tsx +1 -2
  193. package/src/plugins/builtin/econ/fred-series-map.ts +16 -1
  194. package/src/plugins/builtin/econ/index.tsx +2 -1
  195. package/src/plugins/builtin/econ-statistics/cache.ts +4 -0
  196. package/src/plugins/builtin/econ-statistics/client.ts +92 -0
  197. package/src/plugins/builtin/econ-statistics/defs.ts +66 -0
  198. package/src/plugins/builtin/econ-statistics/detail.tsx +154 -0
  199. package/src/plugins/builtin/econ-statistics/headless.test.ts +54 -0
  200. package/src/plugins/builtin/econ-statistics/headless.ts +171 -0
  201. package/src/plugins/builtin/econ-statistics/index.tsx +54 -0
  202. package/src/plugins/builtin/econ-statistics/pane.tsx +356 -0
  203. package/src/plugins/builtin/econ-statistics/stats.ts +522 -0
  204. package/src/plugins/builtin/econ-statistics/transform.test.ts +86 -0
  205. package/src/plugins/builtin/econ-statistics/transform.ts +85 -0
  206. package/src/plugins/builtin/econ-statistics/trend.ts +57 -0
  207. package/src/plugins/builtin/econ-statistics/view.ts +90 -0
  208. package/src/plugins/builtin/fear-greed/charts.tsx +7 -18
  209. package/src/plugins/builtin/fear-greed/data.ts +6 -1
  210. package/src/plugins/builtin/help/index.tsx +4 -0
  211. package/src/plugins/builtin/holders/index.tsx +1 -0
  212. package/src/plugins/builtin/insider/index.tsx +1 -0
  213. package/src/plugins/builtin/kelly-sizer/pane.tsx +1 -1
  214. package/src/plugins/builtin/kelly-sizer/sections.tsx +2 -3
  215. package/src/plugins/builtin/market-valuation/align.ts +122 -0
  216. package/src/plugins/builtin/market-valuation/cache.ts +32 -0
  217. package/src/plugins/builtin/market-valuation/chart-projection.ts +217 -0
  218. package/src/plugins/builtin/market-valuation/chart-series.ts +89 -0
  219. package/src/plugins/builtin/market-valuation/client.test.ts +141 -0
  220. package/src/plugins/builtin/market-valuation/client.ts +116 -0
  221. package/src/plugins/builtin/market-valuation/defs.ts +251 -0
  222. package/src/plugins/builtin/market-valuation/detail.tsx +159 -0
  223. package/src/plugins/builtin/market-valuation/headless.test.ts +58 -0
  224. package/src/plugins/builtin/market-valuation/headless.ts +170 -0
  225. package/src/plugins/builtin/market-valuation/index.tsx +64 -0
  226. package/src/plugins/builtin/market-valuation/indicators.ts +422 -0
  227. package/src/plugins/builtin/market-valuation/model.test.ts +350 -0
  228. package/src/plugins/builtin/market-valuation/model.ts +8 -0
  229. package/src/plugins/builtin/market-valuation/pane.test.tsx +247 -0
  230. package/src/plugins/builtin/market-valuation/pane.tsx +344 -0
  231. package/src/plugins/builtin/market-valuation/series.ts +12 -0
  232. package/src/plugins/builtin/market-valuation/settings.ts +38 -0
  233. package/src/plugins/builtin/market-valuation/sources.ts +130 -0
  234. package/src/plugins/builtin/market-valuation/trend.ts +89 -0
  235. package/src/plugins/builtin/market-valuation/view.ts +137 -0
  236. package/src/plugins/builtin/market-valuation/zone-scale/desktop.tsx +123 -0
  237. package/src/plugins/builtin/market-valuation/zone-scale/index.tsx +12 -0
  238. package/src/plugins/builtin/market-valuation/zone-scale/model.ts +60 -0
  239. package/src/plugins/builtin/market-valuation/zone-scale/terminal.tsx +94 -0
  240. package/src/plugins/builtin/news/wire/breaking/filters.test.ts +99 -0
  241. package/src/plugins/builtin/news/wire/breaking/filters.ts +110 -0
  242. package/src/plugins/builtin/news/wire/breaking/notifications.test.ts +157 -53
  243. package/src/plugins/builtin/news/wire/breaking/notifications.ts +91 -25
  244. package/src/plugins/builtin/news/wire/index.tsx +31 -44
  245. package/src/plugins/builtin/options/index.tsx +1 -0
  246. package/src/plugins/builtin/options/view.test.tsx +29 -1
  247. package/src/plugins/builtin/options/view.tsx +1 -22
  248. package/src/plugins/builtin/plugin-marketplace/feed.ts +80 -0
  249. package/src/plugins/builtin/plugin-marketplace/index.tsx +31 -0
  250. package/src/plugins/builtin/plugin-marketplace/model.test.ts +200 -0
  251. package/src/plugins/builtin/plugin-marketplace/model.ts +204 -0
  252. package/src/plugins/builtin/plugin-marketplace/pane.tsx +385 -0
  253. package/src/plugins/builtin/plugin-marketplace/store.ts +46 -0
  254. package/src/plugins/builtin/plugin-module.ts +9 -1
  255. package/src/plugins/builtin/research/index.tsx +4 -0
  256. package/src/plugins/builtin/research-search/command-bar-search.test.ts +81 -0
  257. package/src/plugins/builtin/research-search/command-bar-search.ts +175 -0
  258. package/src/plugins/builtin/research-search/data.ts +109 -0
  259. package/src/plugins/builtin/research-search/document-view.test.tsx +75 -0
  260. package/src/plugins/builtin/research-search/document-view.tsx +254 -0
  261. package/src/plugins/builtin/research-search/filter-bar.tsx +62 -0
  262. package/src/plugins/builtin/research-search/focus-handoff.ts +47 -0
  263. package/src/plugins/builtin/research-search/index.tsx +85 -0
  264. package/src/plugins/builtin/research-search/model.test.ts +40 -0
  265. package/src/plugins/builtin/research-search/model.ts +291 -0
  266. package/src/plugins/builtin/research-search/pane.test.tsx +241 -0
  267. package/src/plugins/builtin/research-search/pane.tsx +678 -0
  268. package/src/plugins/builtin/research-search/saved-view.tsx +171 -0
  269. package/src/plugins/builtin/research-search/snippet-text.tsx +35 -0
  270. package/src/plugins/builtin/research-search/snippet.test.ts +78 -0
  271. package/src/plugins/builtin/research-search/snippet.ts +134 -0
  272. package/src/plugins/builtin/scanner/flow-model.test.ts +11 -1
  273. package/src/plugins/builtin/scanner/flow-model.ts +29 -1
  274. package/src/plugins/builtin/scanner/flow-pane.tsx +78 -22
  275. package/src/plugins/builtin/sec/index.tsx +1 -0
  276. package/src/plugins/builtin/shared/series-cache.ts +93 -0
  277. package/src/plugins/builtin/short-interest/index.tsx +1 -0
  278. package/src/plugins/builtin/short-interest/pane.tsx +1 -1
  279. package/src/plugins/builtin/thirteenf/index.tsx +1 -0
  280. package/src/plugins/builtin/ticker-detail/index.tsx +0 -14
  281. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +1 -1
  282. package/src/plugins/builtin/volatility/index.tsx +1 -1
  283. package/src/plugins/builtin/world-venue-map/map.desktop.test.tsx +106 -0
  284. package/src/plugins/builtin/world-venue-map/map.tsx +213 -69
  285. package/src/plugins/builtin/world-venue-map/model.test.ts +32 -0
  286. package/src/plugins/builtin/world-venue-map/model.ts +134 -13
  287. package/src/plugins/builtin/yield-curve/index.tsx +1 -1
  288. package/src/plugins/bundle.test.ts +109 -0
  289. package/src/plugins/bundle.ts +139 -0
  290. package/src/plugins/catalog-browser.test.ts +1 -0
  291. package/src/plugins/catalog-browser.ts +10 -0
  292. package/src/plugins/catalog-manifest.test.ts +43 -0
  293. package/src/plugins/catalog-manifest.ts +92 -0
  294. package/src/plugins/catalog-ui.ts +20 -4
  295. package/src/plugins/catalog.ts +9 -2
  296. package/src/plugins/current-target.ts +28 -0
  297. package/src/plugins/host-contract.ts +37 -0
  298. package/src/plugins/host-link.ts +158 -0
  299. package/src/plugins/host-modules.ts +61 -0
  300. package/src/plugins/loader.ts +41 -22
  301. package/src/plugins/ownership.ts +1 -0
  302. package/src/plugins/prediction-markets/index.tsx +1 -0
  303. package/src/plugins/registry/context.ts +3 -0
  304. package/src/plugins/registry/contributions.ts +27 -0
  305. package/src/plugins/registry/index.test.ts +25 -0
  306. package/src/plugins/registry/index.ts +8 -0
  307. package/src/plugins/registry/pane-settings.ts +6 -13
  308. package/src/plugins/seed.test.ts +83 -0
  309. package/src/plugins/seed.ts +80 -0
  310. package/src/public/broker.ts +31 -0
  311. package/src/public/market-data.ts +9 -0
  312. package/src/public/pane-hooks.ts +57 -0
  313. package/src/public/public-api.test.ts +285 -0
  314. package/src/public/react.ts +62 -0
  315. package/src/public/test-support.ts +23 -0
  316. package/src/public/time-series.ts +8 -0
  317. package/src/public/utils.ts +77 -0
  318. package/src/remote/layout-helpers.ts +6 -8
  319. package/src/remote/resources.ts +2 -0
  320. package/src/remote/schema.ts +1 -0
  321. package/src/remote/semantic-tree.test.tsx +105 -0
  322. package/src/remote/semantic-tree.tsx +53 -35
  323. package/src/renderers/browser/main.tsx +6 -0
  324. package/src/renderers/cloudflare/worker.test.ts +19 -0
  325. package/src/renderers/cloudflare/worker.ts +1 -1
  326. package/src/renderers/electrobun/bun/external-plugins.ts +140 -0
  327. package/src/renderers/electrobun/bun/index.ts +5 -2
  328. package/src/renderers/electrobun/shared/protocol.ts +21 -0
  329. package/src/renderers/electrobun/view/build-assets.ts +0 -1
  330. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +51 -1
  331. package/src/renderers/electrobun/view/external-plugins.test.ts +45 -0
  332. package/src/renderers/electrobun/view/external-plugins.ts +63 -0
  333. package/src/renderers/electrobun/view/host/ascii-text.test.ts +48 -0
  334. package/src/renderers/electrobun/view/host/ascii-text.ts +11 -1
  335. package/src/renderers/electrobun/view/host/chart-surface.tsx +38 -32
  336. package/src/renderers/electrobun/view/host/mouse.ts +16 -2
  337. package/src/renderers/electrobun/view/main.tsx +27 -2
  338. package/src/renderers/electrobun/view/styles.css +5 -0
  339. package/src/renderers/electrobun/view/toast-host.tsx +18 -0
  340. package/src/renderers/opentui/chart-surface.tsx +5 -1
  341. package/src/renderers/opentui/host.tsx +16 -2
  342. package/src/renderers/opentui/interaction-performance.test.ts +79 -0
  343. package/src/renderers/opentui/interaction-performance.ts +188 -0
  344. package/src/renderers/opentui/start.tsx +26 -1
  345. package/src/renderers/opentui/test-utils.tsx +13 -0
  346. package/src/renderers/opentui/toast-host.tsx +26 -2
  347. package/src/renderers/opentui/ui-host.tsx +1 -0
  348. package/src/sources/provider-router/search.test.ts +191 -0
  349. package/src/sources/provider-router/search.ts +92 -39
  350. package/src/state/app/context/index.tsx +2 -0
  351. package/src/state/app/context/selector.test.tsx +33 -1
  352. package/src/state/hooks/quote-streaming.test.tsx +93 -0
  353. package/src/state/hooks/quote-streaming.ts +106 -30
  354. package/src/state/pane-table-export-registry.ts +19 -0
  355. package/src/theme/color-utils.ts +1 -1
  356. package/src/theme/colors.ts +50 -0
  357. package/src/theme/themes.test.ts +15 -2
  358. package/src/theme/themes.ts +19 -1
  359. package/src/tickers/search/index.test.ts +26 -0
  360. package/src/tickers/search/index.ts +42 -17
  361. package/src/tickers/search/ranking.ts +14 -0
  362. package/src/time-series/chart-data.test.ts +216 -0
  363. package/src/time-series/resolution.test.ts +25 -26
  364. package/src/time-series/resolution.ts +41 -10
  365. package/src/time-series/resolve.ts +7 -0
  366. package/src/time-series/types.ts +3 -1
  367. package/src/time-series/use-chart-resolution.ts +5 -0
  368. package/src/types/config.ts +123 -3
  369. package/src/types/data-provider.ts +11 -0
  370. package/src/types/headless.ts +161 -0
  371. package/src/types/plugin.ts +102 -0
  372. package/src/ui/host.tsx +12 -1
  373. package/src/ui/index.tsx +1 -0
  374. package/src/ui/toast.tsx +8 -4
  375. package/src/utils/format.ts +35 -0
  376. package/src/utils/fuzzy-search.test.ts +94 -0
  377. package/src/utils/fuzzy-search.ts +81 -22
  378. package/src/utils/shortcut-labels.ts +14 -0
  379. package/src/components/chart/core/axis-format.ts +0 -157
  380. package/src/components/chart/core/index.test.ts +0 -1075
  381. package/src/components/chart/core/projection.ts +0 -357
  382. package/src/components/chart/core/render-utils.ts +0 -4
  383. package/src/components/chart/core/renderer.ts +0 -244
  384. package/src/components/chart/core/scene.ts +0 -180
  385. package/src/components/chart/core/time-axis.ts +0 -330
  386. package/src/components/chart/cursor-motion.ts +0 -47
  387. package/src/components/chart/indicators/types.ts +0 -26
  388. package/src/components/chart/market-session.ts +0 -176
  389. package/src/components/chart/multi-line/renderer.test.ts +0 -83
  390. package/src/components/chart/multi-line/renderer.ts +0 -161
  391. package/src/components/chart/multi-line/scene.ts +0 -188
  392. package/src/components/chart/native/raster/layout.ts +0 -129
  393. package/src/components/chart/native/raster/price-chart.ts +0 -244
  394. package/src/components/chart/rendering/chart-draw.ts +0 -270
  395. package/src/components/chart/rendering/geometry.ts +0 -95
  396. package/src/components/chart/rendering/pixel-buffer.ts +0 -267
  397. package/src/components/chart/scatter-chart-renderer.test.ts +0 -31
  398. package/src/components/chart/scatter-chart-renderer.ts +0 -223
  399. package/src/components/chart/static/chart/surface.tsx +0 -358
  400. package/src/components/command-bar/routes/root/article-results.test.ts +0 -83
  401. package/src/components/command-bar/routes/root/article-results.ts +0 -55
  402. package/src/plugins/builtin/news/wire/article-search.test.ts +0 -72
  403. package/src/plugins/builtin/news/wire/article-search.ts +0 -140
  404. package/src/plugins/builtin/substack/api/auth.ts +0 -185
  405. package/src/plugins/builtin/substack/api/cache.ts +0 -82
  406. package/src/plugins/builtin/substack/api/loaders.ts +0 -170
  407. package/src/plugins/builtin/substack/api/store.ts +0 -186
  408. package/src/plugins/builtin/substack/api/types.ts +0 -42
  409. package/src/plugins/builtin/substack/api.test.ts +0 -298
  410. package/src/plugins/builtin/substack/article-detail.tsx +0 -397
  411. package/src/plugins/builtin/substack/article-stack.tsx +0 -133
  412. package/src/plugins/builtin/substack/content.ts +0 -478
  413. package/src/plugins/builtin/substack/feed-tabs.tsx +0 -44
  414. package/src/plugins/builtin/substack/index.tsx +0 -49
  415. package/src/plugins/builtin/substack/login-view.tsx +0 -126
  416. package/src/plugins/builtin/substack/model.test.ts +0 -155
  417. package/src/plugins/builtin/substack/normalize.ts +0 -343
  418. package/src/plugins/builtin/substack/pane-footer.ts +0 -52
  419. package/src/plugins/builtin/substack/pane-state.ts +0 -170
  420. package/src/plugins/builtin/substack/pane.tsx +0 -547
  421. package/src/plugins/builtin/substack/read-state.ts +0 -30
  422. package/src/plugins/builtin/substack/table.ts +0 -111
  423. package/src/plugins/builtin/substack/types.ts +0 -71
  424. package/src/plugins/builtin/substack/utils.ts +0 -131
  425. package/src/plugins/builtin/ticker-detail/data-panes/provider-search.tsx +0 -229
  426. package/src/plugins/ibkr/account-cache.test.ts +0 -102
  427. package/src/plugins/ibkr/account-cache.ts +0 -24
  428. package/src/plugins/ibkr/broker-adapter.ts +0 -286
  429. package/src/plugins/ibkr/config.test.ts +0 -79
  430. package/src/plugins/ibkr/config.ts +0 -250
  431. package/src/plugins/ibkr/dialogs.tsx +0 -40
  432. package/src/plugins/ibkr/flex/index.test.ts +0 -264
  433. package/src/plugins/ibkr/flex/index.ts +0 -367
  434. package/src/plugins/ibkr/flex/statement-client.ts +0 -235
  435. package/src/plugins/ibkr/fundamental-parser.ts +0 -149
  436. package/src/plugins/ibkr/gateway/account-loaders.ts +0 -266
  437. package/src/plugins/ibkr/gateway/account-summary.ts +0 -164
  438. package/src/plugins/ibkr/gateway/client-lock.ts +0 -154
  439. package/src/plugins/ibkr/gateway/connection-events.ts +0 -114
  440. package/src/plugins/ibkr/gateway/connection.ts +0 -152
  441. package/src/plugins/ibkr/gateway/contracts.ts +0 -132
  442. package/src/plugins/ibkr/gateway/fundamentals.ts +0 -83
  443. package/src/plugins/ibkr/gateway/helpers.ts +0 -33
  444. package/src/plugins/ibkr/gateway/history-requests.ts +0 -158
  445. package/src/plugins/ibkr/gateway/history.ts +0 -120
  446. package/src/plugins/ibkr/gateway/instrument-lookup.ts +0 -117
  447. package/src/plugins/ibkr/gateway/market-data.ts +0 -345
  448. package/src/plugins/ibkr/gateway/order-execution.ts +0 -218
  449. package/src/plugins/ibkr/gateway/orders.ts +0 -63
  450. package/src/plugins/ibkr/gateway/price-normalization.test.ts +0 -44
  451. package/src/plugins/ibkr/gateway/price-normalization.ts +0 -50
  452. package/src/plugins/ibkr/gateway/quote-stream-controller.ts +0 -203
  453. package/src/plugins/ibkr/gateway/quote-stream.ts +0 -171
  454. package/src/plugins/ibkr/gateway/selection.ts +0 -51
  455. package/src/plugins/ibkr/gateway/service/index.test.ts +0 -675
  456. package/src/plugins/ibkr/gateway/service/index.ts +0 -429
  457. package/src/plugins/ibkr/gateway/service/lifecycle.ts +0 -191
  458. package/src/plugins/ibkr/gateway/service/manager.ts +0 -52
  459. package/src/plugins/ibkr/gateway/service/market-data.ts +0 -48
  460. package/src/plugins/ibkr/gateway/service/native-loader.ts +0 -12
  461. package/src/plugins/ibkr/gateway/service/native.ts +0 -543
  462. package/src/plugins/ibkr/gateway/timeouts.ts +0 -13
  463. package/src/plugins/ibkr/gateway/types.ts +0 -27
  464. package/src/plugins/ibkr/index.test.tsx +0 -162
  465. package/src/plugins/ibkr/index.tsx +0 -159
  466. package/src/plugins/ibkr/instance-selection.test.ts +0 -120
  467. package/src/plugins/ibkr/instance-selection.ts +0 -36
  468. package/src/plugins/ibkr/portfolio-performance.test.ts +0 -32
  469. package/src/plugins/ibkr/portfolio-performance.ts +0 -129
  470. package/src/plugins/ibkr/trade/badge.tsx +0 -28
  471. package/src/plugins/ibkr/trade/dialogs.tsx +0 -44
  472. package/src/plugins/ibkr/trade/preview-panel.tsx +0 -130
  473. package/src/plugins/ibkr/trade/tab/actions.tsx +0 -443
  474. package/src/plugins/ibkr/trade/tab/field-editors.tsx +0 -79
  475. package/src/plugins/ibkr/trade/tab/footer.ts +0 -53
  476. package/src/plugins/ibkr/trade/tab/header.tsx +0 -167
  477. package/src/plugins/ibkr/trade/tab/index.test.tsx +0 -178
  478. package/src/plugins/ibkr/trade/tab/index.tsx +0 -298
  479. package/src/plugins/ibkr/trade/tab/model.ts +0 -159
  480. package/src/plugins/ibkr/trade/tab/order-request.ts +0 -64
  481. package/src/plugins/ibkr/trade/tab/shortcuts.ts +0 -89
  482. package/src/plugins/ibkr/trade/tab/state-sync.ts +0 -86
  483. package/src/plugins/ibkr/trade/ticket-panel.tsx +0 -301
  484. package/src/plugins/ibkr/trade/utils.test.ts +0 -175
  485. package/src/plugins/ibkr/trade/utils.ts +0 -144
  486. package/src/plugins/ibkr/trading/pane-view.tsx +0 -237
  487. package/src/plugins/ibkr/trading/pane.tsx +0 -332
  488. package/src/plugins/ibkr/trading/state.test.ts +0 -109
  489. package/src/plugins/ibkr/trading/state.ts +0 -335
package/src/app.tsx CHANGED
@@ -17,6 +17,7 @@ import { DetachedPaneShell } from "./components/layout/detached-pane-shell";
17
17
  import { TransientLayoutProvider } from "./components/layout/transient-layout";
18
18
  import { CommandBar } from "./components/command-bar/surface";
19
19
  import { OnboardingWizard } from "./components/onboarding/onboarding-wizard";
20
+ import { SignInGate } from "./components/sign-in-gate";
20
21
  import { useDialog } from "./ui/dialog";
21
22
  import { PluginRegistry } from "./plugins/registry";
22
23
  import type { LoadedExternalPlugin } from "./plugins/loader";
@@ -55,10 +56,12 @@ import { measurePerf } from "./utils/perf-marks";
55
56
  import { useAppLanguage } from "./i18n/react";
56
57
  import { AppLanguageConfigObserver } from "./app/language-observer";
57
58
  import { isPaneShareHandoff } from "./shares/location";
59
+ import { apiClient } from "./api-client";
58
60
 
59
61
  const EMPTY_EXTERNAL_PLUGINS: LoadedExternalPlugin[] = [];
60
62
 
61
63
  interface AppInnerProps {
64
+ externalPlugins: readonly LoadedExternalPlugin[];
62
65
  pluginRegistry: PluginRegistry;
63
66
  tickerRepository: AppTickerRepositoryPort;
64
67
  dataProvider: DataProvider;
@@ -71,6 +74,8 @@ interface AppInnerProps {
71
74
  updatesEnabled?: boolean;
72
75
  onboardingActive?: boolean;
73
76
  onOnboardingComplete?: (config: AppConfig) => void | Promise<void>;
77
+ /** Hosted browser terminal: nothing is reachable until a session exists. */
78
+ signInGateActive?: boolean;
74
79
  }
75
80
 
76
81
  function ThemedAppRoot({ children }: { children: ReactNode }) {
@@ -92,6 +97,7 @@ function ThemedAppRoot({ children }: { children: ReactNode }) {
92
97
  }
93
98
 
94
99
  function AppInner({
100
+ externalPlugins,
95
101
  pluginRegistry,
96
102
  tickerRepository,
97
103
  dataProvider,
@@ -104,6 +110,7 @@ function AppInner({
104
110
  updatesEnabled = true,
105
111
  onboardingActive = false,
106
112
  onOnboardingComplete,
113
+ signInGateActive = false,
107
114
  }: AppInnerProps) {
108
115
  const dispatch = useAppDispatch();
109
116
  const stateRef = useAppStateRef();
@@ -158,6 +165,13 @@ function AppInner({
158
165
  renderToast: (notification) => {
159
166
  const type = notification.type ?? "info";
160
167
  let toastId: string | number | undefined;
168
+ const dismissAfter = (run: () => void) => () => {
169
+ try {
170
+ run();
171
+ } finally {
172
+ if (toastId != null) toast.dismiss(toastId);
173
+ }
174
+ };
161
175
  const options = {
162
176
  title: notification.title,
163
177
  subtitle: notification.subtitle,
@@ -165,13 +179,13 @@ function AppInner({
165
179
  action: notification.action
166
180
  ? {
167
181
  label: notification.action.label,
168
- onClick: () => {
169
- try {
170
- notification.action?.onClick();
171
- } finally {
172
- if (toastId != null) toast.dismiss(toastId);
173
- }
174
- },
182
+ onClick: dismissAfter(() => notification.action?.onClick()),
183
+ }
184
+ : undefined,
185
+ secondaryAction: notification.secondaryAction
186
+ ? {
187
+ label: notification.secondaryAction.label,
188
+ onClick: dismissAfter(() => notification.secondaryAction?.onClick()),
175
189
  }
176
190
  : undefined,
177
191
  };
@@ -249,7 +263,10 @@ function AppInner({
249
263
  desktopDeepLinkBridge,
250
264
  desktopWindowKind: desktopWindowBridge?.kind,
251
265
  dispatch,
252
- initialized: state.initialized,
266
+ // The browser bridge re-emits from the URL on subscribe and nothing rewrites
267
+ // it, so a `?layout=` / `?share=` intent survives the gate and lands once a
268
+ // session exists. Running it earlier would only 401 behind the scrim.
269
+ initialized: state.initialized && !signInGateActive,
253
270
  pluginRegistry,
254
271
  stateRef,
255
272
  });
@@ -294,16 +311,23 @@ function AppInner({
294
311
  // onboarding finishes, the normal pull-before-push sync starts immediately.
295
312
  initialized: state.initialized
296
313
  && desktopWindowBridge?.kind !== "detached"
297
- && !onboardingActive,
314
+ && !onboardingActive
315
+ && !signInGateActive,
298
316
  });
299
317
 
318
+ const persistConfig = useCallback((nextConfig: AppState["config"]) => {
319
+ scheduleConfigSave(nextConfig);
320
+ }, []);
321
+
300
322
  useAppPaneRuntime({
301
323
  dataProvider,
302
324
  detachedPaneId,
303
325
  dialog,
304
326
  dispatch,
327
+ externalPlugins,
305
328
  isDetachedWindow,
306
329
  notify,
330
+ persistConfig,
307
331
  pluginRegistry,
308
332
  state,
309
333
  stateRef,
@@ -376,14 +400,7 @@ function AppInner({
376
400
  desktopWindowBridge={desktopWindowBridge}
377
401
  >
378
402
  <ThemedAppRoot>
379
- <Header
380
- onOpenHelp={() => pluginRegistry.showPane("help")}
381
- onOpenChangelog={(version) => {
382
- void pluginRegistry.createPaneFromTemplateAsyncFn("changelog-pane", {
383
- values: { version },
384
- }).catch(() => {});
385
- }}
386
- />
403
+ <Header onOpenHelp={() => pluginRegistry.showPane("help")} />
387
404
  <TransientLayoutProvider>
388
405
  <Shell
389
406
  pluginRegistry={pluginRegistry}
@@ -391,7 +408,13 @@ function AppInner({
391
408
  desktopDockPreview={desktopDockPreview}
392
409
  commandBarNativeOccluder={commandBarNativeOccluder}
393
410
  />
394
- <StatusBar />
411
+ <StatusBar
412
+ onOpenChangelog={(version) => {
413
+ void pluginRegistry.createPaneFromTemplateAsyncFn("changelog-pane", {
414
+ values: { version },
415
+ }).catch(() => {});
416
+ }}
417
+ />
395
418
  </TransientLayoutProvider>
396
419
  {onboardingActive && onOnboardingComplete ? (
397
420
  <OnboardingWizard
@@ -400,6 +423,7 @@ function AppInner({
400
423
  onComplete={onOnboardingComplete}
401
424
  />
402
425
  ) : null}
426
+ {signInGateActive ? <SignInGate /> : null}
403
427
  {state.commandBarOpen && (
404
428
  <CommandBar
405
429
  dataProvider={dataProvider}
@@ -430,6 +454,11 @@ interface AppProps {
430
454
  desktopThemePreview?: DesktopThemePreviewState | null;
431
455
  remoteControlAdapter?: RemoteControlAdapter;
432
456
  updatesEnabled?: boolean;
457
+ /**
458
+ * Requires a Gloom Cloud session before the app is usable. The hosted browser
459
+ * terminal sets this; desktop and the TUI keep sign-in optional.
460
+ */
461
+ requireSignIn?: boolean;
433
462
  }
434
463
 
435
464
  export function App({
@@ -445,6 +474,7 @@ export function App({
445
474
  desktopThemePreview = null,
446
475
  remoteControlAdapter,
447
476
  updatesEnabled = true,
477
+ requireSignIn = false,
448
478
  }: AppProps) {
449
479
  useAppLanguage();
450
480
  const externalPlugins = providedExternalPlugins ?? EMPTY_EXTERNAL_PLUGINS;
@@ -469,6 +499,16 @@ export function App({
469
499
  const [config, setConfig] = useState(() => {
470
500
  return initialCliLaunch.config;
471
501
  });
502
+ // Only the surfaces that require a session subscribe, so desktop and the
503
+ // terminal keep their current render profile.
504
+ const [signedIn, setSignedIn] = useState(() => !requireSignIn || apiClient.isSignedIn());
505
+ useEffect(() => {
506
+ if (!requireSignIn) return;
507
+ const sync = () => setSignedIn(apiClient.isSignedIn());
508
+ sync();
509
+ return apiClient.subscribeCurrentUser(sync);
510
+ }, [requireSignIn]);
511
+ const signInGateActive = requireSignIn && !signedIn;
472
512
  const shareHandoff = isPaneShareHandoff();
473
513
  const [showOnboarding, setShowOnboarding] = useState(() => (
474
514
  desktopWindowBridge?.kind !== "detached"
@@ -518,6 +558,7 @@ export function App({
518
558
  >
519
559
  <AppLanguageConfigObserver />
520
560
  <AppInner
561
+ externalPlugins={externalPlugins}
521
562
  pluginRegistry={services.pluginRegistry}
522
563
  tickerRepository={services.tickerRepository}
523
564
  dataProvider={services.dataProvider}
@@ -529,6 +570,7 @@ export function App({
529
570
  remoteControlAdapter={remoteControlAdapter}
530
571
  updatesEnabled={updatesEnabled}
531
572
  onboardingActive={showOnboarding}
573
+ signInGateActive={signInGateActive}
532
574
  onOnboardingComplete={(updatedConfig) => {
533
575
  setConfig(updatedConfig);
534
576
  setShowOnboarding(false);
@@ -0,0 +1,96 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { existsSync } from "fs";
3
+ import { dirname, join, relative, resolve } from "path";
4
+
5
+ const SOURCE_ROOT = process.cwd();
6
+ const IMPORT_PATTERN = /\b(?:import|export)\s+(?:[^'"]*?\s+from\s+)?["']([^"']+)["']|import\(["']([^"']+)["']\)/g;
7
+ const TYPE_ONLY_PATTERN = /\b(?:import|export)\s+type\s/;
8
+
9
+ /**
10
+ * Entry points that run in a browser context: the Electrobun desktop view and
11
+ * the hosted web app. Neither has `process`, a filesystem, or a Bun runtime.
12
+ */
13
+ const BROWSER_ENTRIES = [
14
+ "src/renderers/electrobun/view/main.tsx",
15
+ "src/renderers/browser/main.tsx",
16
+ ];
17
+
18
+ /**
19
+ * Modules that only work under Bun. Each reads the filesystem or the process
20
+ * environment at module scope, so merely importing one from a browser bundle
21
+ * throws on load — the bundle still builds, which is exactly what makes this
22
+ * worth a test.
23
+ *
24
+ * `plugins/loader` reaching the desktop view through `plugins/bundle` shipped a
25
+ * broken desktop build: the view crashed with "Can't find variable: process"
26
+ * before rendering anything, while `desktop:view:build` reported success.
27
+ */
28
+ const BUN_ONLY_MODULES = [
29
+ "src/plugins/loader.ts",
30
+ "src/plugins/bundle.ts",
31
+ "src/plugins/seed.ts",
32
+ "src/plugins/host-link.ts",
33
+ "src/cli/restore-plugins.ts",
34
+ ];
35
+
36
+ const EXTENSIONS = [".ts", ".tsx", ".js", ".jsx"];
37
+
38
+ function resolveSpecifier(fromFile: string, specifier: string): string | null {
39
+ if (!specifier.startsWith(".")) return null;
40
+ const base = resolve(dirname(fromFile), specifier);
41
+ for (const candidate of [base, ...EXTENSIONS.map((ext) => `${base}${ext}`), ...EXTENSIONS.map((ext) => join(base, `index${ext}`))]) {
42
+ if (existsSync(candidate) && !candidate.endsWith("/")) {
43
+ try {
44
+ if (Bun.file(candidate).size >= 0 && !candidate.match(/\/$/)) return candidate;
45
+ } catch {
46
+ // Directory or unreadable entry: keep trying the other candidates.
47
+ }
48
+ }
49
+ }
50
+ return null;
51
+ }
52
+
53
+ /** Walks runtime imports only; `import type` is erased and cannot pull code in. */
54
+ async function reachableFrom(entry: string): Promise<Map<string, string[]>> {
55
+ const seen = new Map<string, string[]>();
56
+ const queue: Array<{ file: string; path: string[] }> = [{ file: resolve(SOURCE_ROOT, entry), path: [entry] }];
57
+
58
+ while (queue.length > 0) {
59
+ const current = queue.shift()!;
60
+ const key = relative(SOURCE_ROOT, current.file);
61
+ if (seen.has(key)) continue;
62
+ seen.set(key, current.path);
63
+
64
+ let source: string;
65
+ try {
66
+ source = await Bun.file(current.file).text();
67
+ } catch {
68
+ continue;
69
+ }
70
+
71
+ for (const line of source.split("\n")) {
72
+ if (TYPE_ONLY_PATTERN.test(line)) continue;
73
+ for (const match of line.matchAll(IMPORT_PATTERN)) {
74
+ const specifier = match[1] ?? match[2] ?? "";
75
+ const resolved = resolveSpecifier(current.file, specifier);
76
+ if (resolved) queue.push({ file: resolved, path: [...current.path, relative(SOURCE_ROOT, resolved)] });
77
+ }
78
+ }
79
+ }
80
+
81
+ return seen;
82
+ }
83
+
84
+ describe("browser entry import graph", () => {
85
+ for (const entry of BROWSER_ENTRIES) {
86
+ test(`${entry} does not reach a Bun-only module`, async () => {
87
+ const reachable = await reachableFrom(entry);
88
+
89
+ const violations = BUN_ONLY_MODULES
90
+ .filter((module) => reachable.has(module))
91
+ .map((module) => `${module} via ${reachable.get(module)!.slice(-3).join(" -> ")}`);
92
+
93
+ expect(violations).toEqual([]);
94
+ });
95
+ }
96
+ });
@@ -1,62 +1,60 @@
1
1
  import { describe, expect, test } from "bun:test";
2
2
  import type { BrokerAdapter } from "../types/broker";
3
3
  import type { BrokerInstanceConfig } from "../types/config";
4
- import { ibkrBroker } from "../plugins/ibkr/broker-adapter";
4
+ import { testBroker } from "./test-broker";
5
5
  import {
6
6
  buildBrokerProfileConfig,
7
7
  createBrokerProfileDraft,
8
8
  validateBrokerProfileValues,
9
9
  } from "./profile-form";
10
10
 
11
- function createFlexInstance(): BrokerInstanceConfig {
11
+ function createSavedInstance(): BrokerInstanceConfig {
12
12
  return {
13
- id: "ibkr-flex",
14
- brokerType: "ibkr",
15
- label: "IBKR Flex",
16
- connectionMode: "flex",
13
+ id: "saved-profile",
14
+ brokerType: "test-broker",
15
+ label: "Saved Profile",
16
+ connectionMode: "token",
17
17
  config: {
18
- connectionMode: "flex",
19
- gatewaySetupMode: "auto",
20
- flex: { token: "saved-token", queryId: "123", endpoint: "https://example.test/flex" },
21
- gateway: { host: "127.0.0.1", marketDataType: "auto" },
18
+ connectionMode: "token",
19
+ credentials: { token: "saved-token", accountId: "123" },
20
+ host: "127.0.0.1",
22
21
  },
23
22
  enabled: true,
24
23
  };
25
24
  }
26
25
 
27
26
  describe("broker profile form helpers", () => {
28
- test("builds canonical IBKR Flex config from flat values", () => {
29
- const config = buildBrokerProfileConfig(ibkrBroker, {
30
- connectionMode: "flex",
27
+ test("builds a nested broker config from flat form values", () => {
28
+ const config = buildBrokerProfileConfig(testBroker, {
29
+ connectionMode: "token",
31
30
  token: "token",
32
- queryId: "456",
33
- endpoint: "",
34
- gatewaySetupMode: "auto",
31
+ accountId: "456",
32
+ host: "",
35
33
  });
36
34
 
37
35
  expect(config).toMatchObject({
38
- connectionMode: "flex",
39
- flex: { token: "token", queryId: "456" },
36
+ connectionMode: "token",
37
+ credentials: { token: "token", accountId: "456" },
40
38
  });
41
39
  });
42
40
 
43
41
  test("preserves saved password fields when editing leaves them blank", () => {
44
- const previous = createFlexInstance();
45
- const draft = createBrokerProfileDraft(ibkrBroker, previous);
42
+ const previous = createSavedInstance();
43
+ const draft = createBrokerProfileDraft(testBroker, previous);
46
44
  draft.values.token = "";
47
45
 
48
- expect(validateBrokerProfileValues(ibkrBroker, draft.values, previous)).toBeNull();
49
- expect(buildBrokerProfileConfig(ibkrBroker, draft.values, previous)).toMatchObject({
50
- flex: { token: "saved-token" },
46
+ expect(validateBrokerProfileValues(testBroker, draft.values, previous)).toBeNull();
47
+ expect(buildBrokerProfileConfig(testBroker, draft.values, previous)).toMatchObject({
48
+ credentials: { token: "saved-token" },
51
49
  });
52
50
  });
53
51
 
54
52
  test("requires password fields for new profiles", () => {
55
- expect(validateBrokerProfileValues(ibkrBroker, {
56
- connectionMode: "flex",
53
+ expect(validateBrokerProfileValues(testBroker, {
54
+ connectionMode: "token",
57
55
  token: "",
58
- queryId: "123",
59
- })).toBe("Flex Token is required.");
56
+ accountId: "123",
57
+ })).toBe("API Token is required.");
60
58
  });
61
59
 
62
60
  test("falls back to raw values for generic brokers", () => {
@@ -0,0 +1,68 @@
1
+ import type { BrokerAdapter } from "../types/broker";
2
+
3
+ /**
4
+ * A stand-in broker for tests of the generic broker machinery.
5
+ *
6
+ * These tests used to reach for the real IBKR adapter, which now lives in its
7
+ * own repository. Depending on a plugin to exercise host code is backwards
8
+ * anyway: it made a core test fail whenever that plugin's schema changed, and
9
+ * it hid which behaviour was actually under test.
10
+ *
11
+ * The shape mirrors what the machinery has to handle — a mode switch, a nested
12
+ * config, and a password field that must survive an edit left blank.
13
+ */
14
+ export const testBroker: BrokerAdapter = {
15
+ id: "test-broker",
16
+ name: "Test Broker",
17
+ configSchema: [
18
+ {
19
+ key: "connectionMode",
20
+ label: "Connection",
21
+ type: "select",
22
+ options: [
23
+ { value: "token", label: "Token" },
24
+ { value: "local", label: "Local" },
25
+ ],
26
+ defaultValue: "token",
27
+ },
28
+ { key: "token", label: "API Token", type: "password", required: true, visibleWhen: { connectionMode: "token" } },
29
+ { key: "accountId", label: "Account", type: "text", required: true, visibleWhen: { connectionMode: "token" } },
30
+ { key: "host", label: "Host", type: "text", defaultValue: "127.0.0.1", visibleWhen: { connectionMode: "local" } },
31
+ ],
32
+
33
+ async validate() {
34
+ return true;
35
+ },
36
+
37
+ async importPositions() {
38
+ return [];
39
+ },
40
+
41
+ getStatus() {
42
+ return { state: "disconnected", message: "", updatedAt: 0 };
43
+ },
44
+
45
+ getProfileActions() {
46
+ // The pane renders and invokes profile actions; one is enough to cover that.
47
+ return [{ id: "test-console", label: "Console", paneId: "ibkr-trading" }];
48
+ },
49
+
50
+ toConfigValues(instance) {
51
+ const config = instance.config as Record<string, unknown>;
52
+ const credentials = (config.credentials ?? {}) as Record<string, unknown>;
53
+ return {
54
+ connectionMode: String(config.connectionMode ?? "token"),
55
+ token: String(credentials.token ?? ""),
56
+ accountId: String(credentials.accountId ?? ""),
57
+ host: String(config.host ?? "127.0.0.1"),
58
+ };
59
+ },
60
+
61
+ fromConfigValues(values) {
62
+ return {
63
+ connectionMode: values.connectionMode,
64
+ credentials: { token: values.token, accountId: values.accountId },
65
+ host: values.host,
66
+ };
67
+ },
68
+ };
@@ -2,6 +2,7 @@ import { join } from "path";
2
2
  import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync } from "fs";
3
3
  import { execFileSync } from "child_process";
4
4
  import { getPluginsDir } from "../../plugins/loader";
5
+ import { linkHostPackages } from "../../plugins/host-link";
5
6
  import {
6
7
  cliStyles,
7
8
  renderSection,
@@ -50,7 +51,20 @@ function parseGitHubRef(rawRef: string): { url: string; name: string } {
50
51
  throw new Error(`Invalid plugin reference: ${ref}. Use user/repo or a GitHub URL.`);
51
52
  }
52
53
 
53
- export async function installPlugin(ref: string) {
54
+ export interface InstallPluginOptions {
55
+ /**
56
+ * Suppress child-process output and progress logging. The marketplace pane
57
+ * installs while the terminal UI owns the screen, and git and bun writing to
58
+ * stdout corrupts the rendered frame.
59
+ */
60
+ quiet?: boolean;
61
+ }
62
+
63
+ export async function installPlugin(ref: string, options: InstallPluginOptions = {}) {
64
+ const stdio = options.quiet ? "pipe" : "inherit";
65
+ const say = (message: string) => {
66
+ if (!options.quiet) console.log(message);
67
+ };
54
68
  ensurePluginsDir();
55
69
  const { url, name } = parseGitHubRef(ref);
56
70
  const targetDir = join(PLUGINS_DIR, name);
@@ -59,11 +73,11 @@ export async function installPlugin(ref: string) {
59
73
  fail(`Plugin "${name}" already exists.`, `Use "gloomberb update ${name}" to refresh it.`);
60
74
  }
61
75
 
62
- console.log(cliStyles.accent(`Installing ${name}`));
63
- console.log(cliStyles.muted(url));
76
+ say(cliStyles.accent(`Installing ${name}`));
77
+ say(cliStyles.muted(url));
64
78
 
65
79
  try {
66
- execFileSync("git", ["clone", "--depth", "1", url, targetDir], { stdio: "inherit" });
80
+ execFileSync("git", ["clone", "--depth", "1", url, targetDir], { stdio });
67
81
  } catch {
68
82
  rmSync(targetDir, { recursive: true, force: true });
69
83
  fail(`Failed to clone ${url}.`);
@@ -71,11 +85,24 @@ export async function installPlugin(ref: string) {
71
85
 
72
86
  const pkgPath = join(targetDir, "package.json");
73
87
  if (existsSync(pkgPath)) {
74
- console.log(cliStyles.muted("Installing plugin dependencies..."));
88
+ say(cliStyles.muted("Installing plugin dependencies..."));
75
89
  try {
76
- execFileSync("bun", ["install"], { cwd: targetDir, stdio: "inherit" });
90
+ // --production: plugin repos depend on `gloomberb` as a devDependency so
91
+ // their own CI can typecheck against the real API. At runtime the host is
92
+ // symlinked in instead, and pulling a second full copy here would both
93
+ // waste a lot of disk and risk a duplicate React.
94
+ execFileSync("bun", ["install", "--production"], { cwd: targetDir, stdio });
77
95
  } catch {
78
- console.error(cliStyles.warning("Warning: failed to install plugin dependencies."));
96
+ if (!options.quiet) console.error(cliStyles.warning("Warning: failed to install plugin dependencies."));
97
+ }
98
+ }
99
+
100
+ // After `bun install`, which prunes links it does not know about.
101
+ const link = linkHostPackages(targetDir);
102
+ if (link.error) {
103
+ if (!options.quiet) {
104
+ console.error(cliStyles.warning(`Warning: could not link the Gloomberb runtime (${link.error}).`));
105
+ console.error(cliStyles.muted("The plugin's \"gloomberb/*\" imports will not resolve."));
79
106
  }
80
107
  }
81
108
 
@@ -98,13 +125,13 @@ export async function installPlugin(ref: string) {
98
125
  const mod = await import(entryFile);
99
126
  const plugin = mod.default ?? mod.plugin;
100
127
  if (plugin?.id && plugin?.name) {
101
- console.log(cliStyles.success(`Installed ${plugin.name} v${plugin.version || "0.0.0"}`));
128
+ say(cliStyles.success(`Installed ${plugin.name} v${plugin.version || "0.0.0"}`));
102
129
  return;
103
130
  }
104
131
  }
105
- console.log(cliStyles.warning("Installed files, but no valid GloomPlugin export was found."));
132
+ say(cliStyles.warning("Installed files, but no valid GloomPlugin export was found."));
106
133
  } catch (err) {
107
- console.log(cliStyles.warning(`Plugin validation failed: ${err}`));
134
+ say(cliStyles.warning(`Plugin validation failed: ${err}`));
108
135
  }
109
136
  }
110
137
 
@@ -141,8 +168,9 @@ export async function updatePlugins(name?: string) {
141
168
  execFileSync("git", ["pull", "--ff-only"], { cwd: targetDir, stdio: "inherit" });
142
169
  const pkgPath = join(targetDir, "package.json");
143
170
  if (existsSync(pkgPath)) {
144
- execFileSync("bun", ["install"], { cwd: targetDir, stdio: "inherit" });
171
+ execFileSync("bun", ["install", "--production"], { cwd: targetDir, stdio: "inherit" });
145
172
  }
173
+ linkHostPackages(targetDir);
146
174
  } catch {
147
175
  console.error(cliStyles.danger(`Failed to update ${dir}.`));
148
176
  }