gloomberb 0.5.1 → 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 -14
  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 +9 -10
  85. package/src/components/layout/pane-title.ts +35 -0
  86. package/src/components/layout/pane.tsx +52 -11
  87. package/src/components/layout/shell.test.tsx +315 -107
  88. package/src/components/layout/shell.tsx +1010 -368
  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 +129 -0
  180. package/src/plugins/builtin/chat-controller.ts +85 -14
  181. package/src/plugins/builtin/chat.test.tsx +283 -36
  182. package/src/plugins/builtin/chat.tsx +363 -170
  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 +99 -9
  440. package/src/utils/api-client.ts +378 -18
  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,24 +1,25 @@
1
1
  import { afterEach, describe, expect, test } from "bun:test";
2
2
  import { act, type ReactElement } from "react";
3
3
  import { createTestRenderer } from "@opentui/core/testing";
4
- import { createRoot } from "@opentui/react";
4
+ import { createOpenTuiTestRoot as createRoot } from "../../renderers/opentui/test-utils";
5
5
  import { MarketDataCoordinator, setSharedMarketDataCoordinator } from "../../market-data/coordinator";
6
+ import { PaneFooterBar, PaneFooterProvider } from "../../components/layout/pane-footer";
6
7
  import {
7
8
  AppContext,
8
9
  createInitialState,
9
10
  PaneInstanceProvider,
10
11
  } from "../../state/app-context";
12
+ import { Box } from "../../ui";
11
13
  import { cloneLayout, createDefaultConfig } from "../../types/config";
14
+ import { createTestPluginRuntime } from "../../test-support/plugin-runtime";
12
15
  import type { DataProvider } from "../../types/data-provider";
13
16
  import type { TickerFinancials } from "../../types/financials";
14
17
  import type { PluginRegistry } from "../../plugins/registry";
15
- import { setSharedDataProviderForTests, setSharedRegistryForTests } from "../../plugins/registry";
18
+ import { PluginRenderProvider, type PluginRuntimeAccess } from "../../plugins/plugin-runtime";
19
+ import { setSharedMarketDataForTests, setSharedRegistryForTests } from "../../plugins/registry";
16
20
  import type { TickerRecord } from "../../types/ticker";
17
21
  import {
18
- buildComparisonChartPaneTitle,
19
22
  comparisonChartPlugin,
20
- COMPARISON_CHART_PANE_ID,
21
- COMPARISON_CHART_TEMPLATE_ID,
22
23
  getComparisonChartPaneSettings,
23
24
  } from "./comparison-chart";
24
25
 
