gloomberb 0.11.3 → 0.13.1

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 (574) hide show
  1. package/README.md +8 -4
  2. package/README.zh-CN.md +4 -2
  3. package/package.json +28 -2
  4. package/src/api-client/auth.ts +12 -1
  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 +139 -3
  8. package/src/api-client/normalizers.ts +39 -0
  9. package/src/api-client/paths.ts +85 -1
  10. package/src/api-client/research-activity.ts +61 -0
  11. package/src/api-client/types.ts +197 -0
  12. package/src/app/pane-runtime/index.tsx +15 -12
  13. package/src/app/pane-runtime/layout-placement.test.ts +27 -0
  14. package/src/app/pane-runtime/layout-placement.ts +13 -0
  15. package/src/app/pane-runtime/plugin-bindings.ts +62 -0
  16. package/src/app.tsx +60 -18
  17. package/src/architecture/browser-entry-graph.test.ts +96 -0
  18. package/src/brokers/profile-form.test.ts +25 -27
  19. package/src/brokers/test-broker.ts +68 -0
  20. package/src/cli/commands/plugins.ts +43 -14
  21. package/src/cli/desktop-pane-shot.ts +362 -24
  22. package/src/cli/index.ts +1 -1
  23. package/src/cli/pane-functions/capabilities.ts +126 -43
  24. package/src/cli/pane-functions/catalog.ts +16 -4
  25. package/src/cli/pane-functions/discovery.ts +1 -0
  26. package/src/cli/pane-functions/dom.test.ts +41 -0
  27. package/src/cli/pane-functions/dom.ts +199 -0
  28. package/src/cli/pane-functions/headless.test.ts +247 -0
  29. package/src/cli/pane-functions/headless.ts +423 -0
  30. package/src/cli/pane-functions/index.test.ts +20 -0
  31. package/src/cli/pane-functions/index.ts +10 -6
  32. package/src/cli/pane-functions/intraday-shot.test.ts +307 -0
  33. package/src/cli/pane-functions/intraday-shot.ts +334 -0
  34. package/src/cli/pane-functions/options.ts +27 -2
  35. package/src/cli/pane-functions/report.test.ts +29 -1
  36. package/src/cli/pane-functions/report.ts +69 -4
  37. package/src/cli/pane-functions/resolver.ts +26 -8
  38. package/src/cli/pane-functions/screenshot.test.ts +76 -0
  39. package/src/cli/pane-functions/screenshot.ts +428 -42
  40. package/src/cli/restore-plugins.ts +41 -0
  41. package/src/components/chart/{static/chart → composite}/axis-overlays.test.tsx +3 -3
  42. package/src/components/chart/{static/chart → composite}/axis-overlays.tsx +6 -11
  43. package/src/components/chart/{static/chart → composite}/bitmap.ts +17 -3
  44. package/src/components/chart/composite/composite-chart.test.tsx +136 -21
  45. package/src/components/chart/composite/composite-chart.tsx +378 -215
  46. package/src/components/chart/composite/format.ts +8 -2
  47. package/src/components/chart/composite/interactions.test.ts +68 -102
  48. package/src/components/chart/composite/interactions.ts +214 -264
  49. package/src/components/chart/composite/price-axis-labels.test.ts +21 -0
  50. package/src/components/chart/{price-axis-labels.tsx → composite/price-axis-labels.tsx} +9 -4
  51. package/src/components/chart/composite/rasterizer.ts +33 -0
  52. package/src/components/chart/composite/renderers.test.ts +51 -4
  53. package/src/components/chart/composite/scene.test.ts +128 -2
  54. package/src/components/chart/composite/scene.ts +149 -19
  55. package/src/components/chart/composite/text-renderer.ts +16 -2
  56. package/src/components/chart/composite/time-scale.ts +163 -62
  57. package/src/components/chart/composite/types.ts +51 -1
  58. package/src/components/chart/core/data.ts +9 -13
  59. package/src/components/chart/core/date-window.ts +7 -1
  60. package/src/components/chart/core/pointer.ts +3 -0
  61. package/src/components/chart/core/types.ts +0 -45
  62. package/src/components/chart/native/chart-rasterizer.ts +0 -1
  63. package/src/components/chart/native/kitty/index.test.ts +0 -188
  64. package/src/components/chart/native/raster/crosshair.ts +6 -5
  65. package/src/components/chart/native/raster/primitives.ts +0 -23
  66. package/src/components/chart/native/renderer-selection.ts +81 -31
  67. package/src/components/chart/static/{chart/surface.test.tsx → chart-surface.test.tsx} +28 -80
  68. package/src/components/chart/static/chart-surface.tsx +150 -0
  69. package/src/components/chart/static/index.ts +8 -0
  70. package/src/components/chart/static/multi-line-chart-surface.tsx +51 -166
  71. package/src/components/chart/static/scatter-chart-surface.test.ts +30 -0
  72. package/src/components/chart/static/scatter-chart-surface.tsx +135 -34
  73. package/src/components/chart/static/series.ts +49 -0
  74. package/src/components/command-bar/action-runtime.ts +1 -3
  75. package/src/components/command-bar/assist/model.test.ts +17 -2
  76. package/src/components/command-bar/assist/model.ts +40 -9
  77. package/src/components/command-bar/commands/direct/index.ts +1 -1
  78. package/src/components/command-bar/commands/direct/runtime.ts +1 -1
  79. package/src/components/command-bar/commands/registry.ts +0 -11
  80. package/src/components/command-bar/helpers.test.ts +2 -2
  81. package/src/components/command-bar/helpers.ts +2 -6
  82. package/src/components/command-bar/keyboard-shortcuts.ts +0 -11
  83. package/src/components/command-bar/list/badge.test.ts +54 -0
  84. package/src/components/command-bar/list/badge.ts +62 -0
  85. package/src/components/command-bar/list/model.test.ts +103 -0
  86. package/src/components/command-bar/list/model.ts +61 -4
  87. package/src/components/command-bar/list/view.test.tsx +113 -0
  88. package/src/components/command-bar/list/view.tsx +147 -131
  89. package/src/components/command-bar/pane-templates/workflow.ts +1 -1
  90. package/src/components/command-bar/panel/body.tsx +51 -65
  91. package/src/components/command-bar/panel/index.tsx +48 -13
  92. package/src/components/command-bar/panel/layout.test.ts +139 -0
  93. package/src/components/command-bar/panel/layout.ts +65 -28
  94. package/src/components/command-bar/panel/native-surface.ts +24 -0
  95. package/src/components/command-bar/panel/prompt-binding.ts +54 -0
  96. package/src/components/command-bar/panel/runtime.ts +6 -3
  97. package/src/components/command-bar/panel/state.ts +41 -7
  98. package/src/components/command-bar/panel/types.ts +7 -1
  99. package/src/components/command-bar/routes/root/provider-search.ts +34 -15
  100. package/src/components/command-bar/routes/root/results.test.ts +161 -0
  101. package/src/components/command-bar/routes/root/results.ts +30 -28
  102. package/src/components/command-bar/routes/root/runtime.ts +37 -20
  103. package/src/components/command-bar/routes/root/search-providers.test.tsx +224 -0
  104. package/src/components/command-bar/routes/root/search-providers.ts +205 -0
  105. package/src/components/command-bar/routes/root/selection.ts +1 -12
  106. package/src/components/command-bar/routes/root/shortcuts.test.ts +8 -8
  107. package/src/components/command-bar/routes/ticker-search/actions.ts +10 -9
  108. package/src/components/command-bar/routes/ticker-search/results.test.ts +54 -1
  109. package/src/components/command-bar/routes/ticker-search/results.ts +60 -19
  110. package/src/components/command-bar/routes/ticker-search/route.ts +1 -0
  111. package/src/components/command-bar/routing/actions.ts +1 -81
  112. package/src/components/command-bar/routing/launch-request.ts +1 -1
  113. package/src/components/command-bar/routing/list-state.ts +6 -21
  114. package/src/components/command-bar/selection-runtime.ts +1 -1
  115. package/src/components/command-bar/surface/assist.test.tsx +34 -19
  116. package/src/components/command-bar/surface/environment.ts +8 -1
  117. package/src/components/command-bar/surface/index.test.tsx +84 -68
  118. package/src/components/command-bar/surface/index.tsx +33 -36
  119. package/src/components/command-bar/surface/test-harness.tsx +38 -7
  120. package/src/components/command-bar/theme-picker.tsx +43 -15
  121. package/src/components/command-bar/view-model.test.ts +53 -8
  122. package/src/components/command-bar/view-model.ts +43 -14
  123. package/src/components/command-bar/workflow/tickers.ts +1 -0
  124. package/src/components/command-bar/workflow/types.ts +1 -1
  125. package/src/components/data-table/view.test.tsx +45 -6
  126. package/src/components/data-table/view.tsx +55 -9
  127. package/src/components/index.ts +10 -2
  128. package/src/components/input-search-bar.test.tsx +36 -2
  129. package/src/components/input-search-bar.tsx +18 -1
  130. package/src/components/layout/header.test.tsx +57 -10
  131. package/src/components/layout/header.tsx +292 -149
  132. package/src/components/layout/market-summary.test.ts +81 -0
  133. package/src/components/layout/market-summary.ts +109 -0
  134. package/src/components/layout/shell/chrome.ts +96 -1
  135. package/src/components/layout/shell/index.tsx +11 -3
  136. package/src/components/layout/shell/menu.ts +9 -0
  137. package/src/components/layout/shell/pane/actions.ts +29 -0
  138. package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
  139. package/src/components/layout/shell/shortcuts.ts +3 -0
  140. package/src/components/layout/status-bar.test.tsx +9 -8
  141. package/src/components/layout/status-bar.tsx +85 -54
  142. package/src/components/onboarding/account-step/chooser-panel.tsx +2 -2
  143. package/src/components/onboarding/account-step/qr-panel.tsx +2 -10
  144. package/src/components/onboarding/onboarding-frame.tsx +2 -26
  145. package/src/components/onboarding/onboarding-wizard.test.tsx +38 -12
  146. package/src/components/onboarding/onboarding-wizard.tsx +49 -14
  147. package/src/components/price-sparkline/index.ts +61 -34
  148. package/src/components/price-sparkline/view.tsx +1 -1
  149. package/src/components/sign-in-gate.test.tsx +128 -0
  150. package/src/components/sign-in-gate.tsx +149 -0
  151. package/src/components/speedometer-gauge/desktop.tsx +2 -12
  152. package/src/components/ticker/badge/list.tsx +17 -3
  153. package/src/components/ticker/list-table-view.tsx +6 -3
  154. package/src/components/ui/data-table/index.tsx +3 -2
  155. package/src/components/ui/data-table/opentui/index.tsx +165 -120
  156. package/src/components/ui/frame.tsx +28 -0
  157. package/src/components/ui/index.ts +3 -0
  158. package/src/components/ui/native-select.tsx +20 -10
  159. package/src/components/ui/select-button.tsx +138 -0
  160. package/src/components/ui/status.tsx +2 -2
  161. package/src/components/ui/table-layout.ts +1 -1
  162. package/src/components/use-double-click-activation.ts +7 -3
  163. package/src/core/app-services.ts +3 -3
  164. package/src/data/config/layout.ts +12 -6
  165. package/src/data/config/store/index.test.ts +120 -0
  166. package/src/data/config/store/migrations.ts +41 -0
  167. package/src/data/config/store/node.ts +7 -1
  168. package/src/data/config/store/normalize.ts +9 -3
  169. package/src/i18n/es.ts +6 -2
  170. package/src/i18n/ja.ts +6 -2
  171. package/src/i18n/ko.ts +6 -2
  172. package/src/i18n/zh-cn.ts +6 -2
  173. package/src/i18n/zh-tw.ts +6 -2
  174. package/src/layout-marketplace/pane-imagery.ts +1 -0
  175. package/src/layout-marketplace/payload.test.ts +17 -0
  176. package/src/layout-marketplace/payload.ts +40 -5
  177. package/src/market-data/coordinator/index.ts +3 -2
  178. package/src/market-data/coordinator/quotes.ts +173 -66
  179. package/src/market-data/coordinator/subscriptions.test.ts +215 -37
  180. package/src/pane-settings.ts +1 -1
  181. package/src/plugins/builtin/analytics/pane-model.ts +1 -1
  182. package/src/plugins/builtin/analytics/view.tsx +1 -1
  183. package/src/plugins/builtin/broker-manager/index.test.tsx +3 -2
  184. package/src/plugins/builtin/broker-manager/model.test.ts +2 -2
  185. package/src/plugins/builtin/cds/client.test.ts +20 -0
  186. package/src/plugins/builtin/cds/client.ts +13 -3
  187. package/src/plugins/builtin/cds/headless.test.ts +30 -0
  188. package/src/plugins/builtin/cds/headless.ts +113 -0
  189. package/src/plugins/builtin/cds/index.tsx +4 -0
  190. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +16 -2
  191. package/src/plugins/builtin/chart-composer/cli-options.ts +4 -1
  192. package/src/plugins/builtin/chart-composer/index.tsx +1 -1
  193. package/src/plugins/builtin/chart-composer/pane.tsx +22 -8
  194. package/src/plugins/builtin/chat/content/composer.tsx +8 -4
  195. package/src/plugins/builtin/cloud/askg/protocol.test.ts +189 -0
  196. package/src/plugins/builtin/cloud/askg/protocol.ts +274 -0
  197. package/src/plugins/builtin/cloud/auth-dialog.tsx +19 -227
  198. package/src/plugins/builtin/cloud/auth-form.tsx +277 -0
  199. package/src/plugins/builtin/cloud/device-signin-dialog.tsx +21 -8
  200. package/src/plugins/builtin/cloud/plugin.tsx +1 -0
  201. package/src/plugins/builtin/cloud/verification-panel.tsx +56 -0
  202. package/src/plugins/builtin/composite-plugins.ts +14 -1
  203. package/src/plugins/builtin/correlation/relationship/model.ts +1 -1
  204. package/src/plugins/builtin/correlation/relationship/pane.tsx +2 -6
  205. package/src/plugins/builtin/correlation/relationship/view-model.ts +2 -2
  206. package/src/plugins/builtin/credit-conditions/client.ts +1 -1
  207. package/src/plugins/builtin/credit-conditions/headless.test.ts +31 -0
  208. package/src/plugins/builtin/credit-conditions/headless.ts +55 -0
  209. package/src/plugins/builtin/credit-conditions/index.test.tsx +12 -6
  210. package/src/plugins/builtin/credit-conditions/index.tsx +6 -21
  211. package/src/plugins/builtin/dividend-yield/index.tsx +1 -0
  212. package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
  213. package/src/plugins/builtin/earnings-calls/data.ts +223 -0
  214. package/src/plugins/builtin/earnings-calls/format.ts +49 -0
  215. package/src/plugins/builtin/earnings-calls/index.tsx +69 -0
  216. package/src/plugins/builtin/earnings-calls/pane.tsx +760 -0
  217. package/src/plugins/builtin/earnings-calls/prose.test.ts +36 -0
  218. package/src/plugins/builtin/earnings-calls/prose.ts +59 -0
  219. package/src/plugins/builtin/earnings-calls/transcript-view.tsx +337 -0
  220. package/src/plugins/builtin/econ/detail-view.tsx +1 -2
  221. package/src/plugins/builtin/econ/fred-series-map.ts +16 -1
  222. package/src/plugins/builtin/econ/index.tsx +2 -1
  223. package/src/plugins/builtin/econ-statistics/cache.ts +4 -0
  224. package/src/plugins/builtin/econ-statistics/client.ts +92 -0
  225. package/src/plugins/builtin/econ-statistics/defs.ts +66 -0
  226. package/src/plugins/builtin/econ-statistics/detail.tsx +154 -0
  227. package/src/plugins/builtin/econ-statistics/headless.test.ts +54 -0
  228. package/src/plugins/builtin/econ-statistics/headless.ts +171 -0
  229. package/src/plugins/builtin/econ-statistics/index.tsx +54 -0
  230. package/src/plugins/builtin/econ-statistics/pane.tsx +356 -0
  231. package/src/plugins/builtin/econ-statistics/stats.ts +522 -0
  232. package/src/plugins/builtin/econ-statistics/transform.test.ts +86 -0
  233. package/src/plugins/builtin/econ-statistics/transform.ts +85 -0
  234. package/src/plugins/builtin/econ-statistics/trend.ts +57 -0
  235. package/src/plugins/builtin/econ-statistics/view.ts +90 -0
  236. package/src/plugins/builtin/fear-greed/charts.tsx +7 -18
  237. package/src/plugins/builtin/fear-greed/data.ts +6 -1
  238. package/src/plugins/builtin/fear-greed/headless.test.ts +53 -0
  239. package/src/plugins/builtin/fear-greed/headless.ts +84 -0
  240. package/src/plugins/builtin/fear-greed/index.tsx +4 -0
  241. package/src/plugins/builtin/help/index.tsx +4 -0
  242. package/src/plugins/builtin/holders/index.tsx +1 -0
  243. package/src/plugins/builtin/insider/index.tsx +1 -0
  244. package/src/plugins/builtin/kelly-sizer/pane.tsx +1 -1
  245. package/src/plugins/builtin/kelly-sizer/sections.tsx +2 -3
  246. package/src/plugins/builtin/market-movers/client.ts +76 -0
  247. package/src/plugins/builtin/market-movers/headless.test.ts +34 -0
  248. package/src/plugins/builtin/market-movers/headless.ts +73 -0
  249. package/src/plugins/builtin/market-movers/index.tsx +11 -51
  250. package/src/plugins/builtin/market-valuation/align.ts +122 -0
  251. package/src/plugins/builtin/market-valuation/cache.ts +32 -0
  252. package/src/plugins/builtin/market-valuation/chart-projection.ts +217 -0
  253. package/src/plugins/builtin/market-valuation/chart-series.ts +89 -0
  254. package/src/plugins/builtin/market-valuation/client.test.ts +141 -0
  255. package/src/plugins/builtin/market-valuation/client.ts +116 -0
  256. package/src/plugins/builtin/market-valuation/defs.ts +251 -0
  257. package/src/plugins/builtin/market-valuation/detail.tsx +159 -0
  258. package/src/plugins/builtin/market-valuation/headless.test.ts +58 -0
  259. package/src/plugins/builtin/market-valuation/headless.ts +170 -0
  260. package/src/plugins/builtin/market-valuation/index.tsx +64 -0
  261. package/src/plugins/builtin/market-valuation/indicators.ts +422 -0
  262. package/src/plugins/builtin/market-valuation/model.test.ts +350 -0
  263. package/src/plugins/builtin/market-valuation/model.ts +8 -0
  264. package/src/plugins/builtin/market-valuation/pane.test.tsx +247 -0
  265. package/src/plugins/builtin/market-valuation/pane.tsx +344 -0
  266. package/src/plugins/builtin/market-valuation/series.ts +12 -0
  267. package/src/plugins/builtin/market-valuation/settings.ts +38 -0
  268. package/src/plugins/builtin/market-valuation/sources.ts +130 -0
  269. package/src/plugins/builtin/market-valuation/trend.ts +89 -0
  270. package/src/plugins/builtin/market-valuation/view.ts +137 -0
  271. package/src/plugins/builtin/market-valuation/zone-scale/desktop.tsx +123 -0
  272. package/src/plugins/builtin/market-valuation/zone-scale/index.tsx +12 -0
  273. package/src/plugins/builtin/market-valuation/zone-scale/model.ts +60 -0
  274. package/src/plugins/builtin/market-valuation/zone-scale/terminal.tsx +94 -0
  275. package/src/plugins/builtin/news/wire/breaking/filters.test.ts +99 -0
  276. package/src/plugins/builtin/news/wire/breaking/filters.ts +110 -0
  277. package/src/plugins/builtin/news/wire/breaking/notifications.test.ts +157 -53
  278. package/src/plugins/builtin/news/wire/breaking/notifications.ts +91 -25
  279. package/src/plugins/builtin/news/wire/index.tsx +31 -44
  280. package/src/plugins/builtin/options/index.tsx +1 -0
  281. package/src/plugins/builtin/options/view.test.tsx +29 -1
  282. package/src/plugins/builtin/options/view.tsx +1 -22
  283. package/src/plugins/builtin/plugin-marketplace/feed.ts +80 -0
  284. package/src/plugins/builtin/plugin-marketplace/index.tsx +31 -0
  285. package/src/plugins/builtin/plugin-marketplace/model.test.ts +200 -0
  286. package/src/plugins/builtin/plugin-marketplace/model.ts +204 -0
  287. package/src/plugins/builtin/plugin-marketplace/pane.tsx +385 -0
  288. package/src/plugins/builtin/plugin-marketplace/store.ts +46 -0
  289. package/src/plugins/builtin/plugin-module.ts +9 -1
  290. package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -237
  291. package/src/plugins/builtin/research/event-model.ts +245 -0
  292. package/src/plugins/builtin/research/events-headless.test.ts +32 -0
  293. package/src/plugins/builtin/research/events-headless.ts +87 -0
  294. package/src/plugins/builtin/research/index.tsx +8 -0
  295. package/src/plugins/builtin/research-search/command-bar-search.test.ts +81 -0
  296. package/src/plugins/builtin/research-search/command-bar-search.ts +175 -0
  297. package/src/plugins/builtin/research-search/data.ts +109 -0
  298. package/src/plugins/builtin/research-search/document-view.test.tsx +75 -0
  299. package/src/plugins/builtin/research-search/document-view.tsx +254 -0
  300. package/src/plugins/builtin/research-search/filter-bar.tsx +62 -0
  301. package/src/plugins/builtin/research-search/focus-handoff.ts +47 -0
  302. package/src/plugins/builtin/research-search/index.tsx +85 -0
  303. package/src/plugins/builtin/research-search/model.test.ts +40 -0
  304. package/src/plugins/builtin/research-search/model.ts +291 -0
  305. package/src/plugins/builtin/research-search/pane.test.tsx +241 -0
  306. package/src/plugins/builtin/research-search/pane.tsx +682 -0
  307. package/src/plugins/builtin/research-search/saved-view.tsx +171 -0
  308. package/src/plugins/builtin/research-search/snippet-text.tsx +35 -0
  309. package/src/plugins/builtin/research-search/snippet.test.ts +78 -0
  310. package/src/plugins/builtin/research-search/snippet.ts +134 -0
  311. package/src/plugins/builtin/scanner/flow-model.test.ts +11 -1
  312. package/src/plugins/builtin/scanner/flow-model.ts +29 -1
  313. package/src/plugins/builtin/scanner/flow-pane.tsx +78 -22
  314. package/src/plugins/builtin/scanner/headless.test.ts +59 -0
  315. package/src/plugins/builtin/scanner/headless.ts +125 -0
  316. package/src/plugins/builtin/scanner/index.ts +4 -0
  317. package/src/plugins/builtin/sec/index.tsx +1 -0
  318. package/src/plugins/builtin/sectors/client.ts +54 -0
  319. package/src/plugins/builtin/sectors/headless.test.ts +28 -0
  320. package/src/plugins/builtin/sectors/headless.ts +80 -0
  321. package/src/plugins/builtin/sectors/index.tsx +6 -51
  322. package/src/plugins/builtin/shared/cloud-upgrade.ts +8 -4
  323. package/src/plugins/builtin/shared/series-cache.ts +93 -0
  324. package/src/plugins/builtin/short-interest/index.tsx +1 -0
  325. package/src/plugins/builtin/short-interest/pane.tsx +1 -1
  326. package/src/plugins/builtin/thirteenf/index.tsx +1 -0
  327. package/src/plugins/builtin/ticker-detail/index.tsx +0 -14
  328. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +1 -1
  329. package/src/plugins/builtin/ticker-detail/pane.tsx +14 -0
  330. package/src/plugins/builtin/treasury-auctions/headless.test.ts +34 -0
  331. package/src/plugins/builtin/treasury-auctions/headless.ts +95 -0
  332. package/src/plugins/builtin/treasury-auctions/index.tsx +4 -0
  333. package/src/plugins/builtin/volatility/client.ts +16 -3
  334. package/src/plugins/builtin/volatility/headless.test.ts +35 -0
  335. package/src/plugins/builtin/volatility/headless.ts +75 -0
  336. package/src/plugins/builtin/volatility/index.tsx +5 -1
  337. package/src/plugins/builtin/world-indices/client.ts +43 -0
  338. package/src/plugins/builtin/world-indices/headless.test.ts +32 -0
  339. package/src/plugins/builtin/world-indices/headless.ts +109 -0
  340. package/src/plugins/builtin/world-indices/index.tsx +4 -0
  341. package/src/plugins/builtin/world-venue-map/map.desktop.test.tsx +106 -0
  342. package/src/plugins/builtin/world-venue-map/map.tsx +213 -69
  343. package/src/plugins/builtin/world-venue-map/model.test.ts +32 -0
  344. package/src/plugins/builtin/world-venue-map/model.ts +134 -13
  345. package/src/plugins/builtin/yield-curve/headless.test.ts +24 -0
  346. package/src/plugins/builtin/yield-curve/headless.ts +67 -0
  347. package/src/plugins/builtin/yield-curve/index.tsx +7 -9
  348. package/src/plugins/builtin/yield-curve/treasury-data.ts +16 -6
  349. package/src/plugins/bundle.test.ts +166 -0
  350. package/src/plugins/bundle.ts +141 -0
  351. package/src/plugins/catalog-backend.test.ts +26 -0
  352. package/src/plugins/catalog-backend.ts +9 -2
  353. package/src/plugins/catalog-browser.test.ts +1 -13
  354. package/src/plugins/catalog-browser.ts +14 -0
  355. package/src/plugins/catalog-manifest.test.ts +58 -0
  356. package/src/plugins/catalog-manifest.ts +99 -0
  357. package/src/plugins/catalog-ui.ts +20 -10
  358. package/src/plugins/catalog.ts +9 -2
  359. package/src/plugins/current-target.ts +28 -0
  360. package/src/plugins/host-contract.ts +46 -0
  361. package/src/plugins/host-link.ts +158 -0
  362. package/src/plugins/host-modules.ts +73 -0
  363. package/src/plugins/loader.ts +90 -23
  364. package/src/plugins/ownership.ts +1 -0
  365. package/src/plugins/prediction-markets/index.tsx +1 -0
  366. package/src/plugins/registry/context.ts +3 -0
  367. package/src/plugins/registry/contributions.ts +27 -0
  368. package/src/plugins/registry/index.test.ts +25 -0
  369. package/src/plugins/registry/index.ts +8 -0
  370. package/src/plugins/registry/pane-settings.ts +6 -13
  371. package/src/plugins/seed.test.ts +83 -0
  372. package/src/plugins/seed.ts +83 -0
  373. package/src/public/broker.ts +37 -0
  374. package/src/public/market-data.ts +9 -0
  375. package/src/public/pane-hooks.ts +57 -0
  376. package/src/public/public-api.test.ts +285 -0
  377. package/src/public/react.ts +62 -0
  378. package/src/public/test-support.ts +23 -0
  379. package/src/public/time-series.ts +8 -0
  380. package/src/public/utils.ts +77 -0
  381. package/src/remote/layout-helpers.ts +6 -8
  382. package/src/remote/resources.ts +2 -0
  383. package/src/remote/schema.test.ts +65 -0
  384. package/src/remote/schema.ts +323 -39
  385. package/src/remote/semantic-tree.test.tsx +105 -0
  386. package/src/remote/semantic-tree.tsx +53 -35
  387. package/src/remote/types.ts +49 -2
  388. package/src/renderers/browser/app-services.ts +3 -0
  389. package/src/renderers/browser/config-host.ts +24 -8
  390. package/src/renderers/browser/deeplink-bridge.test.ts +11 -0
  391. package/src/renderers/browser/deeplink-bridge.ts +15 -3
  392. package/src/renderers/browser/main.tsx +8 -0
  393. package/src/renderers/browser/research-entry.test.ts +8 -0
  394. package/src/renderers/browser/research-entry.ts +7 -0
  395. package/src/renderers/cloudflare/worker.test.ts +19 -0
  396. package/src/renderers/cloudflare/worker.ts +1 -1
  397. package/src/renderers/electrobun/bun/desktop/backend-requests.ts +2 -2
  398. package/src/renderers/electrobun/bun/desktop/initialization.ts +10 -3
  399. package/src/renderers/electrobun/bun/external-plugins.ts +156 -0
  400. package/src/renderers/electrobun/bun/index.ts +5 -2
  401. package/src/renderers/electrobun/shared/protocol.ts +21 -0
  402. package/src/renderers/electrobun/view/build-assets.ts +0 -3
  403. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +114 -11
  404. package/src/renderers/electrobun/view/external-plugins.test.ts +45 -0
  405. package/src/renderers/electrobun/view/external-plugins.ts +63 -0
  406. package/src/renderers/electrobun/view/host/ascii-text.test.ts +48 -0
  407. package/src/renderers/electrobun/view/host/ascii-text.ts +11 -1
  408. package/src/renderers/electrobun/view/host/chart-surface.tsx +38 -32
  409. package/src/renderers/electrobun/view/host/mouse.ts +16 -2
  410. package/src/renderers/electrobun/view/main.tsx +27 -2
  411. package/src/renderers/electrobun/view/styles.css +5 -0
  412. package/src/renderers/electrobun/view/toast-host.tsx +18 -0
  413. package/src/renderers/opentui/chart-surface.tsx +5 -1
  414. package/src/renderers/opentui/host.tsx +16 -2
  415. package/src/renderers/opentui/interaction-performance.test.ts +79 -0
  416. package/src/renderers/opentui/interaction-performance.ts +188 -0
  417. package/src/renderers/opentui/start.tsx +26 -1
  418. package/src/renderers/opentui/test-utils.tsx +13 -0
  419. package/src/renderers/opentui/toast-host.tsx +26 -2
  420. package/src/renderers/opentui/ui-host.tsx +1 -0
  421. package/src/sources/provider-router/search.test.ts +191 -0
  422. package/src/sources/provider-router/search.ts +92 -39
  423. package/src/state/app/context/index.tsx +2 -0
  424. package/src/state/app/context/selector.test.tsx +33 -1
  425. package/src/state/hooks/quote-streaming.test.tsx +93 -0
  426. package/src/state/hooks/quote-streaming.ts +106 -30
  427. package/src/state/pane-table-export-registry.ts +19 -0
  428. package/src/sync/core-contributors.test.ts +111 -0
  429. package/src/sync/core-contributors.ts +155 -1
  430. package/src/theme/color-utils.ts +1 -1
  431. package/src/theme/colors.ts +50 -0
  432. package/src/theme/themes.test.ts +15 -2
  433. package/src/theme/themes.ts +19 -1
  434. package/src/tickers/search/index.test.ts +26 -0
  435. package/src/tickers/search/index.ts +42 -17
  436. package/src/tickers/search/ranking.ts +14 -0
  437. package/src/time-series/chart-data.test.ts +216 -0
  438. package/src/time-series/resolution.test.ts +25 -26
  439. package/src/time-series/resolution.ts +41 -10
  440. package/src/time-series/resolve.test.ts +118 -1
  441. package/src/time-series/resolve.ts +21 -16
  442. package/src/time-series/types.ts +3 -1
  443. package/src/time-series/use-chart-resolution.ts +5 -0
  444. package/src/types/config.ts +125 -3
  445. package/src/types/data-provider.ts +11 -0
  446. package/src/types/headless.ts +161 -0
  447. package/src/types/plugin.ts +102 -0
  448. package/src/ui/host.tsx +12 -1
  449. package/src/ui/index.tsx +1 -0
  450. package/src/ui/toast.tsx +8 -4
  451. package/src/utils/format.ts +35 -0
  452. package/src/utils/fuzzy-search.test.ts +94 -0
  453. package/src/utils/fuzzy-search.ts +81 -22
  454. package/src/utils/shortcut-labels.ts +14 -0
  455. package/src/components/chart/core/axis-format.ts +0 -157
  456. package/src/components/chart/core/index.test.ts +0 -1075
  457. package/src/components/chart/core/projection.ts +0 -357
  458. package/src/components/chart/core/render-utils.ts +0 -4
  459. package/src/components/chart/core/renderer.ts +0 -244
  460. package/src/components/chart/core/scene.ts +0 -180
  461. package/src/components/chart/core/time-axis.ts +0 -330
  462. package/src/components/chart/cursor-motion.ts +0 -47
  463. package/src/components/chart/indicators/types.ts +0 -26
  464. package/src/components/chart/market-session.ts +0 -176
  465. package/src/components/chart/multi-line/renderer.test.ts +0 -83
  466. package/src/components/chart/multi-line/renderer.ts +0 -161
  467. package/src/components/chart/multi-line/scene.ts +0 -188
  468. package/src/components/chart/native/raster/layout.ts +0 -129
  469. package/src/components/chart/native/raster/price-chart.ts +0 -244
  470. package/src/components/chart/rendering/chart-draw.ts +0 -270
  471. package/src/components/chart/rendering/geometry.ts +0 -95
  472. package/src/components/chart/rendering/pixel-buffer.ts +0 -267
  473. package/src/components/chart/scatter-chart-renderer.test.ts +0 -31
  474. package/src/components/chart/scatter-chart-renderer.ts +0 -223
  475. package/src/components/chart/static/chart/surface.tsx +0 -358
  476. package/src/components/command-bar/routes/root/article-results.test.ts +0 -83
  477. package/src/components/command-bar/routes/root/article-results.ts +0 -55
  478. package/src/plugins/broker-sync/broker-sync.test.ts +0 -188
  479. package/src/plugins/broker-sync/native-loader.ts +0 -10
  480. package/src/plugins/broker-sync/normalize.ts +0 -308
  481. package/src/plugins/broker-sync/public.ts +0 -122
  482. package/src/plugins/broker-sync/robinhood-native.ts +0 -367
  483. package/src/plugins/broker-sync/robinhood.ts +0 -114
  484. package/src/plugins/broker-sync/simplefin-native.ts +0 -201
  485. package/src/plugins/broker-sync/simplefin.ts +0 -70
  486. package/src/plugins/builtin/news/wire/article-search.test.ts +0 -72
  487. package/src/plugins/builtin/news/wire/article-search.ts +0 -140
  488. package/src/plugins/builtin/substack/api/auth.ts +0 -185
  489. package/src/plugins/builtin/substack/api/cache.ts +0 -82
  490. package/src/plugins/builtin/substack/api/loaders.ts +0 -170
  491. package/src/plugins/builtin/substack/api/store.ts +0 -186
  492. package/src/plugins/builtin/substack/api/types.ts +0 -42
  493. package/src/plugins/builtin/substack/api.test.ts +0 -298
  494. package/src/plugins/builtin/substack/article-detail.tsx +0 -397
  495. package/src/plugins/builtin/substack/article-stack.tsx +0 -133
  496. package/src/plugins/builtin/substack/content.ts +0 -478
  497. package/src/plugins/builtin/substack/feed-tabs.tsx +0 -44
  498. package/src/plugins/builtin/substack/index.tsx +0 -49
  499. package/src/plugins/builtin/substack/login-view.tsx +0 -126
  500. package/src/plugins/builtin/substack/model.test.ts +0 -155
  501. package/src/plugins/builtin/substack/normalize.ts +0 -343
  502. package/src/plugins/builtin/substack/pane-footer.ts +0 -52
  503. package/src/plugins/builtin/substack/pane-state.ts +0 -170
  504. package/src/plugins/builtin/substack/pane.tsx +0 -547
  505. package/src/plugins/builtin/substack/read-state.ts +0 -30
  506. package/src/plugins/builtin/substack/table.ts +0 -111
  507. package/src/plugins/builtin/substack/types.ts +0 -71
  508. package/src/plugins/builtin/substack/utils.ts +0 -131
  509. package/src/plugins/builtin/ticker-detail/data-panes/provider-search.tsx +0 -229
  510. package/src/plugins/ibkr/account-cache.test.ts +0 -102
  511. package/src/plugins/ibkr/account-cache.ts +0 -24
  512. package/src/plugins/ibkr/broker-adapter.ts +0 -286
  513. package/src/plugins/ibkr/config.test.ts +0 -79
  514. package/src/plugins/ibkr/config.ts +0 -250
  515. package/src/plugins/ibkr/dialogs.tsx +0 -40
  516. package/src/plugins/ibkr/flex/index.test.ts +0 -264
  517. package/src/plugins/ibkr/flex/index.ts +0 -367
  518. package/src/plugins/ibkr/flex/statement-client.ts +0 -235
  519. package/src/plugins/ibkr/fundamental-parser.ts +0 -149
  520. package/src/plugins/ibkr/gateway/account-loaders.ts +0 -266
  521. package/src/plugins/ibkr/gateway/account-summary.ts +0 -164
  522. package/src/plugins/ibkr/gateway/client-lock.ts +0 -154
  523. package/src/plugins/ibkr/gateway/connection-events.ts +0 -114
  524. package/src/plugins/ibkr/gateway/connection.ts +0 -152
  525. package/src/plugins/ibkr/gateway/contracts.ts +0 -132
  526. package/src/plugins/ibkr/gateway/fundamentals.ts +0 -83
  527. package/src/plugins/ibkr/gateway/helpers.ts +0 -33
  528. package/src/plugins/ibkr/gateway/history-requests.ts +0 -158
  529. package/src/plugins/ibkr/gateway/history.ts +0 -120
  530. package/src/plugins/ibkr/gateway/instrument-lookup.ts +0 -117
  531. package/src/plugins/ibkr/gateway/market-data.ts +0 -345
  532. package/src/plugins/ibkr/gateway/order-execution.ts +0 -218
  533. package/src/plugins/ibkr/gateway/orders.ts +0 -63
  534. package/src/plugins/ibkr/gateway/price-normalization.test.ts +0 -44
  535. package/src/plugins/ibkr/gateway/price-normalization.ts +0 -50
  536. package/src/plugins/ibkr/gateway/quote-stream-controller.ts +0 -203
  537. package/src/plugins/ibkr/gateway/quote-stream.ts +0 -171
  538. package/src/plugins/ibkr/gateway/selection.ts +0 -51
  539. package/src/plugins/ibkr/gateway/service/index.test.ts +0 -675
  540. package/src/plugins/ibkr/gateway/service/index.ts +0 -429
  541. package/src/plugins/ibkr/gateway/service/lifecycle.ts +0 -191
  542. package/src/plugins/ibkr/gateway/service/manager.ts +0 -52
  543. package/src/plugins/ibkr/gateway/service/market-data.ts +0 -48
  544. package/src/plugins/ibkr/gateway/service/native-loader.ts +0 -12
  545. package/src/plugins/ibkr/gateway/service/native.ts +0 -543
  546. package/src/plugins/ibkr/gateway/timeouts.ts +0 -13
  547. package/src/plugins/ibkr/gateway/types.ts +0 -27
  548. package/src/plugins/ibkr/index.test.tsx +0 -162
  549. package/src/plugins/ibkr/index.tsx +0 -159
  550. package/src/plugins/ibkr/instance-selection.test.ts +0 -120
  551. package/src/plugins/ibkr/instance-selection.ts +0 -36
  552. package/src/plugins/ibkr/portfolio-performance.test.ts +0 -32
  553. package/src/plugins/ibkr/portfolio-performance.ts +0 -129
  554. package/src/plugins/ibkr/trade/badge.tsx +0 -28
  555. package/src/plugins/ibkr/trade/dialogs.tsx +0 -44
  556. package/src/plugins/ibkr/trade/preview-panel.tsx +0 -130
  557. package/src/plugins/ibkr/trade/tab/actions.tsx +0 -443
  558. package/src/plugins/ibkr/trade/tab/field-editors.tsx +0 -79
  559. package/src/plugins/ibkr/trade/tab/footer.ts +0 -53
  560. package/src/plugins/ibkr/trade/tab/header.tsx +0 -167
  561. package/src/plugins/ibkr/trade/tab/index.test.tsx +0 -178
  562. package/src/plugins/ibkr/trade/tab/index.tsx +0 -298
  563. package/src/plugins/ibkr/trade/tab/model.ts +0 -159
  564. package/src/plugins/ibkr/trade/tab/order-request.ts +0 -64
  565. package/src/plugins/ibkr/trade/tab/shortcuts.ts +0 -89
  566. package/src/plugins/ibkr/trade/tab/state-sync.ts +0 -86
  567. package/src/plugins/ibkr/trade/ticket-panel.tsx +0 -301
  568. package/src/plugins/ibkr/trade/utils.test.ts +0 -175
  569. package/src/plugins/ibkr/trade/utils.ts +0 -144
  570. package/src/plugins/ibkr/trading/pane-view.tsx +0 -237
  571. package/src/plugins/ibkr/trading/pane.tsx +0 -332
  572. package/src/plugins/ibkr/trading/state.test.ts +0 -109
  573. package/src/plugins/ibkr/trading/state.ts +0 -335
  574. package/src/renderers/electrobun/view/native-stubs/broker-sync-native-loader.ts +0 -7
@@ -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", () => {