gloomberb 0.5.0 → 0.6.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 (500) hide show
  1. package/README.md +91 -11
  2. package/package.json +12 -5
  3. package/src/app.tsx +414 -191
  4. package/src/architecture/import-boundaries.test.ts +118 -0
  5. package/src/assets/gloomberb-logo.png +0 -0
  6. package/src/assets/gloomberb-logo.svg +73 -0
  7. package/src/brokers/account-cache.ts +157 -0
  8. package/src/brokers/profile-form.test.ts +73 -0
  9. package/src/brokers/profile-form.ts +109 -0
  10. package/src/brokers/remote-broker-adapter.ts +91 -0
  11. package/src/brokers/sync-broker-instance.test.ts +124 -1
  12. package/src/brokers/sync-broker-instance.ts +113 -17
  13. package/src/capabilities/factories.ts +100 -0
  14. package/src/capabilities/index.ts +4 -0
  15. package/src/capabilities/registry.test.ts +137 -0
  16. package/src/capabilities/registry.ts +131 -0
  17. package/src/capabilities/service-capabilities.ts +27 -0
  18. package/src/capabilities/types.ts +84 -0
  19. package/src/cli/commands/ticker.ts +11 -3
  20. package/src/cli/context.ts +19 -13
  21. package/src/cli/helpers.ts +1 -0
  22. package/src/cli/types.ts +2 -2
  23. package/src/components/chart/chart-controller.ts +4 -24
  24. package/src/components/chart/chart-data.ts +3 -14
  25. package/src/components/chart/chart-indicator-selector.tsx +28 -6
  26. package/src/components/chart/chart-pane-settings.ts +15 -28
  27. package/src/components/chart/chart-renderer.ts +74 -99
  28. package/src/components/chart/chart-resolution.ts +10 -10
  29. package/src/components/chart/chart-scroll.test.ts +6 -5
  30. package/src/components/chart/chart-scroll.ts +2 -2
  31. package/src/components/chart/chart-types.ts +0 -1
  32. package/src/components/chart/chart-viewport.ts +2 -1
  33. package/src/components/chart/chart.test.ts +37 -0
  34. package/src/components/chart/comparison-chart-data.ts +3 -17
  35. package/src/components/chart/comparison-chart-renderer.test.ts +27 -4
  36. package/src/components/chart/comparison-chart-renderer.ts +1 -1
  37. package/src/components/chart/comparison-stock-chart.tsx +304 -108
  38. package/src/components/chart/cursor-motion.ts +3 -2
  39. package/src/components/chart/indicators/bands.test.ts +0 -14
  40. package/src/components/chart/indicators/moving-averages.test.ts +0 -15
  41. package/src/components/chart/indicators/options.ts +3 -3
  42. package/src/components/chart/indicators/oscillators.test.ts +0 -21
  43. package/src/components/chart/indicators/types.ts +1 -2
  44. package/src/components/chart/native/chart-rasterizer.ts +37 -44
  45. package/src/components/chart/native/kitty-adapter.ts +2 -5
  46. package/src/components/chart/native/kitty-manager.ts +1 -1
  47. package/src/components/chart/native/kitty-protocol.ts +0 -1
  48. package/src/components/chart/native/kitty-support.ts +1 -1
  49. package/src/components/chart/native/kitty.test.ts +30 -13
  50. package/src/components/chart/native/renderer-selection.ts +1 -1
  51. package/src/components/chart/native/surface-manager.ts +1 -1
  52. package/src/components/chart/static-chart-surface.test.tsx +51 -0
  53. package/src/components/chart/static-chart-surface.tsx +191 -0
  54. package/src/components/chart/stock-chart-renderer-switch.test.tsx +242 -35
  55. package/src/components/chart/stock-chart.tsx +438 -141
  56. package/src/components/command-bar/__snapshots__/command-bar.test.tsx.snap +4 -4
  57. package/src/components/command-bar/command-bar.test.tsx +269 -20
  58. package/src/components/command-bar/command-bar.tsx +830 -353
  59. package/src/components/command-bar/command-registry.ts +10 -20
  60. package/src/components/command-bar/helpers.test.ts +1 -1
  61. package/src/components/command-bar/helpers.ts +9 -11
  62. package/src/components/command-bar/pane-template-display.ts +4 -8
  63. package/src/components/command-bar/root-shortcuts.ts +12 -11
  64. package/src/components/command-bar/view-model.test.ts +8 -42
  65. package/src/components/command-bar/view-model.ts +8 -36
  66. package/src/components/command-bar/workflow-ops.test.ts +76 -2
  67. package/src/components/command-bar/workflow-ops.ts +40 -9
  68. package/src/components/command-bar/workflow-types.ts +2 -1
  69. package/src/components/data-table-stack-view.test.tsx +206 -0
  70. package/src/components/data-table-stack-view.tsx +152 -0
  71. package/src/components/data-table-view.test.tsx +188 -0
  72. package/src/components/data-table-view.tsx +279 -0
  73. package/src/components/feed-data-table-stack-view.test.tsx +225 -0
  74. package/src/components/feed-data-table-stack-view.tsx +365 -0
  75. package/src/components/index.ts +26 -31
  76. package/src/components/layout/detached-pane-shell.tsx +185 -0
  77. package/src/components/layout/floating-pane.tsx +87 -13
  78. package/src/components/layout/header.tsx +139 -49
  79. package/src/components/layout/pane-content.tsx +40 -0
  80. package/src/components/layout/pane-footer.test.tsx +195 -0
  81. package/src/components/layout/pane-footer.tsx +410 -0
  82. package/src/components/layout/pane-header.test.tsx +39 -0
  83. package/src/components/layout/pane-header.tsx +192 -14
  84. package/src/components/layout/pane-sizing.ts +13 -4
  85. package/src/components/layout/pane-title.ts +35 -0
  86. package/src/components/layout/pane.tsx +56 -8
  87. package/src/components/layout/shell.test.tsx +322 -19
  88. package/src/components/layout/shell.tsx +1010 -366
  89. package/src/components/layout/status-bar.test.tsx +17 -10
  90. package/src/components/layout/status-bar.tsx +367 -100
  91. package/src/components/layout/titlebar-overlay.ts +2 -0
  92. package/src/components/markdown-editor.tsx +29 -14
  93. package/src/components/markdown-text.tsx +25 -24
  94. package/src/components/onboarding/onboarding-wizard.test.tsx +8 -3
  95. package/src/components/onboarding/onboarding-wizard.tsx +347 -473
  96. package/src/components/pane-settings-dialog.tsx +521 -89
  97. package/src/components/pane-template-wizard.test.tsx +1 -1
  98. package/src/components/pane-template-wizard.tsx +38 -37
  99. package/src/components/price-selector-dialog.tsx +22 -21
  100. package/src/components/ticker-badge-list.tsx +53 -0
  101. package/src/components/ticker-badge-text.test.tsx +44 -1
  102. package/src/components/ticker-badge-text.tsx +78 -38
  103. package/src/components/ticker-badge.tsx +28 -7
  104. package/src/components/ticker-list-table-view.tsx +248 -0
  105. package/src/components/ticker-list-table.test.tsx +47 -1
  106. package/src/components/ticker-list-table.tsx +212 -41
  107. package/src/components/toggle-list.tsx +114 -8
  108. package/src/components/ui/button.tsx +24 -16
  109. package/src/components/ui/choice-dialog.tsx +89 -0
  110. package/src/components/ui/data-table.tsx +313 -114
  111. package/src/components/ui/external-link.tsx +52 -13
  112. package/src/components/ui/fields.tsx +44 -30
  113. package/src/components/ui/frame.tsx +24 -57
  114. package/src/components/ui/index.ts +15 -50
  115. package/src/components/ui/list-view.tsx +65 -25
  116. package/src/components/ui/loading.tsx +5 -60
  117. package/src/components/ui/message-composer.test.tsx +81 -0
  118. package/src/components/ui/message-composer.tsx +139 -0
  119. package/src/components/ui/multi-select-dialog.tsx +100 -32
  120. package/src/components/ui/multi-select.ts +112 -0
  121. package/src/components/ui/native-select.tsx +120 -0
  122. package/src/components/ui/page-stack-view.tsx +49 -19
  123. package/src/components/ui/remote-image.tsx +75 -0
  124. package/src/components/ui/row-value-cache.test.ts +17 -0
  125. package/src/components/ui/row-value-cache.ts +41 -0
  126. package/src/components/ui/shortcut-hint.tsx +55 -0
  127. package/src/components/ui/status.tsx +12 -67
  128. package/src/components/ui/table-layout.ts +59 -0
  129. package/src/components/ui/tabs.tsx +249 -27
  130. package/src/components/ui/toggle.tsx +19 -127
  131. package/src/components/ui/ui.test.tsx +266 -53
  132. package/src/components/use-double-click-activation.ts +7 -2
  133. package/src/components/use-external-link-footer.ts +79 -0
  134. package/src/core/app-services.ts +95 -0
  135. package/src/core/state/app-state.ts +886 -0
  136. package/src/{state → core/state}/session-persistence.ts +7 -6
  137. package/src/data/config-layout.ts +225 -0
  138. package/src/data/config-store-node.ts +390 -0
  139. package/src/data/config-store.test.ts +72 -0
  140. package/src/data/config-store.ts +35 -478
  141. package/src/data/sqlite-json.ts +2 -5
  142. package/src/data/sqlite-retry.ts +1 -0
  143. package/src/index.tsx +2 -68
  144. package/src/market-data/coordinator-subscriptions.test.ts +125 -0
  145. package/src/market-data/coordinator.test.ts +73 -4
  146. package/src/market-data/coordinator.ts +190 -24
  147. package/src/market-data/hooks.test.tsx +1 -1
  148. package/src/market-data/hooks.tsx +109 -87
  149. package/src/market-data/query-store.ts +3 -3
  150. package/src/market-data/request-types.test.ts +0 -14
  151. package/src/market-data/result-types.ts +1 -0
  152. package/src/news/aggregator.test.ts +267 -0
  153. package/src/news/aggregator.ts +452 -0
  154. package/src/news/hooks.ts +39 -0
  155. package/src/news/ticker-symbols.ts +21 -0
  156. package/src/news/types.ts +77 -0
  157. package/src/notifications/app-notifier.ts +21 -10
  158. package/src/pane-settings.test.ts +0 -2
  159. package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +124 -134
  160. package/src/plugins/builtin/ai/gloomberb-cli.ts +14 -13
  161. package/src/plugins/builtin/ai/providers.ts +1 -9
  162. package/src/plugins/builtin/ai/runner.ts +41 -5
  163. package/src/plugins/builtin/ai/screener-pane.test.tsx +37 -55
  164. package/src/plugins/builtin/ai/screener-pane.tsx +231 -276
  165. package/src/plugins/builtin/ai/settings.ts +0 -1
  166. package/src/plugins/builtin/ai/utils.ts +1 -71
  167. package/src/plugins/builtin/alerts/alert-engine.test.ts +1 -38
  168. package/src/plugins/builtin/alerts/index.test.tsx +61 -82
  169. package/src/plugins/builtin/alerts/index.tsx +263 -155
  170. package/src/plugins/builtin/alerts/types.ts +7 -0
  171. package/src/plugins/builtin/analytics/index.test.tsx +13 -8
  172. package/src/plugins/builtin/analytics/index.tsx +78 -120
  173. package/src/plugins/builtin/analytics/metrics.test.ts +0 -4
  174. package/src/plugins/builtin/ask-ai.test.tsx +55 -54
  175. package/src/plugins/builtin/broker-manager/index.test.tsx +168 -0
  176. package/src/plugins/builtin/broker-manager/index.tsx +759 -0
  177. package/src/plugins/builtin/broker-manager/model.test.ts +107 -0
  178. package/src/plugins/builtin/broker-manager/model.ts +142 -0
  179. package/src/plugins/builtin/chat-controller.test.ts +150 -0
  180. package/src/plugins/builtin/chat-controller.ts +88 -16
  181. package/src/plugins/builtin/chat.test.tsx +283 -36
  182. package/src/plugins/builtin/chat.tsx +373 -172
  183. package/src/plugins/builtin/cloud-auth-actions.tsx +72 -0
  184. package/src/plugins/builtin/cloud-tweets.tsx +896 -0
  185. package/src/plugins/builtin/comparison-chart.test.tsx +46 -57
  186. package/src/plugins/builtin/comparison-chart.tsx +11 -11
  187. package/src/plugins/builtin/correlation/compute.test.ts +0 -17
  188. package/src/plugins/builtin/correlation/index.tsx +51 -46
  189. package/src/plugins/builtin/correlation/settings.ts +1 -2
  190. package/src/plugins/builtin/debug.tsx +99 -70
  191. package/src/plugins/builtin/earnings/earnings-cache.test.ts +2 -2
  192. package/src/plugins/builtin/earnings/earnings-cache.ts +12 -3
  193. package/src/plugins/builtin/earnings/earnings-data.test.ts +42 -11
  194. package/src/plugins/builtin/earnings/earnings-data.ts +70 -5
  195. package/src/plugins/builtin/earnings/index.tsx +187 -94
  196. package/src/plugins/builtin/econ/calendar-source.ts +9 -116
  197. package/src/plugins/builtin/econ/fred-client.test.ts +1 -14
  198. package/src/plugins/builtin/econ/fred-series-map.ts +1 -0
  199. package/src/plugins/builtin/econ/index.tsx +236 -421
  200. package/src/plugins/builtin/econ/types.ts +1 -0
  201. package/src/plugins/builtin/fx-matrix/index.tsx +43 -45
  202. package/src/plugins/builtin/fx-matrix/pairs.ts +1 -0
  203. package/src/plugins/builtin/help.test.tsx +120 -60
  204. package/src/plugins/builtin/help.tsx +266 -82
  205. package/src/plugins/builtin/holders/index.test.ts +45 -0
  206. package/src/plugins/builtin/holders/index.tsx +949 -0
  207. package/src/plugins/builtin/insider/index.tsx +207 -253
  208. package/src/plugins/builtin/insider/insider-data.test.ts +1 -11
  209. package/src/plugins/builtin/insider/insider-data.ts +1 -0
  210. package/src/plugins/builtin/layout-manager.tsx +12 -40
  211. package/src/plugins/builtin/market-movers/index.tsx +70 -144
  212. package/src/plugins/builtin/market-movers/screener.test.ts +0 -18
  213. package/src/plugins/builtin/news-wire/breaking-notifications.test.ts +98 -0
  214. package/src/plugins/builtin/news-wire/breaking-notifications.ts +110 -0
  215. package/src/plugins/builtin/news-wire/breaking-pane.tsx +157 -0
  216. package/src/plugins/builtin/news-wire/categories.ts +114 -0
  217. package/src/plugins/builtin/news-wire/default-feeds.ts +3 -0
  218. package/src/plugins/builtin/news-wire/digest-store.ts +44 -0
  219. package/src/plugins/builtin/news-wire/feed-config.test.ts +90 -0
  220. package/src/plugins/builtin/news-wire/feed-config.ts +216 -0
  221. package/src/plugins/builtin/news-wire/feed-pane.tsx +21 -0
  222. package/src/plugins/builtin/news-wire/hash.ts +9 -0
  223. package/src/plugins/builtin/news-wire/index.tsx +92 -0
  224. package/src/plugins/builtin/news-wire/industry-pane.tsx +140 -0
  225. package/src/plugins/builtin/news-wire/news-detail-view.tsx +256 -0
  226. package/src/plugins/builtin/news-wire/news-footer.ts +28 -0
  227. package/src/plugins/builtin/news-wire/news-preset-pane.tsx +94 -0
  228. package/src/plugins/builtin/news-wire/news-query-presets.ts +40 -0
  229. package/src/plugins/builtin/news-wire/news-table.test.tsx +206 -0
  230. package/src/plugins/builtin/news-wire/news-table.tsx +296 -0
  231. package/src/plugins/builtin/news-wire/persisted-articles.ts +92 -0
  232. package/src/plugins/builtin/news-wire/read-state.test.ts +30 -0
  233. package/src/plugins/builtin/news-wire/read-state.ts +76 -0
  234. package/src/plugins/builtin/news-wire/rss-parser.test.ts +184 -0
  235. package/src/plugins/builtin/news-wire/rss-parser.ts +213 -0
  236. package/src/plugins/builtin/news-wire/rss-source.test.ts +159 -0
  237. package/src/plugins/builtin/news-wire/rss-source.ts +181 -0
  238. package/src/plugins/builtin/news-wire/top-pane.tsx +21 -0
  239. package/src/plugins/builtin/news.tsx +98 -25
  240. package/src/plugins/builtin/notes-files.ts +51 -10
  241. package/src/plugins/builtin/notes.tsx +332 -131
  242. package/src/plugins/builtin/options.test.tsx +185 -0
  243. package/src/plugins/builtin/options.tsx +346 -206
  244. package/src/plugins/builtin/plugin-groups.ts +92 -0
  245. package/src/plugins/builtin/portfolio-list/header.tsx +72 -37
  246. package/src/plugins/builtin/portfolio-list/index.tsx +25 -42
  247. package/src/plugins/builtin/portfolio-list/metrics.ts +1 -1
  248. package/src/plugins/builtin/portfolio-list/pane.tsx +246 -164
  249. package/src/plugins/builtin/portfolio-list/settings.ts +19 -51
  250. package/src/plugins/builtin/portfolio-list/summary.tsx +21 -16
  251. package/src/plugins/builtin/portfolio-list/table.tsx +66 -25
  252. package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.test.tsx +1 -1
  253. package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.ts +1 -1
  254. package/src/plugins/builtin/portfolio-list.test.ts +60 -1
  255. package/src/plugins/builtin/portfolio-list.test.tsx +122 -55
  256. package/src/plugins/builtin/quote-monitor.test.tsx +2 -27
  257. package/src/plugins/builtin/research/index.tsx +724 -0
  258. package/src/plugins/builtin/sec.tsx +67 -19
  259. package/src/plugins/builtin/sectors/index.tsx +42 -80
  260. package/src/plugins/builtin/sectors/sector-data.ts +1 -0
  261. package/src/plugins/builtin/ticker-detail/chart-tab.tsx +25 -39
  262. package/src/plugins/builtin/ticker-detail/financials-tab.tsx +108 -58
  263. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +383 -271
  264. package/src/plugins/builtin/ticker-detail/pane.tsx +135 -49
  265. package/src/plugins/builtin/ticker-detail/quote-monitor.tsx +43 -27
  266. package/src/plugins/builtin/ticker-detail/settings.ts +3 -6
  267. package/src/plugins/builtin/ticker-detail-chart-tab-switch.test.tsx +16 -10
  268. package/src/plugins/builtin/ticker-detail.test.tsx +156 -33
  269. package/src/plugins/builtin/ticker-surface.ts +68 -0
  270. package/src/plugins/builtin/world-indices/index.tsx +44 -82
  271. package/src/plugins/builtin/world-indices/indices.ts +1 -0
  272. package/src/plugins/builtin/yahoo.ts +55 -1
  273. package/src/plugins/builtin/yield-curve/index.tsx +87 -145
  274. package/src/plugins/builtin/yield-curve/treasury-data.ts +6 -77
  275. package/src/plugins/catalog-ui.test.ts +24 -0
  276. package/src/plugins/catalog-ui.ts +44 -0
  277. package/src/plugins/catalog.ts +2 -50
  278. package/src/plugins/ibkr/__snapshots__/trade-tab.test.tsx.snap +5 -5
  279. package/src/plugins/ibkr/account-cache.test.ts +15 -14
  280. package/src/plugins/ibkr/account-cache.ts +15 -120
  281. package/src/plugins/ibkr/broker-adapter.ts +67 -0
  282. package/src/plugins/ibkr/config.test.ts +0 -18
  283. package/src/plugins/ibkr/dialogs.tsx +14 -56
  284. package/src/plugins/ibkr/flex.test.ts +57 -2
  285. package/src/plugins/ibkr/flex.ts +116 -12
  286. package/src/plugins/ibkr/gateway-helpers.ts +8 -3
  287. package/src/plugins/ibkr/gateway-service-native.ts +2381 -0
  288. package/src/plugins/ibkr/gateway-service.test.ts +47 -9
  289. package/src/plugins/ibkr/gateway-service.ts +264 -2235
  290. package/src/plugins/ibkr/gateway-types.ts +27 -0
  291. package/src/plugins/ibkr/index.test.tsx +3 -11
  292. package/src/plugins/ibkr/index.tsx +1 -0
  293. package/src/plugins/ibkr/price-normalization.test.ts +1 -6
  294. package/src/plugins/ibkr/price-normalization.ts +1 -0
  295. package/src/plugins/ibkr/trade-badge.tsx +6 -5
  296. package/src/plugins/ibkr/trade-tab.test.tsx +2 -2
  297. package/src/plugins/ibkr/trade-tab.tsx +164 -117
  298. package/src/plugins/ibkr/trading-pane.tsx +117 -83
  299. package/src/plugins/pane-manager.test.ts +3 -1
  300. package/src/plugins/pane-manager.ts +93 -19
  301. package/src/plugins/plugin-runtime.test.tsx +176 -74
  302. package/src/plugins/plugin-runtime.tsx +228 -10
  303. package/src/plugins/prediction-markets/cache.ts +49 -0
  304. package/src/plugins/prediction-markets/categories.ts +0 -10
  305. package/src/plugins/prediction-markets/chart.test.tsx +1 -1
  306. package/src/plugins/prediction-markets/chart.tsx +21 -20
  307. package/src/plugins/prediction-markets/cli.ts +1 -0
  308. package/src/plugins/prediction-markets/controller-data.ts +135 -89
  309. package/src/plugins/prediction-markets/controller.ts +37 -108
  310. package/src/plugins/prediction-markets/detail/book.tsx +23 -22
  311. package/src/plugins/prediction-markets/detail/outcomes.tsx +33 -32
  312. package/src/plugins/prediction-markets/detail/overview.tsx +17 -12
  313. package/src/plugins/prediction-markets/detail/pane.tsx +59 -45
  314. package/src/plugins/prediction-markets/detail/rules.tsx +22 -7
  315. package/src/plugins/prediction-markets/detail/shared.tsx +3 -2
  316. package/src/plugins/prediction-markets/detail/trades.tsx +12 -11
  317. package/src/plugins/prediction-markets/detail.test.tsx +1 -1
  318. package/src/plugins/prediction-markets/keyboard.ts +1 -0
  319. package/src/plugins/prediction-markets/launch.test.ts +1 -1
  320. package/src/plugins/prediction-markets/launch.ts +1 -1
  321. package/src/plugins/prediction-markets/metrics.ts +25 -12
  322. package/src/plugins/prediction-markets/pane.test.tsx +16 -7
  323. package/src/plugins/prediction-markets/pane.tsx +180 -82
  324. package/src/plugins/prediction-markets/plugin.test.tsx +37 -45
  325. package/src/plugins/prediction-markets/services/fetch.ts +27 -10
  326. package/src/plugins/prediction-markets/services/kalshi-adapter.ts +20 -3
  327. package/src/plugins/prediction-markets/services/polymarket-adapter.ts +29 -11
  328. package/src/plugins/prediction-markets/settings.ts +0 -5
  329. package/src/plugins/prediction-markets/test-helpers.tsx +19 -44
  330. package/src/plugins/registry.test.ts +307 -0
  331. package/src/plugins/registry.ts +293 -82
  332. package/src/react/input.ts +53 -0
  333. package/src/react/plugins/plugin-slot.tsx +21 -0
  334. package/src/react/use-raf-callback.ts +44 -0
  335. package/src/renderers/electrobun/bun/application-menu-click.test.ts +41 -0
  336. package/src/renderers/electrobun/bun/application-menu-click.ts +55 -0
  337. package/src/renderers/electrobun/bun/application-menu.ts +118 -0
  338. package/src/renderers/electrobun/bun/context-menu-click.test.ts +67 -0
  339. package/src/renderers/electrobun/bun/context-menu-click.ts +61 -0
  340. package/src/renderers/electrobun/bun/context-menu-normalize.test.ts +52 -0
  341. package/src/renderers/electrobun/bun/context-menu-normalize.ts +67 -0
  342. package/src/renderers/electrobun/bun/core-capabilities.ts +328 -0
  343. package/src/renderers/electrobun/bun/desktop-workspace.test.ts +63 -0
  344. package/src/renderers/electrobun/bun/desktop-workspace.ts +162 -0
  345. package/src/renderers/electrobun/bun/index.ts +1149 -0
  346. package/src/renderers/electrobun/bun/window-frame.test.ts +40 -0
  347. package/src/renderers/electrobun/bun/window-frame.ts +46 -0
  348. package/src/renderers/electrobun/shared/protocol.ts +72 -0
  349. package/src/renderers/electrobun/view/ai-host.ts +93 -0
  350. package/src/renderers/electrobun/view/app-services.ts +348 -0
  351. package/src/renderers/electrobun/view/application-menu-bridge.ts +12 -0
  352. package/src/renderers/electrobun/view/backend-rpc.ts +173 -0
  353. package/src/renderers/electrobun/view/broker-remote-client.ts +154 -0
  354. package/src/renderers/electrobun/view/config-host.ts +35 -0
  355. package/src/renderers/electrobun/view/context-menu.test.ts +140 -0
  356. package/src/renderers/electrobun/view/context-menu.ts +164 -0
  357. package/src/renderers/electrobun/view/data-table.tsx +773 -0
  358. package/src/renderers/electrobun/view/desktop-controls.tsx +651 -0
  359. package/src/renderers/electrobun/view/desktop-window-bridge.ts +59 -0
  360. package/src/renderers/electrobun/view/desktop-window-snapshot.test.ts +57 -0
  361. package/src/renderers/electrobun/view/desktop-window-snapshot.ts +41 -0
  362. package/src/renderers/electrobun/view/dialog-host.tsx +62 -0
  363. package/src/renderers/electrobun/view/http-fetch.ts +113 -0
  364. package/src/renderers/electrobun/view/input-host.tsx +65 -0
  365. package/src/renderers/electrobun/view/key-event.test.ts +34 -0
  366. package/src/renderers/electrobun/view/key-event.ts +134 -0
  367. package/src/renderers/electrobun/view/main.tsx +104 -0
  368. package/src/renderers/electrobun/view/native-renderer.ts +110 -0
  369. package/src/renderers/electrobun/view/native-stubs/chart-kitty-support.ts +9 -0
  370. package/src/renderers/electrobun/view/native-stubs/chart-surface-manager.ts +25 -0
  371. package/src/renderers/electrobun/view/native-stubs/chart-surface-sync.ts +1 -0
  372. package/src/renderers/electrobun/view/notes-files.ts +56 -0
  373. package/src/renderers/electrobun/view/resource-store.test.ts +44 -0
  374. package/src/renderers/electrobun/view/resource-store.ts +106 -0
  375. package/src/renderers/electrobun/view/rpc-codec.ts +42 -0
  376. package/src/renderers/electrobun/view/scrollbar-activity.ts +23 -0
  377. package/src/renderers/electrobun/view/toast-host.tsx +65 -0
  378. package/src/renderers/electrobun/view/ui-host.tsx +1490 -0
  379. package/src/renderers/electrobun/view/update-host.ts +38 -0
  380. package/src/renderers/opentui/dialog-host.test.tsx +44 -0
  381. package/src/renderers/opentui/dialog-host.tsx +56 -0
  382. package/src/renderers/opentui/host.tsx +134 -0
  383. package/src/renderers/opentui/input-host.tsx +24 -0
  384. package/src/renderers/opentui/start.tsx +71 -0
  385. package/src/renderers/opentui/test-utils.tsx +76 -0
  386. package/src/renderers/opentui/toast-host.tsx +10 -0
  387. package/src/renderers/opentui/ui-host.tsx +97 -0
  388. package/src/sources/gloomberb-cloud.test.ts +288 -17
  389. package/src/sources/gloomberb-cloud.ts +176 -9
  390. package/src/sources/provider-errors.ts +1 -0
  391. package/src/sources/provider-router.test.ts +220 -81
  392. package/src/sources/provider-router.ts +294 -111
  393. package/src/sources/sec-edgar.ts +19 -6
  394. package/src/sources/yahoo-finance.test.ts +0 -5
  395. package/src/sources/yahoo-finance.ts +420 -11
  396. package/src/state/app-activity.ts +7 -7
  397. package/src/state/app-bootstrap.ts +114 -40
  398. package/src/state/app-context-selector.test.tsx +70 -1
  399. package/src/state/app-context.test.ts +71 -3
  400. package/src/state/app-context.test.tsx +14 -7
  401. package/src/state/app-context.tsx +205 -869
  402. package/src/state/config-save-scheduler.ts +35 -0
  403. package/src/state/persist-scheduler.test.ts +67 -0
  404. package/src/state/persist-scheduler.ts +69 -0
  405. package/src/state/selectors-ui.ts +55 -0
  406. package/src/state/selectors.ts +15 -71
  407. package/src/state/session-persistence.test.ts +1 -1
  408. package/src/state/ticker-refresh-queue.ts +52 -3
  409. package/src/state/use-inline-tickers.ts +18 -16
  410. package/src/state/use-quote-streaming.test.ts +0 -6
  411. package/src/state/use-quote-streaming.test.tsx +1 -1
  412. package/src/state/use-quote-streaming.ts +3 -2
  413. package/src/state/use-session-snapshot.ts +32 -17
  414. package/src/test-support/plugin-runtime.ts +149 -0
  415. package/src/theme/color-utils.ts +1 -0
  416. package/src/theme/colors.ts +16 -0
  417. package/src/types/broker.ts +16 -1
  418. package/src/types/capability-route-source.ts +23 -0
  419. package/src/types/config.test.ts +23 -0
  420. package/src/types/config.ts +145 -10
  421. package/src/types/context-menu.ts +95 -0
  422. package/src/types/data-provider.ts +34 -3
  423. package/src/types/desktop-menu.ts +13 -0
  424. package/src/types/desktop-window.ts +29 -0
  425. package/src/types/financials.ts +123 -2
  426. package/src/types/instrument.ts +1 -0
  427. package/src/types/news-source.ts +9 -0
  428. package/src/types/persistence.ts +1 -0
  429. package/src/types/plugin.ts +32 -8
  430. package/src/ui/ascii-font.ts +85 -0
  431. package/src/ui/context-menu.test.ts +94 -0
  432. package/src/ui/context-menu.tsx +433 -0
  433. package/src/ui/dialog.tsx +55 -0
  434. package/src/ui/host.tsx +361 -0
  435. package/src/ui/index.tsx +126 -0
  436. package/src/ui/toast.tsx +40 -0
  437. package/src/updater.test.ts +69 -0
  438. package/src/updater.ts +61 -18
  439. package/src/utils/api-client.test.ts +186 -6
  440. package/src/utils/api-client.ts +433 -33
  441. package/src/utils/back-navigation.ts +6 -0
  442. package/src/utils/cli-output.ts +1 -0
  443. package/src/utils/currency-units.test.ts +1 -6
  444. package/src/utils/currency-units.ts +1 -0
  445. package/src/utils/debug-log.ts +68 -0
  446. package/src/utils/dom-screenshot.ts +326 -0
  447. package/src/utils/exchange-rate-map.ts +1 -0
  448. package/src/utils/exchanges.ts +214 -0
  449. package/src/utils/format.test.ts +7 -0
  450. package/src/utils/format.ts +1 -0
  451. package/src/utils/fuzzy-search.ts +1 -0
  452. package/src/utils/html-entities.test.ts +8 -0
  453. package/src/utils/html-entities.ts +32 -0
  454. package/src/utils/http-transport.ts +11 -0
  455. package/src/utils/main-thread-monitor.ts +127 -0
  456. package/src/utils/perf-marks.ts +54 -0
  457. package/src/utils/quote-freshness.ts +27 -50
  458. package/src/utils/quote-resolution.test.ts +5 -5
  459. package/src/utils/quote-resolution.ts +0 -1
  460. package/src/utils/quote-time.ts +1 -5
  461. package/src/utils/sec.test.ts +0 -12
  462. package/src/utils/selection-clipboard.ts +14 -6
  463. package/src/utils/shortcut-labels.test.ts +20 -0
  464. package/src/utils/shortcut-labels.ts +67 -0
  465. package/src/utils/slugify.ts +1 -0
  466. package/src/utils/terminal-input-reset.ts +1 -0
  467. package/src/utils/text-wrap.ts +82 -0
  468. package/src/utils/throttled-fetch.test.ts +19 -0
  469. package/src/utils/throttled-fetch.ts +11 -9
  470. package/src/utils/ticker-list.test.ts +1 -7
  471. package/src/utils/ticker-list.ts +1 -0
  472. package/src/utils/timestamp.ts +29 -0
  473. package/src/utils/tweet-text.test.ts +13 -0
  474. package/src/utils/tweet-text.ts +18 -0
  475. package/src/utils/url.ts +11 -0
  476. package/src/version.ts +1 -1
  477. package/src/components/chart/chart-control-hint.tsx +0 -39
  478. package/src/components/detail-feed-view.test.tsx +0 -180
  479. package/src/components/detail-feed-view.tsx +0 -531
  480. package/src/components/layout/pixel-mouse-mode.test.ts +0 -122
  481. package/src/components/layout/pixel-mouse.test.ts +0 -40
  482. package/src/components/spinner.tsx +0 -2
  483. package/src/components/tab-bar.tsx +0 -17
  484. package/src/components/ui/multi-select-chips.tsx +0 -69
  485. package/src/plugins/builtin/ai/index.test.tsx +0 -139
  486. package/src/plugins/builtin/ask-ai.tsx +0 -9
  487. package/src/plugins/builtin/correlation/plugin.test.ts +0 -44
  488. package/src/plugins/builtin/correlation/settings.test.ts +0 -34
  489. package/src/plugins/builtin/econ/calendar-source.test.ts +0 -219
  490. package/src/plugins/builtin/econ/fred-client.ts +0 -79
  491. package/src/plugins/builtin/fred-settings.ts +0 -10
  492. package/src/plugins/builtin/options-availability.test.tsx +0 -179
  493. package/src/plugins/builtin/options-availability.ts +0 -158
  494. package/src/plugins/builtin/portfolio-list/metrics.test.ts +0 -61
  495. package/src/plugins/builtin/yield-curve/treasury-data.test.ts +0 -212
  496. package/src/plugins/prediction-markets/status.ts +0 -24
  497. package/src/plugins/prediction-markets/table.tsx +0 -87
  498. package/src/utils/quote-time.test.ts +0 -22
  499. package/src/utils/selection-clipboard.test.ts +0 -43
  500. package/src/utils/terminal-input-reset.test.ts +0 -50
