gloomberb 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (500) hide show
  1. package/README.md +91 -11
  2. package/package.json +12 -5
  3. package/src/app.tsx +414 -191
  4. package/src/architecture/import-boundaries.test.ts +118 -0
  5. package/src/assets/gloomberb-logo.png +0 -0
  6. package/src/assets/gloomberb-logo.svg +73 -0
  7. package/src/brokers/account-cache.ts +157 -0
  8. package/src/brokers/profile-form.test.ts +73 -0
  9. package/src/brokers/profile-form.ts +109 -0
  10. package/src/brokers/remote-broker-adapter.ts +91 -0
  11. package/src/brokers/sync-broker-instance.test.ts +124 -1
  12. package/src/brokers/sync-broker-instance.ts +113 -17
  13. package/src/capabilities/factories.ts +100 -0
  14. package/src/capabilities/index.ts +4 -0
  15. package/src/capabilities/registry.test.ts +137 -0
  16. package/src/capabilities/registry.ts +131 -0
  17. package/src/capabilities/service-capabilities.ts +27 -0
  18. package/src/capabilities/types.ts +84 -0
  19. package/src/cli/commands/ticker.ts +11 -3
  20. package/src/cli/context.ts +19 -13
  21. package/src/cli/helpers.ts +1 -0
  22. package/src/cli/types.ts +2 -2
  23. package/src/components/chart/chart-controller.ts +4 -24
  24. package/src/components/chart/chart-data.ts +3 -14
  25. package/src/components/chart/chart-indicator-selector.tsx +28 -6
  26. package/src/components/chart/chart-pane-settings.ts +15 -28
  27. package/src/components/chart/chart-renderer.ts +74 -99
  28. package/src/components/chart/chart-resolution.ts +10 -10
  29. package/src/components/chart/chart-scroll.test.ts +6 -5
  30. package/src/components/chart/chart-scroll.ts +2 -2
  31. package/src/components/chart/chart-types.ts +0 -1
  32. package/src/components/chart/chart-viewport.ts +2 -1
  33. package/src/components/chart/chart.test.ts +37 -0
  34. package/src/components/chart/comparison-chart-data.ts +3 -17
  35. package/src/components/chart/comparison-chart-renderer.test.ts +27 -4
  36. package/src/components/chart/comparison-chart-renderer.ts +1 -1
  37. package/src/components/chart/comparison-stock-chart.tsx +304 -108
  38. package/src/components/chart/cursor-motion.ts +3 -2
  39. package/src/components/chart/indicators/bands.test.ts +0 -14
  40. package/src/components/chart/indicators/moving-averages.test.ts +0 -15
  41. package/src/components/chart/indicators/options.ts +3 -3
  42. package/src/components/chart/indicators/oscillators.test.ts +0 -21
  43. package/src/components/chart/indicators/types.ts +1 -2
  44. package/src/components/chart/native/chart-rasterizer.ts +37 -44
  45. package/src/components/chart/native/kitty-adapter.ts +2 -5
  46. package/src/components/chart/native/kitty-manager.ts +1 -1
  47. package/src/components/chart/native/kitty-protocol.ts +0 -1
  48. package/src/components/chart/native/kitty-support.ts +1 -1
  49. package/src/components/chart/native/kitty.test.ts +30 -13
  50. package/src/components/chart/native/renderer-selection.ts +1 -1
  51. package/src/components/chart/native/surface-manager.ts +1 -1
  52. package/src/components/chart/static-chart-surface.test.tsx +51 -0
  53. package/src/components/chart/static-chart-surface.tsx +191 -0
  54. package/src/components/chart/stock-chart-renderer-switch.test.tsx +242 -35
  55. package/src/components/chart/stock-chart.tsx +438 -141
  56. package/src/components/command-bar/__snapshots__/command-bar.test.tsx.snap +4 -4
  57. package/src/components/command-bar/command-bar.test.tsx +269 -20
  58. package/src/components/command-bar/command-bar.tsx +830 -353
  59. package/src/components/command-bar/command-registry.ts +10 -20
  60. package/src/components/command-bar/helpers.test.ts +1 -1
  61. package/src/components/command-bar/helpers.ts +9 -11
  62. package/src/components/command-bar/pane-template-display.ts +4 -8
  63. package/src/components/command-bar/root-shortcuts.ts +12 -11
  64. package/src/components/command-bar/view-model.test.ts +8 -42
  65. package/src/components/command-bar/view-model.ts +8 -36
  66. package/src/components/command-bar/workflow-ops.test.ts +76 -2
  67. package/src/components/command-bar/workflow-ops.ts +40 -9
  68. package/src/components/command-bar/workflow-types.ts +2 -1
  69. package/src/components/data-table-stack-view.test.tsx +206 -0
  70. package/src/components/data-table-stack-view.tsx +152 -0
  71. package/src/components/data-table-view.test.tsx +188 -0
  72. package/src/components/data-table-view.tsx +279 -0
  73. package/src/components/feed-data-table-stack-view.test.tsx +225 -0
  74. package/src/components/feed-data-table-stack-view.tsx +365 -0
  75. package/src/components/index.ts +26 -31
  76. package/src/components/layout/detached-pane-shell.tsx +185 -0
  77. package/src/components/layout/floating-pane.tsx +87 -13
  78. package/src/components/layout/header.tsx +139 -49
  79. package/src/components/layout/pane-content.tsx +40 -0
  80. package/src/components/layout/pane-footer.test.tsx +195 -0
  81. package/src/components/layout/pane-footer.tsx +410 -0
  82. package/src/components/layout/pane-header.test.tsx +39 -0
  83. package/src/components/layout/pane-header.tsx +192 -14
  84. package/src/components/layout/pane-sizing.ts +13 -4
  85. package/src/components/layout/pane-title.ts +35 -0
  86. package/src/components/layout/pane.tsx +56 -8
  87. package/src/components/layout/shell.test.tsx +322 -19
  88. package/src/components/layout/shell.tsx +1010 -366
  89. package/src/components/layout/status-bar.test.tsx +17 -10
  90. package/src/components/layout/status-bar.tsx +367 -100
  91. package/src/components/layout/titlebar-overlay.ts +2 -0
  92. package/src/components/markdown-editor.tsx +29 -14
  93. package/src/components/markdown-text.tsx +25 -24
  94. package/src/components/onboarding/onboarding-wizard.test.tsx +8 -3
  95. package/src/components/onboarding/onboarding-wizard.tsx +347 -473
  96. package/src/components/pane-settings-dialog.tsx +521 -89
  97. package/src/components/pane-template-wizard.test.tsx +1 -1
  98. package/src/components/pane-template-wizard.tsx +38 -37
  99. package/src/components/price-selector-dialog.tsx +22 -21
  100. package/src/components/ticker-badge-list.tsx +53 -0
  101. package/src/components/ticker-badge-text.test.tsx +44 -1
  102. package/src/components/ticker-badge-text.tsx +78 -38
  103. package/src/components/ticker-badge.tsx +28 -7
  104. package/src/components/ticker-list-table-view.tsx +248 -0
  105. package/src/components/ticker-list-table.test.tsx +47 -1
  106. package/src/components/ticker-list-table.tsx +212 -41
  107. package/src/components/toggle-list.tsx +114 -8
  108. package/src/components/ui/button.tsx +24 -16
  109. package/src/components/ui/choice-dialog.tsx +89 -0
  110. package/src/components/ui/data-table.tsx +313 -114
  111. package/src/components/ui/external-link.tsx +52 -13
  112. package/src/components/ui/fields.tsx +44 -30
  113. package/src/components/ui/frame.tsx +24 -57
  114. package/src/components/ui/index.ts +15 -50
  115. package/src/components/ui/list-view.tsx +65 -25
  116. package/src/components/ui/loading.tsx +5 -60
  117. package/src/components/ui/message-composer.test.tsx +81 -0
  118. package/src/components/ui/message-composer.tsx +139 -0
  119. package/src/components/ui/multi-select-dialog.tsx +100 -32
  120. package/src/components/ui/multi-select.ts +112 -0
  121. package/src/components/ui/native-select.tsx +120 -0
  122. package/src/components/ui/page-stack-view.tsx +49 -19
  123. package/src/components/ui/remote-image.tsx +75 -0
  124. package/src/components/ui/row-value-cache.test.ts +17 -0
  125. package/src/components/ui/row-value-cache.ts +41 -0
  126. package/src/components/ui/shortcut-hint.tsx +55 -0
  127. package/src/components/ui/status.tsx +12 -67
  128. package/src/components/ui/table-layout.ts +59 -0
  129. package/src/components/ui/tabs.tsx +249 -27
  130. package/src/components/ui/toggle.tsx +19 -127
  131. package/src/components/ui/ui.test.tsx +266 -53
  132. package/src/components/use-double-click-activation.ts +7 -2
  133. package/src/components/use-external-link-footer.ts +79 -0
  134. package/src/core/app-services.ts +95 -0
  135. package/src/core/state/app-state.ts +886 -0
  136. package/src/{state → core/state}/session-persistence.ts +7 -6
  137. package/src/data/config-layout.ts +225 -0
  138. package/src/data/config-store-node.ts +390 -0
  139. package/src/data/config-store.test.ts +72 -0
  140. package/src/data/config-store.ts +35 -478
  141. package/src/data/sqlite-json.ts +2 -5
  142. package/src/data/sqlite-retry.ts +1 -0
  143. package/src/index.tsx +2 -68
  144. package/src/market-data/coordinator-subscriptions.test.ts +125 -0
  145. package/src/market-data/coordinator.test.ts +73 -4
  146. package/src/market-data/coordinator.ts +190 -24
  147. package/src/market-data/hooks.test.tsx +1 -1
  148. package/src/market-data/hooks.tsx +109 -87
  149. package/src/market-data/query-store.ts +3 -3
  150. package/src/market-data/request-types.test.ts +0 -14
  151. package/src/market-data/result-types.ts +1 -0
  152. package/src/news/aggregator.test.ts +267 -0
  153. package/src/news/aggregator.ts +452 -0
  154. package/src/news/hooks.ts +39 -0
  155. package/src/news/ticker-symbols.ts +21 -0
  156. package/src/news/types.ts +77 -0
  157. package/src/notifications/app-notifier.ts +21 -10
  158. package/src/pane-settings.test.ts +0 -2
  159. package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +124 -134
  160. package/src/plugins/builtin/ai/gloomberb-cli.ts +14 -13
  161. package/src/plugins/builtin/ai/providers.ts +1 -9
  162. package/src/plugins/builtin/ai/runner.ts +41 -5
  163. package/src/plugins/builtin/ai/screener-pane.test.tsx +37 -55
  164. package/src/plugins/builtin/ai/screener-pane.tsx +231 -276
  165. package/src/plugins/builtin/ai/settings.ts +0 -1
  166. package/src/plugins/builtin/ai/utils.ts +1 -71
  167. package/src/plugins/builtin/alerts/alert-engine.test.ts +1 -38
  168. package/src/plugins/builtin/alerts/index.test.tsx +61 -82
  169. package/src/plugins/builtin/alerts/index.tsx +263 -155
  170. package/src/plugins/builtin/alerts/types.ts +7 -0
  171. package/src/plugins/builtin/analytics/index.test.tsx +13 -8
  172. package/src/plugins/builtin/analytics/index.tsx +78 -120
  173. package/src/plugins/builtin/analytics/metrics.test.ts +0 -4
  174. package/src/plugins/builtin/ask-ai.test.tsx +55 -54
  175. package/src/plugins/builtin/broker-manager/index.test.tsx +168 -0
  176. package/src/plugins/builtin/broker-manager/index.tsx +759 -0
  177. package/src/plugins/builtin/broker-manager/model.test.ts +107 -0
  178. package/src/plugins/builtin/broker-manager/model.ts +142 -0
  179. package/src/plugins/builtin/chat-controller.test.ts +150 -0
  180. package/src/plugins/builtin/chat-controller.ts +88 -16
  181. package/src/plugins/builtin/chat.test.tsx +283 -36
  182. package/src/plugins/builtin/chat.tsx +373 -172
  183. package/src/plugins/builtin/cloud-auth-actions.tsx +72 -0
  184. package/src/plugins/builtin/cloud-tweets.tsx +896 -0
  185. package/src/plugins/builtin/comparison-chart.test.tsx +46 -57
  186. package/src/plugins/builtin/comparison-chart.tsx +11 -11
  187. package/src/plugins/builtin/correlation/compute.test.ts +0 -17
  188. package/src/plugins/builtin/correlation/index.tsx +51 -46
  189. package/src/plugins/builtin/correlation/settings.ts +1 -2
  190. package/src/plugins/builtin/debug.tsx +99 -70
  191. package/src/plugins/builtin/earnings/earnings-cache.test.ts +2 -2
  192. package/src/plugins/builtin/earnings/earnings-cache.ts +12 -3
  193. package/src/plugins/builtin/earnings/earnings-data.test.ts +42 -11
  194. package/src/plugins/builtin/earnings/earnings-data.ts +70 -5
  195. package/src/plugins/builtin/earnings/index.tsx +187 -94
  196. package/src/plugins/builtin/econ/calendar-source.ts +9 -116
  197. package/src/plugins/builtin/econ/fred-client.test.ts +1 -14
  198. package/src/plugins/builtin/econ/fred-series-map.ts +1 -0
  199. package/src/plugins/builtin/econ/index.tsx +236 -421
  200. package/src/plugins/builtin/econ/types.ts +1 -0
  201. package/src/plugins/builtin/fx-matrix/index.tsx +43 -45
  202. package/src/plugins/builtin/fx-matrix/pairs.ts +1 -0
  203. package/src/plugins/builtin/help.test.tsx +120 -60
  204. package/src/plugins/builtin/help.tsx +266 -82
  205. package/src/plugins/builtin/holders/index.test.ts +45 -0
  206. package/src/plugins/builtin/holders/index.tsx +949 -0
  207. package/src/plugins/builtin/insider/index.tsx +207 -253
  208. package/src/plugins/builtin/insider/insider-data.test.ts +1 -11
  209. package/src/plugins/builtin/insider/insider-data.ts +1 -0
  210. package/src/plugins/builtin/layout-manager.tsx +12 -40
  211. package/src/plugins/builtin/market-movers/index.tsx +70 -144
  212. package/src/plugins/builtin/market-movers/screener.test.ts +0 -18
  213. package/src/plugins/builtin/news-wire/breaking-notifications.test.ts +98 -0
  214. package/src/plugins/builtin/news-wire/breaking-notifications.ts +110 -0
  215. package/src/plugins/builtin/news-wire/breaking-pane.tsx +157 -0
  216. package/src/plugins/builtin/news-wire/categories.ts +114 -0
  217. package/src/plugins/builtin/news-wire/default-feeds.ts +3 -0
  218. package/src/plugins/builtin/news-wire/digest-store.ts +44 -0
  219. package/src/plugins/builtin/news-wire/feed-config.test.ts +90 -0
  220. package/src/plugins/builtin/news-wire/feed-config.ts +216 -0
  221. package/src/plugins/builtin/news-wire/feed-pane.tsx +21 -0
  222. package/src/plugins/builtin/news-wire/hash.ts +9 -0
  223. package/src/plugins/builtin/news-wire/index.tsx +92 -0
  224. package/src/plugins/builtin/news-wire/industry-pane.tsx +140 -0
  225. package/src/plugins/builtin/news-wire/news-detail-view.tsx +256 -0
  226. package/src/plugins/builtin/news-wire/news-footer.ts +28 -0
  227. package/src/plugins/builtin/news-wire/news-preset-pane.tsx +94 -0
  228. package/src/plugins/builtin/news-wire/news-query-presets.ts +40 -0
  229. package/src/plugins/builtin/news-wire/news-table.test.tsx +206 -0
  230. package/src/plugins/builtin/news-wire/news-table.tsx +296 -0
  231. package/src/plugins/builtin/news-wire/persisted-articles.ts +92 -0
  232. package/src/plugins/builtin/news-wire/read-state.test.ts +30 -0
  233. package/src/plugins/builtin/news-wire/read-state.ts +76 -0
  234. package/src/plugins/builtin/news-wire/rss-parser.test.ts +184 -0
  235. package/src/plugins/builtin/news-wire/rss-parser.ts +213 -0
  236. package/src/plugins/builtin/news-wire/rss-source.test.ts +159 -0
  237. package/src/plugins/builtin/news-wire/rss-source.ts +181 -0
  238. package/src/plugins/builtin/news-wire/top-pane.tsx +21 -0
  239. package/src/plugins/builtin/news.tsx +98 -25
  240. package/src/plugins/builtin/notes-files.ts +51 -10
  241. package/src/plugins/builtin/notes.tsx +332 -131
  242. package/src/plugins/builtin/options.test.tsx +185 -0
  243. package/src/plugins/builtin/options.tsx +346 -206
  244. package/src/plugins/builtin/plugin-groups.ts +92 -0
  245. package/src/plugins/builtin/portfolio-list/header.tsx +72 -37
  246. package/src/plugins/builtin/portfolio-list/index.tsx +25 -42
  247. package/src/plugins/builtin/portfolio-list/metrics.ts +1 -1
  248. package/src/plugins/builtin/portfolio-list/pane.tsx +246 -164
  249. package/src/plugins/builtin/portfolio-list/settings.ts +19 -51
  250. package/src/plugins/builtin/portfolio-list/summary.tsx +21 -16
  251. package/src/plugins/builtin/portfolio-list/table.tsx +66 -25
  252. package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.test.tsx +1 -1
  253. package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.ts +1 -1
  254. package/src/plugins/builtin/portfolio-list.test.ts +60 -1
  255. package/src/plugins/builtin/portfolio-list.test.tsx +122 -55
  256. package/src/plugins/builtin/quote-monitor.test.tsx +2 -27
  257. package/src/plugins/builtin/research/index.tsx +724 -0
  258. package/src/plugins/builtin/sec.tsx +67 -19
  259. package/src/plugins/builtin/sectors/index.tsx +42 -80
  260. package/src/plugins/builtin/sectors/sector-data.ts +1 -0
  261. package/src/plugins/builtin/ticker-detail/chart-tab.tsx +25 -39
  262. package/src/plugins/builtin/ticker-detail/financials-tab.tsx +108 -58
  263. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +383 -271
  264. package/src/plugins/builtin/ticker-detail/pane.tsx +135 -49
  265. package/src/plugins/builtin/ticker-detail/quote-monitor.tsx +43 -27
  266. package/src/plugins/builtin/ticker-detail/settings.ts +3 -6
  267. package/src/plugins/builtin/ticker-detail-chart-tab-switch.test.tsx +16 -10
  268. package/src/plugins/builtin/ticker-detail.test.tsx +156 -33
  269. package/src/plugins/builtin/ticker-surface.ts +68 -0
  270. package/src/plugins/builtin/world-indices/index.tsx +44 -82
  271. package/src/plugins/builtin/world-indices/indices.ts +1 -0
  272. package/src/plugins/builtin/yahoo.ts +55 -1
  273. package/src/plugins/builtin/yield-curve/index.tsx +87 -145
  274. package/src/plugins/builtin/yield-curve/treasury-data.ts +6 -77
  275. package/src/plugins/catalog-ui.test.ts +24 -0
  276. package/src/plugins/catalog-ui.ts +44 -0
  277. package/src/plugins/catalog.ts +2 -50
  278. package/src/plugins/ibkr/__snapshots__/trade-tab.test.tsx.snap +5 -5
  279. package/src/plugins/ibkr/account-cache.test.ts +15 -14
  280. package/src/plugins/ibkr/account-cache.ts +15 -120
  281. package/src/plugins/ibkr/broker-adapter.ts +67 -0
  282. package/src/plugins/ibkr/config.test.ts +0 -18
  283. package/src/plugins/ibkr/dialogs.tsx +14 -56
  284. package/src/plugins/ibkr/flex.test.ts +57 -2
  285. package/src/plugins/ibkr/flex.ts +116 -12
  286. package/src/plugins/ibkr/gateway-helpers.ts +8 -3
  287. package/src/plugins/ibkr/gateway-service-native.ts +2381 -0
  288. package/src/plugins/ibkr/gateway-service.test.ts +47 -9
  289. package/src/plugins/ibkr/gateway-service.ts +264 -2235
  290. package/src/plugins/ibkr/gateway-types.ts +27 -0
  291. package/src/plugins/ibkr/index.test.tsx +3 -11
  292. package/src/plugins/ibkr/index.tsx +1 -0
  293. package/src/plugins/ibkr/price-normalization.test.ts +1 -6
  294. package/src/plugins/ibkr/price-normalization.ts +1 -0
  295. package/src/plugins/ibkr/trade-badge.tsx +6 -5
  296. package/src/plugins/ibkr/trade-tab.test.tsx +2 -2
  297. package/src/plugins/ibkr/trade-tab.tsx +164 -117
  298. package/src/plugins/ibkr/trading-pane.tsx +117 -83
  299. package/src/plugins/pane-manager.test.ts +3 -1
  300. package/src/plugins/pane-manager.ts +93 -19
  301. package/src/plugins/plugin-runtime.test.tsx +176 -74
  302. package/src/plugins/plugin-runtime.tsx +228 -10
  303. package/src/plugins/prediction-markets/cache.ts +49 -0
  304. package/src/plugins/prediction-markets/categories.ts +0 -10
  305. package/src/plugins/prediction-markets/chart.test.tsx +1 -1
  306. package/src/plugins/prediction-markets/chart.tsx +21 -20
  307. package/src/plugins/prediction-markets/cli.ts +1 -0
  308. package/src/plugins/prediction-markets/controller-data.ts +135 -89
  309. package/src/plugins/prediction-markets/controller.ts +37 -108
  310. package/src/plugins/prediction-markets/detail/book.tsx +23 -22
  311. package/src/plugins/prediction-markets/detail/outcomes.tsx +33 -32
  312. package/src/plugins/prediction-markets/detail/overview.tsx +17 -12
  313. package/src/plugins/prediction-markets/detail/pane.tsx +59 -45
  314. package/src/plugins/prediction-markets/detail/rules.tsx +22 -7
  315. package/src/plugins/prediction-markets/detail/shared.tsx +3 -2
  316. package/src/plugins/prediction-markets/detail/trades.tsx +12 -11
  317. package/src/plugins/prediction-markets/detail.test.tsx +1 -1
  318. package/src/plugins/prediction-markets/keyboard.ts +1 -0
  319. package/src/plugins/prediction-markets/launch.test.ts +1 -1
  320. package/src/plugins/prediction-markets/launch.ts +1 -1
  321. package/src/plugins/prediction-markets/metrics.ts +25 -12
  322. package/src/plugins/prediction-markets/pane.test.tsx +16 -7
  323. package/src/plugins/prediction-markets/pane.tsx +180 -82
  324. package/src/plugins/prediction-markets/plugin.test.tsx +37 -45
  325. package/src/plugins/prediction-markets/services/fetch.ts +27 -10
  326. package/src/plugins/prediction-markets/services/kalshi-adapter.ts +20 -3
  327. package/src/plugins/prediction-markets/services/polymarket-adapter.ts +29 -11
  328. package/src/plugins/prediction-markets/settings.ts +0 -5
  329. package/src/plugins/prediction-markets/test-helpers.tsx +19 -44
  330. package/src/plugins/registry.test.ts +307 -0
  331. package/src/plugins/registry.ts +293 -82
  332. package/src/react/input.ts +53 -0
  333. package/src/react/plugins/plugin-slot.tsx +21 -0
  334. package/src/react/use-raf-callback.ts +44 -0
  335. package/src/renderers/electrobun/bun/application-menu-click.test.ts +41 -0
  336. package/src/renderers/electrobun/bun/application-menu-click.ts +55 -0
  337. package/src/renderers/electrobun/bun/application-menu.ts +118 -0
  338. package/src/renderers/electrobun/bun/context-menu-click.test.ts +67 -0
  339. package/src/renderers/electrobun/bun/context-menu-click.ts +61 -0
  340. package/src/renderers/electrobun/bun/context-menu-normalize.test.ts +52 -0
  341. package/src/renderers/electrobun/bun/context-menu-normalize.ts +67 -0
  342. package/src/renderers/electrobun/bun/core-capabilities.ts +328 -0
  343. package/src/renderers/electrobun/bun/desktop-workspace.test.ts +63 -0
  344. package/src/renderers/electrobun/bun/desktop-workspace.ts +162 -0
  345. package/src/renderers/electrobun/bun/index.ts +1149 -0
  346. package/src/renderers/electrobun/bun/window-frame.test.ts +40 -0
  347. package/src/renderers/electrobun/bun/window-frame.ts +46 -0
  348. package/src/renderers/electrobun/shared/protocol.ts +72 -0
  349. package/src/renderers/electrobun/view/ai-host.ts +93 -0
  350. package/src/renderers/electrobun/view/app-services.ts +348 -0
  351. package/src/renderers/electrobun/view/application-menu-bridge.ts +12 -0
  352. package/src/renderers/electrobun/view/backend-rpc.ts +173 -0
  353. package/src/renderers/electrobun/view/broker-remote-client.ts +154 -0
  354. package/src/renderers/electrobun/view/config-host.ts +35 -0
  355. package/src/renderers/electrobun/view/context-menu.test.ts +140 -0
  356. package/src/renderers/electrobun/view/context-menu.ts +164 -0
  357. package/src/renderers/electrobun/view/data-table.tsx +773 -0
  358. package/src/renderers/electrobun/view/desktop-controls.tsx +651 -0
  359. package/src/renderers/electrobun/view/desktop-window-bridge.ts +59 -0
  360. package/src/renderers/electrobun/view/desktop-window-snapshot.test.ts +57 -0
  361. package/src/renderers/electrobun/view/desktop-window-snapshot.ts +41 -0
  362. package/src/renderers/electrobun/view/dialog-host.tsx +62 -0
  363. package/src/renderers/electrobun/view/http-fetch.ts +113 -0
  364. package/src/renderers/electrobun/view/input-host.tsx +65 -0
  365. package/src/renderers/electrobun/view/key-event.test.ts +34 -0
  366. package/src/renderers/electrobun/view/key-event.ts +134 -0
  367. package/src/renderers/electrobun/view/main.tsx +104 -0
  368. package/src/renderers/electrobun/view/native-renderer.ts +110 -0
  369. package/src/renderers/electrobun/view/native-stubs/chart-kitty-support.ts +9 -0
  370. package/src/renderers/electrobun/view/native-stubs/chart-surface-manager.ts +25 -0
  371. package/src/renderers/electrobun/view/native-stubs/chart-surface-sync.ts +1 -0
  372. package/src/renderers/electrobun/view/notes-files.ts +56 -0
  373. package/src/renderers/electrobun/view/resource-store.test.ts +44 -0
  374. package/src/renderers/electrobun/view/resource-store.ts +106 -0
  375. package/src/renderers/electrobun/view/rpc-codec.ts +42 -0
  376. package/src/renderers/electrobun/view/scrollbar-activity.ts +23 -0
  377. package/src/renderers/electrobun/view/toast-host.tsx +65 -0
  378. package/src/renderers/electrobun/view/ui-host.tsx +1490 -0
  379. package/src/renderers/electrobun/view/update-host.ts +38 -0
  380. package/src/renderers/opentui/dialog-host.test.tsx +44 -0
  381. package/src/renderers/opentui/dialog-host.tsx +56 -0
  382. package/src/renderers/opentui/host.tsx +134 -0
  383. package/src/renderers/opentui/input-host.tsx +24 -0
  384. package/src/renderers/opentui/start.tsx +71 -0
  385. package/src/renderers/opentui/test-utils.tsx +76 -0
  386. package/src/renderers/opentui/toast-host.tsx +10 -0
  387. package/src/renderers/opentui/ui-host.tsx +97 -0
  388. package/src/sources/gloomberb-cloud.test.ts +288 -17
  389. package/src/sources/gloomberb-cloud.ts +176 -9
  390. package/src/sources/provider-errors.ts +1 -0
  391. package/src/sources/provider-router.test.ts +220 -81
  392. package/src/sources/provider-router.ts +294 -111
  393. package/src/sources/sec-edgar.ts +19 -6
  394. package/src/sources/yahoo-finance.test.ts +0 -5
  395. package/src/sources/yahoo-finance.ts +420 -11
  396. package/src/state/app-activity.ts +7 -7
  397. package/src/state/app-bootstrap.ts +114 -40
  398. package/src/state/app-context-selector.test.tsx +70 -1
  399. package/src/state/app-context.test.ts +71 -3
  400. package/src/state/app-context.test.tsx +14 -7
  401. package/src/state/app-context.tsx +205 -869
  402. package/src/state/config-save-scheduler.ts +35 -0
  403. package/src/state/persist-scheduler.test.ts +67 -0
  404. package/src/state/persist-scheduler.ts +69 -0
  405. package/src/state/selectors-ui.ts +55 -0
  406. package/src/state/selectors.ts +15 -71
  407. package/src/state/session-persistence.test.ts +1 -1
  408. package/src/state/ticker-refresh-queue.ts +52 -3
  409. package/src/state/use-inline-tickers.ts +18 -16
  410. package/src/state/use-quote-streaming.test.ts +0 -6
  411. package/src/state/use-quote-streaming.test.tsx +1 -1
  412. package/src/state/use-quote-streaming.ts +3 -2
  413. package/src/state/use-session-snapshot.ts +32 -17
  414. package/src/test-support/plugin-runtime.ts +149 -0
  415. package/src/theme/color-utils.ts +1 -0
  416. package/src/theme/colors.ts +16 -0
  417. package/src/types/broker.ts +16 -1
  418. package/src/types/capability-route-source.ts +23 -0
  419. package/src/types/config.test.ts +23 -0
  420. package/src/types/config.ts +145 -10
  421. package/src/types/context-menu.ts +95 -0
  422. package/src/types/data-provider.ts +34 -3
  423. package/src/types/desktop-menu.ts +13 -0
  424. package/src/types/desktop-window.ts +29 -0
  425. package/src/types/financials.ts +123 -2
  426. package/src/types/instrument.ts +1 -0
  427. package/src/types/news-source.ts +9 -0
  428. package/src/types/persistence.ts +1 -0
  429. package/src/types/plugin.ts +32 -8
  430. package/src/ui/ascii-font.ts +85 -0
  431. package/src/ui/context-menu.test.ts +94 -0
  432. package/src/ui/context-menu.tsx +433 -0
  433. package/src/ui/dialog.tsx +55 -0
  434. package/src/ui/host.tsx +361 -0
  435. package/src/ui/index.tsx +126 -0
  436. package/src/ui/toast.tsx +40 -0
  437. package/src/updater.test.ts +69 -0
  438. package/src/updater.ts +61 -18
  439. package/src/utils/api-client.test.ts +186 -6
  440. package/src/utils/api-client.ts +433 -33
  441. package/src/utils/back-navigation.ts +6 -0
  442. package/src/utils/cli-output.ts +1 -0
  443. package/src/utils/currency-units.test.ts +1 -6
  444. package/src/utils/currency-units.ts +1 -0
  445. package/src/utils/debug-log.ts +68 -0
  446. package/src/utils/dom-screenshot.ts +326 -0
  447. package/src/utils/exchange-rate-map.ts +1 -0
  448. package/src/utils/exchanges.ts +214 -0
  449. package/src/utils/format.test.ts +7 -0
  450. package/src/utils/format.ts +1 -0
  451. package/src/utils/fuzzy-search.ts +1 -0
  452. package/src/utils/html-entities.test.ts +8 -0
  453. package/src/utils/html-entities.ts +32 -0
  454. package/src/utils/http-transport.ts +11 -0
  455. package/src/utils/main-thread-monitor.ts +127 -0
  456. package/src/utils/perf-marks.ts +54 -0
  457. package/src/utils/quote-freshness.ts +27 -50
  458. package/src/utils/quote-resolution.test.ts +5 -5
  459. package/src/utils/quote-resolution.ts +0 -1
  460. package/src/utils/quote-time.ts +1 -5
  461. package/src/utils/sec.test.ts +0 -12
  462. package/src/utils/selection-clipboard.ts +14 -6
  463. package/src/utils/shortcut-labels.test.ts +20 -0
  464. package/src/utils/shortcut-labels.ts +67 -0
  465. package/src/utils/slugify.ts +1 -0
  466. package/src/utils/terminal-input-reset.ts +1 -0
  467. package/src/utils/text-wrap.ts +82 -0
  468. package/src/utils/throttled-fetch.test.ts +19 -0
  469. package/src/utils/throttled-fetch.ts +11 -9
  470. package/src/utils/ticker-list.test.ts +1 -7
  471. package/src/utils/ticker-list.ts +1 -0
  472. package/src/utils/timestamp.ts +29 -0
  473. package/src/utils/tweet-text.test.ts +13 -0
  474. package/src/utils/tweet-text.ts +18 -0
  475. package/src/utils/url.ts +11 -0
  476. package/src/version.ts +1 -1
  477. package/src/components/chart/chart-control-hint.tsx +0 -39
  478. package/src/components/detail-feed-view.test.tsx +0 -180
  479. package/src/components/detail-feed-view.tsx +0 -531
  480. package/src/components/layout/pixel-mouse-mode.test.ts +0 -122
  481. package/src/components/layout/pixel-mouse.test.ts +0 -40
  482. package/src/components/spinner.tsx +0 -2
  483. package/src/components/tab-bar.tsx +0 -17
  484. package/src/components/ui/multi-select-chips.tsx +0 -69
  485. package/src/plugins/builtin/ai/index.test.tsx +0 -139
  486. package/src/plugins/builtin/ask-ai.tsx +0 -9
  487. package/src/plugins/builtin/correlation/plugin.test.ts +0 -44
  488. package/src/plugins/builtin/correlation/settings.test.ts +0 -34
  489. package/src/plugins/builtin/econ/calendar-source.test.ts +0 -219
  490. package/src/plugins/builtin/econ/fred-client.ts +0 -79
  491. package/src/plugins/builtin/fred-settings.ts +0 -10
  492. package/src/plugins/builtin/options-availability.test.tsx +0 -179
  493. package/src/plugins/builtin/options-availability.ts +0 -158
  494. package/src/plugins/builtin/portfolio-list/metrics.test.ts +0 -61
  495. package/src/plugins/builtin/yield-curve/treasury-data.test.ts +0 -212
  496. package/src/plugins/prediction-markets/status.ts +0 -24
  497. package/src/plugins/prediction-markets/table.tsx +0 -87
  498. package/src/utils/quote-time.test.ts +0 -22
  499. package/src/utils/selection-clipboard.test.ts +0 -43
  500. package/src/utils/terminal-input-reset.test.ts +0 -50
