gloomberb 0.10.4 → 0.11.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 (555) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +30 -3
  3. package/README.zh-CN.md +1 -1
  4. package/package.json +5 -5
  5. package/src/api-client/auth.ts +34 -0
  6. package/src/api-client/data.ts +25 -0
  7. package/src/api-client/index.test.ts +81 -0
  8. package/src/api-client/index.ts +37 -0
  9. package/src/api-client/request.test.ts +30 -0
  10. package/src/api-client/request.ts +44 -20
  11. package/src/api-client/socket-health.test.ts +79 -0
  12. package/src/api-client/socket.ts +108 -5
  13. package/src/api-client/types.ts +139 -0
  14. package/src/app/global-shortcuts.test.tsx +144 -9
  15. package/src/app/global-shortcuts.ts +33 -11
  16. package/src/app/runtime/broker-import.ts +39 -5
  17. package/src/app/runtime/startup.ts +7 -1
  18. package/src/app/runtime/update.ts +26 -1
  19. package/src/app.tsx +36 -19
  20. package/src/brokers/broker-ticker-sync.ts +3 -8
  21. package/src/brokers/sync-broker-instance.test.ts +59 -0
  22. package/src/brokers/sync-broker-instance.ts +37 -13
  23. package/src/capabilities/chart-series.ts +318 -0
  24. package/src/capabilities/factories.ts +46 -0
  25. package/src/capabilities/index.ts +1 -0
  26. package/src/capabilities/registry.test.ts +135 -0
  27. package/src/capabilities/registry.ts +31 -4
  28. package/src/capabilities/types.ts +52 -0
  29. package/src/cli/commands/overview.ts +2 -2
  30. package/src/cli/desktop-pane-shot.ts +2 -0
  31. package/src/cli/entry.ts +11 -1
  32. package/src/cli/native-smoke.ts +5 -0
  33. package/src/cli/pane-functions/catalog.ts +4 -0
  34. package/src/cli/pane-functions/data.ts +6 -1
  35. package/src/cli/pane-functions/discovery.ts +76 -10
  36. package/src/cli/pane-functions/report.test.ts +76 -0
  37. package/src/cli/pane-functions/report.ts +7 -1
  38. package/src/cli/pane-functions/resolver.ts +4 -1
  39. package/src/cli/pane-functions/screenshot.test.ts +56 -3
  40. package/src/cli/pane-functions/screenshot.ts +68 -5
  41. package/src/components/chart/composite/composite-chart.test.tsx +54 -0
  42. package/src/components/chart/composite/composite-chart.tsx +92 -15
  43. package/src/components/chart/composite/downsample.test.ts +136 -0
  44. package/src/components/chart/composite/downsample.ts +208 -0
  45. package/src/components/chart/composite/panel-series.test.ts +66 -0
  46. package/src/components/chart/composite/panel-series.ts +101 -0
  47. package/src/components/chart/multi-line/renderer.ts +1 -0
  48. package/src/components/chart/native/kitty/index.test.ts +25 -3
  49. package/src/components/chart/native/kitty/support.ts +37 -3
  50. package/src/components/chart/native/use-chart-text-fallback.ts +19 -0
  51. package/src/components/chart/static/chart/surface.tsx +43 -12
  52. package/src/components/chart/static/multi-line-chart-surface.tsx +41 -5
  53. package/src/components/command-bar/commands/direct/index.ts +18 -0
  54. package/src/components/command-bar/commands/registry.ts +14 -0
  55. package/src/components/command-bar/routes/root/article-results.test.ts +83 -0
  56. package/src/components/command-bar/routes/root/article-results.ts +55 -0
  57. package/src/components/command-bar/routes/root/command-items.ts +3 -0
  58. package/src/components/command-bar/routes/root/provider-search.ts +7 -3
  59. package/src/components/command-bar/routes/root/results.ts +38 -11
  60. package/src/components/command-bar/routes/root/runtime.ts +6 -2
  61. package/src/components/command-bar/routes/ticker-search/actions.ts +1 -1
  62. package/src/components/command-bar/routes/ticker-search/results.test.ts +34 -0
  63. package/src/components/command-bar/routes/ticker-search/results.ts +4 -6
  64. package/src/components/command-bar/routes/ticker-search/route.ts +2 -2
  65. package/src/components/command-bar/routing/list-state.ts +2 -1
  66. package/src/components/command-bar/surface/assist.test.tsx +13 -6
  67. package/src/components/command-bar/surface/index.test.tsx +65 -0
  68. package/src/components/command-bar/surface/index.tsx +35 -0
  69. package/src/components/command-bar/view-model.test.ts +43 -0
  70. package/src/components/command-bar/view-model.ts +41 -4
  71. package/src/components/command-bar/workflow/collection-actions.test.ts +94 -0
  72. package/src/components/command-bar/workflow/collection-actions.ts +14 -3
  73. package/src/components/command-bar/workflow/ops.test.ts +136 -0
  74. package/src/components/command-bar/workflow/ops.ts +71 -1
  75. package/src/components/index.ts +1 -1
  76. package/src/components/layout/header.test.tsx +35 -0
  77. package/src/components/layout/header.tsx +101 -9
  78. package/src/components/layout/pane/cycle-order.test.ts +0 -1
  79. package/src/components/layout/pane/footer/index.test.tsx +19 -19
  80. package/src/components/layout/pane/header.tsx +2 -13
  81. package/src/components/layout/shell/index.tsx +3 -0
  82. package/src/components/layout/status-bar.test.tsx +36 -1
  83. package/src/components/layout/status-bar.tsx +48 -2
  84. package/src/components/onboarding/account-step/chooser-panel.tsx +27 -50
  85. package/src/components/onboarding/account-step/form-panel.tsx +85 -29
  86. package/src/components/onboarding/account-step/index.tsx +10 -1
  87. package/src/components/onboarding/account-step/model.ts +3 -3
  88. package/src/components/onboarding/account-step/qr-panel.tsx +68 -0
  89. package/src/components/onboarding/account-step/signed-in-panel.tsx +4 -7
  90. package/src/components/onboarding/onboarding-frame.tsx +517 -0
  91. package/src/components/onboarding/onboarding-steps.tsx +0 -264
  92. package/src/components/onboarding/onboarding-wizard.test.tsx +626 -200
  93. package/src/components/onboarding/onboarding-wizard.tsx +848 -266
  94. package/src/components/onboarding/portfolio-step/broker-fields-panel.tsx +81 -51
  95. package/src/components/onboarding/portfolio-step/broker-setup-panel.tsx +14 -20
  96. package/src/components/onboarding/portfolio-step/broker-sync-panel.tsx +8 -11
  97. package/src/components/onboarding/portfolio-step/choose-panel.tsx +18 -22
  98. package/src/components/onboarding/portfolio-step/index.tsx +3 -0
  99. package/src/components/onboarding/portfolio-step/types.ts +3 -0
  100. package/src/components/onboarding/wizard-account.ts +38 -3
  101. package/src/components/onboarding/wizard-broker-sync.ts +45 -67
  102. package/src/components/onboarding/wizard-model.ts +18 -2
  103. package/src/components/ui/data-table/opentui/index.tsx +29 -9
  104. package/src/components/ui/data-table/types.ts +2 -0
  105. package/src/components/ui/external-link.tsx +4 -2
  106. package/src/components/ui/loading.tsx +1 -1
  107. package/src/components/ui/status.tsx +76 -4
  108. package/src/components/ui/table-layout.test.ts +24 -0
  109. package/src/components/ui/table-layout.ts +90 -23
  110. package/src/components/ui/tabs.tsx +86 -4
  111. package/src/core/app-services.ts +14 -3
  112. package/src/core/connection-health.test.ts +143 -0
  113. package/src/core/connection-health.ts +295 -0
  114. package/src/core/state/app/layout-reducer.ts +34 -0
  115. package/src/core/state/app/layout.ts +21 -0
  116. package/src/core/state/app/state.ts +10 -0
  117. package/src/core/state/app/types.ts +7 -1
  118. package/src/data/config/chart-settings.ts +11 -2
  119. package/src/data/config/store/index.test.ts +83 -9
  120. package/src/data/config/store/migrations.ts +16 -0
  121. package/src/data/config/store/normalize.ts +59 -1
  122. package/src/data/fred-series.ts +8 -2
  123. package/src/i18n/es.ts +126 -0
  124. package/src/i18n/ja.ts +126 -0
  125. package/src/i18n/ko.ts +126 -0
  126. package/src/i18n/zh-cn.ts +126 -0
  127. package/src/i18n/zh-tw.ts +126 -0
  128. package/src/market-data/coordinator/chart.ts +3 -2
  129. package/src/market-data/hooks.tsx +3 -2
  130. package/src/market-data/market/status.test.ts +12 -0
  131. package/src/market-data/market/status.ts +34 -17
  132. package/src/news/aggregator.test.ts +39 -0
  133. package/src/news/aggregator.ts +78 -22
  134. package/src/news/hooks.ts +4 -0
  135. package/src/notifications/app-notifier.test.ts +28 -6
  136. package/src/notifications/app-notifier.ts +24 -5
  137. package/src/plugins/broker-sync/broker-sync.test.ts +130 -0
  138. package/src/plugins/broker-sync/native-loader.ts +10 -0
  139. package/src/plugins/broker-sync/normalize.ts +271 -0
  140. package/src/plugins/broker-sync/public.ts +122 -0
  141. package/src/plugins/broker-sync/robinhood-native.ts +367 -0
  142. package/src/plugins/broker-sync/robinhood.ts +114 -0
  143. package/src/plugins/broker-sync/simplefin-native.ts +201 -0
  144. package/src/plugins/broker-sync/simplefin.ts +70 -0
  145. package/src/plugins/builtin/account-management/footer.ts +8 -19
  146. package/src/plugins/builtin/account-management/pane.tsx +13 -8
  147. package/src/plugins/builtin/ai/pi/host.ts +6 -5
  148. package/src/plugins/builtin/ai/providers.ts +1 -1
  149. package/src/plugins/builtin/ai/screener/footer.ts +0 -7
  150. package/src/plugins/builtin/ai/screener/pane.test.tsx +2 -1
  151. package/src/plugins/builtin/ai/screener/pane.tsx +3 -1
  152. package/src/plugins/builtin/ai/screener/results-view.tsx +8 -0
  153. package/src/plugins/builtin/ai/screener/runner.ts +7 -3
  154. package/src/plugins/builtin/ai/workspace/pane.tsx +13 -2
  155. package/src/plugins/builtin/alerts/alert-engine.ts +13 -0
  156. package/src/plugins/builtin/alerts/constants.ts +2 -1
  157. package/src/plugins/builtin/alerts/index.tsx +67 -36
  158. package/src/plugins/builtin/alerts/pane.tsx +17 -65
  159. package/src/plugins/builtin/analytics/index.tsx +24 -11
  160. package/src/plugins/builtin/analytics/pane-model.ts +48 -14
  161. package/src/plugins/builtin/analytics/portfolio-selection.ts +19 -0
  162. package/src/plugins/builtin/analytics/view.tsx +5 -3
  163. package/src/plugins/builtin/broker-manager/detail.tsx +7 -2
  164. package/src/plugins/builtin/broker-manager/footer.ts +4 -9
  165. package/src/plugins/builtin/broker-manager/index.tsx +1 -6
  166. package/src/plugins/builtin/buildout/detail/ui.tsx +15 -4
  167. package/src/plugins/builtin/buildout/pane/header.tsx +1 -0
  168. package/src/plugins/builtin/changelog/index.tsx +60 -45
  169. package/src/plugins/builtin/chart-composer/catalog-inventory.test.ts +97 -0
  170. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +366 -0
  171. package/src/plugins/builtin/chart-composer/chart-spec.ts +21 -1
  172. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +307 -0
  173. package/src/plugins/builtin/chart-composer/editor/controller.ts +1 -1
  174. package/src/plugins/builtin/chart-composer/index.tsx +45 -5
  175. package/src/plugins/builtin/chart-composer/pane.tsx +7 -88
  176. package/src/plugins/builtin/chart-composer/presets.test.ts +48 -0
  177. package/src/plugins/builtin/chart-composer/presets.ts +69 -3
  178. package/src/plugins/builtin/chart-composer/quick-add.tsx +8 -5
  179. package/src/plugins/builtin/chart-composer/semantic.ts +18 -14
  180. package/src/plugins/builtin/chart-composer/series-catalog.test.ts +39 -0
  181. package/src/plugins/builtin/chart-composer/series-catalog.ts +104 -3
  182. package/src/plugins/builtin/chart-composer/shortcuts.test.ts +10 -10
  183. package/src/plugins/builtin/chart-composer/shortcuts.ts +4 -12
  184. package/src/plugins/builtin/chart-composer/use-series-catalog.ts +230 -23
  185. package/src/plugins/builtin/chat/chat.test.tsx +22 -0
  186. package/src/plugins/builtin/chat/content/composer.tsx +6 -4
  187. package/src/plugins/builtin/chat/content/index.tsx +7 -0
  188. package/src/plugins/builtin/chat/content/snapshot.ts +9 -2
  189. package/src/plugins/builtin/chat/content/transcript.tsx +10 -0
  190. package/src/plugins/builtin/chat/controller/channel-actions.ts +11 -1
  191. package/src/plugins/builtin/chat/controller/channels.ts +13 -3
  192. package/src/plugins/builtin/chat/controller/index.ts +61 -7
  193. package/src/plugins/builtin/chat/controller/lifecycle.ts +3 -0
  194. package/src/plugins/builtin/chat/controller/message-loading.ts +5 -1
  195. package/src/plugins/builtin/chat/controller/messages.ts +3 -1
  196. package/src/plugins/builtin/chat/controller/notifications.ts +32 -11
  197. package/src/plugins/builtin/chat/controller/state.ts +7 -0
  198. package/src/plugins/builtin/chat/controller/view.ts +1 -0
  199. package/src/plugins/builtin/chat/controller.test.ts +117 -28
  200. package/src/plugins/builtin/cloud/auth-commands.ts +18 -0
  201. package/src/plugins/builtin/cloud/device-signin-dialog.tsx +243 -0
  202. package/src/plugins/builtin/cloud/device-signin.test.ts +217 -0
  203. package/src/plugins/builtin/cloud/device-signin.ts +240 -0
  204. package/src/plugins/builtin/cloud/plugin.tsx +7 -1
  205. package/src/plugins/builtin/cloud-tweets/table.tsx +57 -10
  206. package/src/plugins/builtin/composite-plugins.ts +39 -5
  207. package/src/plugins/builtin/congress-trades/detail.tsx +2 -4
  208. package/src/plugins/builtin/congress-trades/footer.ts +7 -16
  209. package/src/plugins/builtin/congress-trades/pane.tsx +12 -20
  210. package/src/plugins/builtin/connections/index.ts +53 -0
  211. package/src/plugins/builtin/connections/pane.test.tsx +79 -0
  212. package/src/plugins/builtin/connections/pane.tsx +258 -0
  213. package/src/plugins/builtin/correlation/index.test.tsx +2 -1
  214. package/src/plugins/builtin/correlation/index.tsx +58 -19
  215. package/src/plugins/builtin/correlation/matrix/model.ts +18 -7
  216. package/src/plugins/builtin/correlation/relationship/pane.tsx +43 -19
  217. package/src/plugins/builtin/correlation/settings.ts +1 -1
  218. package/src/plugins/builtin/credit-conditions/client.test.ts +80 -0
  219. package/src/plugins/builtin/credit-conditions/client.ts +105 -0
  220. package/src/plugins/builtin/credit-conditions/index.test.tsx +111 -0
  221. package/src/plugins/builtin/credit-conditions/index.tsx +227 -0
  222. package/src/plugins/builtin/credit-conditions/model.test.ts +43 -0
  223. package/src/plugins/builtin/credit-conditions/model.ts +64 -0
  224. package/src/plugins/builtin/debug/index.tsx +11 -6
  225. package/src/plugins/builtin/dividend-yield/client.test.ts +58 -0
  226. package/src/plugins/builtin/dividend-yield/client.ts +252 -0
  227. package/src/plugins/builtin/dividend-yield/index.tsx +61 -0
  228. package/src/plugins/builtin/dividend-yield/model.ts +83 -0
  229. package/src/plugins/builtin/dividend-yield/pane.tsx +305 -0
  230. package/src/plugins/builtin/dividend-yield/types.ts +22 -0
  231. package/src/plugins/builtin/earnings/data/cache.test.ts +10 -7
  232. package/src/plugins/builtin/earnings/data/cache.ts +23 -9
  233. package/src/plugins/builtin/earnings/index.tsx +57 -6
  234. package/src/plugins/builtin/econ/calendar-model.test.ts +37 -3
  235. package/src/plugins/builtin/econ/calendar-model.ts +32 -26
  236. package/src/plugins/builtin/econ/fred-cache.test.ts +8 -1
  237. package/src/plugins/builtin/econ/fred-series-map.ts +18 -0
  238. package/src/plugins/builtin/econ/index.test.tsx +105 -0
  239. package/src/plugins/builtin/econ/index.tsx +95 -52
  240. package/src/plugins/builtin/fear-greed/cache.test.ts +18 -3
  241. package/src/plugins/builtin/fear-greed/cache.ts +18 -6
  242. package/src/plugins/builtin/fear-greed/charts.tsx +9 -2
  243. package/src/plugins/builtin/fear-greed/format.ts +0 -9
  244. package/src/plugins/builtin/fear-greed/pane.tsx +21 -21
  245. package/src/plugins/builtin/futures/contracts.ts +100 -0
  246. package/src/plugins/builtin/futures/index.tsx +298 -0
  247. package/src/plugins/builtin/futures/model.test.ts +129 -0
  248. package/src/plugins/builtin/futures/model.ts +136 -0
  249. package/src/plugins/builtin/futures/pane.test.tsx +239 -0
  250. package/src/plugins/builtin/futures/table.test.ts +155 -0
  251. package/src/plugins/builtin/futures/table.tsx +241 -0
  252. package/src/plugins/builtin/fx-matrix/index.tsx +160 -134
  253. package/src/plugins/builtin/fx-matrix/pairs.ts +12 -1
  254. package/src/plugins/builtin/fx-matrix/pane.test.tsx +109 -0
  255. package/src/plugins/builtin/help/components.tsx +1 -30
  256. package/src/plugins/builtin/help/index.tsx +5 -25
  257. package/src/plugins/builtin/holders/pane.tsx +17 -12
  258. package/src/plugins/builtin/holders/treemap.tsx +4 -2
  259. package/src/plugins/builtin/insider/index.tsx +20 -10
  260. package/src/plugins/builtin/ipo-calendar/cache.ts +101 -0
  261. package/src/plugins/builtin/ipo-calendar/client.test.ts +31 -0
  262. package/src/plugins/builtin/ipo-calendar/client.ts +296 -0
  263. package/src/plugins/builtin/ipo-calendar/index.tsx +65 -0
  264. package/src/plugins/builtin/ipo-calendar/model.ts +145 -0
  265. package/src/plugins/builtin/ipo-calendar/pane.tsx +328 -0
  266. package/src/plugins/builtin/ipo-calendar/types.ts +19 -0
  267. package/src/plugins/builtin/kelly-sizer/index.test.tsx +3 -3
  268. package/src/plugins/builtin/kelly-sizer/model.ts +8 -1
  269. package/src/plugins/builtin/kelly-sizer/pane.tsx +33 -15
  270. package/src/plugins/builtin/kelly-sizer/view.tsx +12 -5
  271. package/src/plugins/builtin/market-halts/client.ts +114 -0
  272. package/src/plugins/builtin/market-halts/index.tsx +29 -0
  273. package/src/plugins/builtin/market-halts/model.test.ts +187 -0
  274. package/src/plugins/builtin/market-halts/model.ts +291 -0
  275. package/src/plugins/builtin/market-halts/pane.tsx +256 -0
  276. package/src/plugins/builtin/market-heatmap/data.test.ts +1 -0
  277. package/src/plugins/builtin/market-heatmap/data.ts +12 -3
  278. package/src/plugins/builtin/market-heatmap/index.tsx +131 -46
  279. package/src/plugins/builtin/market-movers/index.tsx +145 -63
  280. package/src/plugins/builtin/market-movers/model.ts +19 -3
  281. package/src/plugins/builtin/market-movers/screener.test.ts +108 -0
  282. package/src/plugins/builtin/market-movers/screener.ts +168 -10
  283. package/src/plugins/builtin/news/index.tsx +78 -99
  284. package/src/plugins/builtin/news/wire/article-search.test.ts +72 -0
  285. package/src/plugins/builtin/news/wire/article-search.ts +140 -0
  286. package/src/plugins/builtin/news/wire/breaking/pane.tsx +18 -8
  287. package/src/plugins/builtin/news/wire/categories.ts +35 -0
  288. package/src/plugins/builtin/news/wire/default-feeds.ts +47 -1
  289. package/src/plugins/builtin/news/wire/feed-config.test.ts +10 -5
  290. package/src/plugins/builtin/news/wire/index.tsx +41 -4
  291. package/src/plugins/builtin/news/wire/industry-pane.tsx +38 -19
  292. package/src/plugins/builtin/news/wire/news/detail-view.tsx +10 -5
  293. package/src/plugins/builtin/news/wire/news/footer.ts +1 -0
  294. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +15 -6
  295. package/src/plugins/builtin/news/wire/news/query-presets.ts +16 -2
  296. package/src/plugins/builtin/news/wire/news/table.test.tsx +54 -0
  297. package/src/plugins/builtin/news/wire/news/table.tsx +111 -17
  298. package/src/plugins/builtin/notes/files.test.ts +18 -0
  299. package/src/plugins/builtin/notes/files.ts +10 -4
  300. package/src/plugins/builtin/notes/index.tsx +4 -1
  301. package/src/plugins/builtin/notes/quick-notes-pane.test.tsx +182 -0
  302. package/src/plugins/builtin/notes/quick-notes-pane.tsx +74 -31
  303. package/src/plugins/builtin/notes/ticker-notes-tab.test.tsx +380 -0
  304. package/src/plugins/builtin/notes/ticker-notes-tab.tsx +97 -40
  305. package/src/plugins/builtin/options/calc-seed.test.ts +118 -0
  306. package/src/plugins/builtin/options/calc-seed.ts +52 -0
  307. package/src/plugins/builtin/options/footer.ts +4 -1
  308. package/src/plugins/builtin/options/index.tsx +22 -1
  309. package/src/plugins/builtin/options/live-quotes.ts +8 -0
  310. package/src/plugins/builtin/options/table.ts +3 -1
  311. package/src/plugins/builtin/options/view.test.tsx +5 -1
  312. package/src/plugins/builtin/options/view.tsx +112 -17
  313. package/src/plugins/builtin/options-calculator/index.tsx +35 -0
  314. package/src/plugins/builtin/options-calculator/model.test.ts +172 -0
  315. package/src/plugins/builtin/options-calculator/model.ts +273 -0
  316. package/src/plugins/builtin/options-calculator/pane.test.tsx +138 -0
  317. package/src/plugins/builtin/options-calculator/pane.tsx +218 -0
  318. package/src/plugins/builtin/plugin-module.test.ts +16 -2
  319. package/src/plugins/builtin/plugin-module.ts +5 -1
  320. package/src/plugins/builtin/polls/client.ts +57 -0
  321. package/src/plugins/builtin/polls/hooks.ts +20 -0
  322. package/src/plugins/builtin/polls/index.tsx +35 -0
  323. package/src/plugins/builtin/polls/normalize.test.ts +230 -0
  324. package/src/plugins/builtin/polls/normalize.ts +319 -0
  325. package/src/plugins/builtin/polls/pane.tsx +722 -0
  326. package/src/plugins/builtin/polls/types.ts +77 -0
  327. package/src/plugins/builtin/portfolio-list/header.tsx +15 -4
  328. package/src/plugins/builtin/portfolio-list/pane/index.tsx +7 -3
  329. package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +1 -1
  330. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -0
  331. package/src/plugins/builtin/portfolio-list/summary/index.tsx +2 -1
  332. package/src/plugins/builtin/research/analyst-pane.tsx +40 -67
  333. package/src/plugins/builtin/research/corporate-actions-pane.tsx +25 -19
  334. package/src/plugins/builtin/research/index.test.ts +6 -25
  335. package/src/plugins/builtin/research/index.tsx +11 -5
  336. package/src/plugins/builtin/research/relative-valuation-pane.tsx +86 -26
  337. package/src/plugins/builtin/scanner/denied.tsx +31 -0
  338. package/src/plugins/builtin/scanner/feed.ts +94 -0
  339. package/src/plugins/builtin/scanner/flow-model.test.ts +85 -0
  340. package/src/plugins/builtin/scanner/flow-model.ts +133 -0
  341. package/src/plugins/builtin/scanner/flow-pane.tsx +197 -0
  342. package/src/plugins/builtin/scanner/hilo-bars.tsx +121 -0
  343. package/src/plugins/builtin/scanner/hilo-model.test.ts +44 -0
  344. package/src/plugins/builtin/scanner/hilo-model.ts +75 -0
  345. package/src/plugins/builtin/scanner/hilo-pane.tsx +165 -0
  346. package/src/plugins/builtin/scanner/index.ts +109 -0
  347. package/src/plugins/builtin/scanner/waiting.tsx +11 -0
  348. package/src/plugins/builtin/sec/index.tsx +11 -7
  349. package/src/plugins/builtin/sectors/index.tsx +105 -81
  350. package/src/plugins/builtin/sectors/move-bar.tsx +43 -0
  351. package/src/plugins/builtin/sectors/sector-data.ts +18 -0
  352. package/src/plugins/builtin/sectors/sector-model.ts +13 -15
  353. package/src/plugins/builtin/shared/auto-refresh.ts +51 -0
  354. package/src/plugins/builtin/shared/cloud-upgrade.ts +29 -5
  355. package/src/plugins/builtin/shared/pane-footer.ts +16 -3
  356. package/src/plugins/builtin/shared/screener-live-quotes.test.ts +91 -0
  357. package/src/plugins/builtin/shared/screener-live-quotes.ts +134 -0
  358. package/src/plugins/builtin/shared/table-pane.ts +1 -5
  359. package/src/plugins/builtin/shared/use-quote-board.test.tsx +211 -0
  360. package/src/plugins/builtin/shared/use-quote-board.ts +195 -0
  361. package/src/plugins/builtin/short-interest/client.ts +124 -0
  362. package/src/plugins/builtin/short-interest/index.tsx +62 -0
  363. package/src/plugins/builtin/short-interest/model.ts +113 -0
  364. package/src/plugins/builtin/short-interest/pane.tsx +252 -0
  365. package/src/plugins/builtin/short-interest/types.ts +9 -0
  366. package/src/plugins/builtin/substack/article-detail.tsx +15 -1
  367. package/src/plugins/builtin/substack/pane-footer.ts +3 -22
  368. package/src/plugins/builtin/substack/pane.tsx +1 -2
  369. package/src/plugins/builtin/thirteenf/api.ts +4 -1
  370. package/src/plugins/builtin/thirteenf/model.ts +2 -0
  371. package/src/plugins/builtin/thirteenf/pane.tsx +6 -2
  372. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +10 -4
  373. package/src/plugins/builtin/ticker-detail/data-panes/provider-search.tsx +67 -11
  374. package/src/plugins/builtin/ticker-detail/financials/tab.tsx +15 -8
  375. package/src/plugins/builtin/ticker-detail/index.tsx +5 -21
  376. package/src/plugins/builtin/ticker-detail/overview/components.tsx +72 -32
  377. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +0 -10
  378. package/src/plugins/builtin/ticker-detail/pane.tsx +4 -1
  379. package/src/plugins/builtin/ticker-detail/quote-monitor/card.test.tsx +70 -0
  380. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +34 -26
  381. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +96 -49
  382. package/src/plugins/builtin/ticker-detail/research-tabs.ts +32 -0
  383. package/src/plugins/builtin/ticker-research-backend-plugin.ts +2 -9
  384. package/src/plugins/builtin/ticker-research-plugin.ts +6 -0
  385. package/src/plugins/builtin/treasury-auctions/cache.test.ts +173 -0
  386. package/src/plugins/builtin/treasury-auctions/cache.ts +104 -0
  387. package/src/plugins/builtin/treasury-auctions/client.test.ts +181 -0
  388. package/src/plugins/builtin/treasury-auctions/client.ts +151 -0
  389. package/src/plugins/builtin/treasury-auctions/index.tsx +90 -0
  390. package/src/plugins/builtin/treasury-auctions/model.test.ts +147 -0
  391. package/src/plugins/builtin/treasury-auctions/model.ts +188 -0
  392. package/src/plugins/builtin/treasury-auctions/pane.test.tsx +170 -0
  393. package/src/plugins/builtin/treasury-auctions/pane.tsx +417 -0
  394. package/src/plugins/builtin/treasury-auctions/types.ts +59 -0
  395. package/src/plugins/builtin/tv/pane.tsx +67 -7
  396. package/src/plugins/builtin/tv/youtube-embed.test.ts +84 -0
  397. package/src/plugins/builtin/tv/youtube-embed.ts +173 -0
  398. package/src/plugins/builtin/tv/youtube-stream.ts +27 -2
  399. package/src/plugins/builtin/volatility/client.ts +87 -0
  400. package/src/plugins/builtin/volatility/index.test.tsx +107 -0
  401. package/src/plugins/builtin/volatility/index.tsx +193 -0
  402. package/src/plugins/builtin/volatility/model.test.ts +66 -0
  403. package/src/plugins/builtin/volatility/model.ts +113 -0
  404. package/src/plugins/builtin/world-indices/footer.ts +43 -23
  405. package/src/plugins/builtin/world-indices/index.tsx +44 -93
  406. package/src/plugins/builtin/world-indices/indices.ts +11 -2
  407. package/src/plugins/builtin/world-indices/model.ts +17 -24
  408. package/src/plugins/builtin/world-indices/pane.test.tsx +116 -0
  409. package/src/plugins/builtin/world-indices/table.tsx +82 -37
  410. package/src/plugins/builtin/yield-curve/index.tsx +16 -7
  411. package/src/plugins/builtin/yield-curve/treasury-data.test.ts +25 -0
  412. package/src/plugins/builtin/yield-curve/treasury-data.ts +16 -0
  413. package/src/plugins/catalog-backend.ts +8 -0
  414. package/src/plugins/catalog-ui.ts +8 -0
  415. package/src/plugins/event-bus.ts +1 -0
  416. package/src/plugins/ibkr/gateway/account-loaders.ts +1 -1
  417. package/src/plugins/ibkr/trade/tab/footer.ts +9 -23
  418. package/src/plugins/ibkr/trade/tab/index.tsx +1 -5
  419. package/src/plugins/ibkr/trading/pane-view.tsx +149 -87
  420. package/src/plugins/ibkr/trading/pane.tsx +2 -2
  421. package/src/plugins/ownership.ts +3 -0
  422. package/src/plugins/pane-manager/floating.ts +8 -2
  423. package/src/plugins/pane-manager.test.ts +13 -0
  424. package/src/plugins/prediction-markets/backend-plugin.ts +2 -0
  425. package/src/plugins/prediction-markets/capability.test.ts +281 -0
  426. package/src/plugins/prediction-markets/capability.ts +171 -0
  427. package/src/plugins/prediction-markets/chart.tsx +18 -26
  428. package/src/plugins/prediction-markets/columns.ts +3 -2
  429. package/src/plugins/prediction-markets/controller/catalog.ts +15 -12
  430. package/src/plugins/prediction-markets/controller/detail.ts +4 -0
  431. package/src/plugins/prediction-markets/controller/index.ts +0 -15
  432. package/src/plugins/prediction-markets/detail/book.tsx +3 -23
  433. package/src/plugins/prediction-markets/detail/overview.tsx +0 -18
  434. package/src/plugins/prediction-markets/detail/pane.tsx +36 -12
  435. package/src/plugins/prediction-markets/detail.test.tsx +1 -1
  436. package/src/plugins/prediction-markets/index.tsx +2 -0
  437. package/src/plugins/prediction-markets/metrics.ts +28 -18
  438. package/src/plugins/prediction-markets/navigation.ts +1 -0
  439. package/src/plugins/prediction-markets/pane.test.tsx +1 -1
  440. package/src/plugins/prediction-markets/pane.tsx +0 -1
  441. package/src/plugins/prediction-markets/services/fetch.ts +3 -2
  442. package/src/plugins/prediction-markets/services/kalshi/adapter.ts +82 -23
  443. package/src/plugins/prediction-markets/services/kalshi/normalize.ts +3 -1
  444. package/src/plugins/prediction-markets/services/kalshi/types.ts +14 -0
  445. package/src/plugins/prediction-markets/services/polymarket/adapter.ts +26 -14
  446. package/src/plugins/prediction-markets/services/polymarket/detail.ts +67 -18
  447. package/src/plugins/prediction-markets/test-helpers.tsx +0 -1
  448. package/src/plugins/prediction-markets/types.ts +3 -9
  449. package/src/plugins/registry/context.ts +6 -1
  450. package/src/plugins/registry/index.test.ts +2 -0
  451. package/src/plugins/registry/index.ts +53 -3
  452. package/src/plugins/runtime/context.tsx +4 -2
  453. package/src/plugins/runtime/index.tsx +9 -0
  454. package/src/remote/controller.test.ts +17 -4
  455. package/src/renderers/electrobun/bun/application-menu/index.ts +1 -1
  456. package/src/renderers/electrobun/bun/core-capabilities.ts +2 -2
  457. package/src/renderers/electrobun/bun/desktop/capability-bridge.test.ts +64 -0
  458. package/src/renderers/electrobun/bun/desktop/capability-bridge.ts +41 -7
  459. package/src/renderers/electrobun/bun/desktop/host-requests.ts +5 -2
  460. package/src/renderers/electrobun/bun/desktop/http-fetch.test.ts +33 -0
  461. package/src/renderers/electrobun/bun/desktop/http-fetch.ts +53 -6
  462. package/src/renderers/electrobun/bun/desktop/initialization.test.ts +27 -0
  463. package/src/renderers/electrobun/bun/desktop/initialization.ts +6 -1
  464. package/src/renderers/electrobun/bun/index.ts +1 -0
  465. package/src/renderers/electrobun/shared/protocol.ts +2 -0
  466. package/src/renderers/electrobun/view/app-services.ts +25 -2
  467. package/src/renderers/electrobun/view/build-assets.ts +5 -0
  468. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +186 -65
  469. package/src/renderers/electrobun/view/cli-pane-shot-health.test.ts +25 -0
  470. package/src/renderers/electrobun/view/cli-pane-shot-health.ts +31 -0
  471. package/src/renderers/electrobun/view/data-table/index.tsx +10 -11
  472. package/src/renderers/electrobun/view/data-table/row.tsx +2 -0
  473. package/src/renderers/electrobun/view/desktop/controls.test.tsx +68 -0
  474. package/src/renderers/electrobun/view/desktop/controls.tsx +25 -1
  475. package/src/renderers/electrobun/view/host/box.test.tsx +80 -0
  476. package/src/renderers/electrobun/view/host/media-surface.tsx +70 -3
  477. package/src/renderers/electrobun/view/host/tabs.tsx +97 -5
  478. package/src/renderers/electrobun/view/host/text.tsx +5 -2
  479. package/src/renderers/electrobun/view/input-host.test.tsx +140 -0
  480. package/src/renderers/electrobun/view/input-host.tsx +4 -2
  481. package/src/renderers/electrobun/view/native-stubs/backend-rpc.ts +19 -1
  482. package/src/renderers/electrobun/view/native-stubs/broker-sync-native-loader.ts +7 -0
  483. package/src/renderers/electrobun/view/native-stubs/chart/kitty-support.ts +10 -0
  484. package/src/renderers/electrobun/view/remote/capability-invoker.test.ts +46 -0
  485. package/src/renderers/electrobun/view/remote/capability-invoker.ts +34 -13
  486. package/src/renderers/electrobun/view/remote/connection-health-backend.ts +19 -0
  487. package/src/renderers/electrobun/view/remote/connection-health.test.ts +44 -0
  488. package/src/renderers/electrobun/view/remote/connection-health.ts +49 -0
  489. package/src/renderers/electrobun/view/styles.css +5 -0
  490. package/src/renderers/opentui/dialog-host.tsx +279 -80
  491. package/src/renderers/opentui/host.tsx +15 -1
  492. package/src/renderers/opentui/terminal-media.test.ts +102 -0
  493. package/src/renderers/opentui/terminal-media.ts +142 -0
  494. package/src/renderers/opentui/test-utils.tsx +9 -6
  495. package/src/renderers/opentui/toast-host.tsx +136 -7
  496. package/src/renderers/opentui/ui-host.tsx +19 -3
  497. package/src/sources/provider-router/connection-health.test.ts +28 -0
  498. package/src/sources/provider-router/connection-health.ts +47 -0
  499. package/src/sources/provider-router/history.test.ts +53 -4
  500. package/src/sources/provider-router/history.ts +119 -14
  501. package/src/sources/provider-router/index.ts +12 -1
  502. package/src/sources/yahoo-finance/history.ts +2 -9
  503. package/src/sources/yahoo-finance/http.ts +1 -1
  504. package/src/sources/yahoo-finance/symbols.ts +1 -1
  505. package/src/sources/yahoo-finance/types.ts +19 -0
  506. package/src/state/app/context/index.test.tsx +101 -0
  507. package/src/state/app/context/index.tsx +10 -0
  508. package/src/state/config-save-scheduler.ts +1 -6
  509. package/src/state/persist-scheduler.test.ts +31 -0
  510. package/src/state/persist-scheduler.ts +21 -8
  511. package/src/sync/auth-transition.test.ts +36 -0
  512. package/src/sync/auth-transition.ts +22 -0
  513. package/src/sync/core-contributors.test.ts +58 -1
  514. package/src/sync/core-contributors.ts +39 -2
  515. package/src/sync/react.ts +9 -0
  516. package/src/test-support/plugin-runtime.ts +3 -0
  517. package/src/theme/font-scale.test.ts +14 -0
  518. package/src/theme/font-scale.ts +69 -0
  519. package/src/tickers/search/index.test.ts +284 -2
  520. package/src/tickers/search/index.ts +55 -18
  521. package/src/tickers/search/ranking.ts +143 -18
  522. package/src/tickers/search/types.ts +1 -0
  523. package/src/time-series/alignment.ts +15 -11
  524. package/src/time-series/field-catalog.ts +10 -0
  525. package/src/time-series/fundamentals.ts +19 -11
  526. package/src/time-series/hooks.ts +8 -2
  527. package/src/time-series/parsed-history-cache.ts +35 -0
  528. package/src/time-series/resolution.ts +10 -0
  529. package/src/time-series/resolve.test.ts +332 -3
  530. package/src/time-series/resolve.ts +225 -25
  531. package/src/time-series/spec-studies.test.ts +23 -0
  532. package/src/time-series/spec.ts +21 -2
  533. package/src/time-series/transforms.test.ts +76 -1
  534. package/src/time-series/transforms.ts +39 -8
  535. package/src/time-series/types.ts +9 -2
  536. package/src/time-series/use-chart-resolution.test.tsx +105 -1
  537. package/src/time-series/use-chart-resolution.ts +67 -13
  538. package/src/types/config.ts +30 -27
  539. package/src/types/media.ts +2 -0
  540. package/src/types/plugin.ts +8 -1
  541. package/src/ui/host.tsx +4 -0
  542. package/src/ui/pointer-drag.ts +12 -0
  543. package/src/ui/qr.test.ts +79 -0
  544. package/src/ui/qr.ts +82 -0
  545. package/src/updater/github-releases.test.ts +3 -3
  546. package/src/updater/github-releases.ts +1 -1
  547. package/src/updater.test.ts +117 -16
  548. package/src/updater.ts +32 -5
  549. package/src/utils/exchanges.ts +1 -0
  550. package/src/utils/external-url.test.ts +32 -0
  551. package/src/utils/external-url.ts +23 -0
  552. package/src/utils/sort-values.ts +31 -0
  553. package/src/utils/zoned-date-time.ts +67 -0
  554. package/src/components/onboarding/wizard-keyboard.ts +0 -306
  555. package/src/plugins/builtin/chart-composer/date-window-dialog.tsx +0 -120