@@ -1,15 +1,20 @@
1
- import type { CliRenderer } from "@opentui/core";
2
- import { createReactSlotRegistry, createSlot } from "@opentui/react";
3
- import { createElement } from "react";
1
+ import { Fragment, createElement, type ReactNode } from "react";
4
2
  import type { AppPersistence } from "../data/app-persistence";
5
3
  import type { TickerRepository } from "../data/ticker-repository";
6
4
  import type { BrokerAdapter } from "../types/broker";
5
+ import {
6
+ CapabilityRegistry,
7
+ type NewsCapability,
8
+ type PluginCapability,
9
+ } from "../capabilities";
7
10
  import type { BrokerInstanceConfig, LayoutConfig, PaneInstanceConfig } from "../types/config";
8
11
  import type { DataProvider } from "../types/data-provider";
9
12
  import type { TickerFinancials } from "../types/financials";
10
13
  import type {
11
14
  AppNotificationRequest,
15
+ BrokerInstanceUpdateOptions,
12
16
  CommandDef,
17
+ ContextMenuProviderDef,
13
18
  CustomColumnDef,
14
19
  DetailTabDef,
15
20
  GloomPlugin,
@@ -25,6 +30,7 @@ import type {
25
30
  PluginResumeState,
26
31
  TickerAction,
27
32
  } from "../types/plugin";
33
+ import type { ContextMenuContext, ContextMenuItem } from "../types/context-menu";
28
34
  import type { TickerRecord } from "../types/ticker";
29
35
  import { addPaneFloating, removePane } from "./pane-manager";
30
36
  import { EventBus, type PluginEvents } from "./event-bus";
@@ -36,17 +42,31 @@ import {
36
42
  resolveCollectionForPane,
37
43
  resolveTickerForPane,
38
44
  type PaneRuntimeState,
39
- } from "../state/app-context";
45
+ } from "../core/state/app-state";
40
46
  import { deletePaneSetting, getPaneSettings, setPaneSetting } from "../pane-settings";
