gloomberb 0.10.4 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (555) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +30 -3
  3. package/README.zh-CN.md +1 -1
  4. package/package.json +5 -5
  5. package/src/api-client/auth.ts +34 -0
  6. package/src/api-client/data.ts +25 -0
  7. package/src/api-client/index.test.ts +81 -0
  8. package/src/api-client/index.ts +37 -0
  9. package/src/api-client/request.test.ts +30 -0
  10. package/src/api-client/request.ts +44 -20
  11. package/src/api-client/socket-health.test.ts +79 -0
  12. package/src/api-client/socket.ts +108 -5
  13. package/src/api-client/types.ts +139 -0
  14. package/src/app/global-shortcuts.test.tsx +144 -9
  15. package/src/app/global-shortcuts.ts +33 -11
  16. package/src/app/runtime/broker-import.ts +39 -5
  17. package/src/app/runtime/startup.ts +7 -1
  18. package/src/app/runtime/update.ts +26 -1
  19. package/src/app.tsx +36 -19
  20. package/src/brokers/broker-ticker-sync.ts +3 -8
  21. package/src/brokers/sync-broker-instance.test.ts +59 -0
  22. package/src/brokers/sync-broker-instance.ts +37 -13
  23. package/src/capabilities/chart-series.ts +318 -0
  24. package/src/capabilities/factories.ts +46 -0
  25. package/src/capabilities/index.ts +1 -0
  26. package/src/capabilities/registry.test.ts +135 -0
  27. package/src/capabilities/registry.ts +31 -4
  28. package/src/capabilities/types.ts +52 -0
  29. package/src/cli/commands/overview.ts +2 -2
  30. package/src/cli/desktop-pane-shot.ts +2 -0
  31. package/src/cli/entry.ts +11 -1
  32. package/src/cli/native-smoke.ts +5 -0
  33. package/src/cli/pane-functions/catalog.ts +4 -0
  34. package/src/cli/pane-functions/data.ts +6 -1
  35. package/src/cli/pane-functions/discovery.ts +76 -10
  36. package/src/cli/pane-functions/report.test.ts +76 -0
  37. package/src/cli/pane-functions/report.ts +7 -1
  38. package/src/cli/pane-functions/resolver.ts +4 -1
  39. package/src/cli/pane-functions/screenshot.test.ts +56 -3
  40. package/src/cli/pane-functions/screenshot.ts +68 -5
  41. package/src/components/chart/composite/composite-chart.test.tsx +54 -0
  42. package/src/components/chart/composite/composite-chart.tsx +92 -15
  43. package/src/components/chart/composite/downsample.test.ts +136 -0
  44. package/src/components/chart/composite/downsample.ts +208 -0
  45. package/src/components/chart/composite/panel-series.test.ts +66 -0
  46. package/src/components/chart/composite/panel-series.ts +101 -0
  47. package/src/components/chart/multi-line/renderer.ts +1 -0
  48. package/src/components/chart/native/kitty/index.test.ts +25 -3
  49. package/src/components/chart/native/kitty/support.ts +37 -3
  50. package/src/components/chart/native/use-chart-text-fallback.ts +19 -0
  51. package/src/components/chart/static/chart/surface.tsx +43 -12
  52. package/src/components/chart/static/multi-line-chart-surface.tsx +41 -5
  53. package/src/components/command-bar/commands/direct/index.ts +18 -0
  54. package/src/components/command-bar/commands/registry.ts +14 -0
  55. package/src/components/command-bar/routes/root/article-results.test.ts +83 -0
  56. package/src/components/command-bar/routes/root/article-results.ts +55 -0
  57. package/src/components/command-bar/routes/root/command-items.ts +3 -0
  58. package/src/components/command-bar/routes/root/provider-search.ts +7 -3
  59. package/src/components/command-bar/routes/root/results.ts +38 -11
  60. package/src/components/command-bar/routes/root/runtime.ts +6 -2
  61. package/src/components/command-bar/routes/ticker-search/actions.ts +1 -1
  62. package/src/components/command-bar/routes/ticker-search/results.test.ts +34 -0
  63. package/src/components/command-bar/routes/ticker-search/results.ts +4 -6
  64. package/src/components/command-bar/routes/ticker-search/route.ts +2 -2
  65. package/src/components/command-bar/routing/list-state.ts +2 -1
  66. package/src/components/command-bar/surface/assist.test.tsx +13 -6
  67. package/src/components/command-bar/surface/index.test.tsx +65 -0
  68. package/src/components/command-bar/surface/index.tsx +35 -0
  69. package/src/components/command-bar/view-model.test.ts +43 -0
  70. package/src/components/command-bar/view-model.ts +41 -4
  71. package/src/components/command-bar/workflow/collection-actions.test.ts +94 -0
  72. package/src/components/command-bar/workflow/collection-actions.ts +14 -3
  73. package/src/components/command-bar/workflow/ops.test.ts +136 -0
  74. package/src/components/command-bar/workflow/ops.ts +71 -1
  75. package/src/components/index.ts +1 -1
  76. package/src/components/layout/header.test.tsx +35 -0
  77. package/src/components/layout/header.tsx +101 -9
  78. package/src/components/layout/pane/cycle-order.test.ts +0 -1
  79. package/src/components/layout/pane/footer/index.test.tsx +19 -19
  80. package/src/components/layout/pane/header.tsx +2 -13
  81. package/src/components/layout/shell/index.tsx +3 -0
  82. package/src/components/layout/status-bar.test.tsx +36 -1
  83. package/src/components/layout/status-bar.tsx +48 -2
  84. package/src/components/onboarding/account-step/chooser-panel.tsx +27 -50
  85. package/src/components/onboarding/account-step/form-panel.tsx +85 -29
  86. package/src/components/onboarding/account-step/index.tsx +10 -1
  87. package/src/components/onboarding/account-step/model.ts +3 -3
  88. package/src/components/onboarding/account-step/qr-panel.tsx +68 -0
  89. package/src/components/onboarding/account-step/signed-in-panel.tsx +4 -7
  90. package/src/components/onboarding/onboarding-frame.tsx +517 -0
  91. package/src/components/onboarding/onboarding-steps.tsx +0 -264
  92. package/src/components/onboarding/onboarding-wizard.test.tsx +626 -200
  93. package/src/components/onboarding/onboarding-wizard.tsx +848 -266
  94. package/src/components/onboarding/portfolio-step/broker-fields-panel.tsx +81 -51
  95. package/src/components/onboarding/portfolio-step/broker-setup-panel.tsx +14 -20
  96. package/src/components/onboarding/portfolio-step/broker-sync-panel.tsx +8 -11
  97. package/src/components/onboarding/portfolio-step/choose-panel.tsx +18 -22
  98. package/src/components/onboarding/portfolio-step/index.tsx +3 -0
  99. package/src/components/onboarding/portfolio-step/types.ts +3 -0
  100. package/src/components/onboarding/wizard-account.ts +38 -3
  101. package/src/components/onboarding/wizard-broker-sync.ts +45 -67
  102. package/src/components/onboarding/wizard-model.ts +18 -2
  103. package/src/components/ui/data-table/opentui/index.tsx +29 -9
  104. package/src/components/ui/data-table/types.ts +2 -0
  105. package/src/components/ui/external-link.tsx +4 -2
  106. package/src/components/ui/loading.tsx +1 -1
  107. package/src/components/ui/status.tsx +76 -4
  108. package/src/components/ui/table-layout.test.ts +24 -0
  109. package/src/components/ui/table-layout.ts +90 -23
  110. package/src/components/ui/tabs.tsx +86 -4
  111. package/src/core/app-services.ts +14 -3
  112. package/src/core/connection-health.test.ts +143 -0
  113. package/src/core/connection-health.ts +295 -0
  114. package/src/core/state/app/layout-reducer.ts +34 -0
  115. package/src/core/state/app/layout.ts +21 -0
  116. package/src/core/state/app/state.ts +10 -0
  117. package/src/core/state/app/types.ts +7 -1
  118. package/src/data/config/chart-settings.ts +11 -2
  119. package/src/data/config/store/index.test.ts +83 -9
  120. package/src/data/config/store/migrations.ts +16 -0
  121. package/src/data/config/store/normalize.ts +59 -1
  122. package/src/data/fred-series.ts +8 -2
  123. package/src/i18n/es.ts +126 -0
  124. package/src/i18n/ja.ts +126 -0
  125. package/src/i18n/ko.ts +126 -0
  126. package/src/i18n/zh-cn.ts +126 -0
  127. package/src/i18n/zh-tw.ts +126 -0
  128. package/src/market-data/coordinator/chart.ts +3 -2
  129. package/src/market-data/hooks.tsx +3 -2
  130. package/src/market-data/market/status.test.ts +12 -0
  131. package/src/market-data/market/status.ts +34 -17
  132. package/src/news/aggregator.test.ts +39 -0
  133. package/src/news/aggregator.ts +78 -22
  134. package/src/news/hooks.ts +4 -0
  135. package/src/notifications/app-notifier.test.ts +28 -6
  136. package/src/notifications/app-notifier.ts +24 -5
  137. package/src/plugins/broker-sync/broker-sync.test.ts +130 -0
  138. package/src/plugins/broker-sync/native-loader.ts +10 -0
  139. package/src/plugins/broker-sync/normalize.ts +271 -0
  140. package/src/plugins/broker-sync/public.ts +122 -0
  141. package/src/plugins/broker-sync/robinhood-native.ts +367 -0
  142. package/src/plugins/broker-sync/robinhood.ts +114 -0
  143. package/src/plugins/broker-sync/simplefin-native.ts +201 -0
  144. package/src/plugins/broker-sync/simplefin.ts +70 -0
  145. package/src/plugins/builtin/account-management/footer.ts +8 -19
  146. package/src/plugins/builtin/account-management/pane.tsx +13 -8
  147. package/src/plugins/builtin/ai/pi/host.ts +6 -5
  148. package/src/plugins/builtin/ai/providers.ts +1 -1
  149. package/src/plugins/builtin/ai/screener/footer.ts +0 -7
  150. package/src/plugins/builtin/ai/screener/pane.test.tsx +2 -1
  151. package/src/plugins/builtin/ai/screener/pane.tsx +3 -1
  152. package/src/plugins/builtin/ai/screener/results-view.tsx +8 -0
  153. package/src/plugins/builtin/ai/screener/runner.ts +7 -3
  154. package/src/plugins/builtin/ai/workspace/pane.tsx +13 -2
  155. package/src/plugins/builtin/alerts/alert-engine.ts +13 -0
  156. package/src/plugins/builtin/alerts/constants.ts +2 -1
  157. package/src/plugins/builtin/alerts/index.tsx +67 -36
  158. package/src/plugins/builtin/alerts/pane.tsx +17 -65
  159. package/src/plugins/builtin/analytics/index.tsx +24 -11
  160. package/src/plugins/builtin/analytics/pane-model.ts +48 -14
  161. package/src/plugins/builtin/analytics/portfolio-selection.ts +19 -0
  162. package/src/plugins/builtin/analytics/view.tsx +5 -3
  163. package/src/plugins/builtin/broker-manager/detail.tsx +7 -2
  164. package/src/plugins/builtin/broker-manager/footer.ts +4 -9
  165. package/src/plugins/builtin/broker-manager/index.tsx +1 -6
  166. package/src/plugins/builtin/buildout/detail/ui.tsx +15 -4
  167. package/src/plugins/builtin/buildout/pane/header.tsx +1 -0
  168. package/src/plugins/builtin/changelog/index.tsx +60 -45
  169. package/src/plugins/builtin/chart-composer/catalog-inventory.test.ts +97 -0
  170. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +366 -0
  171. package/src/plugins/builtin/chart-composer/chart-spec.ts +21 -1
  172. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +307 -0
  173. package/src/plugins/builtin/chart-composer/editor/controller.ts +1 -1
  174. package/src/plugins/builtin/chart-composer/index.tsx +45 -5
  175. package/src/plugins/builtin/chart-composer/pane.tsx +7 -88
  176. package/src/plugins/builtin/chart-composer/presets.test.ts +48 -0
  177. package/src/plugins/builtin/chart-composer/presets.ts +69 -3
  178. package/src/plugins/builtin/chart-composer/quick-add.tsx +8 -5
  179. package/src/plugins/builtin/chart-composer/semantic.ts +18 -14
  180. package/src/plugins/builtin/chart-composer/series-catalog.test.ts +39 -0
  181. package/src/plugins/builtin/chart-composer/series-catalog.ts +104 -3
  182. package/src/plugins/builtin/chart-composer/shortcuts.test.ts +10 -10
  183. package/src/plugins/builtin/chart-composer/shortcuts.ts +4 -12
  184. package/src/plugins/builtin/chart-composer/use-series-catalog.ts +230 -23
  185. package/src/plugins/builtin/chat/chat.test.tsx +22 -0
  186. package/src/plugins/builtin/chat/content/composer.tsx +6 -4
  187. package/src/plugins/builtin/chat/content/index.tsx +7 -0
  188. package/src/plugins/builtin/chat/content/snapshot.ts +9 -2
  189. package/src/plugins/builtin/chat/content/transcript.tsx +10 -0
  190. package/src/plugins/builtin/chat/controller/channel-actions.ts +11 -1
  191. package/src/plugins/builtin/chat/controller/channels.ts +13 -3
  192. package/src/plugins/builtin/chat/controller/index.ts +61 -7
  193. package/src/plugins/builtin/chat/controller/lifecycle.ts +3 -0
  194. package/src/plugins/builtin/chat/controller/message-loading.ts +5 -1
  195. package/src/plugins/builtin/chat/controller/messages.ts +3 -1
  196. package/src/plugins/builtin/chat/controller/notifications.ts +32 -11
  197. package/src/plugins/builtin/chat/controller/state.ts +7 -0
  198. package/src/plugins/builtin/chat/controller/view.ts +1 -0
  199. package/src/plugins/builtin/chat/controller.test.ts +117 -28
  200. package/src/plugins/builtin/cloud/auth-commands.ts +18 -0
  201. package/src/plugins/builtin/cloud/device-signin-dialog.tsx +243 -0
  202. package/src/plugins/builtin/cloud/device-signin.test.ts +217 -0
  203. package/src/plugins/builtin/cloud/device-signin.ts +240 -0
  204. package/src/plugins/builtin/cloud/plugin.tsx +7 -1
  205. package/src/plugins/builtin/cloud-tweets/table.tsx +57 -10
  206. package/src/plugins/builtin/composite-plugins.ts +39 -5
  207. package/src/plugins/builtin/congress-trades/detail.tsx +2 -4
  208. package/src/plugins/builtin/congress-trades/footer.ts +7 -16
  209. package/src/plugins/builtin/congress-trades/pane.tsx +12 -20
  210. package/src/plugins/builtin/connections/index.ts +53 -0
  211. package/src/plugins/builtin/connections/pane.test.tsx +79 -0
  212. package/src/plugins/builtin/connections/pane.tsx +258 -0
  213. package/src/plugins/builtin/correlation/index.test.tsx +2 -1
  214. package/src/plugins/builtin/correlation/index.tsx +58 -19
  215. package/src/plugins/builtin/correlation/matrix/model.ts +18 -7
  216. package/src/plugins/builtin/correlation/relationship/pane.tsx +43 -19
  217. package/src/plugins/builtin/correlation/settings.ts +1 -1
  218. package/src/plugins/builtin/credit-conditions/client.test.ts +80 -0
  219. package/src/plugins/builtin/credit-conditions/client.ts +105 -0
  220. package/src/plugins/builtin/credit-conditions/index.test.tsx +111 -0
  221. package/src/plugins/builtin/credit-conditions/index.tsx +227 -0
  222. package/src/plugins/builtin/credit-conditions/model.test.ts +43 -0
  223. package/src/plugins/builtin/credit-conditions/model.ts +64 -0
  224. package/src/plugins/builtin/debug/index.tsx +11 -6
  225. package/src/plugins/builtin/dividend-yield/client.test.ts +58 -0
  226. package/src/plugins/builtin/dividend-yield/client.ts +252 -0
  227. package/src/plugins/builtin/dividend-yield/index.tsx +61 -0
  228. package/src/plugins/builtin/dividend-yield/model.ts +83 -0
  229. package/src/plugins/builtin/dividend-yield/pane.tsx +305 -0
  230. package/src/plugins/builtin/dividend-yield/types.ts +22 -0
  231. package/src/plugins/builtin/earnings/data/cache.test.ts +10 -7
  232. package/src/plugins/builtin/earnings/data/cache.ts +23 -9
  233. package/src/plugins/builtin/earnings/index.tsx +57 -6
  234. package/src/plugins/builtin/econ/calendar-model.test.ts +37 -3
  235. package/src/plugins/builtin/econ/calendar-model.ts +32 -26
  236. package/src/plugins/builtin/econ/fred-cache.test.ts +8 -1
  237. package/src/plugins/builtin/econ/fred-series-map.ts +18 -0
  238. package/src/plugins/builtin/econ/index.test.tsx +105 -0
  239. package/src/plugins/builtin/econ/index.tsx +95 -52
  240. package/src/plugins/builtin/fear-greed/cache.test.ts +18 -3
  241. package/src/plugins/builtin/fear-greed/cache.ts +18 -6
  242. package/src/plugins/builtin/fear-greed/charts.tsx +9 -2
  243. package/src/plugins/builtin/fear-greed/format.ts +0 -9
  244. package/src/plugins/builtin/fear-greed/pane.tsx +21 -21
  245. package/src/plugins/builtin/futures/contracts.ts +100 -0
  246. package/src/plugins/builtin/futures/index.tsx +298 -0
  247. package/src/plugins/builtin/futures/model.test.ts +129 -0
  248. package/src/plugins/builtin/futures/model.ts +136 -0
  249. package/src/plugins/builtin/futures/pane.test.tsx +239 -0
  250. package/src/plugins/builtin/futures/table.test.ts +155 -0
  251. package/src/plugins/builtin/futures/table.tsx +241 -0
  252. package/src/plugins/builtin/fx-matrix/index.tsx +160 -134
  253. package/src/plugins/builtin/fx-matrix/pairs.ts +12 -1
  254. package/src/plugins/builtin/fx-matrix/pane.test.tsx +109 -0
  255. package/src/plugins/builtin/help/components.tsx +1 -30
  256. package/src/plugins/builtin/help/index.tsx +5 -25
  257. package/src/plugins/builtin/holders/pane.tsx +17 -12
  258. package/src/plugins/builtin/holders/treemap.tsx +4 -2
  259. package/src/plugins/builtin/insider/index.tsx +20 -10
  260. package/src/plugins/builtin/ipo-calendar/cache.ts +101 -0
  261. package/src/plugins/builtin/ipo-calendar/client.test.ts +31 -0
  262. package/src/plugins/builtin/ipo-calendar/client.ts +296 -0
  263. package/src/plugins/builtin/ipo-calendar/index.tsx +65 -0
  264. package/src/plugins/builtin/ipo-calendar/model.ts +145 -0
  265. package/src/plugins/builtin/ipo-calendar/pane.tsx +328 -0
  266. package/src/plugins/builtin/ipo-calendar/types.ts +19 -0
  267. package/src/plugins/builtin/kelly-sizer/index.test.tsx +3 -3
  268. package/src/plugins/builtin/kelly-sizer/model.ts +8 -1
  269. package/src/plugins/builtin/kelly-sizer/pane.tsx +33 -15
  270. package/src/plugins/builtin/kelly-sizer/view.tsx +12 -5
  271. package/src/plugins/builtin/market-halts/client.ts +114 -0
  272. package/src/plugins/builtin/market-halts/index.tsx +29 -0
  273. package/src/plugins/builtin/market-halts/model.test.ts +187 -0
  274. package/src/plugins/builtin/market-halts/model.ts +291 -0
  275. package/src/plugins/builtin/market-halts/pane.tsx +256 -0
  276. package/src/plugins/builtin/market-heatmap/data.test.ts +1 -0
  277. package/src/plugins/builtin/market-heatmap/data.ts +12 -3
  278. package/src/plugins/builtin/market-heatmap/index.tsx +131 -46
  279. package/src/plugins/builtin/market-movers/index.tsx +145 -63
  280. package/src/plugins/builtin/market-movers/model.ts +19 -3
  281. package/src/plugins/builtin/market-movers/screener.test.ts +108 -0
  282. package/src/plugins/builtin/market-movers/screener.ts +168 -10
  283. package/src/plugins/builtin/news/index.tsx +78 -99
  284. package/src/plugins/builtin/news/wire/article-search.test.ts +72 -0
  285. package/src/plugins/builtin/news/wire/article-search.ts +140 -0
  286. package/src/plugins/builtin/news/wire/breaking/pane.tsx +18 -8
  287. package/src/plugins/builtin/news/wire/categories.ts +35 -0
  288. package/src/plugins/builtin/news/wire/default-feeds.ts +47 -1
  289. package/src/plugins/builtin/news/wire/feed-config.test.ts +10 -5
  290. package/src/plugins/builtin/news/wire/index.tsx +41 -4
  291. package/src/plugins/builtin/news/wire/industry-pane.tsx +38 -19
  292. package/src/plugins/builtin/news/wire/news/detail-view.tsx +10 -5
  293. package/src/plugins/builtin/news/wire/news/footer.ts +1 -0
  294. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +15 -6
  295. package/src/plugins/builtin/news/wire/news/query-presets.ts +16 -2
  296. package/src/plugins/builtin/news/wire/news/table.test.tsx +54 -0
  297. package/src/plugins/builtin/news/wire/news/table.tsx +111 -17
  298. package/src/plugins/builtin/notes/files.test.ts +18 -0
  299. package/src/plugins/builtin/notes/files.ts +10 -4
  300. package/src/plugins/builtin/notes/index.tsx +4 -1
  301. package/src/plugins/builtin/notes/quick-notes-pane.test.tsx +182 -0
  302. package/src/plugins/builtin/notes/quick-notes-pane.tsx +74 -31
  303. package/src/plugins/builtin/notes/ticker-notes-tab.test.tsx +380 -0
  304. package/src/plugins/builtin/notes/ticker-notes-tab.tsx +97 -40
  305. package/src/plugins/builtin/options/calc-seed.test.ts +118 -0
  306. package/src/plugins/builtin/options/calc-seed.ts +52 -0
  307. package/src/plugins/builtin/options/footer.ts +4 -1
  308. package/src/plugins/builtin/options/index.tsx +22 -1
  309. package/src/plugins/builtin/options/live-quotes.ts +8 -0
  310. package/src/plugins/builtin/options/table.ts +3 -1
  311. package/src/plugins/builtin/options/view.test.tsx +5 -1
  312. package/src/plugins/builtin/options/view.tsx +112 -17
  313. package/src/plugins/builtin/options-calculator/index.tsx +35 -0
  314. package/src/plugins/builtin/options-calculator/model.test.ts +172 -0
  315. package/src/plugins/builtin/options-calculator/model.ts +273 -0
  316. package/src/plugins/builtin/options-calculator/pane.test.tsx +138 -0
  317. package/src/plugins/builtin/options-calculator/pane.tsx +218 -0
  318. package/src/plugins/builtin/plugin-module.test.ts +16 -2
  319. package/src/plugins/builtin/plugin-module.ts +5 -1
  320. package/src/plugins/builtin/polls/client.ts +57 -0
  321. package/src/plugins/builtin/polls/hooks.ts +20 -0
  322. package/src/plugins/builtin/polls/index.tsx +35 -0
  323. package/src/plugins/builtin/polls/normalize.test.ts +230 -0
  324. package/src/plugins/builtin/polls/normalize.ts +319 -0
  325. package/src/plugins/builtin/polls/pane.tsx +722 -0
  326. package/src/plugins/builtin/polls/types.ts +77 -0
  327. package/src/plugins/builtin/portfolio-list/header.tsx +15 -4
  328. package/src/plugins/builtin/portfolio-list/pane/index.tsx +7 -3
  329. package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +1 -1
  330. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -0
  331. package/src/plugins/builtin/portfolio-list/summary/index.tsx +2 -1
  332. package/src/plugins/builtin/research/analyst-pane.tsx +40 -67
  333. package/src/plugins/builtin/research/corporate-actions-pane.tsx +25 -19
  334. package/src/plugins/builtin/research/index.test.ts +6 -25
  335. package/src/plugins/builtin/research/index.tsx +11 -5
  336. package/src/plugins/builtin/research/relative-valuation-pane.tsx +86 -26
  337. package/src/plugins/builtin/scanner/denied.tsx +31 -0
  338. package/src/plugins/builtin/scanner/feed.ts +94 -0
  339. package/src/plugins/builtin/scanner/flow-model.test.ts +85 -0
  340. package/src/plugins/builtin/scanner/flow-model.ts +133 -0
  341. package/src/plugins/builtin/scanner/flow-pane.tsx +197 -0
  342. package/src/plugins/builtin/scanner/hilo-bars.tsx +121 -0
  343. package/src/plugins/builtin/scanner/hilo-model.test.ts +44 -0
  344. package/src/plugins/builtin/scanner/hilo-model.ts +75 -0
  345. package/src/plugins/builtin/scanner/hilo-pane.tsx +165 -0
  346. package/src/plugins/builtin/scanner/index.ts +109 -0
  347. package/src/plugins/builtin/scanner/waiting.tsx +11 -0
  348. package/src/plugins/builtin/sec/index.tsx +11 -7
  349. package/src/plugins/builtin/sectors/index.tsx +105 -81
  350. package/src/plugins/builtin/sectors/move-bar.tsx +43 -0
  351. package/src/plugins/builtin/sectors/sector-data.ts +18 -0
  352. package/src/plugins/builtin/sectors/sector-model.ts +13 -15
  353. package/src/plugins/builtin/shared/auto-refresh.ts +51 -0
  354. package/src/plugins/builtin/shared/cloud-upgrade.ts +29 -5
  355. package/src/plugins/builtin/shared/pane-footer.ts +16 -3
  356. package/src/plugins/builtin/shared/screener-live-quotes.test.ts +91 -0
  357. package/src/plugins/builtin/shared/screener-live-quotes.ts +134 -0
  358. package/src/plugins/builtin/shared/table-pane.ts +1 -5
  359. package/src/plugins/builtin/shared/use-quote-board.test.tsx +211 -0
  360. package/src/plugins/builtin/shared/use-quote-board.ts +195 -0
  361. package/src/plugins/builtin/short-interest/client.ts +124 -0
  362. package/src/plugins/builtin/short-interest/index.tsx +62 -0
  363. package/src/plugins/builtin/short-interest/model.ts +113 -0
  364. package/src/plugins/builtin/short-interest/pane.tsx +252 -0
  365. package/src/plugins/builtin/short-interest/types.ts +9 -0
  366. package/src/plugins/builtin/substack/article-detail.tsx +15 -1
  367. package/src/plugins/builtin/substack/pane-footer.ts +3 -22
  368. package/src/plugins/builtin/substack/pane.tsx +1 -2
  369. package/src/plugins/builtin/thirteenf/api.ts +4 -1
  370. package/src/plugins/builtin/thirteenf/model.ts +2 -0
  371. package/src/plugins/builtin/thirteenf/pane.tsx +6 -2
  372. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +10 -4
  373. package/src/plugins/builtin/ticker-detail/data-panes/provider-search.tsx +67 -11
  374. package/src/plugins/builtin/ticker-detail/financials/tab.tsx +15 -8
  375. package/src/plugins/builtin/ticker-detail/index.tsx +5 -21
  376. package/src/plugins/builtin/ticker-detail/overview/components.tsx +72 -32
  377. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +0 -10
  378. package/src/plugins/builtin/ticker-detail/pane.tsx +4 -1
  379. package/src/plugins/builtin/ticker-detail/quote-monitor/card.test.tsx +70 -0
  380. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +34 -26
  381. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +96 -49
  382. package/src/plugins/builtin/ticker-detail/research-tabs.ts +32 -0
  383. package/src/plugins/builtin/ticker-research-backend-plugin.ts +2 -9
  384. package/src/plugins/builtin/ticker-research-plugin.ts +6 -0
  385. package/src/plugins/builtin/treasury-auctions/cache.test.ts +173 -0
  386. package/src/plugins/builtin/treasury-auctions/cache.ts +104 -0
  387. package/src/plugins/builtin/treasury-auctions/client.test.ts +181 -0
  388. package/src/plugins/builtin/treasury-auctions/client.ts +151 -0
  389. package/src/plugins/builtin/treasury-auctions/index.tsx +90 -0
  390. package/src/plugins/builtin/treasury-auctions/model.test.ts +147 -0
  391. package/src/plugins/builtin/treasury-auctions/model.ts +188 -0
  392. package/src/plugins/builtin/treasury-auctions/pane.test.tsx +170 -0
  393. package/src/plugins/builtin/treasury-auctions/pane.tsx +417 -0
  394. package/src/plugins/builtin/treasury-auctions/types.ts +59 -0
  395. package/src/plugins/builtin/tv/pane.tsx +67 -7
  396. package/src/plugins/builtin/tv/youtube-embed.test.ts +84 -0
  397. package/src/plugins/builtin/tv/youtube-embed.ts +173 -0
  398. package/src/plugins/builtin/tv/youtube-stream.ts +27 -2
  399. package/src/plugins/builtin/volatility/client.ts +87 -0
  400. package/src/plugins/builtin/volatility/index.test.tsx +107 -0
  401. package/src/plugins/builtin/volatility/index.tsx +193 -0
  402. package/src/plugins/builtin/volatility/model.test.ts +66 -0
  403. package/src/plugins/builtin/volatility/model.ts +113 -0
  404. package/src/plugins/builtin/world-indices/footer.ts +43 -23
  405. package/src/plugins/builtin/world-indices/index.tsx +44 -93
  406. package/src/plugins/builtin/world-indices/indices.ts +11 -2
  407. package/src/plugins/builtin/world-indices/model.ts +17 -24
  408. package/src/plugins/builtin/world-indices/pane.test.tsx +116 -0
  409. package/src/plugins/builtin/world-indices/table.tsx +82 -37
  410. package/src/plugins/builtin/yield-curve/index.tsx +16 -7
  411. package/src/plugins/builtin/yield-curve/treasury-data.test.ts +25 -0
  412. package/src/plugins/builtin/yield-curve/treasury-data.ts +16 -0
  413. package/src/plugins/catalog-backend.ts +8 -0
  414. package/src/plugins/catalog-ui.ts +8 -0
  415. package/src/plugins/event-bus.ts +1 -0
  416. package/src/plugins/ibkr/gateway/account-loaders.ts +1 -1
  417. package/src/plugins/ibkr/trade/tab/footer.ts +9 -23
  418. package/src/plugins/ibkr/trade/tab/index.tsx +1 -5
  419. package/src/plugins/ibkr/trading/pane-view.tsx +149 -87
  420. package/src/plugins/ibkr/trading/pane.tsx +2 -2
  421. package/src/plugins/ownership.ts +3 -0
  422. package/src/plugins/pane-manager/floating.ts +8 -2
  423. package/src/plugins/pane-manager.test.ts +13 -0
  424. package/src/plugins/prediction-markets/backend-plugin.ts +2 -0
  425. package/src/plugins/prediction-markets/capability.test.ts +281 -0
  426. package/src/plugins/prediction-markets/capability.ts +171 -0
  427. package/src/plugins/prediction-markets/chart.tsx +18 -26
  428. package/src/plugins/prediction-markets/columns.ts +3 -2
  429. package/src/plugins/prediction-markets/controller/catalog.ts +15 -12
  430. package/src/plugins/prediction-markets/controller/detail.ts +4 -0
  431. package/src/plugins/prediction-markets/controller/index.ts +0 -15
  432. package/src/plugins/prediction-markets/detail/book.tsx +3 -23
  433. package/src/plugins/prediction-markets/detail/overview.tsx +0 -18
  434. package/src/plugins/prediction-markets/detail/pane.tsx +36 -12
  435. package/src/plugins/prediction-markets/detail.test.tsx +1 -1
  436. package/src/plugins/prediction-markets/index.tsx +2 -0
  437. package/src/plugins/prediction-markets/metrics.ts +28 -18
  438. package/src/plugins/prediction-markets/navigation.ts +1 -0
  439. package/src/plugins/prediction-markets/pane.test.tsx +1 -1
  440. package/src/plugins/prediction-markets/pane.tsx +0 -1
  441. package/src/plugins/prediction-markets/services/fetch.ts +3 -2
  442. package/src/plugins/prediction-markets/services/kalshi/adapter.ts +82 -23
  443. package/src/plugins/prediction-markets/services/kalshi/normalize.ts +3 -1
  444. package/src/plugins/prediction-markets/services/kalshi/types.ts +14 -0
  445. package/src/plugins/prediction-markets/services/polymarket/adapter.ts +26 -14
  446. package/src/plugins/prediction-markets/services/polymarket/detail.ts +67 -18
  447. package/src/plugins/prediction-markets/test-helpers.tsx +0 -1
  448. package/src/plugins/prediction-markets/types.ts +3 -9
  449. package/src/plugins/registry/context.ts +6 -1
  450. package/src/plugins/registry/index.test.ts +2 -0
  451. package/src/plugins/registry/index.ts +53 -3
  452. package/src/plugins/runtime/context.tsx +4 -2
  453. package/src/plugins/runtime/index.tsx +9 -0
  454. package/src/remote/controller.test.ts +17 -4
  455. package/src/renderers/electrobun/bun/application-menu/index.ts +1 -1
  456. package/src/renderers/electrobun/bun/core-capabilities.ts +2 -2
  457. package/src/renderers/electrobun/bun/desktop/capability-bridge.test.ts +64 -0
  458. package/src/renderers/electrobun/bun/desktop/capability-bridge.ts +41 -7
  459. package/src/renderers/electrobun/bun/desktop/host-requests.ts +5 -2
  460. package/src/renderers/electrobun/bun/desktop/http-fetch.test.ts +33 -0
  461. package/src/renderers/electrobun/bun/desktop/http-fetch.ts +53 -6
  462. package/src/renderers/electrobun/bun/desktop/initialization.test.ts +27 -0
  463. package/src/renderers/electrobun/bun/desktop/initialization.ts +6 -1
  464. package/src/renderers/electrobun/bun/index.ts +1 -0
  465. package/src/renderers/electrobun/shared/protocol.ts +2 -0
  466. package/src/renderers/electrobun/view/app-services.ts +25 -2
  467. package/src/renderers/electrobun/view/build-assets.ts +5 -0
  468. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +186 -65
  469. package/src/renderers/electrobun/view/cli-pane-shot-health.test.ts +25 -0
  470. package/src/renderers/electrobun/view/cli-pane-shot-health.ts +31 -0
  471. package/src/renderers/electrobun/view/data-table/index.tsx +10 -11
  472. package/src/renderers/electrobun/view/data-table/row.tsx +2 -0
  473. package/src/renderers/electrobun/view/desktop/controls.test.tsx +68 -0
  474. package/src/renderers/electrobun/view/desktop/controls.tsx +25 -1
  475. package/src/renderers/electrobun/view/host/box.test.tsx +80 -0
  476. package/src/renderers/electrobun/view/host/media-surface.tsx +70 -3
  477. package/src/renderers/electrobun/view/host/tabs.tsx +97 -5
  478. package/src/renderers/electrobun/view/host/text.tsx +5 -2
  479. package/src/renderers/electrobun/view/input-host.test.tsx +140 -0
  480. package/src/renderers/electrobun/view/input-host.tsx +4 -2
  481. package/src/renderers/electrobun/view/native-stubs/backend-rpc.ts +19 -1
  482. package/src/renderers/electrobun/view/native-stubs/broker-sync-native-loader.ts +7 -0
  483. package/src/renderers/electrobun/view/native-stubs/chart/kitty-support.ts +10 -0
  484. package/src/renderers/electrobun/view/remote/capability-invoker.test.ts +46 -0
  485. package/src/renderers/electrobun/view/remote/capability-invoker.ts +34 -13
  486. package/src/renderers/electrobun/view/remote/connection-health-backend.ts +19 -0
  487. package/src/renderers/electrobun/view/remote/connection-health.test.ts +44 -0
  488. package/src/renderers/electrobun/view/remote/connection-health.ts +49 -0
  489. package/src/renderers/electrobun/view/styles.css +5 -0
  490. package/src/renderers/opentui/dialog-host.tsx +279 -80
  491. package/src/renderers/opentui/host.tsx +15 -1
  492. package/src/renderers/opentui/terminal-media.test.ts +102 -0
  493. package/src/renderers/opentui/terminal-media.ts +142 -0
  494. package/src/renderers/opentui/test-utils.tsx +9 -6
  495. package/src/renderers/opentui/toast-host.tsx +136 -7
  496. package/src/renderers/opentui/ui-host.tsx +19 -3
  497. package/src/sources/provider-router/connection-health.test.ts +28 -0
  498. package/src/sources/provider-router/connection-health.ts +47 -0
  499. package/src/sources/provider-router/history.test.ts +53 -4
  500. package/src/sources/provider-router/history.ts +119 -14
  501. package/src/sources/provider-router/index.ts +12 -1
  502. package/src/sources/yahoo-finance/history.ts +2 -9
  503. package/src/sources/yahoo-finance/http.ts +1 -1
  504. package/src/sources/yahoo-finance/symbols.ts +1 -1
  505. package/src/sources/yahoo-finance/types.ts +19 -0
  506. package/src/state/app/context/index.test.tsx +101 -0
  507. package/src/state/app/context/index.tsx +10 -0
  508. package/src/state/config-save-scheduler.ts +1 -6
  509. package/src/state/persist-scheduler.test.ts +31 -0
  510. package/src/state/persist-scheduler.ts +21 -8
  511. package/src/sync/auth-transition.test.ts +36 -0
  512. package/src/sync/auth-transition.ts +22 -0
  513. package/src/sync/core-contributors.test.ts +58 -1
  514. package/src/sync/core-contributors.ts +39 -2
  515. package/src/sync/react.ts +9 -0
  516. package/src/test-support/plugin-runtime.ts +3 -0
  517. package/src/theme/font-scale.test.ts +14 -0
  518. package/src/theme/font-scale.ts +69 -0
  519. package/src/tickers/search/index.test.ts +284 -2
  520. package/src/tickers/search/index.ts +55 -18
  521. package/src/tickers/search/ranking.ts +143 -18
  522. package/src/tickers/search/types.ts +1 -0
  523. package/src/time-series/alignment.ts +15 -11
  524. package/src/time-series/field-catalog.ts +10 -0
  525. package/src/time-series/fundamentals.ts +19 -11
  526. package/src/time-series/hooks.ts +8 -2
  527. package/src/time-series/parsed-history-cache.ts +35 -0
  528. package/src/time-series/resolution.ts +10 -0
  529. package/src/time-series/resolve.test.ts +332 -3
  530. package/src/time-series/resolve.ts +225 -25
  531. package/src/time-series/spec-studies.test.ts +23 -0
  532. package/src/time-series/spec.ts +21 -2
  533. package/src/time-series/transforms.test.ts +76 -1
  534. package/src/time-series/transforms.ts +39 -8
  535. package/src/time-series/types.ts +9 -2
  536. package/src/time-series/use-chart-resolution.test.tsx +105 -1
  537. package/src/time-series/use-chart-resolution.ts +67 -13
  538. package/src/types/config.ts +30 -27
  539. package/src/types/media.ts +2 -0
  540. package/src/types/plugin.ts +8 -1
  541. package/src/ui/host.tsx +4 -0
  542. package/src/ui/pointer-drag.ts +12 -0
  543. package/src/ui/qr.test.ts +79 -0
  544. package/src/ui/qr.ts +82 -0
  545. package/src/updater/github-releases.test.ts +3 -3
  546. package/src/updater/github-releases.ts +1 -1
  547. package/src/updater.test.ts +117 -16
  548. package/src/updater.ts +32 -5
  549. package/src/utils/exchanges.ts +1 -0
  550. package/src/utils/external-url.test.ts +32 -0
  551. package/src/utils/external-url.ts +23 -0
  552. package/src/utils/sort-values.ts +31 -0
  553. package/src/utils/zoned-date-time.ts +67 -0
  554. package/src/components/onboarding/wizard-keyboard.ts +0 -306
  555. package/src/plugins/builtin/chart-composer/date-window-dialog.tsx +0 -120