@@ -7,10 +7,8 @@ export interface PortfolioPaneSettings {
7
7
  columnIds: string[];
8
8
  collectionScope: CollectionScope;
9
9
  visibleCollectionIds: string[];
10
- hideTabs: boolean;
11
10
  hideHeader: boolean;
12
11
  hideCash: boolean;
13
- lockedCollectionId: string;
14
12
  showSparklines?: boolean;
15
13
  }
16
14
 
@@ -95,13 +93,6 @@ function resolveCollectionOptions(entries: CollectionEntry[]): PaneSettingOption
95
93
  }));
96
94
  }
97
95
 
98
- function resolveLockedCollectionId(settings: PortfolioPaneSettings, visibleCollections: CollectionEntry[]): string {
99
- if (visibleCollections.some((entry) => entry.id === settings.lockedCollectionId)) {
100
- return settings.lockedCollectionId;
101
- }
102
- return visibleCollections[0]?.id ?? "";
103
- }
104
-
105
96
  export function getPortfolioPaneSettings(settings: Record<string, unknown> | undefined): PortfolioPaneSettings {
106
97
  const columnIds = Array.isArray(settings?.columnIds)
107
98
  ? settings.columnIds.filter((value): value is string => typeof value === "string")
@@ -114,25 +105,20 @@ export function getPortfolioPaneSettings(settings: Record<string, unknown> | und
114
105
  columnIds: columnIds.length > 0 ? columnIds : DEFAULT_PORTFOLIO_COLUMN_IDS,
115
106
  collectionScope: isCollectionScope(settings?.collectionScope) ? settings.collectionScope : "all",
116
107
  visibleCollectionIds,
117
- hideTabs: settings?.hideTabs === true,
118
108
  hideHeader: settings?.hideHeader === true,
119
109
  hideCash: settings?.hideCash === true,
120
- lockedCollectionId: typeof settings?.lockedCollectionId === "string" ? settings.lockedCollectionId : "",
121
110
  showSparklines: settings?.showSparklines === true,
122
111
  };
123
112
  }