@@ -99,23 +100,33 @@ function createProvider(historyBySymbol: Record<string, number[]>, currencyBySym
99
100
 
100
101
  function createRegistrySpy(spy: { selected: string[]; focused: string[] }): PluginRegistry {
101
102
  return {
102
- navigateTickerFn: (symbol: string) => {
103
+ selectTicker: (symbol: string) => { spy.selected.push(symbol); },
104
+ focusPaneFn: (paneId: string) => { spy.focused.push(paneId); },
105
+ navigateTicker: (symbol: string) => {
103
106
  spy.selected.push(symbol);
104
107
  spy.focused.push("ticker-detail");
105
108
  },
106
- selectTickerFn: (symbol: string) => { spy.selected.push(symbol); },
107
- focusPaneFn: (paneId: string) => { spy.focused.push(paneId); },
108
- navigateTickerFn: (symbol: string) => {
109
+ } as unknown as PluginRegistry;
110
+ }
111
+
112
+ function createRuntimeSpy(spy: { selected: string[]; focused: string[] }): PluginRuntimeAccess {
113
+ return createTestPluginRuntime({
114
+ pinTicker: (symbol: string) => {
109
115
  spy.selected.push(symbol);
110
116
  spy.focused.push("ticker-detail");
111
117
  },
112
- } as unknown as PluginRegistry;
118
+ navigateTicker: (symbol: string) => {
119
+ spy.selected.push(symbol);
120
+ spy.focused.push("ticker-detail");
121
+ },
122
+ });
113
123
  }
114
124
 
115
125
  function createComparisonHarness(
116
126
  settings: Record<string, unknown>,
117
127
  tickers: TickerRecord[],
118
128
  financials: Array<[string, TickerFinancials]>,
129
+ runtime: PluginRuntimeAccess,
119
130
  ) {
120
131
  const config = createDefaultConfig("/tmp/gloomberb-compare");
121
132
  const layout = {
@@ -126,6 +137,7 @@ function createComparisonHarness(
126
137
  settings,
127
138
  }],
128
139
  floating: [],
140
+ detached: [],
129
141
  };
130
142
  const nextConfig = {
131
143
  ...config,
@@ -148,13 +160,22 @@ function createComparisonHarness(
148
160
  return (
149
161
  <AppContext value={{ state, dispatch: () => {} }}>
150
162
  <PaneInstanceProvider paneId={TEST_PANE_ID}>
151
- <ComparisonPane
152
- paneId={TEST_PANE_ID}
153
- paneType="comparison-chart"
154
- focused
155
- width={120}
156
- height={20}
157
- />
163
+ <PluginRenderProvider pluginId="comparison-chart" runtime={runtime}>
164
+ <PaneFooterProvider>
165
+ {(footer) => (
166
+ <Box flexDirection="column" width={120} height={20}>
167
+ <ComparisonPane
168
+ paneId={TEST_PANE_ID}
169
+ paneType="comparison-chart"
170
+ focused
171
+ width={120}
172
+ height={19}
173
+ />
174
+ <PaneFooterBar footer={footer} focused width={120} />
175
+ </Box>
176
+ )}
177
+ </PaneFooterProvider>
178
+ </PluginRenderProvider>
158
179
  </PaneInstanceProvider>
159
180
  </AppContext>
160
181
  );
@@ -175,13 +196,14 @@ async function mountComparisonHarness(
175
196
  settings: Record<string, unknown>,
176
197
  tickers: TickerRecord[],
177
198
  financials: Array<[string, TickerFinancials]>,
199
+ spy: { selected: string[]; focused: string[] } = { selected: [], focused: [] },
178
200
  ) {
179
201
  actEnvironment.IS_REACT_ACT_ENVIRONMENT = true;
180
202
  testSetup = await createTestRenderer({ width: 120, height: 20 });
181
203
  root = createRoot(testSetup.renderer);
182
204
  await act(async () => {
183
205
  root!.render(
184
- createComparisonHarness(settings, tickers, financials),
206
+ createComparisonHarness(settings, tickers, financials, createRuntimeSpy(spy)),
185
207
  );
186
208
  await Promise.resolve();
187
209
  await new Promise((resolve) => setTimeout(resolve, 0));
@@ -212,37 +234,10 @@ afterEach(async () => {
212
234
  sharedCoordinator = null;
213
235
  setSharedMarketDataCoordinator(null);
214
236
  setSharedRegistryForTests(undefined);
215
- setSharedDataProviderForTests(undefined);
237
+ setSharedMarketDataForTests(undefined);
216
238
  });
217
239
 
218
240
  describe("comparisonChartPlugin", () => {
219
- test("creates a configured comparison pane with percent as the default axis", () => {
220
- const template = comparisonChartPlugin.paneTemplates?.find((entry) => entry.id === COMPARISON_CHART_TEMPLATE_ID);
221
- const paneDef = comparisonChartPlugin.panes?.find((entry) => entry.id === COMPARISON_CHART_PANE_ID);
222
-
223
- expect(template).toBeDefined();
224
- expect(paneDef?.defaultMode).toBe("floating");
225
- expect(template?.createInstance?.({
226
- config: createDefaultConfig("/tmp/gloomberb-compare"),
227
- layout: createDefaultConfig("/tmp/gloomberb-compare").layout,
228
- focusedPaneId: "ticker-detail:main",
229
- activeTicker: null,
230
- activeCollectionId: null,
231
- }, {
232
- symbols: ["AAPL", "MSFT", "NVDA"],
233
- })).toEqual({
234
- placement: "floating",
235
- title: "AAPL · MSFT · NVDA",
236
- settings: {
237
- axisMode: "percent",
238
- rangePreset: "1Y",
239
- chartResolution: "1d",
240
- symbols: ["AAPL", "MSFT", "NVDA"],
241
- symbolsText: "AAPL, MSFT, NVDA",
242
- },
243
- });
244
- });
245
-
246
241
  test("parses stored pane settings and backfills the text form", () => {
247
242
  expect(getComparisonChartPaneSettings({
248
243
  axisMode: "percent",
@@ -264,7 +259,7 @@ describe("comparisonChartPlugin", () => {
264
259
  AAPL: "USD",
265
260
  "7203": "JPY",
266
261
  });
267
- setSharedDataProviderForTests(provider);
262
+ setSharedMarketDataForTests(provider);
268
263
  sharedCoordinator = new MarketDataCoordinator(provider);
269
264
  setSharedMarketDataCoordinator(sharedCoordinator);
270
265
  setSharedRegistryForTests(createRegistrySpy({ selected: [], focused: [] }));
@@ -291,7 +286,7 @@ describe("comparisonChartPlugin", () => {
291
286
  expect(frame).toContain("view:");
292
287
  expect(frame).toContain("[m]ode");
293
288
  expect(frame).toContain("[r]es");
294
- expect(frame).toContain("[up/down]legend");
289
+ expect(frame).not.toContain("[up/down]legend");
295
290
  expect(frame).not.toContain("arrows legend");
296
291
  expect(frame).not.toContain("wheel pan");
297
292
  expect(frame).not.toContain("wheel zoom");
@@ -306,7 +301,7 @@ describe("comparisonChartPlugin", () => {
306
301
  AAPL: "USD",
307
302
  MSFT: "USD",
308
303
  });
309
- setSharedDataProviderForTests(provider);
304
+ setSharedMarketDataForTests(provider);
310
305
  sharedCoordinator = new MarketDataCoordinator(provider);
311
306
  setSharedMarketDataCoordinator(sharedCoordinator);
312
307
  setSharedRegistryForTests(createRegistrySpy({ selected: [], focused: [] }));
@@ -343,7 +338,7 @@ describe("comparisonChartPlugin", () => {
343
338
  MSFT: "USD",
344
339
  NVDA: "USD",
345
340
  });
346
- setSharedDataProviderForTests(provider);
341
+ setSharedMarketDataForTests(provider);
347
342
  sharedCoordinator = new MarketDataCoordinator(provider);
348
343
  setSharedMarketDataCoordinator(sharedCoordinator);
349
344
  setSharedRegistryForTests(createRegistrySpy(spy));
@@ -360,7 +355,7 @@ describe("comparisonChartPlugin", () => {
360
355
  ["AAPL", makeFinancials("AAPL", "USD", [100, 102, 104])],
361
356
  ["MSFT", makeFinancials("MSFT", "USD", [200, 202, 204])],
362
357
  ["NVDA", makeFinancials("NVDA", "USD", [300, 305, 310])],
363
- ]);
358
+ ], spy);
364
359
 
365
360
  await flushFrames();
366
361
  await pressComparisonInput(() => testSetup!.mockInput.pressArrow("right"));
@@ -370,9 +365,3 @@ describe("comparisonChartPlugin", () => {
370
365
  expect(spy.focused).toEqual(["ticker-detail"]);
371
366
  });
372
367
  });
373
-
374
- describe("buildComparisonChartPaneTitle", () => {
375
- test("summarizes longer symbol lists", () => {
376
- expect(buildComparisonChartPaneTitle(["AAPL", "MSFT", "NVDA", "META"])).toBe("AAPL · MSFT +2");
377
- });
378
- });
@@ -1,3 +1,4 @@
1
+ import { Box } from "../../ui";
1
2
  import { useCallback, useMemo } from "react";
2
3
  import { EmptyState } from "../../components";
3
4
  import { ComparisonStockChart } from "../../components/chart/comparison-stock-chart";
@@ -7,8 +8,8 @@ import {
7
8
  DEFAULT_COMPARISON_CHART_RESOLUTION,
8
9
  normalizeChartResolution,
9
10
  } from "../../components/chart/chart-resolution";
10
- import { getSharedRegistry } from "../../plugins/registry";
11
- import { useAppState } from "../../state/app-context";
11
+ import { usePluginTickerActions } from "../../plugins/plugin-runtime";
12
+ import { usePaneInstance } from "../../state/app-context";
12
13
  import { colors } from "../../theme/colors";
13
14
  import type { GloomPlugin, PaneProps, PaneSettingsDef } from "../../types/plugin";
14
15
  import { formatTickerListInput, MAX_TICKER_LIST_SIZE, parseTickerListInput } from "../../utils/ticker-list";
@@ -100,25 +101,24 @@ export function buildComparisonChartPaneTitle(symbols: string[]): string {
100
101
  return `${symbols.slice(0, 2).join(" · ")} +${symbols.length - 2}`;
101
102
  }
102
103
  function ComparisonChartPane({ paneId, focused, width, height }: PaneProps) {
103
- const registry = getSharedRegistry();
104
- const { state } = useAppState();
105
- const pane = state.config.layout.instances.find((instance) => instance.instanceId === paneId);
104
+ const { navigateTicker } = usePluginTickerActions();
105
+ const pane = usePaneInstance();
106
106
  const settings = useMemo(() => getComparisonChartPaneSettings(pane?.settings), [pane?.settings]);
107
107
 
108
108
  const openTicker = useCallback((symbol: string) => {
109
- registry?.navigateTickerFn(symbol);
110
- }, [registry]);
109
+ navigateTicker(symbol);
110
+ }, [navigateTicker]);
111
111
 
112
112
  if (settings.symbols.length === 0) {
113
113
  return (
114
- <box flexDirection="column" flexGrow={1} padding={1}>
114
+ <Box flexDirection="column" flexGrow={1} padding={1}>
115
115
  <EmptyState title="No comparison tickers configured." message="Open pane settings to add up to 10 tickers." />
116
- </box>
116
+ </Box>
117
117
  );
118
118
  }
119
119
 
120
120
  return (
121
- <box flexDirection="column" flexGrow={1} paddingX={1} backgroundColor={colors.panel}>
121
+ <Box flexDirection="column" flexGrow={1} paddingX={1} backgroundColor={colors.panel}>
122
122
  <ComparisonStockChart
123
123
  paneId={paneId}
124
124
  width={Math.max(width - 2, 20)}
@@ -128,7 +128,7 @@ function ComparisonChartPane({ paneId, focused, width, height }: PaneProps) {
128
128
  axisMode={settings.axisMode}
129
129
  onOpenSymbol={openTicker}
130
130
  />
131
- </box>
131
+ </Box>
132
132
  );
133
133
  }
134
134
 
@@ -4,7 +4,6 @@ import {
4
4
  computeDatedReturns,
5
5
  computeReturns,
6
6
  correlateDatedReturns,
7
- formatCorrelation,
8
7
  pearsonCorrelation,
9
8
  } from "./compute";
10
9
 
@@ -17,10 +16,6 @@ describe("computeReturns", () => {
17
16
  expect(returns[2]).toBeCloseTo(0.0952, 3);
18
17
  });
19
18
 
20
- test("returns empty for single value", () => {
21
- expect(computeReturns([100])).toEqual([]);
22
- });
23
-
24
19
  test("skips zero or invalid previous closes", () => {
25
20
  expect(computeReturns([0, 10, 20, Number.NaN, 30])).toEqual([1]);
26
21
  });
@@ -133,15 +128,3 @@ describe("correlateDatedReturns", () => {
133
128
  )).toEqual({ correlation: null, sampleSize: 2 });
134
129
  });
135
130
  });
136
-
137
- describe("formatCorrelation", () => {
138
- test("formats positive", () => {
139
- expect(formatCorrelation(0.85)).toContain("0.85");
140
- });
141
- test("formats negative", () => {
142
- expect(formatCorrelation(-0.42)).toContain("-0.42");
143
- });
144
- test("formats null as dash", () => {
145
- expect(formatCorrelation(null)).toContain("—");
146
- });
147
- });
@@ -1,9 +1,11 @@
1
+ import { Box, ScrollBox, Text } from "../../../ui";
1
2
  import { useMemo } from "react";
2
- import { TextAttributes } from "@opentui/core";
3
+ import { TextAttributes } from "../../../ui";
4
+ import { usePaneFooter } from "../../../components";
3
5
  import type { GloomPlugin, PaneProps } from "../../../types/plugin";
4
6
  import { colors } from "../../../theme/colors";
5
- import { getSharedRegistry } from "../../registry";
6
- import { useAppSelector } from "../../../state/app-context";
7
+ import { usePluginTickerActions } from "../../plugin-runtime";
8
+ import { useAppSelector, usePaneInstance } from "../../../state/app-context";
7
9
  import { useChartQueries } from "../../../market-data/hooks";
8
10
  import { buildChartKey } from "../../../market-data/selectors";
9
11
  import type { PricePoint } from "../../../types/financials";
@@ -145,21 +147,22 @@ function buildCorrelationPaneTitle(symbols: string[], rangePreset: CorrelationRa
145
147
  return `${symbols.slice(0, 2).join(" · ")} +${symbols.length - 2} ${rangePreset}`;
146
148
  }
147
149
 
148
- export function CorrelationMatrixPane({ paneId, width, height }: PaneProps) {
149
- const state = useAppSelector((s) => s);
150
- const pane = state.config.layout.instances.find((instance) => instance.instanceId === paneId);
150
+ export function CorrelationMatrixPane({ width, height }: PaneProps) {
151
+ const pane = usePaneInstance();
152
+ const { navigateTicker } = usePluginTickerActions();
153
+ const tickers = useAppSelector((state) => state.tickers);
151
154
  const settings = useMemo(() => getCorrelationPaneSettings(pane?.settings), [pane?.settings]);
152
155
 
153
156
  const instruments = useMemo(() => {
154
157
  if (settings.symbolsError) return [];
155
158
  return settings.symbols.map((symbol) => {
156
- const ticker = state.tickers.get(symbol);
159
+ const ticker = tickers.get(symbol);
157
160
  return {
158
161
  symbol,
159
162
  exchange: ticker?.metadata.exchange ?? "",
160
163
  };
161
164
  });
162
- }, [state.tickers, settings.symbols.join(","), settings.symbolsError]);
165
+ }, [settings.symbols.join(","), settings.symbolsError, tickers]);
163
166
 
164
167
  const instrumentKey = instruments.map((instrument) => `${instrument.symbol}|${instrument.exchange}`).join(",");
165
168
 
@@ -226,20 +229,30 @@ export function CorrelationMatrixPane({ paneId, width, height }: PaneProps) {
226
229
  [symbolsKey, seriesBySymbol, matrix.sampleMin, matrix.sampleMax],
227
230
  );
228
231
 
232
+ usePaneFooter("correlation", () => ({
233
+ info: [
234
+ { id: "tickers", parts: [{ text: `${symbols.length} tickers`, tone: symbols.length >= 2 ? "value" : "warning", bold: symbols.length >= 2 }] },
235
+ { id: "range", parts: [{ text: settings.rangePreset, tone: "muted" }] },
236
+ ...(settings.symbolsError
237
+ ? [{ id: "error", parts: [{ text: settings.symbolsError, tone: "warning" as const }] }]
238
+ : [{ id: "status", parts: [{ text: statusSummary, tone: "muted" as const }] }]),
239
+ ],
240
+ }), [settings.rangePreset, settings.symbolsError, statusSummary, symbols.length]);
241
+
229
242
  if (settings.symbolsError) {
230
243
  return (
231
- <box flexDirection="column" width={width} height={height} paddingX={2} paddingY={1}>
232
- <text fg={colors.negative}>Invalid CORR tickers: {settings.symbolsError}</text>
233
- <text fg={colors.textMuted}>Open pane settings and enter tickers like AAPL, MSFT, NVDA.</text>
234
- </box>
244
+ <Box flexDirection="column" width={width} height={height} paddingX={2} paddingY={1}>
245
+ <Text fg={colors.negative}>Invalid CORR tickers: {settings.symbolsError}</Text>
246
+ <Text fg={colors.textMuted}>Open pane settings and enter tickers like AAPL, MSFT, NVDA.</Text>
247
+ </Box>
235
248
  );
236
249
  }
237
250
 
238
251
  if (symbols.length < 2) {
239
252
  return (
240
- <box flexDirection="column" width={width} height={height} paddingX={2} paddingY={1}>
241
- <text fg={colors.textMuted}>Enter at least 2 tickers in pane settings</text>
242
- </box>
253
+ <Box flexDirection="column" width={width} height={height} paddingX={2} paddingY={1}>
254
+ <Text fg={colors.textMuted}>Enter at least 2 tickers in pane settings</Text>
255
+ </Box>
243
256
  );
244
257
  }
245
258
 
@@ -252,46 +265,38 @@ export function CorrelationMatrixPane({ paneId, width, height }: PaneProps) {
252
265
  const cellWidth = Math.max(MIN_MATRIX_CELL_WIDTH, Math.min(MATRIX_CELL_WIDTH, availableCellWidth));
253
266
 
254
267
  return (
255
- <box flexDirection="column" width={width} height={height}>
256
- {/* Title */}
257
- <box flexDirection="row" height={1} paddingX={1}>
258
- <text fg={colors.textMuted}>{symbols.length} tickers · {settings.rangePreset} daily returns</text>
259
- </box>
260
- <box flexDirection="row" height={1} paddingX={1}>
261
- <text fg={colors.textDim}>{statusSummary}</text>
262
- </box>
263
-
268
+ <Box flexDirection="column" width={width} height={height}>
264
269
  {/* Column header row */}
265
- <box flexDirection="row" paddingX={1} height={1} backgroundColor={headerBg}>
266
- <box width={rowHeaderWidth} flexShrink={0} />
270
+ <Box flexDirection="row" paddingX={1} height={1} backgroundColor={headerBg}>
271
+ <Box width={rowHeaderWidth} flexShrink={0} />
267
272
  {symbols.map((sym) => (
268
- <box key={sym} width={cellWidth} justifyContent="flex-end" paddingRight={1} overflow="hidden">
269
- <text fg={colors.textDim} attributes={TextAttributes.BOLD}>
273
+ <Box key={sym} width={cellWidth} justifyContent="flex-end" paddingRight={1} overflow="hidden">
274
+ <Text fg={colors.textDim} attributes={TextAttributes.BOLD}>
270
275
  {displaySymbol(sym)}
271
- </text>
272
- </box>
276
+ </Text>
277
+ </Box>
273
278
  ))}
274
- </box>
279
+ </Box>
275
280
 
276
281
  {/* Matrix rows */}
277
- <scrollbox flexGrow={1} scrollY focusable={false}>
278
- <box flexDirection="column">
282
+ <ScrollBox flexGrow={1} scrollY focusable={false}>
283
+ <Box flexDirection="column">
279
284
  {symbols.map((rowSym, rowIndex) => (
280
- <box key={rowSym} flexDirection="row" paddingX={1} backgroundColor={rowIndex % 2 === 0 ? colors.bg : undefined}>
285
+ <Box key={rowSym} flexDirection="row" paddingX={1} backgroundColor={rowIndex % 2 === 0 ? colors.bg : undefined}>
281
286
  {/* Row header */}
282
- <box
287
+ <Box
283
288
  width={rowHeaderWidth}
284
289
  flexShrink={0}
285
290
  overflow="hidden"
286
- onMouseDown={() => getSharedRegistry()?.navigateTickerFn(rowSym)}
291
+ onMouseDown={() => navigateTicker(rowSym)}
287
292
  >
288
- <text
293
+ <Text
289
294
  fg={rowHeaderColor(seriesBySymbol.get(rowSym)?.status ?? "loading")}
290
295
  attributes={TextAttributes.BOLD | TextAttributes.UNDERLINE}
291
296
  >
292
297
  {displaySymbol(rowSym)}
293
- </text>
294
- </box>
298
+ </Text>
299
+ </Box>
295
300
  {/* Cells */}
296
301
  {symbols.map((colSym) => {
297
302
  const isDiag = rowSym === colSym;
@@ -306,23 +311,23 @@ export function CorrelationMatrixPane({ paneId, width, height }: PaneProps) {
306
311
  : correlationColor(r, colors.positive, colors.negative, colors.textMuted);
307
312
  const text = isDiag ? " 1.00" : formatCorrelation(r);
308
313
  return (
309
- <box
314
+ <Box
310
315
  key={colSym}
311
316
  width={cellWidth}
312
317
  justifyContent="flex-end"
313
318
  paddingRight={1}
314
319
  backgroundColor={isDiag ? headerBg : undefined}
315
320
  >
316
- <text fg={cellColor}>{text}</text>
317
- </box>
321
+ <Text fg={cellColor}>{text}</Text>
322
+ </Box>
318
323
  );
319
324
  })}
320
- </box>
325
+ </Box>
321
326
  ))}
322
- </box>
323
- </scrollbox>
327
+ </Box>
328
+ </ScrollBox>
324
329
 
325
- </box>
330
+ </Box>
326
331
  );
327
332
  }
328
333
 
@@ -24,7 +24,7 @@ export function normalizeCorrelationRange(value: unknown): CorrelationRangePrese
24
24
  return isCorrelationRangePreset(value) ? value : DEFAULT_CORRELATION_RANGE;
25
25
  }
26
26
 
27
- export function parseCorrelationSymbolsInput(raw: string, maxTickers = MAX_CORRELATION_TICKERS): string[] {
27
+ function parseCorrelationSymbolsInput(raw: string, maxTickers = MAX_CORRELATION_TICKERS): string[] {
28
28
  return parseTickerListInput(raw, maxTickers);
29
29
  }
30
30
 
@@ -83,7 +83,6 @@ export function buildCorrelationSettingsDef(): PaneSettingsDef {
83
83
  {
84
84
  key: "rangePreset",
85
85
  label: "Range",
86
- description: "Use daily closes over this range.",
87
86
  type: "select",
88
87
  options: CORRELATION_RANGE_OPTIONS.map((range) => ({
89
88
  value: range,