@@ -0,0 +1,258 @@
1
+ import { useCallback, useEffect, useMemo, useState } from "react";
2
+ import { Box, ScrollBox, Text, TextAttributes } from "../../../ui";
3
+ import {
4
+ DataTableStackView,
5
+ Spinner,
6
+ type DataTableCell,
7
+ type DataTableColumn,
8
+ usePaneFooter,
9
+ } from "../../../components";
10
+ import { useConnectionHealth } from "../../runtime";
11
+ import { useShortcut } from "../../../react/input";
12
+ import type { PaneProps } from "../../../types/plugin";
13
+ import type {
14
+ ConnectionHealthState,
15
+ ConnectionHealthStatus,
16
+ } from "../../../core/connection-health";
17
+ import { colors } from "../../../theme/colors";
18
+ import { formatRelativeAge } from "../../../utils/relative-time";
19
+ import { truncateToDisplayWidth } from "../../../utils/format";
20
+
21
+ interface ConnectionColumn extends DataTableColumn {
22
+ id: "service" | "status" | "request" | "latency" | "last";
23
+ }
24
+
25
+ const SORT_COLUMNS: ConnectionColumn["id"][] = ["status", "service", "request", "latency", "last"];
26
+
27
+ const STATUS_ORDER: Record<ConnectionHealthStatus, number> = {
28
+ error: 0,
29
+ disconnected: 1,
30
+ connecting: 2,
31
+ connected: 3,
32
+ idle: 4,
33
+ };
34
+
35
+ function statusLabel(status: ConnectionHealthStatus): string {
36
+ if (status === "connected") return "Connected";
37
+ if (status === "connecting") return "Connecting";
38
+ if (status === "disconnected") return "Disconnected";
39
+ if (status === "error") return "Error";
40
+ return "Idle";
41
+ }
42
+
43
+ function statusColor(status: ConnectionHealthStatus): string {
44
+ if (status === "connected") return colors.positive;
45
+ if (status === "connecting") return colors.warning;
46
+ if (status === "disconnected" || status === "error") return colors.negative;
47
+ return colors.textMuted;
48
+ }
49
+
50
+ function formatLatency(latencyMs: number | null): string {
51
+ if (latencyMs == null) return "-";
52
+ return latencyMs < 1000 ? `${Math.round(latencyMs)}ms` : `${(latencyMs / 1000).toFixed(1)}s`;
53
+ }
54
+
55
+ function columnsForWidth(width: number): ConnectionColumn[] {
56
+ const statusWidth = 13;
57
+ const latencyWidth = 8;
58
+ const lastWidth = 9;
59
+ const requestWidth = width >= 72 ? 18 : 0;
60
+ // Floor low enough that a narrow pane scrolls horizontally instead of clipping.
61
+ const serviceWidth = Math.max(10, width - statusWidth - latencyWidth - lastWidth - requestWidth - 4);
62
+ return [
63
+ { id: "service", label: "SERVICE", width: serviceWidth, align: "left" },
64
+ { id: "status", label: "STATUS", width: statusWidth, align: "left" },
65
+ ...(requestWidth > 0 ? [{ id: "request", label: "REQUEST", width: requestWidth, align: "left" } as ConnectionColumn] : []),
66
+ { id: "latency", label: "LATENCY", width: latencyWidth, align: "right" },
67
+ { id: "last", label: "LAST", width: lastWidth, align: "right" },
68
+ ];
69
+ }
70
+
71
+ function DetailLine({ label, value, color = colors.text }: { label: string; value: string; color?: string }) {
72
+ return (
73
+ <Box height={1} flexDirection="row">
74
+ <Box width={16}><Text fg={colors.textDim}>{label}</Text></Box>
75
+ <Text fg={color}>{value}</Text>
76
+ </Box>
77
+ );
78
+ }
79
+
80
+ function ConnectionDetail({ source, width, now }: { source: ConnectionHealthState; width: number; now: number }) {
81
+ const lineWidth = Math.max(24, width - 2);
82
+ return (
83
+ <ScrollBox scrollY focusable={false} flexGrow={1} paddingX={1}>
84
+ <Box flexDirection="column" width={lineWidth}>
85
+ <DetailLine label="Status" value={statusLabel(source.status)} color={statusColor(source.status)} />
86
+ <DetailLine label="Type" value={source.kind} color={colors.textDim} />
87
+ {source.ownerId ? <DetailLine label="Owner" value={source.ownerId} color={colors.textDim} /> : null}
88
+ {source.socketState ? <DetailLine label="Socket" value={source.socketState} color={statusColor(source.status)} /> : null}
89
+ {source.lastTransitionAt ? (
90
+ <DetailLine label="Transition" value={formatRelativeAge(source.lastTransitionAt, now)} color={colors.textMuted} />
91
+ ) : null}
92
+ <DetailLine label="Last request" value={source.lastRequestAt ? formatRelativeAge(source.lastRequestAt, now) : "never"} color={colors.textMuted} />
93
+ <DetailLine label="Operation" value={source.lastOperation ?? "-"} color={colors.textDim} />
94
+ <DetailLine label="Latency" value={formatLatency(source.lastLatencyMs)} color={colors.textMuted} />
95
+ {source.currentDetail ? (
96
+ <DetailLine label="Detail" value={truncateToDisplayWidth(source.currentDetail, Math.max(8, lineWidth - 16))} color={source.status === "error" ? colors.negative : colors.textDim} />
97
+ ) : null}
98
+ {source.lastSuccess ? (
99
+ <DetailLine
100
+ label="Last success"
101
+ value={`${formatRelativeAge(source.lastSuccess.at, now)} · ${formatLatency(source.lastSuccess.latencyMs)} · ${source.lastSuccess.operation}`}
102
+ color={colors.positive}
103
+ />
104
+ ) : null}
105
+ {source.lastError ? (
106
+ <DetailLine
107
+ label="Last error"
108
+ value={`${formatRelativeAge(source.lastError.at, now)} · ${source.lastError.error ?? source.lastError.operation}`}
109
+ color={colors.negative}
110
+ />
111
+ ) : null}
112
+ {source.recentRequests.length > 0 ? (
113
+ <>
114
+ <Box height={1} />
115
+ <Text fg={colors.textBright} attributes={TextAttributes.BOLD}>Recent requests</Text>
116
+ {source.recentRequests.slice(0, 8).map((request, index) => (
117
+ <Box key={`${request.at}:${index}`} height={1} flexDirection="row">
118
+ <Box width={7}><Text fg={request.success ? colors.positive : colors.negative}>{request.success ? "ok" : "error"}</Text></Box>
119
+ <Box width={9}><Text fg={colors.textMuted}>{formatLatency(request.latencyMs)}</Text></Box>
120
+ <Box width={10}><Text fg={colors.textMuted}>{formatRelativeAge(request.at, now)}</Text></Box>
121
+ <Text fg={colors.textDim}>{truncateToDisplayWidth(request.error ?? request.operation, Math.max(8, lineWidth - 26))}</Text>
122
+ </Box>
123
+ ))}
124
+ </>
125
+ ) : null}
126
+ </Box>
127
+ </ScrollBox>
128
+ );
129
+ }
130
+
131
+ export function ConnectionsPane({ focused, width, height }: PaneProps) {
132
+ const health = useConnectionHealth();
133
+ const [snapshot, setSnapshot] = useState(() => health.getSnapshot());
134
+ const [selectedId, setSelectedId] = useState<string | null>(null);
135
+ const [detailOpen, setDetailOpen] = useState(false);
136
+ const [sort, setSort] = useState<{ columnId: ConnectionColumn["id"]; direction: "asc" | "desc" }>({
137
+ columnId: "status",
138
+ direction: "asc",
139
+ });
140
+ const [now, setNow] = useState(Date.now());
141
+ // Sources register asynchronously at boot, so an empty first snapshot is a load,
142
+ // not an answer.
143
+ const [settled, setSettled] = useState(() => health.getSnapshot().sources.length > 0);
144
+
145
+ useEffect(() => {
146
+ if (settled) return;
147
+ const timer = setTimeout(() => setSettled(true), 2000);
148
+ return () => clearTimeout(timer);
149
+ }, [settled]);
150
+
151
+ useEffect(() => health.subscribe(() => {
152
+ setSnapshot(health.getSnapshot());
153
+ setSettled(true);
154
+ }), [health]);
155
+ useEffect(() => {
156
+ const timer = setInterval(() => {
157
+ setNow(Date.now());
158
+ setSnapshot(health.getSnapshot());
159
+ }, 5000);
160
+ return () => clearInterval(timer);
161
+ }, [health]);
162
+
163
+ const sources = useMemo(() => [...snapshot.sources].sort((left, right) => {
164
+ let comparison = 0;
165
+ if (sort.columnId === "status") comparison = STATUS_ORDER[left.status] - STATUS_ORDER[right.status];
166
+ else if (sort.columnId === "service") comparison = left.name.localeCompare(right.name);
167
+ else if (sort.columnId === "request") comparison = (left.lastOperation ?? "").localeCompare(right.lastOperation ?? "");
168
+ else if (sort.columnId === "latency") comparison = (left.lastLatencyMs ?? Number.POSITIVE_INFINITY) - (right.lastLatencyMs ?? Number.POSITIVE_INFINITY);
169
+ else comparison = (left.lastRequestAt ?? 0) - (right.lastRequestAt ?? 0);
170
+ return (sort.direction === "asc" ? comparison : -comparison)
171
+ || (left.priority ?? 1000) - (right.priority ?? 1000)
172
+ || left.name.localeCompare(right.name);
173
+ }), [snapshot, sort]);
174
+ const selected = sources.find((source) => source.id === selectedId) ?? sources[0] ?? null;
175
+ const issues = sources.filter((source) => source.status === "error" || source.status === "disconnected").length;
176
+ const connecting = sources.filter((source) => source.status === "connecting").length;
177
+
178
+ useEffect(() => {
179
+ if (!selectedId && sources[0]) setSelectedId(sources[0].id);
180
+ if (selectedId && !sources.some((source) => source.id === selectedId)) {
181
+ setSelectedId(sources[0]?.id ?? null);
182
+ setDetailOpen(false);
183
+ }
184
+ }, [selectedId, sources]);
185
+
186
+ const cycleSort = useCallback(() => {
187
+ setSort((current) => {
188
+ const currentIndex = SORT_COLUMNS.indexOf(current.columnId);
189
+ return current.direction === "asc"
190
+ ? { ...current, direction: "desc" }
191
+ : { columnId: SORT_COLUMNS[(currentIndex + 1) % SORT_COLUMNS.length]!, direction: "asc" };
192
+ });
193
+ }, []);
194
+
195
+ useShortcut((event) => {
196
+ if (!focused || detailOpen || event.name !== "s") return;
197
+ event.stopPropagation();
198
+ cycleSort();
199
+ });
200
+
201
+ usePaneFooter("connections", () => ({
202
+ info: [
203
+ ...(issues > 0 ? [{ id: "issues", parts: [{ text: `${issues} issue${issues === 1 ? "" : "s"}`, tone: "warning" as const }] }] : []),
204
+ ...(connecting > 0 ? [{ id: "connecting", parts: [{ text: `${connecting} connecting`, tone: "muted" as const }] }] : []),
205
+ ],
206
+ hints: detailOpen ? [] : [{ id: "sort", key: "s", label: "ort", onPress: cycleSort }],
207
+ }), [connecting, cycleSort, detailOpen, issues]);
208
+
209
+ const renderCell = useCallback((source: ConnectionHealthState, column: ConnectionColumn): DataTableCell => {
210
+ if (column.id === "service") return { text: truncateToDisplayWidth(source.name, column.width), color: colors.text };
211
+ if (column.id === "status") return { text: statusLabel(source.status), color: statusColor(source.status) };
212
+ if (column.id === "request") return { text: truncateToDisplayWidth(source.lastOperation ?? "-", column.width), color: colors.textDim };
213
+ if (column.id === "latency") return { text: formatLatency(source.lastLatencyMs), color: source.status === "error" ? colors.negative : colors.textMuted };
214
+ return { text: source.lastRequestAt ? formatRelativeAge(source.lastRequestAt, now) : "-", color: colors.textMuted };
215
+ }, [now]);
216
+
217
+ return (
218
+ <Box flexDirection="column" width={width} height={height} overflow="hidden">
219
+ <DataTableStackView<ConnectionHealthState, ConnectionColumn>
220
+ focused={focused}
221
+ detailOpen={detailOpen && !!selected}
222
+ onBack={() => setDetailOpen(false)}
223
+ detailTitle={selected?.name}
224
+ detailContent={selected ? <ConnectionDetail source={selected} width={width} now={now} /> : <Box />}
225
+ rootWidth={width}
226
+ rootHeight={height}
227
+ selection={{
228
+ kind: "id",
229
+ selectedId: selected?.id ?? null,
230
+ getId: (source) => source.id,
231
+ onChange: (id) => setSelectedId(id),
232
+ }}
233
+ onActivate={(source) => {
234
+ setSelectedId(source.id);
235
+ setDetailOpen(true);
236
+ }}
237
+ columns={columnsForWidth(width)}
238
+ items={sources}
239
+ sortColumnId={sort.columnId}
240
+ sortDirection={sort.direction}
241
+ onHeaderClick={(columnId) => {
242
+ setSort((current) => current.columnId === columnId
243
+ ? { ...current, direction: current.direction === "asc" ? "desc" : "asc" }
244
+ : { columnId: columnId as ConnectionColumn["id"], direction: "asc" });
245
+ }}
246
+ getItemKey={(source) => source.id}
247
+ renderCell={renderCell}
248
+ emptyContent={settled ? undefined : (
249
+ <Box paddingX={1} paddingY={1}>
250
+ <Spinner label="Waiting for services to register..." />
251
+ </Box>
252
+ )}
253
+ emptyStateTitle="No connection activity yet."
254
+ emptyStateHint="Sources appear when providers and services register."
255
+ />
256
+ </Box>
257
+ );
258
+ }
@@ -121,7 +121,8 @@ describe("correlationModule", () => {
121
121
  });