124
113
 
125
- export function createPortfolioPaneSettings(overrides: Partial<PortfolioPaneSettings> = {}): PortfolioPaneSettings {
126
- return {
127
- columnIds: [...(overrides.columnIds ?? DEFAULT_PORTFOLIO_COLUMN_IDS)],
128
- collectionScope: overrides.collectionScope ?? "all",
129
- visibleCollectionIds: [...(overrides.visibleCollectionIds ?? [])],
130
- hideTabs: overrides.hideTabs ?? false,
131
- hideHeader: overrides.hideHeader ?? false,
132
- hideCash: overrides.hideCash ?? false,
133
- lockedCollectionId: overrides.lockedCollectionId ?? "",
134
- showSparklines: overrides.showSparklines ?? false,
135
- };
114
+ export function cleanPortfolioPaneSettings(settings: Record<string, unknown>): Record<string, unknown> {
115
+ const nextSettings = { ...settings };
116
+ delete nextSettings.hideTabs;
117
+ delete nextSettings.lockedCollectionId;
118
+ if (nextSettings.collectionScope !== "custom") {
119
+ delete nextSettings.visibleCollectionIds;
120
+ }
121
+ return nextSettings;
136
122
  }
137
123
 
138
124
  export function getCollectionEntries(config: AppConfig): CollectionEntry[] {
@@ -161,16 +147,22 @@ export function resolveScopedCollectionEntries(entries: CollectionEntry[], setti
161
147
  export function resolveActiveCollectionId(
162
148
  currentCollectionId: string,
163
149
  visibleCollections: CollectionEntry[],
164
- settings: PortfolioPaneSettings,
165
150
  ): string {
166
151
  if (visibleCollections.length === 0) return "";
167
- if (settings.hideTabs) {
168
- return resolveLockedCollectionId(settings, visibleCollections);
169
- }
170
152
  if (visibleCollections.some((entry) => entry.id === currentCollectionId)) {
171
153
  return currentCollectionId;
172
154
  }
173
- return resolveLockedCollectionId(settings, visibleCollections);
155
+ return visibleCollections[0]?.id ?? "";
156
+ }
157
+
158
+ export function resolvePortfolioPaneCollectionId(
159
+ config: AppConfig,
160
+ rawSettings: Record<string, unknown> | undefined,
161
+ currentCollectionId: string,
162
+ ): string {
163
+ const settings = getPortfolioPaneSettings(rawSettings);
164
+ const visibleCollections = resolveScopedCollectionEntries(getCollectionEntries(config), settings);
165
+ return resolveActiveCollectionId(currentCollectionId, visibleCollections);
174
166
  }
175
167
 
176
168
  export function resolveVisibleColumns(columnIds: string[], isPortfolioTab: boolean): ColumnConfig[] {
@@ -188,15 +180,12 @@ export function resolveVisibleColumns(columnIds: string[], isPortfolioTab: boole
188
180
 
189
181
  export function buildPortfolioPaneSettingsDef(config: AppConfig, settings: PortfolioPaneSettings): PaneSettingsDef {
190
182
  const collectionEntries = getCollectionEntries(config);
191
- const scopedEntries = resolveScopedCollectionEntries(collectionEntries, settings);
192
183
  const allCollectionOptions = resolveCollectionOptions(collectionEntries);
193
- const lockedCollectionOptions = resolveCollectionOptions(scopedEntries.length > 0 ? scopedEntries : collectionEntries);
194
184
 
195
185
  const fields: PaneSettingsDef["fields"] = [
196
186
  {
197
187
  key: "columnIds",
198
188
  label: "Columns",
199
- description: "Choose which columns this pane shows and in what order.",
200
189
  type: "ordered-multi-select",
201
190
  options: PORTFOLIO_COLUMN_DEFS.map((column) => ({
202
191
  value: column.id,
@@ -209,7 +198,6 @@ export function buildPortfolioPaneSettingsDef(config: AppConfig, settings: Portf
209
198
  {
210
199
  key: "collectionScope",
211
200
  label: "Collections",
212
- description: "Control which portfolios or watchlists appear in this pane.",
213
201
  type: "select",
214
202
  options: COLLECTION_SCOPE_OPTIONS,
215
203
  },
@@ -219,47 +207,27 @@ export function buildPortfolioPaneSettingsDef(config: AppConfig, settings: Portf
219
207
  fields.push({
220
208
  key: "visibleCollectionIds",
221
209
  label: "Visible Collections",
222
- description: "Pick the exact collections that should appear in this pane.",
223
210
  type: "multi-select",
224
211
  options: allCollectionOptions,
225
212
  });
226
213
  }
227
214
 
228
- fields.push({
229
- key: "hideTabs",
230
- label: "Hide Tabs",
231
- description: "Hide the collection tab bar and lock this pane to one collection.",
232
- type: "toggle",
233
- });
234
215
  fields.push({
235
216
  key: "hideHeader",
236
217
  label: "Hide Header Bar",
237
- description: "Hide the summary bar showing portfolio value, P&L, and account metrics.",
238
218
  type: "toggle",
239
219
  });
240
220
  fields.push({
241
221
  key: "hideCash",
242
222
  label: "Hide Cash Positions",
243
- description: "Hide the cash & margin drawer at the bottom of the pane.",
244
223
  type: "toggle",
245
224
  });
246
225
  fields.push({
247
226
  key: "showSparklines",
248
227
  label: "Sparklines",
249
- description: "Show a tiny price history chart in each row.",
250
228
  type: "toggle",
251
229
  });
252
230
 
253
- if (settings.hideTabs && lockedCollectionOptions.length > 0) {
254
- fields.push({
255
- key: "lockedCollectionId",
256
- label: "Locked Collection",
257
- description: "Choose which collection this pane should stay pinned to.",
258
- type: "select",
259
- options: lockedCollectionOptions,
260
- });
261
- }
262
-
263
231
  return {
264
232
  title: "Portfolio Pane Settings",
265
233
  fields,
@@ -1,11 +1,12 @@
1
- import { TextAttributes } from "@opentui/core";
1
+ import { Box, Text } from "../../../ui";
2
+ import { TextAttributes } from "../../../ui";
2
3
  import { colors, priceColor } from "../../../theme/colors";
3
4
  import type { AppState } from "../../../state/app-context";
5
+ import type { BrokerConnectionStatus } from "../../../types/broker";
4
6
  import type { Portfolio } from "../../../types/ticker";
5
7
  import type { BrokerAccount, BrokerCashBalance } from "../../../types/trading";
6
8
  import { formatCompact, formatPercentRaw } from "../../../utils/format";
7
9
  import { getBrokerInstance } from "../../../utils/broker-instances";
8
- import type { IbkrSnapshot } from "../../ibkr/gateway-service";
9
10
  import type { PortfolioSummaryTotals } from "./metrics";
10
11
 
11
12
  export interface PortfolioSummarySegment {
@@ -29,6 +30,11 @@ export interface ResolvedPortfolioAccountState extends PortfolioSummaryAccountSt
29
30
  visibleCashBalances: BrokerCashBalance[];
30
31
  }
31
32
 
33
+ export interface LiveBrokerAccountSnapshot {
34
+ status: BrokerConnectionStatus | null;
35
+ accounts: BrokerAccount[];
36
+ }
37
+
32
38
  function createSummarySegment(
33
39
  id: string,
34
40
  parts: PortfolioSummarySegment["parts"],
@@ -101,7 +107,7 @@ function findPortfolioAccount(
101
107
  export function resolvePortfolioAccountState(
102
108
  portfolio: Portfolio | null,
103
109
  state: Pick<AppState, "config" | "brokerAccounts">,
104
- liveSnapshot: IbkrSnapshot,
110
+ liveSnapshot: LiveBrokerAccountSnapshot,
105
111
  ): ResolvedPortfolioAccountState | null {
106
112
  if (!portfolio?.brokerInstanceId) return null;
107
113
 
@@ -109,9 +115,8 @@ export function resolvePortfolioAccountState(
109
115
  const cachedAccounts = state.brokerAccounts[portfolio.brokerInstanceId] ?? [];
110
116
  const cachedAccount = findPortfolioAccount(cachedAccounts, portfolio);
111
117
 
112
- const liveAccount = brokerInstance?.brokerType === "ibkr"
113
- && brokerInstance.connectionMode === "gateway"
114
- && liveSnapshot.status.state === "connected"
118
+ const liveAccount = brokerInstance
119
+ && liveSnapshot.status?.state === "connected"
115
120
  ? findPortfolioAccount(liveSnapshot.accounts, portfolio)
116
121
  : undefined;
117
122
 
@@ -217,24 +222,24 @@ export function buildPortfolioSummarySegments({
217
222
  export function renderSummarySegments(segments: PortfolioSummarySegment[], width: number) {
218
223
  if (segments.length === 0) return null;
219
224
  return (
220
- <box flexDirection="row" width={width} justifyContent="flex-start" overflow="hidden">
225
+ <Box flexDirection="row" width={width} justifyContent="flex-start" overflow="hidden">
221
226
  {segments.map((segment, segmentIndex) => (
222
- <box key={segment.id} flexDirection="row">
223
- {segmentIndex > 0 && <text fg={colors.textDim}>{" "}</text>}
227
+ <Box key={segment.id} flexDirection="row">
228
+ {segmentIndex > 0 && <Text fg={colors.textDim}>{" "}</Text>}
224
229
  {segment.parts.map((part, partIndex) => (
225
- <box key={`${segment.id}:${partIndex}`} flexDirection="row">
226
- {partIndex > 0 && <text fg={colors.textDim}>{" "}</text>}
227
- <text
230
+ <Box key={`${segment.id}:${partIndex}`} flexDirection="row">
231
+ {partIndex > 0 && <Text fg={colors.textDim}>{" "}</Text>}
232
+ <Text
228
233
  fg={part.color ?? (part.tone === "label" || part.tone === "muted" ? colors.textDim : colors.text)}
229
234
  attributes={part.bold ? TextAttributes.BOLD : 0}
230
235
  >
231
236
  {part.text}
232
- </text>
233
- </box>
237
+ </Text>
238
+ </Box>
234
239
  ))}
235
- </box>
240
+ </Box>
236
241
  ))}
237
- </box>
242
+ </Box>
238
243
  );
239
244
  }
240
245
 
@@ -1,6 +1,11 @@
1
- import { useCallback, type RefObject } from "react";
2
- import type { ScrollBoxRenderable } from "@opentui/core";
3
- import { TickerListTable, type QuoteFlashDirection } from "../../../components/ticker-list-table";
1
+ import { useCallback, useRef } from "react";
2
+ import {
3
+ TickerListTableView,
4
+ type DataTableKeyEvent,
5
+ type QuoteFlashDirection,
6
+ type TickerListVisibleRange,
7
+ } from "../../../components";
8
+ import { createRowValueCache } from "../../../components/ui/row-value-cache";
4
9
  import type { ColumnConfig } from "../../../types/config";
5
10
  import type { TickerFinancials } from "../../../types/financials";
6
11
  import type { TickerRecord } from "../../../types/ticker";
@@ -8,69 +13,105 @@ import { getColumnValue, type ColumnContext } from "./metrics";
8
13
 
9
14
  export type { QuoteFlashDirection };
10
15
 
16
+ const objectVersions = new WeakMap<object, number>();
17
+ let nextObjectVersion = 1;
18
+
19
+ function objectVersion(value: object | undefined): number {
20
+ if (!value) return 0;
21
+ const existing = objectVersions.get(value);
22
+ if (existing != null) return existing;
23
+ const next = nextObjectVersion;
24
+ nextObjectVersion += 1;
25
+ objectVersions.set(value, next);
26
+ return next;
27
+ }
28
+
29
+ function buildCellVersion(
30
+ column: ColumnConfig,
31
+ ticker: TickerRecord,
32
+ financials: TickerFinancials | undefined,
33
+ context: ColumnContext,
34
+ ): string {
35
+ const latencyNow = column.id === "latency" ? context.now : 0;
36
+ return [
37
+ column.id,
38
+ objectVersion(ticker),
39
+ objectVersion(financials),
40
+ objectVersion(context.exchangeRates),
41
+ context.activeTab ?? "",
42
+ context.baseCurrency,
43
+ latencyNow,
44
+ ].join("|");
45
+ }
46
+
11
47
  export function PortfolioTickerTable({
12
48
  columns,
49
+ focused,
13
50
  sortColumnId,
14
51
  sortDirection,
15
52
  onHeaderClick,
16
- headerScrollRef,
17
- scrollRef,
18
- syncHeaderScroll,
19
- onBodyScrollActivity,
20
53
  sortedTickers,
21
54
  cursorSymbol,
22
- hoveredIdx,
23
- setHoveredIdx,
24
55
  setCursorSymbol,
25
56
  financialsMap,
26
57
  columnContext,
27
58
  flashSymbols,
28
59
  showSparklines,
60
+ onRootKeyDown,
61
+ onVisibleRangeChange,
62
+ visibleRangeBuffer,
63
+ resetScrollKey,
64
+ onRowActivate,
29
65
  }: {
30
66
  columns: ColumnConfig[];
67
+ focused?: boolean;
31
68
  sortColumnId: string | null;
32
69
  sortDirection: "asc" | "desc";
33
70
  onHeaderClick: (columnId: string) => void;
34
- headerScrollRef: RefObject<ScrollBoxRenderable | null>;
35
- scrollRef: RefObject<ScrollBoxRenderable | null>;
36
- syncHeaderScroll: () => void;
37
- onBodyScrollActivity: () => void;
38
71
  sortedTickers: TickerRecord[];
39
72
  cursorSymbol: string | null;
40
- hoveredIdx: number | null;
41
- setHoveredIdx: (index: number | null) => void;
42
73
  setCursorSymbol: (symbol: string) => void;
43
74
  financialsMap: Map<string, TickerFinancials>;
44
75
  columnContext: ColumnContext;
45
76
  flashSymbols: Map<string, QuoteFlashDirection>;
46
77
  showSparklines?: boolean;
78
+ onRootKeyDown?: (event: DataTableKeyEvent) => boolean | void;
79
+ onVisibleRangeChange?: (range: TickerListVisibleRange) => void;
80
+ visibleRangeBuffer?: number;
81
+ resetScrollKey?: unknown;
82
+ onRowActivate?: (ticker: TickerRecord) => void;
47
83
  }) {
84
+ const cellCacheRef = useRef(createRowValueCache<string, ReturnType<typeof getColumnValue>>(5000));
48
85
  const resolveCell = useCallback(
49
- (column: ColumnConfig, ticker: TickerRecord, financials: TickerFinancials | undefined) => (
50
- getColumnValue(column, ticker, financials, columnContext)
51
- ),
86
+ (column: ColumnConfig, ticker: TickerRecord, financials: TickerFinancials | undefined) => {
87
+ const key = `${ticker.metadata.ticker}:${column.id}`;
88
+ const version = buildCellVersion(column, ticker, financials, columnContext);
89
+ return cellCacheRef.current.get(key, version, () => (
90
+ getColumnValue(column, ticker, financials, columnContext)
91
+ ));
92
+ },
52
93
  [columnContext],
53
94
  );
54
95
 
55
96
  return (
56
- <TickerListTable
97
+ <TickerListTableView
98
+ focused={focused}
57
99
  columns={columns}
58
100
  tickers={sortedTickers}
59
101
  cursorSymbol={cursorSymbol}
60
- hoveredIdx={hoveredIdx}
61
- setHoveredIdx={setHoveredIdx}
62
102
  setCursorSymbol={setCursorSymbol}
63
103
  resolveCell={resolveCell}
64
104
  financialsMap={financialsMap}
65
- headerScrollRef={headerScrollRef}
66
- scrollRef={scrollRef}
67
- syncHeaderScroll={syncHeaderScroll}
68
- onBodyScrollActivity={onBodyScrollActivity}
69
105
  flashSymbols={flashSymbols}
70
106
  sortColumnId={sortColumnId}
71
107
  sortDirection={sortDirection}
72
108
  onHeaderClick={onHeaderClick}
73
109
  showSparklines={showSparklines}
110
+ onRootKeyDown={onRootKeyDown}
111
+ onVisibleRangeChange={onVisibleRangeChange}
112
+ visibleRangeBuffer={visibleRangeBuffer}
113
+ resetScrollKey={resetScrollKey}
114
+ onRowActivate={onRowActivate}
74
115
  />
75
116
  );
76
117
  }
@@ -1,6 +1,6 @@
1
1
  import { afterEach, describe, expect, test } from "bun:test";
2
2
  import { act, useState } from "react";
3
- import { testRender } from "@opentui/react/test-utils";
3
+ import { testRender } from "../../../renderers/opentui/test-utils";
4
4
  import { useThrottledCursorSymbol } from "./use-throttled-cursor-symbol";
5
5
 
6
6
  let testSetup: Awaited<ReturnType<typeof testRender>> | undefined;
@@ -1,6 +1,6 @@
1
1
  import { useCallback, useEffect, useRef, useState } from "react";
2
2
 
3
- export const FOLLOW_CURSOR_THROTTLE_MS = 80;
3
+ export const FOLLOW_CURSOR_THROTTLE_MS = 300;
4
4
 
5
5
  export function useThrottledCursorSymbol(
6
6
  committedCursorSymbol: string | null,
@@ -1,8 +1,27 @@
1
1
  import { describe, expect, test } from "bun:test";
2
+ import { createDefaultConfig } from "../../types/config";
2
3
  import type { BrokerAccount } from "../../types/trading";
4
+ import type { TickerRecord } from "../../types/ticker";
3
5
  import type { PortfolioSummaryTotals } from "./portfolio-list/metrics";
4
6
  import { buildPortfolioSummarySegments } from "./portfolio-list/summary";
5
- import { shouldToggleCashMarginDrawer } from "./portfolio-list";
7
+ import { portfolioListPlugin, shouldToggleCashMarginDrawer } from "./portfolio-list";
8
+ import { selectStreamTickers } from "./portfolio-list/pane";
9
+
10
+ function ticker(symbol: string): TickerRecord {
11
+ return {
12
+ metadata: {
13
+ ticker: symbol,
14
+ exchange: "NASDAQ",
15
+ currency: "USD",
16
+ name: symbol,
17
+ portfolios: [],
18
+ watchlists: [],
19
+ positions: [],
20
+ custom: {},
21
+ tags: [],
22
+ },
23
+ };
24
+ }
6
25
 
7
26
  describe("buildPortfolioSummarySegments", () => {
8
27
  const totals: PortfolioSummaryTotals = {
@@ -72,3 +91,43 @@ describe("buildPortfolioSummarySegments", () => {
72
91
  expect(shouldToggleCashMarginDrawer("j", true)).toBe(false);
73
92
  });
74
93
  });
94
+
95
+ describe("selectStreamTickers", () => {
96
+ test("includes visible rows with overscan and clamps boundaries", () => {
97
+ const tickers = Array.from({ length: 20 }, (_, index) => ticker(`T${index}`));
98
+ expect(selectStreamTickers(tickers, { start: 3, end: 7 }).map((entry) => entry.metadata.ticker)).toEqual(
99
+ tickers.slice(0, 13).map((entry) => entry.metadata.ticker),
100
+ );
101
+ expect(selectStreamTickers(tickers, { start: 18, end: 20 }).map((entry) => entry.metadata.ticker)).toEqual(
102
+ tickers.slice(12, 20).map((entry) => entry.metadata.ticker),
103
+ );
104
+ });
105
+
106
+ test("includes selected ticker outside the visible streaming window", () => {
107
+ const tickers = Array.from({ length: 20 }, (_, index) => ticker(`T${index}`));
108
+ expect(selectStreamTickers(tickers, { start: 3, end: 7 }, "T19").map((entry) => entry.metadata.ticker)).toContain("T19");
109
+ });
110
+ });
111
+
112
+ describe("portfolio list pane templates", () => {
113
+ test("create panes with default all-collection settings", async () => {
114
+ const config = createDefaultConfig("/tmp/gloomberb-portfolio-list-template");
115
+ const context = {
116
+ config,
117
+ layout: config.layout,
118
+ focusedPaneId: "portfolio-list:main",
119
+ activeTicker: null,
120
+ activeCollectionId: "watchlist",
121
+ };
122
+
123
+ for (const templateId of ["new-collection-pane", "new-watchlist-pane"]) {
124
+ const template = portfolioListPlugin.paneTemplates?.find((entry) => entry.id === templateId);
125
+ const instance = await template?.createInstance?.(context);
126
+ expect(instance).toEqual({ params: { collectionId: "watchlist" } });
127
+ }
128
+
129
+ const portfolioTemplate = portfolioListPlugin.paneTemplates?.find((entry) => entry.id === "new-portfolio-pane");
130
+ const portfolioInstance = await portfolioTemplate?.createInstance?.(context);
131
+ expect(portfolioInstance).toEqual({ params: { collectionId: "main" } });
132
+ });
133
+ });