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
@@ -9,6 +9,7 @@ import {
9
9
  isPaneInLayout,
10
10
  } from "../../plugins/pane-manager";
11
11
  import type { PluginRegistry } from "../../plugins/registry";
12
+ import type { LoadedExternalPlugin } from "../../plugins/loader";
12
13
  import {
13
14
  getFocusedCollectionId,
14
15
  resolveTickerForPane,
@@ -19,7 +20,6 @@ import {
19
20
  import { scheduleConfigSave } from "../../state/config-save-scheduler";
20
21
  import {
21
22
  createPaneInstance,
22
- findPaneInstance,
23
23
  isTickerPaneId,
24
24
  normalizePaneId,
25
25
  normalizePaneLayout,
@@ -36,6 +36,7 @@ import type {
36
36
  import type { DialogApi } from "../../ui/dialog";
37
37
  import {
38
38
  resolvePanelForPane,
39
+ resolvePaneShowTarget,
39
40
  resolvePaneTarget as resolvePaneTargetInLayout,
40
41
  selectEdgeAnchor,
41
42
  } from "./layout-placement";
@@ -50,8 +51,10 @@ interface AppPaneRuntimeArgs {
50
51
  detachedPaneId: string | null;
51
52
  dialog: DialogApi;
52
53
  dispatch: Dispatch<AppAction>;
54
+ externalPlugins: readonly LoadedExternalPlugin[];
53
55
  isDetachedWindow: boolean;
54
56
  notify: (body: string, options?: { type?: "info" | "success" | "error" }) => void;
57
+ persistConfig: (nextConfig: AppState["config"]) => void;
55
58
  pluginRegistry: PluginRegistry;
56
59
  state: AppState;
57
60
  stateRef: { current: AppState };
@@ -63,8 +66,10 @@ export function useAppPaneRuntime({
63
66
  detachedPaneId,
64
67
  dialog,
65
68
  dispatch,
69
+ externalPlugins,
66
70
  isDetachedWindow,
67
71
  notify,
72
+ persistConfig,
68
73
  pluginRegistry,
69
74
  state,
70
75
  stateRef,
@@ -204,26 +209,23 @@ export function useAppPaneRuntime({
204
209
  ]);
205
210
 
206
211
  const showPane = useCallback((paneId: string) => {
207
- const normalizedPaneId = normalizePaneId(paneId);
208
- const paneDef = pluginRegistry.panes.get(normalizedPaneId);
212
+ const target = resolvePaneShowTarget(state.config.layout, paneId);
213
+ const paneDef = pluginRegistry.panes.get(target.paneType);
209
214
  if (!paneDef) return;
210
215
 
211
- if (normalizedPaneId === TICKER_RESEARCH_PANE_ID) {
216
+ if (target.paneType === TICKER_RESEARCH_PANE_ID) {
212
217
  showTickerResearchPane();
213
218
  return;
214
219
  }
215
220
 
216
- const existingInstanceId = resolvePaneTarget(normalizedPaneId);
217
- if (existingInstanceId && isPaneInLayout(state.config.layout, existingInstanceId)) {
218
- pluginRegistry.focusPaneFn(existingInstanceId);
221
+ if (target.instance && isPaneInLayout(state.config.layout, target.instance.instanceId)) {
222
+ pluginRegistry.focusPaneFn(target.instance.instanceId);
219
223
  return;
220
224
  }
221
225
 
222
- const instance = existingInstanceId
223
- ? findPaneInstance(state.config.layout, existingInstanceId)
224
- : buildPaneInstance(normalizedPaneId);
226
+ const instance = target.instance ?? buildPaneInstance(target.paneType);
225
227
  if (!instance) {
226
- if (isTickerPaneId(paneId)) {
228
+ if (isTickerPaneId(target.paneType)) {
227
229
  notify("Open a ticker or collection context first.");
228
230
  }
229
231
  return;
@@ -234,7 +236,6 @@ export function useAppPaneRuntime({
234
236
  notify,
235
237
  placePaneInstance,
236
238
  pluginRegistry,
237
- resolvePaneTarget,
238
239
  showTickerResearchPane,
239
240
  state.config.layout,
240
241
  ]);
@@ -286,10 +287,12 @@ export function useAppPaneRuntime({
286
287
  dataProvider,
287
288
  detachedPaneId,
288
289
  dispatch,
290
+ externalPlugins,
289
291
  focusVisiblePane,
290
292
  isDetachedWindow,
291
293
  openPaneSettings,
292
294
  openPinnedTicker,
295
+ persistConfig,
293
296
  persistLayout,
294
297
  placePaneInstance,
295
298
  placePinnedTickerTarget,
@@ -0,0 +1,27 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import type { LayoutConfig } from "../../types/config";
3
+ import { resolvePaneShowTarget } from "./layout-placement";
4
+
5
+ describe("resolvePaneShowTarget", () => {
6
+ test("resolves a hidden pane by its exact instance id", () => {
7
+ const hiddenPane = {
8
+ instanceId: "news-top:stored",
9
+ paneId: "news-top",
10
+ binding: { kind: "none" as const },
11
+ placementMemory: {
12
+ floating: { x: 12, y: 4, width: 90, height: 30 },
13
+ },
14
+ };
15
+ const layout: LayoutConfig = {
16
+ dockRoot: null,
17
+ instances: [hiddenPane],
18
+ floating: [],
19
+ detached: [],
20
+ };
21
+
22
+ expect(resolvePaneShowTarget(layout, hiddenPane.instanceId)).toEqual({
23
+ paneType: "news-top",
24
+ instance: hiddenPane,
25
+ });
26
+ });
27
+ });
@@ -28,6 +28,19 @@ export function resolvePaneTarget(layout: LayoutConfig, paneId: string): string
28
28
  ?? null;
29
29
  }
30
30
 
31
+ /** Resolve an instance id before looking up its registered pane type. */
32
+ export function resolvePaneShowTarget(
33
+ layout: LayoutConfig,
34
+ paneId: string,
35
+ ): { paneType: string; instance: PaneInstanceConfig | null } {
36
+ const instanceId = resolvePaneTarget(layout, paneId);
37
+ const instance = instanceId ? findPaneInstance(layout, instanceId) ?? null : null;
38
+ return {
39
+ paneType: normalizePaneId(instance?.paneId ?? paneId),
40
+ instance,
41
+ };
42
+ }
43
+
31
44
  export function resolvePanelForPane({
32
45
  layout,
33
46
  paneId,
@@ -5,6 +5,9 @@ import {
5
5
  } from "../../components/command-bar/workflow/ops";
6
6
  import type { AppTickerRepositoryPort } from "../../core/app-service-ports";
7
7
  import { setLayoutManagerDispatch } from "../../plugins/builtin/layout-manager";
8
+ import { setMarketplaceHost } from "../../plugins/builtin/plugin-marketplace/store";
9
+ import type { InstalledPlugin } from "../../plugins/builtin/plugin-marketplace/model";
10
+ import type { LoadedExternalPlugin } from "../../plugins/loader";
8
11
  import { materializeMarketplaceLayout } from "../../layout-marketplace/payload";
9
12
  import {
10
13
  isPaneInLayout,
@@ -46,10 +49,12 @@ interface BindAppPanePluginRegistryOptions {
46
49
  dataProvider: DataProvider;
47
50
  detachedPaneId: string | null;
48
51
  dispatch: Dispatch<AppAction>;
52
+ externalPlugins: readonly LoadedExternalPlugin[];
49
53
  focusVisiblePane: (paneId: string, layout?: LayoutConfig) => void;
50
54
  isDetachedWindow: boolean;
51
55
  openPaneSettings: (paneId?: string) => Promise<void>;
52
56
  openPinnedTicker: (rawSymbol: string, options?: PinTickerOptions) => Promise<void>;
57
+ persistConfig: (nextConfig: AppState["config"]) => void;
53
58
  persistLayout: (layout: LayoutConfig, options?: { pushHistory?: boolean }) => void;
54
59
  placePaneInstance: (
55
60
  instance: PaneInstanceConfig,
@@ -76,10 +81,12 @@ export function bindAppPanePluginRegistry({
76
81
  dataProvider,
77
82
  detachedPaneId,
78
83
  dispatch,
84
+ externalPlugins,
79
85
  focusVisiblePane,
80
86
  isDetachedWindow,
81
87
  openPaneSettings,
82
88
  openPinnedTicker,
89
+ persistConfig,
83
90
  persistLayout,
84
91
  placePaneInstance,
85
92
  placePinnedTickerTarget,
@@ -249,4 +256,59 @@ export function bindAppPanePluginRegistry({
249
256
  termHeight: pluginRegistry.getTermSizeFn().height,
250
257
  focusedPaneId: state.focusedPaneId,
251
258
  }));
259
+
260
+ setMarketplaceHost({
261
+ listInstalled: () => {
262
+ const disabled = new Set(stateRef.current.config.disabledPlugins ?? []);
263
+ const externalById = new Map(externalPlugins.map((entry) => [entry.plugin.id, entry]));
264
+ const installed: InstalledPlugin[] = [];
265
+
266
+ for (const plugin of pluginRegistry.allPlugins.values()) {
267
+ const external = externalById.get(plugin.id);
268
+ externalById.delete(plugin.id);
269
+ installed.push({
270
+ id: plugin.id,
271
+ name: plugin.name,
272
+ version: plugin.version,
273
+ ...(plugin.description ? { description: plugin.description } : {}),
274
+ toggleable: plugin.toggleable === true,
275
+ enabled: !disabled.has(plugin.id),
276
+ source: external ? "external" : "builtin",
277
+ });
278
+ }
279
+
280
+ // Entries that never registered — a broken import, or a plugin this
281
+ // renderer cannot run. They are invisible everywhere else, which is
282
+ // exactly why the marketplace has to show them.
283
+ for (const entry of externalById.values()) {
284
+ installed.push({
285
+ id: entry.plugin.id,
286
+ name: entry.plugin.name,
287
+ version: entry.plugin.version ?? "0.0.0",
288
+ ...(entry.plugin.description ? { description: entry.plugin.description } : {}),
289
+ toggleable: true,
290
+ enabled: !disabled.has(entry.plugin.id),
291
+ source: "external",
292
+ ...(entry.unsupportedTarget ? { unsupportedTarget: entry.unsupportedTarget } : {}),
293
+ ...(entry.error ? { loadError: entry.error } : {}),
294
+ });
295
+ }
296
+
297
+ return installed;
298
+ },
299
+ setPluginEnabled: (pluginId, enabled) => {
300
+ const current = stateRef.current.config;
301
+ if (!enabled) {
302
+ for (const paneId of pluginRegistry.getPluginPaneIds(pluginId)) pluginRegistry.hidePane(paneId);
303
+ }
304
+ dispatch({ type: "TOGGLE_PLUGIN", pluginId });
305
+ const disabled = current.disabledPlugins ?? [];
306
+ persistConfig({
307
+ ...current,
308
+ disabledPlugins: enabled
309
+ ? disabled.filter((entry) => entry !== pluginId)
310
+ : [...disabled, pluginId],
311
+ });
312
+ },
313
+ });
252
314
  }
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", () => {