122
122
 
123
123
  const lines = testSetup!.captureCharFrame().split("\n");
124
- const headerY = lines.findIndex((line) => line.includes("AAPL") && line.includes("MSFT"));
124
+ // The first AAPL+MSFT line is the in-pane ticker input, so skip comma-separated rows.
125
+ const headerY = lines.findIndex((line) => line.includes("AAPL") && line.includes("MSFT") && !line.includes(","));
125
126
  const headerCol = lines[headerY]?.indexOf("AAPL") ?? -1;
126
127
  expect(headerY).toBeGreaterThanOrEqual(0);
127
128
  expect(headerCol).toBeGreaterThanOrEqual(0);
@@ -1,21 +1,23 @@
1
- import { Box, ScrollBox, Text } from "../../../ui";
2
- import { useCallback, useMemo, useState } from "react";
3
- import { usePaneFooter } from "../../../components";
1
+ import { Box, ScrollBox, Text, type InputRenderable } from "../../../ui";
2
+ import { useCallback, useMemo, useRef, useState } from "react";
3
+ import { InputSearchBar, SegmentedControl, usePaneFooter } from "../../../components";
4
4
  import type { PaneProps } from "../../../types/plugin";
5
5
  import type { PluginModule } from "../plugin-module";
6
6
  import { TICKER_RESEARCH_PANE_ID } from "../../../types/config";