41
47
 
42
- let sharedDataProvider: DataProvider | undefined;
48
+ let sharedMarketData: DataProvider | undefined;
43
49
  let sharedRegistry: PluginRegistry | undefined;
44
- const SLOT_REGISTRY_CONTEXT: Record<string, never> = {};
50
+ type SlotEntry = {
51
+ pluginId: string;
52
+ order: number;
53
+ render: (props: unknown) => ReactNode;
54
+ };
55
+
56
+ interface ContextMenuProviderEntry {
57
+ pluginId: string;
58
+ provider: ContextMenuProviderDef;
59
+ }
60
+
61
+ interface PluginRegistryOptions {
62
+ enableCapabilityHandlers?: boolean;
63
+ wrapBrokerAdapter?: (broker: BrokerAdapter, pluginId: string) => BrokerAdapter;
64
+ }
45
65
 
46
- export function getSharedDataProvider(): DataProvider | undefined { return sharedDataProvider; }
66
+ export function getSharedMarketData(): DataProvider | undefined { return sharedMarketData; }
47
67
  export function getSharedRegistry(): PluginRegistry | undefined { return sharedRegistry; }
48
- export function setSharedDataProviderForTests(provider: DataProvider | undefined): void {
49
- sharedDataProvider = provider;
68
+ export function setSharedMarketDataForTests(provider: DataProvider | undefined): void {
69
+ sharedMarketData = provider;
50
70
  }
51
71
  export function setSharedRegistryForTests(registry: PluginRegistry | undefined): void {
52
72
  sharedRegistry = registry;
@@ -58,22 +78,27 @@ interface PluginItems {
58
78
  commands: string[];
59
79
  columns: string[];
60
80
  brokers: string[];
61
- dataProviders: string[];
81
+ capabilities: string[];
62
82
  detailTabs: string[];
63
83
  shortcuts: string[];
64
84
  tickerActions: string[];
85
+ contextMenuProviders: string[];
65
86
  eventDisposers: Array<() => void>;
87
+ capabilityDisposers: Array<() => void>;
88
+ newsQueryWatchDisposers: Array<() => void>;
66
89
  }
67
90
 
68
91
  export class PluginRegistry implements PluginRuntimeAccess {
69
- private slotRegistry;
92
+ private slotEntries = new Map<string, SlotEntry[]>();
70
93
  private plugins = new Map<string, GloomPlugin>();
71
94
  private unregisterFns = new Map<string, () => void>();
72
95
  private pluginItems = new Map<string, PluginItems>();
73
96
  private commandOwners = new Map<string, string>();
97
+ private paneOwners = new Map<string, string>();
74
98
  private paneTemplateOwners = new Map<string, string>();
75
99
  private shortcutOwners = new Map<string, string>();
76
- private dataProviderOwners = new Map<string, string>();
100
+ private capabilityOwners = new Map<string, string>();
101
+ private detailTabOwners = new Map<string, string>();
77
102
  private pluginResumeListeners = new Map<string, Set<() => void>>();
78
103
 
79
104
  private panesMap = new Map<string, PaneDef>();
@@ -81,15 +106,18 @@ export class PluginRegistry implements PluginRuntimeAccess {
81
106
  private commandsMap = new Map<string, CommandDef>();
82
107
  private columnsMap = new Map<string, CustomColumnDef>();
83
108
  private brokersMap = new Map<string, BrokerAdapter>();
84
- private dataProvidersMap = new Map<string, DataProvider>();
85
109
  private detailTabsMap = new Map<string, DetailTabDef>();
86
110
  private shortcutsMap = new Map<string, KeyboardShortcut>();
87
111
  private tickerActionsMap = new Map<string, TickerAction>();
112
+ private contextMenuProvidersMap = new Map<string, ContextMenuProviderEntry>();
88
113
 
89
114
  readonly events: EventBus;
90
- readonly dataProvider: DataProvider;
115
+ readonly capabilities: CapabilityRegistry;
116
+ readonly marketData: DataProvider;
91
117
  readonly tickerRepository: TickerRepository;
92
118
  readonly persistence: AppPersistence;
119
+ private readonly enableCapabilityHandlers: boolean;
120
+ private readonly wrapBrokerAdapter?: (broker: BrokerAdapter, pluginId: string) => BrokerAdapter;
93
121
 
94
122
  getTickerFn: ((symbol: string) => TickerRecord | null) = () => null;
95
123
  getDataFn: ((symbol: string) => TickerFinancials | null) = () => null;
@@ -97,7 +125,8 @@ export class PluginRegistry implements PluginRuntimeAccess {
97
125
  createBrokerInstanceFn: ((brokerType: string, label: string, values: Record<string, unknown>) => Promise<BrokerInstanceConfig>) = async () => {
98
126
  throw new Error("createBrokerInstanceFn not set");
99
127
  };
100
- updateBrokerInstanceFn: ((instanceId: string, values: Record<string, unknown>) => Promise<void>) = async () => {};
128
+ connectBrokerInstanceFn: ((instanceId: string) => Promise<void>) = async () => {};
129
+ updateBrokerInstanceFn: ((instanceId: string, values: Record<string, unknown>, options?: BrokerInstanceUpdateOptions) => Promise<void>) = async () => {};
101
130
  syncBrokerInstanceFn: ((instanceId: string) => Promise<void>) = async () => {};
102
131
  removeBrokerInstanceFn: ((instanceId: string) => Promise<void>) = async () => {};
103
132
 
@@ -114,42 +143,86 @@ export class PluginRegistry implements PluginRuntimeAccess {
114
143
  focusPaneFn: ((paneId: string) => void) = () => {};
115
144
  pinTickerFn: ((symbol: string, options?: { floating?: boolean; paneType?: string }) => void) = () => {};
116
145
  navigateTickerFn: ((symbol: string) => void) = () => {};
146
+ getMarketData = () => this.marketData;
147
+ getCapability = (capabilityId: string) => this.capabilities.get(capabilityId)?.capability ?? null;
148
+ getBrokerAdapter = (brokerType: string) => this.brokersMap.get(brokerType) ?? null;
149
+ connectBrokerInstance = (instanceId: string) => this.connectBrokerInstanceFn(instanceId);
150
+ updateBrokerInstance = (instanceId: string, values: Record<string, unknown>, options?: BrokerInstanceUpdateOptions) => (
151
+ this.updateBrokerInstanceFn(instanceId, values, options)
152
+ );
153
+ syncBrokerInstance = (instanceId: string) => this.syncBrokerInstanceFn(instanceId);
154
+ removeBrokerInstance = (instanceId: string) => this.removeBrokerInstanceFn(instanceId);
117
155
  pinTicker = (symbol: string, options?: { floating?: boolean; paneType?: string }) => {
118
156
  this.pinTickerFn(symbol, options);
119
157
  };
120
158
  navigateTicker = (symbol: string) => {
121
159
  this.navigateTickerFn(symbol);
122
160
  };
161
+ selectTicker = (symbol: string, paneId?: string) => {
162
+ this.selectTickerFn(symbol, paneId);
163
+ };
164
+ switchPanel = (panel: "left" | "right") => {
165
+ this.switchPanelFn(panel);
166
+ };
167
+ switchTab = (tabId: string, paneId?: string) => {
168
+ this.switchTabFn(tabId, paneId);
169
+ };
170
+ openCommandBar = (query?: string) => {
171
+ this.openCommandBarFn(query);
172
+ };
123
173
  openPluginCommandWorkflow = (commandId: string) => {
124
174
  this.openPluginCommandWorkflowFn(commandId);
125
175
  };
126
176
 
127
- getLayoutFn: (() => LayoutConfig) = () => ({ dockRoot: null, instances: [], floating: [] });
177
+ getLayoutFn: (() => LayoutConfig) = () => ({ dockRoot: null, instances: [], floating: [], detached: [] });
128
178
  updateLayoutFn: ((layout: LayoutConfig) => void) = () => {};
129
179
  getTermSizeFn: (() => { width: number; height: number }) = () => ({ width: 120, height: 40 });
130
180
 
181
+ registerNewsCapabilityFn: ((capability: NewsCapability) => () => void) = () => () => {};
182
+ watchNewsQueryFn: ((
183
+ query: import("../types/news-source").NewsQuery,
184
+ listener: (state: import("../types/news-source").NewsQueryState) => void,
185
+ ) => () => void) = () => () => {};
186
+
131
187
  notifyFn: ((notification: AppNotificationRequest) => void) = () => {};
132
188
  getPaneRuntimeStateFn: ((paneId: string) => PaneRuntimeState | null) = () => null;
133
189
  updatePaneRuntimeStateFn: ((paneId: string, patch: Partial<PaneRuntimeState>) => void) = () => {};
134
190
  applyPaneSettingValueFn: ((paneId: string, field: import("../types/plugin").PaneSettingField, value: unknown) => Promise<void>) = async () => {};
135
- getPluginConfigValueFn: (<T = unknown>(pluginId: string, key: string) => T | null) = () => null;
191
+ getPluginConfigValueFn: (<T = unknown>(pluginId: string, key: string) => T | null) = (pluginId, key) => (
192
+ (this.getConfigFn().pluginConfig[pluginId]?.[key] as T | undefined) ?? null
193
+ );
136
194
  setPluginConfigValueFn: ((pluginId: string, key: string, value: unknown) => Promise<void>) = async () => {};
195
+ setPluginConfigValuesFn: ((pluginId: string, values: Record<string, unknown>) => Promise<void>) = async () => {};
137
196
  deletePluginConfigValueFn: ((pluginId: string, key: string) => Promise<void>) = async () => {};
138
197
 
139
198
  readonly Slot;
140
199
 
141
- constructor(renderer: CliRenderer, dataProvider: DataProvider, tickerRepository: TickerRepository, persistence: AppPersistence) {
142
- this.dataProvider = dataProvider;
200
+ constructor(
201
+ marketData: DataProvider,
202
+ tickerRepository: TickerRepository,
203
+ persistence: AppPersistence,
204
+ options: PluginRegistryOptions = {},
205
+ ) {
206
+ this.marketData = marketData;
143
207
  this.tickerRepository = tickerRepository;
144
208
  this.persistence = persistence;
209
+ this.enableCapabilityHandlers = options.enableCapabilityHandlers ?? true;
210
+ this.wrapBrokerAdapter = options.wrapBrokerAdapter;
145
211
  this.events = new EventBus();
146
212
 
147
- sharedDataProvider = dataProvider;
213
+ sharedMarketData = marketData;
148
214
  sharedRegistry = this;
149
215
  (globalThis as any).__gloomRegistry = this;
150
-
151
- this.slotRegistry = createReactSlotRegistry<GloomSlots & Record<string, object>>(renderer, SLOT_REGISTRY_CONTEXT);
152
- this.Slot = createSlot(this.slotRegistry) as any;
216
+ this.capabilities = new CapabilityRegistry({
217
+ isPluginEnabled: (pluginId) => !this.getConfigFn().disabledPlugins.includes(pluginId),
218
+ isCapabilityEnabled: (capability, pluginId) => {
219
+ const disabledSources = this.getConfigFn().disabledSources ?? [];
220
+ return !disabledSources.includes(capability.sourceId ?? capability.id) && !this.getConfigFn().disabledPlugins.includes(pluginId);
221
+ },
222
+ });
223
+ this.Slot = ({ name, ...props }: { name: keyof GloomSlots } & Record<string, unknown>) => (
224
+ this.renderSlot(name, props as any)
225
+ );
153
226
  }
154
227
 
155
228
  get panes(): ReadonlyMap<string, PaneDef> { return this.panesMap; }
@@ -157,28 +230,45 @@ export class PluginRegistry implements PluginRuntimeAccess {
157
230
  get commands(): ReadonlyMap<string, CommandDef> { return this.commandsMap; }
158
231
  get columns(): ReadonlyMap<string, CustomColumnDef> { return this.columnsMap; }
159
232
  get brokers(): ReadonlyMap<string, BrokerAdapter> { return this.brokersMap; }
160
- get dataProviders(): ReadonlyMap<string, DataProvider> { return this.dataProvidersMap; }
161
233
  get detailTabs(): ReadonlyMap<string, DetailTabDef> { return this.detailTabsMap; }
162
234
  get shortcuts(): ReadonlyMap<string, KeyboardShortcut> { return this.shortcutsMap; }
163
235
  get tickerActions(): ReadonlyMap<string, TickerAction> { return this.tickerActionsMap; }
164
236
  get allPlugins(): ReadonlyMap<string, GloomPlugin> { return this.plugins; }
165
237
 
166
- getDataProviderPluginId(providerId: string): string | undefined {
167
- return this.dataProviderOwners.get(providerId);
238
+ getContextMenuItems(context: ContextMenuContext): ContextMenuItem[] {
239
+ const disabledPlugins = new Set(this.getConfigFn().disabledPlugins ?? []);
240
+ const entries = [...this.contextMenuProvidersMap.entries()]
241
+ .filter(([, entry]) => !disabledPlugins.has(entry.pluginId))
242
+ .filter(([, entry]) => !entry.provider.contexts || entry.provider.contexts.includes(context.kind))
243
+ .sort((left, right) => (
244
+ (left[1].provider.order ?? 0) - (right[1].provider.order ?? 0)
245
+ || left[1].pluginId.localeCompare(right[1].pluginId)
246
+ || left[1].provider.id.localeCompare(right[1].provider.id)
247
+ ));
248
+
249
+ const items: ContextMenuItem[] = [];
250
+ for (const [, entry] of entries) {
251
+ try {
252
+ const provided = entry.provider.getItems(context);
253
+ if (provided?.length) items.push(...provided);
254
+ } catch (error) {
255
+ this.registryLog.error("Context menu provider failed", {
256
+ pluginId: entry.pluginId,
257
+ providerId: entry.provider.id,
258
+ context: context.kind,
259
+ error: error instanceof Error ? error.message : String(error),
260
+ });
261
+ }
262
+ }
263
+ return items;
168
264
  }
169
265
 
170
- getEnabledDataProviders(): DataProvider[] {
171
- const disabledPlugins = new Set(this.getConfigFn().disabledPlugins ?? []);
172
- return [...this.dataProvidersMap.values()].filter((provider) => {
173
- const ownerId = this.dataProviderOwners.get(provider.id);
174
- return !ownerId || !disabledPlugins.has(ownerId);
175
- });
266
+ getCapabilityPluginId(capabilityId: string): string | undefined {
267
+ return this.capabilityOwners.get(capabilityId);
176
268
  }
177
269
 
178
- getActiveProvider(): DataProvider | undefined {
179
- let active: DataProvider | undefined;
180
- for (const provider of this.dataProvidersMap.values()) active = provider;
181
- return active;
270
+ getEnabledCapabilities(kind?: string): PluginCapability[] {
271
+ return this.capabilities.list(kind).map((entry) => entry.capability);
182
272
  }
183
273
 
184
274
  getPluginPaneIds(pluginId: string): string[] {
@@ -193,6 +283,20 @@ export class PluginRegistry implements PluginRuntimeAccess {
193
283
  this.notifyFn(notification);
194
284
  }
195
285
 
286
+ renderSlot<K extends keyof GloomSlots>(name: K, props: GloomSlots[K]): ReactNode {
287
+ const entries = this.slotEntries.get(name as string) ?? [];
288
+ if (entries.length === 0) return null;
289
+ return createElement(
290
+ Fragment,
291
+ null,
292
+ ...entries.map((entry) => createElement(
293
+ Fragment,
294
+ { key: entry.pluginId },
295
+ entry.render(props),
296
+ )),
297
+ );
298
+ }
299
+
196
300
  private getOrCreatePluginItems(pluginId: string): PluginItems {
197
301
  const existing = this.pluginItems.get(pluginId);
198
302
  if (existing) return existing;
@@ -203,23 +307,50 @@ export class PluginRegistry implements PluginRuntimeAccess {
203
307
  commands: [],
204
308
  columns: [],
205
309
  brokers: [],
206
- dataProviders: [],
310
+ capabilities: [],
207
311
  detailTabs: [],
208
312
  shortcuts: [],
209
313
  tickerActions: [],
314
+ contextMenuProviders: [],
210
315
  eventDisposers: [],
316
+ capabilityDisposers: [],
317
+ newsQueryWatchDisposers: [],
211
318
  };
212
319
  this.pluginItems.set(pluginId, items);
213
320
  return items;
214
321
  }
215
322
 
323
+ private registerCapabilityForPlugin(pluginId: string, capability: PluginCapability, items: PluginItems): void {
324
+ const ownedCapability: PluginCapability = {
325
+ ...capability,
326
+ isEnabled: () => {
327
+ const config = this.getConfigFn();
328
+ const disabledPlugin = config.disabledPlugins.includes(pluginId);
329
+ const disabledSource = config.disabledSources?.includes(capability.sourceId ?? capability.id) ?? false;
330
+ return !disabledPlugin && !disabledSource && (capability.isEnabled?.() ?? true);
331
+ },
332
+ };
333
+ const disposeCapability = this.capabilities.register(pluginId, ownedCapability);
334
+ this.capabilityOwners.set(capability.id, pluginId);
335
+ items.capabilities.push(capability.id);
336
+ items.capabilityDisposers.push(disposeCapability);
337
+
338
+ if (ownedCapability.kind === "news") {
339
+ const dispose = this.registerNewsCapabilityFn(ownedCapability as NewsCapability);
340
+ items.capabilityDisposers.push(dispose);
341
+ }
342
+ }
343
+
216
344
  private wrapPaneDef(pluginId: string, pane: PaneDef): PaneDef {
217
345
  return {
218
346
  ...pane,
219
347
  component: (props) => createElement(
220
348
  PluginRenderProvider,
221
- { pluginId, runtime: this },
222
- createElement(pane.component as any, props),
349
+ {
350
+ pluginId,
351
+ runtime: this,
352
+ children: createElement(pane.component as any, props),
353
+ },
223
354
  ),
224
355
  };
225
356
  }
@@ -229,8 +360,11 @@ export class PluginRegistry implements PluginRuntimeAccess {
229
360
  ...tab,
230
361
  component: (props) => createElement(
231
362
  PluginRenderProvider,
232
- { pluginId, runtime: this },
233
- createElement(tab.component as any, props),
363
+ {
364
+ pluginId,
365
+ runtime: this,
366
+ children: createElement(tab.component as any, props),
367
+ },
234
368
  ),
235
369
  };
236
370
  }
@@ -280,6 +414,10 @@ export class PluginRegistry implements PluginRuntimeAccess {
280
414
  return this.setPluginConfigValueFn(pluginId, key, value);
281
415
  }
282
416
 
417
+ setConfigStates(pluginId: string, values: Record<string, unknown>): Promise<void> {
418
+ return this.setPluginConfigValuesFn(pluginId, values);
419
+ }
420
+
283
421
  deleteConfigState(pluginId: string, key: string): Promise<void> {
284
422
  return this.deletePluginConfigValueFn(pluginId, key);
285
423
  }
@@ -294,6 +432,7 @@ export class PluginRegistry implements PluginRuntimeAccess {
294
432
 
295
433
  resolvePaneSettings(paneId: string): {
296
434
  paneId: string;
435
+ pluginId?: string;
297
436
  pane: PaneInstanceConfig;
298
437
  paneDef: PaneDef;
299
438
  settingsDef: PaneSettingsDef;
@@ -310,6 +449,8 @@ export class PluginRegistry implements PluginRuntimeAccess {
310
449
  if (!paneDef?.settings) return null;
311
450
 
312
451
  const config = this.getConfigFn();
452
+ const pluginId = this.paneOwners.get(pane.paneId);
453
+ const paneSettings = getPaneSettings(pane);
313
454
  const paneStateMap = Object.fromEntries(
314
455
  layout.instances.map((instance) => [instance.instanceId, this.getPaneRuntimeStateFn(instance.instanceId) ?? {}]),
315
456
  );
@@ -324,7 +465,7 @@ export class PluginRegistry implements PluginRuntimeAccess {
324
465
  paneId: targetPaneId,
325
466
  paneType: pane.paneId,
326
467
  pane,
327
- settings: getPaneSettings(pane),
468
+ settings: paneSettings,
328
469
  paneState,
329
470
  activeTicker: resolveTickerForPane(stateView, targetPaneId),
330
471
  activeCollectionId: resolveCollectionForPane(stateView, targetPaneId),
@@ -334,8 +475,23 @@ export class PluginRegistry implements PluginRuntimeAccess {
334
475
  : paneDef.settings;
335
476
  if (!settingsDef) return null;
336
477
 
478
+ if (pluginId) {
479
+ const resolvedSettings = { ...paneSettings };
480
+ for (const field of settingsDef.fields) {
481
+ if (field.storage !== "plugin") continue;
482
+ const configValue = this.getConfigState(pluginId, field.key);
483
+ if (configValue === null) {
484
+ delete resolvedSettings[field.key];
485
+ } else {
486
+ resolvedSettings[field.key] = configValue;
487
+ }
488
+ }
489
+ context.settings = resolvedSettings;
490
+ }
491
+
337
492
  return {
338
493
  paneId: targetPaneId,
494
+ pluginId,
339
495
  pane,
340
496
  paneDef,
341
497
  settingsDef,
@@ -359,6 +515,10 @@ export class PluginRegistry implements PluginRuntimeAccess {
359
515
  return this.shortcutOwners.get(shortcutId);
360
516
  }
361
517
 
518
+ getDetailTabPluginId(tabId: string): string | undefined {
519
+ return this.detailTabOwners.get(tabId);
520
+ }
521
+
362
522
  isPaneFloating(paneId: string): boolean {
363
523
  try {
364
524
  const target = this.resolvePaneTarget(paneId);
@@ -414,9 +574,10 @@ export class PluginRegistry implements PluginRuntimeAccess {
414
574
  getState: (key, options) => this.getResumeState(pluginId, key, options?.schemaVersion),
415
575
  setState: (key, value, options) => this.setResumeState(pluginId, key, value, options?.schemaVersion),
416
576
  deleteState: (key) => this.deleteResumeState(pluginId, key),
417
- getPaneState: (paneId, key) => (
418
- this.getPaneRuntimeStateFn(paneId)?.pluginState?.[pluginId]?.[key] ?? null
419
- ),
577
+ getPaneState: <T = unknown>(paneId: string, key: string): T | null => {
578
+ const value = this.getPaneRuntimeStateFn(paneId)?.pluginState?.[pluginId]?.[key];
579
+ return value === undefined ? null : value as T;
580
+ },
420
581
  setPaneState: (paneId, key, value) => {
421
582
  const currentPaneState = this.getPaneRuntimeStateFn(paneId) ?? {};
422
583
  const pluginState = {
@@ -466,7 +627,11 @@ export class PluginRegistry implements PluginRuntimeAccess {
466
627
  };
467
628
 
468
629
  return {
469
- registerPane: (pane) => { this.panesMap.set(pane.id, this.wrapPaneDef(pluginId, pane)); items.panes.push(pane.id); },
630
+ registerPane: (pane) => {
631
+ this.panesMap.set(pane.id, this.wrapPaneDef(pluginId, pane));
632
+ this.paneOwners.set(pane.id, pluginId);
633
+ items.panes.push(pane.id);
634
+ },
470
635
  registerPaneTemplate: (template) => {
471
636
  this.paneTemplatesMap.set(template.id, template);
472
637
  this.paneTemplateOwners.set(template.id, pluginId);
@@ -478,25 +643,43 @@ export class PluginRegistry implements PluginRuntimeAccess {
478
643
  items.commands.push(command.id);
479
644
  },
480
645
  registerColumn: (column) => { this.columnsMap.set(column.id, column); items.columns.push(column.id); },
481
- registerBroker: (broker) => { this.brokersMap.set(broker.id, broker); items.brokers.push(broker.id); },
482
- registerDataProvider: (provider) => {
483
- this.dataProvidersMap.set(provider.id, provider);
484
- this.dataProviderOwners.set(provider.id, pluginId);
485
- items.dataProviders.push(provider.id);
646
+ registerBroker: (broker) => {
647
+ this.brokersMap.set(broker.id, this.wrapBrokerAdapter?.(broker, pluginId) ?? broker);
648
+ items.brokers.push(broker.id);
649
+ },
650
+ registerCapability: (capability) => {
651
+ if (this.enableCapabilityHandlers) {
652
+ this.registerCapabilityForPlugin(pluginId, capability, items);
653
+ }
654
+ },
655
+ registerDetailTab: (tab) => {
656
+ this.detailTabsMap.set(tab.id, this.wrapDetailTabDef(pluginId, tab));
657
+ this.detailTabOwners.set(tab.id, pluginId);
658
+ items.detailTabs.push(tab.id);
486
659
  },
487
- registerDetailTab: (tab) => { this.detailTabsMap.set(tab.id, this.wrapDetailTabDef(pluginId, tab)); items.detailTabs.push(tab.id); },
488
660
  registerShortcut: (shortcut) => {
489
661
  this.shortcutsMap.set(shortcut.id, shortcut);
490
662
  this.shortcutOwners.set(shortcut.id, pluginId);
491
663
  items.shortcuts.push(shortcut.id);
492
664
  },
493
665
  registerTickerAction: (action) => { this.tickerActionsMap.set(action.id, action); items.tickerActions.push(action.id); },
666
+ registerContextMenuProvider: (provider) => {
667
+ const providerKey = `${pluginId}:${provider.id}`;
668
+ this.contextMenuProvidersMap.set(providerKey, { pluginId, provider });
669
+ items.contextMenuProviders.push(providerKey);
670
+ },
671
+ watchNewsQuery: (query, listener) => {
672
+ const dispose = this.watchNewsQueryFn(query, listener);
673
+ items.newsQueryWatchDisposers.push(dispose);
674
+ return dispose;
675
+ },
494
676
 
495
677
  getData: (ticker) => this.getDataFn(ticker),
496
678
  getTicker: (ticker) => this.getTickerFn(ticker),
497
679
  getConfig: () => this.getConfigFn(),
680
+ getPaneDef: (paneId) => this.panesMap.get(paneId),
498
681
 
499
- dataProvider: this.dataProvider,
682
+ marketData: this.marketData,
500
683
  tickerRepository: this.tickerRepository,
501
684
  persistence,
502
685
  log,
@@ -510,14 +693,14 @@ export class PluginRegistry implements PluginRuntimeAccess {
510
693
  },
511
694
 
512
695
  createBrokerInstance: (brokerType, label, values) => this.createBrokerInstanceFn(brokerType, label, values),
513
- updateBrokerInstance: (instanceId, values) => this.updateBrokerInstanceFn(instanceId, values),
696
+ updateBrokerInstance: (instanceId, values, options) => this.updateBrokerInstanceFn(instanceId, values, options),
514
697
  syncBrokerInstance: (instanceId) => this.syncBrokerInstanceFn(instanceId),
515
698
  removeBrokerInstance: (instanceId) => this.removeBrokerInstanceFn(instanceId),
516
699
 
517
- selectTicker: (symbol, paneId) => this.selectTickerFn(symbol, paneId),
518
- switchPanel: (panel) => this.switchPanelFn(panel),
519
- switchTab: (tabId, paneId) => this.switchTabFn(tabId, paneId),
520
- openCommandBar: (query) => this.openCommandBarFn(query),
700
+ selectTicker: this.selectTicker,
701
+ switchPanel: this.switchPanel,
702
+ switchTab: this.switchTab,
703
+ openCommandBar: this.openCommandBar,
521
704
  showPane: (paneId) => this.showPaneFn(paneId),
522
705
  createPaneFromTemplate: (templateId, options) => this.createPaneFromTemplateFn(templateId, options),
523
706
  hidePane: (paneId) => this.hidePaneFn(paneId),
@@ -547,6 +730,7 @@ export class PluginRegistry implements PluginRuntimeAccess {
547
730
  if (plugin.panes) {
548
731
  for (const pane of plugin.panes) {
549
732
  this.panesMap.set(pane.id, this.wrapPaneDef(plugin.id, pane));
733
+ this.paneOwners.set(pane.id, plugin.id);
550
734
  items.panes.push(pane.id);
551
735
  }
552
736
  }
@@ -560,34 +744,51 @@ export class PluginRegistry implements PluginRuntimeAccess {
560
744
  }
561
745
 
562
746
  if (plugin.broker) {
563
- this.brokersMap.set(plugin.broker.id, plugin.broker);
747
+ this.brokersMap.set(plugin.broker.id, this.wrapBrokerAdapter?.(plugin.broker, plugin.id) ?? plugin.broker);
564
748
  items.brokers.push(plugin.broker.id);
565
749
  }
566
750
 
567
- if (plugin.dataProvider) {
568
- this.dataProvidersMap.set(plugin.dataProvider.id, plugin.dataProvider);
569
- this.dataProviderOwners.set(plugin.dataProvider.id, plugin.id);
570
- items.dataProviders.push(plugin.dataProvider.id);
751
+ if (this.enableCapabilityHandlers && plugin.capabilities) {
752
+ for (const capability of plugin.capabilities) {
753
+ this.registerCapabilityForPlugin(plugin.id, capability, items);
754
+ }
571
755
  }
572
756
 
573
757
  if (plugin.slots) {
574
- const corePlugin = {
575
- id: plugin.id,
576
- order: plugin.order,
577
- slots: {} as Record<string, unknown>,
578
- };
579
-
758
+ const registeredSlotNames: string[] = [];
580
759
  for (const [slotName, renderer] of Object.entries(plugin.slots)) {
581
760
  if (renderer) {
582
- corePlugin.slots[slotName] = (_ctx: unknown, props: unknown) => createElement(
583
- PluginRenderProvider,
584
- { pluginId: plugin.id, runtime: this },
585
- (renderer as any)(props),
586
- );
761
+ const entries = this.slotEntries.get(slotName) ?? [];
762
+ entries.push({
763
+ pluginId: plugin.id,
764
+ order: plugin.order ?? 0,
765
+ render: (props: unknown) => createElement(
766
+ PluginRenderProvider,
767
+ {
768
+ pluginId: plugin.id,
769
+ runtime: this,
770
+ children: (renderer as any)(props),
771
+ },
772
+ ),
773
+ });
774
+ entries.sort((left, right) => left.order - right.order || left.pluginId.localeCompare(right.pluginId));
775
+ this.slotEntries.set(slotName, entries);
776
+ registeredSlotNames.push(slotName);
587
777
  }
588
778
  }
589
779
 
590
- const unregister = this.slotRegistry.register(corePlugin as any);
780
+ const unregister = () => {
781
+ for (const slotName of registeredSlotNames) {
782
+ const entries = this.slotEntries.get(slotName);
783
+ if (!entries) continue;
784
+ const nextEntries = entries.filter((entry) => entry.pluginId !== plugin.id);
785
+ if (nextEntries.length === 0) {
786
+ this.slotEntries.delete(slotName);
787
+ } else {
788
+ this.slotEntries.set(slotName, nextEntries);
789
+ }
790
+ }
791
+ };
591
792
  this.unregisterFns.set(plugin.id, unregister);
592
793
  }
593
794
 
@@ -611,7 +812,10 @@ export class PluginRegistry implements PluginRuntimeAccess {
611
812
 
612
813
  const items = this.pluginItems.get(pluginId);
613
814
  if (items) {
614
- for (const paneId of items.panes) this.panesMap.delete(paneId);
815
+ for (const paneId of items.panes) {
816
+ this.panesMap.delete(paneId);
817
+ this.paneOwners.delete(paneId);
818
+ }
615
819
  for (const templateId of items.paneTemplates) {
616
820
  this.paneTemplatesMap.delete(templateId);
617
821
  this.paneTemplateOwners.delete(templateId);
@@ -622,17 +826,22 @@ export class PluginRegistry implements PluginRuntimeAccess {
622
826
  }
623
827
  for (const columnId of items.columns) this.columnsMap.delete(columnId);
624
828
  for (const brokerId of items.brokers) this.brokersMap.delete(brokerId);
625
- for (const providerId of items.dataProviders) {
626
- this.dataProvidersMap.delete(providerId);
627
- this.dataProviderOwners.delete(providerId);
829
+ for (const capabilityId of items.capabilities) {
830
+ this.capabilityOwners.delete(capabilityId);
831
+ }
832
+ for (const tabId of items.detailTabs) {
833
+ this.detailTabsMap.delete(tabId);
834
+ this.detailTabOwners.delete(tabId);
628
835
  }
629
- for (const tabId of items.detailTabs) this.detailTabsMap.delete(tabId);
630
836
  for (const shortcutId of items.shortcuts) {
631
837
  this.shortcutsMap.delete(shortcutId);
632
838
  this.shortcutOwners.delete(shortcutId);
633
839
  }
634
840
  for (const actionId of items.tickerActions) this.tickerActionsMap.delete(actionId);
841
+ for (const providerKey of items.contextMenuProviders) this.contextMenuProvidersMap.delete(providerKey);
635
842
  for (const dispose of items.eventDisposers) dispose();
843
+ for (const dispose of items.capabilityDisposers) dispose();
844
+ for (const dispose of items.newsQueryWatchDisposers) dispose();
636
845
  this.pluginItems.delete(pluginId);
637
846
  }
638
847
 
@@ -651,12 +860,14 @@ export class PluginRegistry implements PluginRuntimeAccess {
651
860
  });
652
861
  }
653
862
  }
863
+ this.capabilities.destroy();
864
+ this.capabilityOwners.clear();
654
865
  this.pluginResumeListeners.clear();
655
866
  if (sharedRegistry === this) {
656
867
  sharedRegistry = undefined;
657
868
  }
658
- if (sharedDataProvider === this.dataProvider) {
659
- sharedDataProvider = undefined;
869
+ if (sharedMarketData === this.marketData) {
870
+ sharedMarketData = undefined;
660
871
  }
661
872
  if ((globalThis as any).__gloomRegistry === this) {
662
873
  delete (globalThis as any).__gloomRegistry;