@@ -1,8 +1,13 @@
1
1
  import type { ReactNode } from "react";
2
2
  import type { AppPersistencePort, AppTickerRepositoryPort } from "../../core/app-service-ports";
3
+ import {
4
+ connectionHealth as sharedConnectionHealth,
5
+ type ConnectionHealthRegistry,
6
+ } from "../../core/connection-health";
3
7
  import type { BrokerAdapter } from "../../types/broker";
4
8
  import {
5
9
  CapabilityRegistry,
10
+ type CapabilityManifest,
6
11
  type NewsCapability,
7
12
  type PluginCapability,
8
13
  } from "../../capabilities";
@@ -10,6 +15,7 @@ import type { BrokerInstanceConfig, LayoutConfig } from "../../types/config";
10
15
  import type { DataProvider } from "../../types/data-provider";
11
16
  import type { TickerFinancials } from "../../types/financials";
12
17
  import type {
18
+ AppNotificationDelivery,
13
19
  AppNotificationRequest,
14
20
  BrokerInstanceUpdateOptions,
15
21
  CommandDef,
@@ -62,6 +68,14 @@ import { isReservedBuiltinPluginId } from "../ownership";
62
68
  interface PluginRegistryOptions {
63
69
  enableCapabilityHandlers?: boolean;
64
70
  wrapBrokerAdapter?: (broker: BrokerAdapter, pluginId: string) => BrokerAdapter;
71
+ connectionHealth?: ConnectionHealthRegistry;
72
+ remoteCapabilityManifests?: () => CapabilityManifest[];
73
+ remoteCapabilityInvoke?: <T>(
74
+ capabilityId: string,
75
+ operationId: string,
76
+ payload: unknown,
77
+ options?: { signal?: AbortSignal },
78
+ ) => Promise<T>;
65
79
  }
66
80
 
67
81
  export type WindowEditMode = "move" | "resize";
@@ -80,11 +94,19 @@ export class PluginRegistry implements PluginRuntimeAccess {
80
94
 
81
95
  readonly events: EventBus;
82
96
  readonly capabilities: CapabilityRegistry;
97
+ readonly connectionHealth: ConnectionHealthRegistry;
83
98
  readonly marketData: DataProvider;
84
99
  readonly tickerRepository: AppTickerRepositoryPort;
85
100
  readonly persistence: AppPersistencePort;
86
101
  private readonly enableCapabilityHandlers: boolean;
87
102
  private readonly wrapBrokerAdapter?: (broker: BrokerAdapter, pluginId: string) => BrokerAdapter;
103
+ private readonly remoteCapabilityManifests?: () => CapabilityManifest[];
104
+ private readonly remoteCapabilityInvoke?: <T>(
105
+ capabilityId: string,
106
+ operationId: string,
107
+ payload: unknown,
108
+ options?: { signal?: AbortSignal },
109
+ ) => Promise<T>;
88
110
 
89
111
  getTickerFn: ((symbol: string) => TickerRecord | null) = () => null;
90
112
  getDataFn: ((symbol: string) => TickerFinancials | null) = () => null;
@@ -112,7 +134,20 @@ export class PluginRegistry implements PluginRuntimeAccess {
112
134
  pinTickerFn: ((symbol: string, options?: PinTickerOptions) => void) = () => {};
113
135
  navigateTickerFn: ((symbol: string, options?: { sourcePaneId?: string | null }) => void) = () => {};
114
136
  getMarketData = () => this.marketData;
137
+ getConnectionHealth = () => this.connectionHealth;
115
138
  getCapability = (capabilityId: string) => this.capabilities.get(capabilityId)?.capability ?? null;
139
+ capabilityManifests = (kind?: string) => (this.remoteCapabilityManifests?.() ?? this.capabilities.manifests({ rendererOnly: true }))
140
+ .filter((manifest) => !kind || manifest.kind === kind);
141
+ invokeCapability = <T = unknown>(
142
+ capabilityId: string,
143
+ operationId: string,
144
+ payload: unknown,
145
+ options: { signal?: AbortSignal } = {},
146
+ ): Promise<T> => (
147
+ this.remoteCapabilityInvoke
148
+ ? this.remoteCapabilityInvoke<T>(capabilityId, operationId, payload, options)
149
+ : this.capabilities.invoke<T>(capabilityId, operationId, payload, { renderer: true, signal: options.signal })
150
+ );
116
151
  getBrokerAdapter = (brokerType: string) => this.contributions.brokersMap.get(brokerType) ?? null;
117
152
  connectBrokerInstance = (instanceId: string) => this.connectBrokerInstanceFn(instanceId);
118
153
  updateBrokerInstance = (instanceId: string, values: Record<string, unknown>, options?: BrokerInstanceUpdateOptions) => (
@@ -170,7 +205,7 @@ export class PluginRegistry implements PluginRuntimeAccess {
170
205
  listener: (state: import("../../types/news-source").NewsQueryState) => void,
171
206
  ) => () => void) = () => () => {};
172
207
 
173
- notifyFn: ((notification: AppNotificationRequest) => void) = () => {};
208
+ notifyFn: ((notification: AppNotificationRequest) => AppNotificationDelivery | void) = () => {};
174
209
  getPaneRuntimeStateFn: ((paneId: string) => PaneRuntimeState | null) = () => null;
175
210
  updatePaneRuntimeStateFn: ((paneId: string, patch: Partial<PaneRuntimeState>) => void) = () => {};
176
211
  applyPaneSettingValueFn: ((paneId: string, field: import("../../types/plugin").PaneSettingField, value: unknown) => Promise<void>) = async () => {};
@@ -190,10 +225,13 @@ export class PluginRegistry implements PluginRuntimeAccess {
190
225
  options: PluginRegistryOptions = {},
191
226
  ) {
192
227
  this.marketData = marketData;
228
+ this.connectionHealth = options.connectionHealth ?? sharedConnectionHealth;
193
229
  this.tickerRepository = tickerRepository;
194
230
  this.persistence = persistence;
195
231
  this.enableCapabilityHandlers = options.enableCapabilityHandlers ?? true;
196
232
  this.wrapBrokerAdapter = options.wrapBrokerAdapter;
233
+ this.remoteCapabilityManifests = options.remoteCapabilityManifests;
234
+ this.remoteCapabilityInvoke = options.remoteCapabilityInvoke;
197
235
  this.events = new EventBus();
198
236
  this.contributions = new RegistryContributions({
199
237
  wrapPaneDef: (pluginId, pane) => wrapPaneDefWithRuntime(pluginId, pane, this),
@@ -208,6 +246,7 @@ export class PluginRegistry implements PluginRuntimeAccess {
208
246
  const disabledSources = this.getConfigFn().disabledSources ?? [];
209
247
  return !disabledSources.includes(capability.sourceId ?? capability.id) && !this.getConfigFn().disabledPlugins.includes(pluginId);
210
248
  },
249
+ connectionHealth: this.connectionHealth,
211
250
  });
212
251
  this.Slot = ({ name, ...props }: { name: keyof GloomSlots } & Record<string, unknown>) => (
213
252
  this.renderSlot(name, props as any)
@@ -278,8 +317,8 @@ export class PluginRegistry implements PluginRuntimeAccess {
278
317
  return this.contributions.pluginItems.get(pluginId)?.paneTemplates ?? [];
279
318
  }
280
319
 
281
- notify(notification: AppNotificationRequest): void {
282
- this.notifyFn(notification);
320
+ notify(notification: AppNotificationRequest): AppNotificationDelivery | void {
321
+ return this.notifyFn(notification);
283
322
  }
284
323
 
285
324
  renderSlot<K extends keyof GloomSlots>(name: K, props: GloomSlots[K]): ReactNode {
@@ -299,6 +338,16 @@ export class PluginRegistry implements PluginRuntimeAccess {
299
338
  const disposeCapability = this.capabilities.register(pluginId, ownedCapability);
300
339
  this.contributions.registerCapability(pluginId, capability.id, items);
301
340
  items.capabilityDisposers.push(disposeCapability);
341
+ if (ownedCapability.kind === "asset-data" || ownedCapability.kind === "news") {
342
+ items.capabilityDisposers.push(this.connectionHealth.registerSource({
343
+ id: ownedCapability.id,
344
+ name: `${ownedCapability.name} ${ownedCapability.kind === "news" ? "News" : "Market Data"}`,
345
+ kind: ownedCapability.kind,
346
+ ownerId: pluginId,
347
+ priority: ownedCapability.priority,
348
+ detail: ownedCapability.sourceId,
349
+ }));
350
+ }
302
351
 
303
352
  if (ownedCapability.kind === "news") {
304
353
  const dispose = this.registerNewsCapabilityFn(ownedCapability as NewsCapability);
@@ -412,6 +461,7 @@ export class PluginRegistry implements PluginRuntimeAccess {
412
461
  contributions: this.contributions,
413
462
  enableCapabilityHandlers: this.enableCapabilityHandlers,
414
463
  marketData: this.marketData,
464
+ connectionHealth: this.connectionHealth,
415
465
  tickerRepository: this.tickerRepository,
416
466
  persistence: this.persistence,
417
467
  getLayout: () => this.getLayoutFn(),
@@ -5,7 +5,8 @@ import {
5
5
  type ReactNode,
6
6
  } from "react";
7
7
  import type { BrokerAdapter } from "../../types/broker";
8
- import type { PluginCapability } from "../../capabilities";
8
+ import type { ConnectionHealthRegistry } from "../../core/connection-health";
9
+ import type { CapabilityInvoker, PluginCapability } from "../../capabilities";
9
10
  import type { DataProvider } from "../../types/data-provider";
10
11
  import type {
11
12
  AppNotificationRequest,
@@ -16,8 +17,9 @@ import type {
16
17
  TickerResearchTabDef,
17
18
  } from "../../types/plugin";
18
19
 
19
- export interface PluginRuntimeAccess {
20
+ export interface PluginRuntimeAccess extends CapabilityInvoker {
20
21
  getMarketData(): DataProvider | null;
22
+ getConnectionHealth(): ConnectionHealthRegistry;
21
23
  getCapability(capabilityId: string): PluginCapability | null;
22
24
  getBrokerAdapter(brokerType: string): BrokerAdapter | null;
23
25
  connectBrokerInstance(instanceId: string): Promise<void>;
@@ -103,6 +103,15 @@ export function useAssetData(): ReturnType<PluginRuntimeAccess["getMarketData"]>
103
103
  return useMarketData();
104
104
  }
105
105
 
106
+ export function useConnectionHealth(): ReturnType<PluginRuntimeAccess["getConnectionHealth"]> {
107
+ const { runtime } = usePluginRenderContext();
108
+ return runtime.getConnectionHealth();
109
+ }
110
+
111
+ export function useCapabilityInvoker(): PluginRuntimeAccess {
112
+ return usePluginRenderContext().runtime;
113
+ }
114
+
106
115
  export function usePluginBrokerActions() {
107
116
  const { runtime } = usePluginRenderContext();
108
117
  return {
@@ -7,11 +7,24 @@ import { createAppRemoteController } from "./controller";
7
7
  import type { RemoteControlSchema, RemoteUiNodeSnapshot } from "./types";
8
8
  import type { RemoteUiRegistry } from "./semantic-tree";
9
9
 
10
- function createRegistryHarness() {
11
- let state = createInitialState({
10
+ function createRegistryHarness(options: { withFloatingPane?: boolean } = {}) {
11
+ const config = {
12
12
  ...createDefaultConfig("/tmp/gloom-remote-controller"),
13
13
  onboardingComplete: true,
14
- });
14
+ };
15
+ if (options.withFloatingPane) {
16
+ const instance = {
17
+ instanceId: "help:test",
18
+ paneId: "help",
19
+ binding: { kind: "none" as const },
20
+ };
21
+ config.layout = {
22
+ ...config.layout,
23
+ instances: [...config.layout.instances, instance],
24
+ floating: [{ instanceId: instance.instanceId, x: 8, y: 4, width: 60, height: 24 }],
25
+ };
26
+ }
27
+ let state = createInitialState(config);
15
28
  const actions: AppAction[] = [];
16
29
  const dispatch: Dispatch<AppAction> = (action) => {
17
30
  actions.push(action);
@@ -440,7 +453,7 @@ describe("createAppRemoteController", () => {
440
453
  });
441
454
 
442
455
  test("closes floating panes and grids visible panes through layout helpers", async () => {
443
- const { controller, getState } = createRegistryHarness();
456
+ const { controller, getState } = createRegistryHarness({ withFloatingPane: true });
444
457
  expect(getState().config.layout.floating.length).toBeGreaterThan(0);
445
458
 
446
459
  const closeResponse = await controller.handle({
@@ -2,7 +2,7 @@ import type { ApplicationMenuItemConfig } from "electrobun/bun";
2
2
  import type { DesktopApplicationMenuCommand } from "../../../../types/desktop-menu";
3
3
 
4
4
  export const ELECTROBUN_APPLICATION_MENU_ACTION = "gloom.application-menu.select";
5
- const GITHUB_ISSUE_URL = "https://github.com/vincelwt/gloomberb/issues/new/choose";
5
+ const GITHUB_ISSUE_URL = "https://github.com/gloom-sh/gloomberb/issues/new/choose";
6
6
 
7
7
  export type ElectrobunApplicationMenuCommand =
8
8
  | DesktopApplicationMenuCommand
@@ -225,8 +225,8 @@ async function writeTextEnsuringParent(path: string, value: string): Promise<voi
225
225
  async function deleteFileIfPresent(path: string): Promise<void> {
226
226
  try {
227
227
  await unlink(path);
228
- } catch {
229
- // ignore missing files
228
+ } catch (error) {
229
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
230
230
  }
231
231
  }
232
232
 
@@ -0,0 +1,64 @@
1
+ import { expect, test } from "bun:test";
2
+ import { CapabilityRegistry } from "../../../../capabilities/registry";
3
+ import { DesktopCapabilityBridge } from "./capability-bridge";
4
+
5
+ type TestRpc = {
6
+ key: string;
7
+ send: { "capability.event": (_payload: { subscriptionId: string; event: unknown }) => void };
8
+ };
9
+
10
+ const rpc = (key: string): TestRpc => ({
11
+ key,
12
+ send: { "capability.event": () => {} },
13
+ });
14
+
15
+ test("desktop capability cancellation is window-scoped and window cleanup aborts active work", async () => {
16
+ const signals = new Map<string, AbortSignal>();
17
+ const registry = new CapabilityRegistry();
18
+ registry.register("test", {
19
+ id: "test.capability",
20
+ kind: "plugin-service",
21
+ name: "Test",
22
+ operations: {
23
+ wait: {
24
+ kind: "query",
25
+ rendererSafe: true,
26
+ handler: (input: { name: string }, context) => new Promise((_resolve, reject) => {
27
+ const signal = context.signal!;
28
+ signals.set(input.name, signal);
29
+ signal.addEventListener("abort", () => reject(signal.reason), { once: true });
30
+ }),
31
+ },
32
+ },
33
+ });
34
+ const bridge = new DesktopCapabilityBridge<TestRpc>({
35
+ getRegistry: () => registry,
36
+ getWindowKey: (client) => client.key,
37
+ });
38
+ const windowA = rpc("window-a");
39
+ const windowB = rpc("window-b");
40
+ const invoke = (client: TestRpc, name: string) => bridge.handle(client, {
41
+ method: "capability.invoke",
42
+ payload: {
43
+ capabilityId: "test.capability",
44
+ operationId: "wait",
45
+ payload: { name },
46
+ invocationId: "same-client-id",
47
+ },
48
+ });
49
+
50
+ const first = invoke(windowA, "first").catch((error) => error);
51
+ const second = invoke(windowB, "second").catch((error) => error);
52
+
53
+ await bridge.handle(windowA, {
54
+ method: "capability.cancel",
55
+ payload: { invocationId: "same-client-id" },
56
+ });
57
+ expect(await first).toMatchObject({ name: "AbortError" });
58
+ expect(signals.get("first")?.aborted).toBe(true);
59
+ expect(signals.get("second")?.aborted).toBe(false);
60
+
61
+ bridge.disposeWindow("window-b");
62
+ expect(await second).toMatchObject({ name: "AbortError" });
63
+ expect(signals.get("second")?.aborted).toBe(true);
64
+ });
@@ -17,6 +17,7 @@ interface DesktopCapabilityBridgeOptions<Rpc extends DesktopCapabilityRpc> {
17
17
 
18
18
  export class DesktopCapabilityBridge<Rpc extends DesktopCapabilityRpc> {
19
19
  private readonly subscriptions = new Map<string, () => void>();
20
+ private readonly invocations = new Map<string, AbortController>();
20
21
 
21
22
  constructor(private readonly options: DesktopCapabilityBridgeOptions<Rpc>) {}
22
23
 
@@ -29,6 +30,8 @@ export class DesktopCapabilityBridge<Rpc extends DesktopCapabilityRpc> {
29
30
  }
30
31
  }
31
32
  this.subscriptions.clear();
33
+ for (const controller of this.invocations.values()) controller.abort();
34
+ this.invocations.clear();
32
35
  }
33
36
 
34
37
  disposeWindow(windowKey: string): void {
@@ -42,6 +45,11 @@ export class DesktopCapabilityBridge<Rpc extends DesktopCapabilityRpc> {
42
45
  }
43
46
  this.subscriptions.delete(id);
44
47
  }
48
+ for (const [id, controller] of this.invocations) {
49
+ if (!id.startsWith(scopedPrefix)) continue;
50
+ controller.abort();
51
+ this.invocations.delete(id);
52
+ }
45
53
  }
46
54
 
47
55
  async handle(
@@ -50,13 +58,39 @@ export class DesktopCapabilityBridge<Rpc extends DesktopCapabilityRpc> {
50
58
  ): Promise<unknown> {
51
59
  const registry = this.options.getRegistry();
52
60
  switch (request.method) {
53
- case "capability.invoke":
54
- return registry.invoke(
55
- request.payload.capabilityId,
56
- request.payload.operationId,
57
- request.payload.payload,
58
- { renderer: true },
59
- );
61
+ case "capability.invoke": {
62
+ const clientInvocationId = request.payload.invocationId;
63
+ if (!clientInvocationId) {
64
+ return registry.invoke(
65
+ request.payload.capabilityId,
66
+ request.payload.operationId,
67
+ request.payload.payload,
68
+ { renderer: true },
69
+ );
70
+ }
71
+ const scopedInvocationId = this.scopeClientId(rpc, clientInvocationId);
72
+ if (this.invocations.has(scopedInvocationId)) {
73
+ throw new Error(`Capability invocation "${clientInvocationId}" is already active.`);
74
+ }
75
+ const controller = new AbortController();
76
+ this.invocations.set(scopedInvocationId, controller);
77
+ try {
78
+ return await registry.invoke(
79
+ request.payload.capabilityId,
80
+ request.payload.operationId,
81
+ request.payload.payload,
82
+ { renderer: true, signal: controller.signal },
83
+ );
84
+ } finally {
85
+ if (this.invocations.get(scopedInvocationId) === controller) {
86
+ this.invocations.delete(scopedInvocationId);
87
+ }
88
+ }
89
+ }
90
+ case "capability.cancel": {
91
+ this.invocations.get(this.scopeClientId(rpc, request.payload.invocationId))?.abort();
92
+ return null;
93
+ }
60
94
  case "capability.subscribe": {
61
95
  const clientSubscriptionId = request.payload.subscriptionId;
62
96
  const scopedSubscriptionId = this.scopeClientId(rpc, clientSubscriptionId);
@@ -7,6 +7,7 @@ import type {
7
7
  DesktopRestartMessage,
8
8
  DesktopWindowControlAction,
9
9
  } from "../../shared/protocol";
10
+ import { safeExternalUrl } from "../../../../utils/external-url";
10
11
  import { getContextMenuRequestId, normalizeContextMenuItems } from "../context-menu/normalize";
11
12
  import { MAIN_WINDOW_RPC_KEY } from "../window/focus";
12
13
 
@@ -102,12 +103,14 @@ export function handleDesktopHostRequest<TRpc>({
102
103
  }
103
104
  return null;
104
105
  }
105
- case "host.openExternal":
106
+ case "host.openExternal": {
106
107
  if (typeof request.payload.url !== "string") {
107
108
  throw new Error("host.openExternal requires a URL.");
108
109
  }
109
- Utils.openExternal(request.payload.url);
110
+ const externalUrl = safeExternalUrl(request.payload.url);
111
+ if (externalUrl) Utils.openExternal(externalUrl);
110
112
  return null;
113
+ }
111
114
  case "host.copyText":
112
115
  Utils.clipboardWriteText(normalizeText(request.payload.text) ?? "");
113
116
  return null;
@@ -1,11 +1,21 @@
1
1
  import { afterEach, describe, expect, test } from "bun:test";
2
+ import {
3
+ connectionHealth,
4
+ registerGloomCloudConnectionSources,
5
+ } from "../../../../core/connection-health";
2
6
  import { handleHttpFetch } from "./http-fetch";
3
7
 
4
8
  let server: ReturnType<typeof Bun.serve> | null = null;
9
+ let disposeConnections: (() => void) | null = null;
10
+ const originalApiUrl = process.env.GLOOMBERB_API_URL;
5
11
 
6
12
  afterEach(() => {
7
13
  server?.stop(true);
8
14
  server = null;
15
+ disposeConnections?.();
16
+ disposeConnections = null;
17
+ if (originalApiUrl === undefined) delete process.env.GLOOMBERB_API_URL;
18
+ else process.env.GLOOMBERB_API_URL = originalApiUrl;
9
19
  });
10
20
 
11
21
  describe("handleHttpFetch", () => {
@@ -40,6 +50,29 @@ describe("handleHttpFetch", () => {
40
50
  expect(response.setCookie).toEqual(["substack.sid=sid123; Path=/; HttpOnly"]);
41
51
  });
42
52
 
53
+ test("reports proxied Gloom FRED requests in the backend registry", async () => {
54
+ server = Bun.serve({
55
+ port: 0,
56
+ fetch: () => Response.json({ observations: [], info: null }),
57
+ });
58
+ process.env.GLOOMBERB_API_URL = server.url.origin;
59
+ disposeConnections = registerGloomCloudConnectionSources(connectionHealth);
60
+
61
+ await handleHttpFetch({
62
+ url: new URL("/cloud/econ/series/VIXCLS", server.url).toString(),
63
+ });
64
+
65
+ expect(connectionHealth.getSnapshot().sources.map((source) => ({
66
+ id: source.id,
67
+ status: source.status,
68
+ operation: source.lastOperation,
69
+ }))).toEqual([
70
+ { id: "gloom-cloud-http", status: "connected", operation: "GET /cloud/econ/series/VIXCLS" },
71
+ { id: "gloom-cloud-socket", status: "idle", operation: null },
72
+ { id: "gloom-cloud-fred", status: "connected", operation: "GET /cloud/econ/series/VIXCLS" },
73
+ ]);
74
+ });
75
+
43
76
  test("aborts the backend fetch at the requested deadline", async () => {
44
77
  server = Bun.serve({
45
78
  port: 0,
@@ -1,5 +1,38 @@
1
+ import {
2
+ connectionHealth,
3
+ GLOOM_CLOUD_FRED_CONNECTION_ID,
4
+ GLOOM_CLOUD_HTTP_CONNECTION_ID,
5
+ } from "../../../../core/connection-health";
1
6
  import type { DesktopBackendRequestPayload, DesktopHttpFetchResponse } from "../../shared/protocol";
2
7
 
8
+ function isGloomCloudUrl(url: URL): boolean {
9
+ try {
10
+ return url.origin === new URL(process.env.GLOOMBERB_API_URL ?? "https://api.gloom.sh").origin;
11
+ } catch {
12
+ return false;
13
+ }
14
+ }
15
+
16
+ function reportCloudRequest(
17
+ url: URL,
18
+ method: string,
19
+ latencyMs: number,
20
+ success: boolean,
21
+ error?: unknown,
22
+ ): void {
23
+ if (!isGloomCloudUrl(url)) return;
24
+ const report = {
25
+ operation: `${method} ${url.pathname}`,
26
+ success,
27
+ latencyMs,
28
+ ...(error === undefined ? {} : { error }),
29
+ };
30
+ connectionHealth.reportRequest(GLOOM_CLOUD_HTTP_CONNECTION_ID, report);
31
+ if (url.pathname.startsWith("/cloud/econ/series/")) {
32
+ connectionHealth.reportRequest(GLOOM_CLOUD_FRED_CONNECTION_ID, report);
33
+ }
34
+ }
35
+
3
36
  function normalizeHttpFetchHeaders(headers: unknown): Record<string, string> {
4
37
  if (!headers || typeof headers !== "object" || Array.isArray(headers)) {
5
38
  return {};
@@ -46,13 +79,27 @@ export async function handleHttpFetch(
46
79
  ? init.timeoutMs
47
80
  : undefined;
48
81
 
49
- const response = await fetch(url, {
82
+ const startedAt = performance.now();
83
+ let response: Response;
84
+ try {
85
+ response = await fetch(url, {
86
+ method,
87
+ headers: normalizeHttpFetchHeaders(init.headers),
88
+ body,
89
+ redirect,
90
+ signal: timeoutMs ? AbortSignal.timeout(timeoutMs) : undefined,
91
+ });
92
+ } catch (error) {
93
+ reportCloudRequest(url, method, performance.now() - startedAt, false, error);
94
+ throw error;
95
+ }
96
+ reportCloudRequest(
97
+ url,
50
98
  method,
51
- headers: normalizeHttpFetchHeaders(init.headers),
52
- body,
53
- redirect,
54
- signal: timeoutMs ? AbortSignal.timeout(timeoutMs) : undefined,
55
- });
99
+ performance.now() - startedAt,
100
+ response.ok,
101
+ response.ok ? undefined : new Error(`${response.status} ${response.statusText}`.trim()),
102
+ );
56
103
  const responseHeaders: Record<string, string> = {};
57
104
  response.headers.forEach((value, key) => {
58
105
  responseHeaders[key] = value;
@@ -0,0 +1,27 @@
1
+ import { expect, test } from "bun:test";
2
+ import { CapabilityRegistry } from "../../../../capabilities";
3
+ import { desktopRendererCapabilityManifests } from "./initialization";
4
+
5
+ test("desktop init includes disabled renderer-safe manifests while invocation stays disabled", async () => {
6
+ let enabled = false;
7
+ const registry = new CapabilityRegistry({ isPluginEnabled: () => enabled });
8
+ registry.register("charts", {
9
+ id: "charts.test",
10
+ kind: "chart-series",
11
+ name: "Test Charts",
12
+ operations: {
13
+ resolve: { kind: "query", rendererSafe: true, handler: () => "ok" },
14
+ private: { kind: "read", handler: () => "private" },
15
+ },
16
+ });
17
+
18
+ expect(desktopRendererCapabilityManifests(registry)).toEqual([expect.objectContaining({
19
+ id: "charts.test",
20
+ operations: [expect.objectContaining({ id: "resolve" })],
21
+ })]);
22
+ await expect(registry.invoke("charts.test", "resolve", {}, { renderer: true }))
23
+ .rejects.toThrow("not available");
24
+
25
+ enabled = true;
26
+ await expect(registry.invoke("charts.test", "resolve", {}, { renderer: true })).resolves.toBe("ok");
27
+ });
@@ -25,6 +25,7 @@ import {
25
25
  paneIdFromDetachedRpcKey,
26
26
  } from "../window/focus";
27
27
  import type { DesktopBackendRequestPayload, ElectrobunBackendInit } from "../../shared/protocol";
28
+ import type { CapabilityRegistry } from "../../../../capabilities";
28
29
 
29
30
  interface DesktopWindowTarget {
30
31
  kind: "main" | "detached";
@@ -81,6 +82,10 @@ function normalizeInitWindowTarget<TRpc>(
81
82
  };
82
83
  }
83
84
 
85
+ export function desktopRendererCapabilityManifests(registry: CapabilityRegistry) {
86
+ return registry.manifests({ rendererOnly: true, includeDisabled: true });
87
+ }
88
+
84
89
  function buildInitializationPayload(
85
90
  config: AppConfig,
86
91
  services: AppServices,
@@ -93,7 +98,7 @@ function buildInitializationPayload(
93
98
  desktopSnapshot: options.getDesktopSnapshot(),
94
99
  desktopThemePreview: options.desktopThemePreview,
95
100
  pluginState: loadDesktopPluginState(services.pluginRegistry),
96
- capabilityManifests: services.pluginRegistry.capabilities.manifests({ rendererOnly: true }),
101
+ capabilityManifests: desktopRendererCapabilityManifests(services.pluginRegistry.capabilities),
97
102
  desktopPlatform: process.platform,
98
103
  windowKind: windowTarget.kind,
99
104
  paneId: windowTarget.paneId,
@@ -441,6 +441,7 @@ async function handleBackendRequest(
441
441
  case "remote.forward":
442
442
  return forwardRemoteControlRequest(request.payload.request);
443
443
  case "capability.invoke":
444
+ case "capability.cancel":
444
445
  case "capability.subscribe":
445
446
  case "capability.unsubscribe":
446
447
  return capabilityBridge.handle(rpc, request);
@@ -76,6 +76,7 @@ export interface DesktopCapabilityInvokeRequest {
76
76
  capabilityId: string;
77
77
  operationId: string;
78
78
  payload?: unknown;
79
+ invocationId?: string;
79
80
  }
80
81
 
81
82
  export interface DesktopCapabilitySubscribeRequest extends DesktopCapabilityInvokeRequest {
@@ -91,6 +92,7 @@ export interface DesktopBackendRequestMap {
91
92
  "media.resolveLiveStream": { request: LiveStreamResolveRequest; response: ResolvedLiveStream };
92
93
  "remote.forward": { request: { request: RemoteControlRequest }; response: RemoteControlResponse };
93
94
  "capability.invoke": { request: DesktopCapabilityInvokeRequest; response: unknown };
95
+ "capability.cancel": { request: { invocationId: string }; response: null };
94
96
  "capability.subscribe": { request: DesktopCapabilitySubscribeRequest; response: null };
95
97
  "capability.unsubscribe": { request: { subscriptionId: string }; response: null };
96
98
  "desktop.syncMainState": { request: { snapshot: DesktopSharedStateSnapshot }; response: null };