7
7
  import { colors } from "../../../theme/colors";
8
8
  import { usePluginTickerActions } from "../../runtime";
9
- import { useAppSelector, usePaneInstance } from "../../../state/app/context";
9
+ import { useAppSelector, usePaneInstance, usePaneSettingValue } from "../../../state/app/context";
10
10
  import { useChartQueries } from "../../../market-data/hooks";
11
11
  import { buildChartKey } from "../../../market-data/selectors";
12
12
  import { formatTickerListInput } from "../../../tickers/list";
13
13
  import { formatCorrelation } from "./compute";
14
14
  import {
15
+ CORRELATION_RANGE_OPTIONS,
15
16
  DEFAULT_CORRELATION_SYMBOLS,
16
17
  MAX_CORRELATION_TICKERS,
17
18
  buildCorrelationSettingsDef,
18
19
  getCorrelationPaneSettings,
20
+ type CorrelationRangePreset,
19
21
  } from "./settings";
20
22
  import { resolveCorrelationHeatmapCellColors } from "./colors";
21
23
  import {
@@ -39,12 +41,17 @@ import {
39
41
  } from "./matrix/model";
40
42
  import { SymbolLabelCell } from "./matrix/symbol-cell";
41
43
 
42
- function CorrelationMatrixPane({ width, height }: PaneProps) {
44
+ function CorrelationMatrixPane({ focused, width, height }: PaneProps) {
43
45
  const pane = usePaneInstance();
44
46
  const { navigateTicker, pinTicker } = usePluginTickerActions();
45
47
  const tickers = useAppSelector((state) => state.tickers);
46
48
  const [hoveredSymbol, setHoveredSymbol] = useState<string | null>(null);
47
49
  const settings = useMemo(() => getCorrelationPaneSettings(pane?.settings), [pane?.settings]);
50
+ const [rangePreset, setRangePreset] = usePaneSettingValue<CorrelationRangePreset>("rangePreset", settings.rangePreset);
51
+ const [symbolsText, setSymbolsText] = usePaneSettingValue<string>("symbolsText", settings.symbolsText);
52
+ const [symbolsEditing, setSymbolsEditing] = useState(false);
53
+ const [symbolsFocusToken, setSymbolsFocusToken] = useState(0);
54
+ const symbolsInputRef = useRef<InputRenderable | null>(null);
48
55
 
49
56
  const instruments = useMemo(() => {
50
57
  if (settings.symbolsError) return [];
@@ -94,19 +101,19 @@ function CorrelationMatrixPane({ width, height }: PaneProps) {
94
101
  }, [symbolsKey, seriesBySymbol]);
95
102
 
96
103
  const statusSummary = useMemo(
97
- () => buildStatusSummary(symbols, seriesBySymbol, matrix.sampleMin, matrix.sampleMax),
98
- [symbolsKey, seriesBySymbol, matrix.sampleMin, matrix.sampleMax],
104
+ () => buildStatusSummary(symbols, seriesBySymbol, matrix.sampleMin, matrix.sampleMax, matrix.hasThinPair),
105
+ [symbolsKey, seriesBySymbol, matrix.sampleMin, matrix.sampleMax, matrix.hasThinPair],
99
106
  );
100
107
 
108
+ // The ticker set and range are visible in-pane, so the footer only carries
109
+ // load state and errors.
101
110
  usePaneFooter("correlation", () => ({
102
- info: [
103
- { id: "tickers", parts: [{ text: `${symbols.length} tickers`, tone: symbols.length >= 2 ? "value" : "warning", bold: symbols.length >= 2 }] },
104
- { id: "range", parts: [{ text: settings.rangePreset, tone: "muted" }] },
105
- ...(settings.symbolsError
106
- ? [{ id: "error", parts: [{ text: settings.symbolsError, tone: "warning" as const }] }]
107
- : [{ id: "status", parts: [{ text: statusSummary, tone: "muted" as const }] }]),
108
- ],
109
- }), [settings.rangePreset, settings.symbolsError, statusSummary, symbols.length]);
111
+ info: settings.symbolsError
112
+ ? [{ id: "error", parts: [{ text: settings.symbolsError, tone: "warning" as const }] }]
113
+ : statusSummary
114
+ ? [{ id: "status", parts: [{ text: statusSummary, tone: "muted" as const }] }]
115
+ : [],
116
+ }), [settings.symbolsError, statusSummary]);
110
117
 
111
118
  const openSymbol = useCallback((symbol: string) => {
112
119
  if (tickers.has(symbol)) {
@@ -144,11 +151,43 @@ function CorrelationMatrixPane({ width, height }: PaneProps) {
144
151
  );
145
152
  const availableCellWidth = Math.floor((Math.max(width - rowHeaderWidth - 4, symbols.length * MIN_MATRIX_CELL_WIDTH)) / symbols.length);
146
153
  const cellWidth = Math.max(MIN_MATRIX_CELL_WIDTH, Math.min(MATRIX_CELL_WIDTH, availableCellWidth));
154
+ // Rows are exactly as wide as the matrix, so the zebra and hover bands stop at
155
+ // the last column instead of running to the pane edge.
156
+ const matrixRowWidth = rowHeaderWidth + symbols.length * cellWidth + 2;
147
157
 
148
158
  return (
149
159
  <Box flexDirection="column" width={width} height={height}>
160
+ <Box height={1} flexDirection="row" overflow="hidden">
161
+ <Box flexShrink={1} overflow="hidden">
162
+ <InputSearchBar
163
+ value={symbolsText}
164
+ focused={focused}
165
+ active={symbolsEditing}
166
+ width={Math.max(12, Math.min(40, width - 24))}
167
+ focusToken={symbolsFocusToken}
168
+ inputRef={symbolsInputRef}
169
+ placeholder="tickers"
170
+ debounceMs={500}
171
+ onFocus={() => {
172
+ setSymbolsEditing(true);
173
+ setSymbolsFocusToken((token) => token + 1);
174
+ }}
175
+ onBlur={() => setSymbolsEditing(false)}
176
+ onQueryChange={(query) => setSymbolsText(query)}
177
+ />
178
+ </Box>
179
+ <Box flexGrow={1} />
180
+ <SegmentedControl
181
+ options={CORRELATION_RANGE_OPTIONS.map((range) => ({ label: range, value: range }))}
182
+ value={rangePreset}
183
+ onChange={(value) => setRangePreset(value as CorrelationRangePreset)}
184
+ focused={focused && !symbolsEditing}
185
+ shortcutScope="correlation:range"
186
+ />
187
+ </Box>
188
+
150
189
  {/* Column header row */}
151
- <Box flexDirection="row" paddingX={1} height={1} backgroundColor={headerBg}>
190
+ <Box flexDirection="row" paddingX={1} height={1} width={matrixRowWidth} backgroundColor={headerBg}>
152
191
  <Box width={rowHeaderWidth} flexShrink={0} />
153
192
  {symbols.map((sym) => (
154
193
  <SymbolLabelCell
@@ -166,10 +205,10 @@ function CorrelationMatrixPane({ width, height }: PaneProps) {
166
205
  </Box>
167
206
 
168
207
  {/* Matrix rows */}
169
- <ScrollBox flexGrow={1} scrollY focusable={false}>
208
+ <ScrollBox flexGrow={1} scrollY scrollX focusable={false}>
170
209
  <Box flexDirection="column">
171
210
  {symbols.map((rowSym, rowIndex) => (
172
- <Box key={rowSym} flexDirection="row" paddingX={1} backgroundColor={rowIndex % 2 === 0 ? colors.bg : undefined}>
211
+ <Box key={rowSym} flexDirection="row" paddingX={1} width={matrixRowWidth} backgroundColor={rowIndex % 2 === 0 ? colors.bg : undefined}>
173
212
  {/* Row header */}
174
213
  <Box
175
214
  width={rowHeaderWidth}
@@ -267,7 +306,7 @@ export const correlationModule: PluginModule = {
267
306
  ? options.symbols
268
307
  : DEFAULT_CORRELATION_SYMBOLS;
269
308
  return {
270
- title: buildCorrelationPaneTitle(symbols, "1Y"),
309
+ title: buildCorrelationPaneTitle(),
271
310
  placement: "floating",
272
311
  settings: {
273
312
  rangePreset: "1Y",
@@ -93,9 +93,11 @@ export function buildCorrelationMatrix(
93
93
  results: Map<string, CorrelationResult>;
94
94
  sampleMin: number | null;
95
95
  sampleMax: number | null;
96
+ hasThinPair: boolean;
96
97
  } {
97
98
  const results = new Map<string, CorrelationResult>();
98
99
  const sampleSizes: number[] = [];
100
+ let hasThinPair = false;
99
101
 
100
102
  for (let rowIndex = 0; rowIndex < symbols.length; rowIndex++) {
101
103
  for (let colIndex = 0; colIndex < symbols.length; colIndex++) {
@@ -108,8 +110,11 @@ export function buildCorrelationMatrix(
108
110
  ? correlateDatedReturns(rowSeries.returns, colSeries.returns, MIN_CORRELATION_OBSERVATIONS)
109
111
  : { correlation: null, sampleSize: 0 };
110
112
  results.set(pairKey(rowSym, colSym), result);
111
- if (rowIndex < colIndex && result.sampleSize > 0) {
112
- sampleSizes.push(result.sampleSize);
113
+ if (rowIndex < colIndex) {
114
+ if (result.sampleSize > 0) sampleSizes.push(result.sampleSize);
115
+ if (result.correlation == null && result.sampleSize < MIN_CORRELATION_OBSERVATIONS) {
116
+ hasThinPair = true;
117
+ }
113
118
  }
114
119
  }
115
120
  }
@@ -118,6 +123,7 @@ export function buildCorrelationMatrix(
118
123
  results,
119
124
  sampleMin: sampleSizes.length > 0 ? Math.min(...sampleSizes) : null,
120
125
  sampleMax: sampleSizes.length > 0 ? Math.max(...sampleSizes) : null,
126
+ hasThinPair,
121
127
  };
122
128
  }
123
129
 
@@ -126,6 +132,7 @@ export function buildStatusSummary(
126
132
  seriesBySymbol: Map<string, CorrelationSeries>,
127
133
  sampleMin: number | null,
128
134
  sampleMax: number | null,
135
+ hasThinPair = false,
129
136
  ): string {
130
137
  const parts: string[] = [];
131
138
  const byStatus = (status: SeriesStatus) => symbols.filter((symbol) => seriesBySymbol.get(symbol)?.status === status);
@@ -144,12 +151,16 @@ export function buildStatusSummary(
144
151
  parts.push("No paired dates yet");
145
152
  }
146
153
 
147
- parts.push(`— <${MIN_CORRELATION_OBSERVATIONS} shared`);
154
+ // Only legend the blank cells when at least one pair actually has too few
155
+ // shared dates to correlate.
156
+ if (hasThinPair) parts.push(`- <${MIN_CORRELATION_OBSERVATIONS} shared`);
148
157
  return parts.join(" · ");
149
158
  }
150
159
 
151
- export function buildCorrelationPaneTitle(symbols: string[], rangePreset: CorrelationRangePreset): string {
152
- if (symbols.length === 0) return `Correlation ${rangePreset}`;
153
- if (symbols.length <= 3) return `${symbols.join(" · ")} ${rangePreset}`;
154
- return `${symbols.slice(0, 2).join(" · ")} +${symbols.length - 2} ${rangePreset}`;
160
+ /**
161
+ * The matrix already labels every symbol on both axes and the pane now carries
162
+ * its own range control, so the title stays generic instead of repeating them.
163
+ */
164
+ export function buildCorrelationPaneTitle(): string {
165
+ return "Correlation";
155
166
  }
@@ -1,6 +1,6 @@
1
1
  import { useCallback, useEffect, useMemo, useState } from "react";
2
2
  import { Box, Text } from "../../../../ui";
3
- import { usePaneFooter } from "../../../../components";
3
+ import { Button, usePaneFooter } from "../../../../components";
4
4
  import { useShortcut, type KeyEventLike } from "../../../../react/input";
5
5
  import { StaticMultiLineChartSurface } from "../../../../components/chart/static/multi-line-chart-surface";
6
6
  import { StaticScatterChartSurface } from "../../../../components/chart/static/scatter-chart-surface";
@@ -45,6 +45,10 @@ export {
45
45
 
46
46
  type RelationshipGraphShortcut = "range" | "window" | "correlation" | "regression";
47
47
 
48
+ /**
49
+ * Keys are the first letter of their hint label: [t]ime range, [p]eriod, [c]orr,
50
+ * [f]it line. `r` stays reserved for the app-wide refresh.
51
+ */
48
52
  export function resolveRelationshipGraphShortcut(
49
53
  event: Pick<KeyEventLike, "name" | "key" | "ctrl" | "shift" | "alt" | "meta" | "super">,
50
54
  ): RelationshipGraphShortcut | null {
@@ -57,7 +61,7 @@ export function resolveRelationshipGraphShortcut(
57
61
  return "window";
58
62
  case "c":
59
63
  return "correlation";
60
- case "g":
64
+ case "f":
61
65
  return "regression";
62
66
  default:
63
67
  return null;
@@ -93,19 +97,28 @@ export function RelationshipGraphPane({ focused, width, height }: PaneProps) {
93
97
  : "negative";
94
98
  const ratioPalette = useMemo(() => resolveChartPalette(colors, ratioTrend), [ratioTrend]);
95
99
  const chartWidth = Math.max(20, width - 2);
96
- const headerRows = 1;
97
- const availableChartRows = Math.max(8, height - headerRows);
98
- const showScatter = showRegression && availableChartRows >= 17;
99
- const priceHeight = Math.max(5, Math.floor(availableChartRows * (showScatter ? 0.26 : 0.34)));
100
- const ratioHeight = Math.max(4, Math.floor(availableChartRows * (showScatter ? 0.22 : 0.33)));
101
- const correlationHeight = showCorrelation
102
- ? Math.max(4, Math.floor(availableChartRows * (showScatter ? 0.22 : 0.33)))
100
+ // Panels are allocated in priority order out of the rows that actually exist, so
101
+ // a short pane drops the lowest-priority chart instead of clipping every axis.
102
+ const headerRows = 2;
103
+ const availableChartRows = Math.max(0, height - headerRows);
104
+ const railWidth = chartWidth >= 68 ? Math.min(34, Math.floor(chartWidth * 0.3)) : 0;
105
+ const statsBelowRows = railWidth === 0 ? 1 : 0;
106
+ let remainingRows = availableChartRows;
107
+ const priceHeight = Math.min(remainingRows, Math.max(5, Math.floor(availableChartRows * 0.28)));
108
+ remainingRows -= priceHeight;
109
+ const ratioHeight = remainingRows >= 4
110
+ ? Math.min(remainingRows, Math.max(4, Math.floor(availableChartRows * 0.24)))
103
111
  : 0;
104
- const statsRailWidth = showScatter && chartWidth >= 68 ? Math.min(34, Math.floor(chartWidth * 0.3)) : 0;
105
- const statsBelowRows = showScatter && statsRailWidth === 0 ? 1 : 0;
106
- const scatterHeight = showScatter
107
- ? Math.max(5, availableChartRows - priceHeight - ratioHeight - correlationHeight - statsBelowRows)
112
+ remainingRows -= ratioHeight;
113
+ const showCorrelationChart = showCorrelation && remainingRows >= 4;
114
+ const correlationHeight = showCorrelationChart
115
+ ? Math.min(remainingRows, Math.max(4, Math.floor(availableChartRows * 0.24)))
108
116
  : 0;
117
+ remainingRows -= correlationHeight;
118
+ // Scatter needs its own two label rows on top of a usable plot.
119
+ const showScatter = showRegression && remainingRows >= 7 + statsBelowRows;
120
+ const scatterHeight = showScatter ? remainingRows - statsBelowRows : 0;
121
+ const statsRailWidth = showScatter ? railWidth : 0;
109
122
  const scatterWidth = statsRailWidth > 0 ? Math.max(20, chartWidth - statsRailWidth - 1) : chartWidth;
110
123
  const stats = analysis?.stats ?? null;
111
124
  const alignedDates = useMemo(() => analysis?.aligned.map((entry) => entry.date) ?? [], [analysis]);
@@ -213,10 +226,10 @@ export function RelationshipGraphPane({ focused, width, height }: PaneProps) {
213
226
  ...(error ? [{ id: "error", parts: [{ text: error, tone: "warning" as const }] }] : []),
214
227
  ],
215
228
  hints: [
216
- { id: "range", key: "t", label: "range", onPress: cycleRange },
229
+ { id: "range", key: "t", label: "ime range", onPress: cycleRange },
217
230
  { id: "window", key: "p", label: "eriod", onPress: cycleWindow },
218
231
  { id: "correlation", key: "c", label: "orr", onPress: toggleCorrelation },
219
- { id: "regression", key: "g", label: "reg", onPress: toggleRegression },
232
+ { id: "regression", key: "f", label: "it line", onPress: toggleRegression },
220
233
  ],
221
234
  }), [
222
235
  cycleRange,
@@ -256,8 +269,17 @@ export function RelationshipGraphPane({ focused, width, height }: PaneProps) {
256
269
  >
257
270
  <Box height={1} flexDirection="row" gap={2}>
258
271
  <RelationshipToggle checked={showCorrelation} label="Correlation" onPress={toggleCorrelation} />
259
- <RelationshipToggle checked={showRegression} label="Regression" onPress={toggleRegression} />
260
- <Text fg={colors.textDim}>Range {range} Window {correlationWindow}d</Text>
272
+ <RelationshipToggle checked={showRegression} label="Fit line" onPress={toggleRegression} />
273
+ <Button label={`Range ${range}`} variant="ghost" onPress={cycleRange} />
274
+ <Button label={`Window ${correlationWindow}d`} variant="ghost" onPress={cycleWindow} />
275
+ </Box>
276
+ <Box height={1} flexDirection="row" gap={2}>
277
+ {priceSeries.map((series) => (
278
+ <Box key={series.id} flexDirection="row" gap={1}>
279
+ <Box width={2} height={1} backgroundColor={series.color} />
280
+ <Text fg={colors.textDim}>{series.label}</Text>
281
+ </Box>
282
+ ))}
261
283
  </Box>
262
284
  <StaticMultiLineChartSurface
263
285
  series={priceSeries}
@@ -272,6 +294,7 @@ export function RelationshipGraphPane({ focused, width, height }: PaneProps) {
272
294
  formatYAxisValue={(value) => formatNumber(value, 0)}
273
295
  onCursorDateChange={selectCursorDate}
274
296
  />
297
+ {ratioHeight > 0 ? (
275
298
  <StaticMultiLineChartSurface
276
299
  series={ratioSeries}
277
300
  width={chartWidth}
@@ -285,7 +308,8 @@ export function RelationshipGraphPane({ focused, width, height }: PaneProps) {
285
308
  formatYAxisValue={(value) => formatNumber(value, Math.abs(value) >= 10 ? 1 : 3)}
286
309
  onCursorDateChange={selectCursorDate}
287
310
  />
288
- {showCorrelation ? (
311
+ ) : null}
312
+ {showCorrelationChart ? (
289
313
  <StaticMultiLineChartSurface
290
314
  series={correlationSeries}
291
315
  width={chartWidth}
@@ -320,7 +344,7 @@ export function RelationshipGraphPane({ focused, width, height }: PaneProps) {
320
344
  ) : null}
321
345
  </Box>
322
346
  ) : null}
323
- {showRegression && (!showScatter || statsRailWidth === 0) ? (
347
+ {showScatter && statsRailWidth === 0 ? (
324
348
  <Text fg={colors.textDim}>
325
349
  {metricsRows.map((row) => `${row.label} ${row.value}`).join(" ")}
326
350
  </Text>
@@ -4,7 +4,7 @@ import { formatTickerListInput, MAX_TICKER_LIST_SIZE, parseTickerListInput } fro
4
4
 
5
5
  export const MAX_CORRELATION_TICKERS = MAX_TICKER_LIST_SIZE;
6
6
  const DEFAULT_CORRELATION_RANGE: CorrelationRangePreset = "1Y";
7
- const CORRELATION_RANGE_OPTIONS = ["1M", "3M", "6M", "1Y", "5Y"] as const;
7
+ export const CORRELATION_RANGE_OPTIONS = ["1M", "3M", "6M", "1Y", "5Y"] as const;
8
8
  export const DEFAULT_CORRELATION_SYMBOLS = ["AAPL", "MSFT", "NVDA", "AMD"];
9
9
 
10
10
  export type CorrelationRangePreset = Extract<TimeRange, typeof CORRELATION_RANGE_OPTIONS[number]>;