gloomberb 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (500) hide show
  1. package/README.md +91 -11
  2. package/package.json +12 -5
  3. package/src/app.tsx +414 -191
  4. package/src/architecture/import-boundaries.test.ts +118 -0
  5. package/src/assets/gloomberb-logo.png +0 -0
  6. package/src/assets/gloomberb-logo.svg +73 -0
  7. package/src/brokers/account-cache.ts +157 -0
  8. package/src/brokers/profile-form.test.ts +73 -0
  9. package/src/brokers/profile-form.ts +109 -0
  10. package/src/brokers/remote-broker-adapter.ts +91 -0
  11. package/src/brokers/sync-broker-instance.test.ts +124 -1
  12. package/src/brokers/sync-broker-instance.ts +113 -17
  13. package/src/capabilities/factories.ts +100 -0
  14. package/src/capabilities/index.ts +4 -0
  15. package/src/capabilities/registry.test.ts +137 -0
  16. package/src/capabilities/registry.ts +131 -0
  17. package/src/capabilities/service-capabilities.ts +27 -0
  18. package/src/capabilities/types.ts +84 -0
  19. package/src/cli/commands/ticker.ts +11 -3
  20. package/src/cli/context.ts +19 -13
  21. package/src/cli/helpers.ts +1 -0
  22. package/src/cli/types.ts +2 -2
  23. package/src/components/chart/chart-controller.ts +4 -24
  24. package/src/components/chart/chart-data.ts +3 -14
  25. package/src/components/chart/chart-indicator-selector.tsx +28 -6
  26. package/src/components/chart/chart-pane-settings.ts +15 -28
  27. package/src/components/chart/chart-renderer.ts +74 -99
  28. package/src/components/chart/chart-resolution.ts +10 -10
  29. package/src/components/chart/chart-scroll.test.ts +6 -5
  30. package/src/components/chart/chart-scroll.ts +2 -2
  31. package/src/components/chart/chart-types.ts +0 -1
  32. package/src/components/chart/chart-viewport.ts +2 -1
  33. package/src/components/chart/chart.test.ts +37 -0
  34. package/src/components/chart/comparison-chart-data.ts +3 -17
  35. package/src/components/chart/comparison-chart-renderer.test.ts +27 -4
  36. package/src/components/chart/comparison-chart-renderer.ts +1 -1
  37. package/src/components/chart/comparison-stock-chart.tsx +304 -108
  38. package/src/components/chart/cursor-motion.ts +3 -2
  39. package/src/components/chart/indicators/bands.test.ts +0 -14
  40. package/src/components/chart/indicators/moving-averages.test.ts +0 -15
  41. package/src/components/chart/indicators/options.ts +3 -3
  42. package/src/components/chart/indicators/oscillators.test.ts +0 -21
  43. package/src/components/chart/indicators/types.ts +1 -2
  44. package/src/components/chart/native/chart-rasterizer.ts +37 -44
  45. package/src/components/chart/native/kitty-adapter.ts +2 -5
  46. package/src/components/chart/native/kitty-manager.ts +1 -1
  47. package/src/components/chart/native/kitty-protocol.ts +0 -1
  48. package/src/components/chart/native/kitty-support.ts +1 -1
  49. package/src/components/chart/native/kitty.test.ts +30 -13
  50. package/src/components/chart/native/renderer-selection.ts +1 -1
  51. package/src/components/chart/native/surface-manager.ts +1 -1
  52. package/src/components/chart/static-chart-surface.test.tsx +51 -0
  53. package/src/components/chart/static-chart-surface.tsx +191 -0
  54. package/src/components/chart/stock-chart-renderer-switch.test.tsx +242 -35
  55. package/src/components/chart/stock-chart.tsx +438 -141
  56. package/src/components/command-bar/__snapshots__/command-bar.test.tsx.snap +4 -4
  57. package/src/components/command-bar/command-bar.test.tsx +269 -20
  58. package/src/components/command-bar/command-bar.tsx +830 -353
  59. package/src/components/command-bar/command-registry.ts +10 -20
  60. package/src/components/command-bar/helpers.test.ts +1 -1
  61. package/src/components/command-bar/helpers.ts +9 -11
  62. package/src/components/command-bar/pane-template-display.ts +4 -8
  63. package/src/components/command-bar/root-shortcuts.ts +12 -11
  64. package/src/components/command-bar/view-model.test.ts +8 -42
  65. package/src/components/command-bar/view-model.ts +8 -36
  66. package/src/components/command-bar/workflow-ops.test.ts +76 -2
  67. package/src/components/command-bar/workflow-ops.ts +40 -9
  68. package/src/components/command-bar/workflow-types.ts +2 -1
  69. package/src/components/data-table-stack-view.test.tsx +206 -0
  70. package/src/components/data-table-stack-view.tsx +152 -0
  71. package/src/components/data-table-view.test.tsx +188 -0
  72. package/src/components/data-table-view.tsx +279 -0
  73. package/src/components/feed-data-table-stack-view.test.tsx +225 -0
  74. package/src/components/feed-data-table-stack-view.tsx +365 -0
  75. package/src/components/index.ts +26 -31
  76. package/src/components/layout/detached-pane-shell.tsx +185 -0
  77. package/src/components/layout/floating-pane.tsx +87 -14
  78. package/src/components/layout/header.tsx +139 -49
  79. package/src/components/layout/pane-content.tsx +40 -0
  80. package/src/components/layout/pane-footer.test.tsx +195 -0
  81. package/src/components/layout/pane-footer.tsx +410 -0
  82. package/src/components/layout/pane-header.test.tsx +39 -0
  83. package/src/components/layout/pane-header.tsx +192 -14
  84. package/src/components/layout/pane-sizing.ts +9 -10
  85. package/src/components/layout/pane-title.ts +35 -0
  86. package/src/components/layout/pane.tsx +52 -11
  87. package/src/components/layout/shell.test.tsx +315 -107
  88. package/src/components/layout/shell.tsx +1010 -368
  89. package/src/components/layout/status-bar.test.tsx +17 -10
  90. package/src/components/layout/status-bar.tsx +367 -100
  91. package/src/components/layout/titlebar-overlay.ts +2 -0
  92. package/src/components/markdown-editor.tsx +29 -14
  93. package/src/components/markdown-text.tsx +25 -24
  94. package/src/components/onboarding/onboarding-wizard.test.tsx +8 -3
  95. package/src/components/onboarding/onboarding-wizard.tsx +347 -473
  96. package/src/components/pane-settings-dialog.tsx +521 -89
  97. package/src/components/pane-template-wizard.test.tsx +1 -1
  98. package/src/components/pane-template-wizard.tsx +38 -37
  99. package/src/components/price-selector-dialog.tsx +22 -21
  100. package/src/components/ticker-badge-list.tsx +53 -0
  101. package/src/components/ticker-badge-text.test.tsx +44 -1
  102. package/src/components/ticker-badge-text.tsx +78 -38
  103. package/src/components/ticker-badge.tsx +28 -7
  104. package/src/components/ticker-list-table-view.tsx +248 -0
  105. package/src/components/ticker-list-table.test.tsx +47 -1
  106. package/src/components/ticker-list-table.tsx +212 -41
  107. package/src/components/toggle-list.tsx +114 -8
  108. package/src/components/ui/button.tsx +24 -16
  109. package/src/components/ui/choice-dialog.tsx +89 -0
  110. package/src/components/ui/data-table.tsx +313 -114
  111. package/src/components/ui/external-link.tsx +52 -13
  112. package/src/components/ui/fields.tsx +44 -30
  113. package/src/components/ui/frame.tsx +24 -57
  114. package/src/components/ui/index.ts +15 -50
  115. package/src/components/ui/list-view.tsx +65 -25
  116. package/src/components/ui/loading.tsx +5 -60
  117. package/src/components/ui/message-composer.test.tsx +81 -0
  118. package/src/components/ui/message-composer.tsx +139 -0
  119. package/src/components/ui/multi-select-dialog.tsx +100 -32
  120. package/src/components/ui/multi-select.ts +112 -0
  121. package/src/components/ui/native-select.tsx +120 -0
  122. package/src/components/ui/page-stack-view.tsx +49 -19
  123. package/src/components/ui/remote-image.tsx +75 -0
  124. package/src/components/ui/row-value-cache.test.ts +17 -0
  125. package/src/components/ui/row-value-cache.ts +41 -0
  126. package/src/components/ui/shortcut-hint.tsx +55 -0
  127. package/src/components/ui/status.tsx +12 -67
  128. package/src/components/ui/table-layout.ts +59 -0
  129. package/src/components/ui/tabs.tsx +249 -27
  130. package/src/components/ui/toggle.tsx +19 -127
  131. package/src/components/ui/ui.test.tsx +266 -53
  132. package/src/components/use-double-click-activation.ts +7 -2
  133. package/src/components/use-external-link-footer.ts +79 -0
  134. package/src/core/app-services.ts +95 -0
  135. package/src/core/state/app-state.ts +886 -0
  136. package/src/{state → core/state}/session-persistence.ts +7 -6
  137. package/src/data/config-layout.ts +225 -0
  138. package/src/data/config-store-node.ts +390 -0
  139. package/src/data/config-store.test.ts +72 -0
  140. package/src/data/config-store.ts +35 -478
  141. package/src/data/sqlite-json.ts +2 -5
  142. package/src/data/sqlite-retry.ts +1 -0
  143. package/src/index.tsx +2 -68
  144. package/src/market-data/coordinator-subscriptions.test.ts +125 -0
  145. package/src/market-data/coordinator.test.ts +73 -4
  146. package/src/market-data/coordinator.ts +190 -24
  147. package/src/market-data/hooks.test.tsx +1 -1
  148. package/src/market-data/hooks.tsx +109 -87
  149. package/src/market-data/query-store.ts +3 -3
  150. package/src/market-data/request-types.test.ts +0 -14
  151. package/src/market-data/result-types.ts +1 -0
  152. package/src/news/aggregator.test.ts +267 -0
  153. package/src/news/aggregator.ts +452 -0
  154. package/src/news/hooks.ts +39 -0
  155. package/src/news/ticker-symbols.ts +21 -0
  156. package/src/news/types.ts +77 -0
  157. package/src/notifications/app-notifier.ts +21 -10
  158. package/src/pane-settings.test.ts +0 -2
  159. package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +124 -134
  160. package/src/plugins/builtin/ai/gloomberb-cli.ts +14 -13
  161. package/src/plugins/builtin/ai/providers.ts +1 -9
  162. package/src/plugins/builtin/ai/runner.ts +41 -5
  163. package/src/plugins/builtin/ai/screener-pane.test.tsx +37 -55
  164. package/src/plugins/builtin/ai/screener-pane.tsx +231 -276
  165. package/src/plugins/builtin/ai/settings.ts +0 -1
  166. package/src/plugins/builtin/ai/utils.ts +1 -71
  167. package/src/plugins/builtin/alerts/alert-engine.test.ts +1 -38
  168. package/src/plugins/builtin/alerts/index.test.tsx +61 -82
  169. package/src/plugins/builtin/alerts/index.tsx +263 -155
  170. package/src/plugins/builtin/alerts/types.ts +7 -0
  171. package/src/plugins/builtin/analytics/index.test.tsx +13 -8
  172. package/src/plugins/builtin/analytics/index.tsx +78 -120
  173. package/src/plugins/builtin/analytics/metrics.test.ts +0 -4
  174. package/src/plugins/builtin/ask-ai.test.tsx +55 -54
  175. package/src/plugins/builtin/broker-manager/index.test.tsx +168 -0
  176. package/src/plugins/builtin/broker-manager/index.tsx +759 -0
  177. package/src/plugins/builtin/broker-manager/model.test.ts +107 -0
  178. package/src/plugins/builtin/broker-manager/model.ts +142 -0
  179. package/src/plugins/builtin/chat-controller.test.ts +129 -0
  180. package/src/plugins/builtin/chat-controller.ts +85 -14
  181. package/src/plugins/builtin/chat.test.tsx +283 -36
  182. package/src/plugins/builtin/chat.tsx +363 -170
  183. package/src/plugins/builtin/cloud-auth-actions.tsx +72 -0
  184. package/src/plugins/builtin/cloud-tweets.tsx +896 -0
  185. package/src/plugins/builtin/comparison-chart.test.tsx +46 -57
  186. package/src/plugins/builtin/comparison-chart.tsx +11 -11
  187. package/src/plugins/builtin/correlation/compute.test.ts +0 -17
  188. package/src/plugins/builtin/correlation/index.tsx +51 -46
  189. package/src/plugins/builtin/correlation/settings.ts +1 -2
  190. package/src/plugins/builtin/debug.tsx +99 -70
  191. package/src/plugins/builtin/earnings/earnings-cache.test.ts +2 -2
  192. package/src/plugins/builtin/earnings/earnings-cache.ts +12 -3
  193. package/src/plugins/builtin/earnings/earnings-data.test.ts +42 -11
  194. package/src/plugins/builtin/earnings/earnings-data.ts +70 -5
  195. package/src/plugins/builtin/earnings/index.tsx +187 -94
  196. package/src/plugins/builtin/econ/calendar-source.ts +9 -116
  197. package/src/plugins/builtin/econ/fred-client.test.ts +1 -14
  198. package/src/plugins/builtin/econ/fred-series-map.ts +1 -0
  199. package/src/plugins/builtin/econ/index.tsx +236 -421
  200. package/src/plugins/builtin/econ/types.ts +1 -0
  201. package/src/plugins/builtin/fx-matrix/index.tsx +43 -45
  202. package/src/plugins/builtin/fx-matrix/pairs.ts +1 -0
  203. package/src/plugins/builtin/help.test.tsx +120 -60
  204. package/src/plugins/builtin/help.tsx +266 -82
  205. package/src/plugins/builtin/holders/index.test.ts +45 -0
  206. package/src/plugins/builtin/holders/index.tsx +949 -0
  207. package/src/plugins/builtin/insider/index.tsx +207 -253
  208. package/src/plugins/builtin/insider/insider-data.test.ts +1 -11
  209. package/src/plugins/builtin/insider/insider-data.ts +1 -0
  210. package/src/plugins/builtin/layout-manager.tsx +12 -40
  211. package/src/plugins/builtin/market-movers/index.tsx +70 -144
  212. package/src/plugins/builtin/market-movers/screener.test.ts +0 -18
  213. package/src/plugins/builtin/news-wire/breaking-notifications.test.ts +98 -0
  214. package/src/plugins/builtin/news-wire/breaking-notifications.ts +110 -0
  215. package/src/plugins/builtin/news-wire/breaking-pane.tsx +157 -0
  216. package/src/plugins/builtin/news-wire/categories.ts +114 -0
  217. package/src/plugins/builtin/news-wire/default-feeds.ts +3 -0
  218. package/src/plugins/builtin/news-wire/digest-store.ts +44 -0
  219. package/src/plugins/builtin/news-wire/feed-config.test.ts +90 -0
  220. package/src/plugins/builtin/news-wire/feed-config.ts +216 -0
  221. package/src/plugins/builtin/news-wire/feed-pane.tsx +21 -0
  222. package/src/plugins/builtin/news-wire/hash.ts +9 -0
  223. package/src/plugins/builtin/news-wire/index.tsx +92 -0
  224. package/src/plugins/builtin/news-wire/industry-pane.tsx +140 -0
  225. package/src/plugins/builtin/news-wire/news-detail-view.tsx +256 -0
  226. package/src/plugins/builtin/news-wire/news-footer.ts +28 -0
  227. package/src/plugins/builtin/news-wire/news-preset-pane.tsx +94 -0
  228. package/src/plugins/builtin/news-wire/news-query-presets.ts +40 -0
  229. package/src/plugins/builtin/news-wire/news-table.test.tsx +206 -0
  230. package/src/plugins/builtin/news-wire/news-table.tsx +296 -0
  231. package/src/plugins/builtin/news-wire/persisted-articles.ts +92 -0
  232. package/src/plugins/builtin/news-wire/read-state.test.ts +30 -0
  233. package/src/plugins/builtin/news-wire/read-state.ts +76 -0
  234. package/src/plugins/builtin/news-wire/rss-parser.test.ts +184 -0
  235. package/src/plugins/builtin/news-wire/rss-parser.ts +213 -0
  236. package/src/plugins/builtin/news-wire/rss-source.test.ts +159 -0
  237. package/src/plugins/builtin/news-wire/rss-source.ts +181 -0
  238. package/src/plugins/builtin/news-wire/top-pane.tsx +21 -0
  239. package/src/plugins/builtin/news.tsx +98 -25
  240. package/src/plugins/builtin/notes-files.ts +51 -10
  241. package/src/plugins/builtin/notes.tsx +332 -131
  242. package/src/plugins/builtin/options.test.tsx +185 -0
  243. package/src/plugins/builtin/options.tsx +346 -206
  244. package/src/plugins/builtin/plugin-groups.ts +92 -0
  245. package/src/plugins/builtin/portfolio-list/header.tsx +72 -37
  246. package/src/plugins/builtin/portfolio-list/index.tsx +25 -42
  247. package/src/plugins/builtin/portfolio-list/metrics.ts +1 -1
  248. package/src/plugins/builtin/portfolio-list/pane.tsx +246 -164
  249. package/src/plugins/builtin/portfolio-list/settings.ts +19 -51
  250. package/src/plugins/builtin/portfolio-list/summary.tsx +21 -16
  251. package/src/plugins/builtin/portfolio-list/table.tsx +66 -25
  252. package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.test.tsx +1 -1
  253. package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.ts +1 -1
  254. package/src/plugins/builtin/portfolio-list.test.ts +60 -1
  255. package/src/plugins/builtin/portfolio-list.test.tsx +122 -55
  256. package/src/plugins/builtin/quote-monitor.test.tsx +2 -27
  257. package/src/plugins/builtin/research/index.tsx +724 -0
  258. package/src/plugins/builtin/sec.tsx +67 -19
  259. package/src/plugins/builtin/sectors/index.tsx +42 -80
  260. package/src/plugins/builtin/sectors/sector-data.ts +1 -0
  261. package/src/plugins/builtin/ticker-detail/chart-tab.tsx +25 -39
  262. package/src/plugins/builtin/ticker-detail/financials-tab.tsx +108 -58
  263. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +383 -271
  264. package/src/plugins/builtin/ticker-detail/pane.tsx +135 -49
  265. package/src/plugins/builtin/ticker-detail/quote-monitor.tsx +43 -27
  266. package/src/plugins/builtin/ticker-detail/settings.ts +3 -6
  267. package/src/plugins/builtin/ticker-detail-chart-tab-switch.test.tsx +16 -10
  268. package/src/plugins/builtin/ticker-detail.test.tsx +156 -33
  269. package/src/plugins/builtin/ticker-surface.ts +68 -0
  270. package/src/plugins/builtin/world-indices/index.tsx +44 -82
  271. package/src/plugins/builtin/world-indices/indices.ts +1 -0
  272. package/src/plugins/builtin/yahoo.ts +55 -1
  273. package/src/plugins/builtin/yield-curve/index.tsx +87 -145
  274. package/src/plugins/builtin/yield-curve/treasury-data.ts +6 -77
  275. package/src/plugins/catalog-ui.test.ts +24 -0
  276. package/src/plugins/catalog-ui.ts +44 -0
  277. package/src/plugins/catalog.ts +2 -50
  278. package/src/plugins/ibkr/__snapshots__/trade-tab.test.tsx.snap +5 -5
  279. package/src/plugins/ibkr/account-cache.test.ts +15 -14
  280. package/src/plugins/ibkr/account-cache.ts +15 -120
  281. package/src/plugins/ibkr/broker-adapter.ts +67 -0
  282. package/src/plugins/ibkr/config.test.ts +0 -18
  283. package/src/plugins/ibkr/dialogs.tsx +14 -56
  284. package/src/plugins/ibkr/flex.test.ts +57 -2
  285. package/src/plugins/ibkr/flex.ts +116 -12
  286. package/src/plugins/ibkr/gateway-helpers.ts +8 -3
  287. package/src/plugins/ibkr/gateway-service-native.ts +2381 -0
  288. package/src/plugins/ibkr/gateway-service.test.ts +47 -9
  289. package/src/plugins/ibkr/gateway-service.ts +264 -2235
  290. package/src/plugins/ibkr/gateway-types.ts +27 -0
  291. package/src/plugins/ibkr/index.test.tsx +3 -11
  292. package/src/plugins/ibkr/index.tsx +1 -0
  293. package/src/plugins/ibkr/price-normalization.test.ts +1 -6
  294. package/src/plugins/ibkr/price-normalization.ts +1 -0
  295. package/src/plugins/ibkr/trade-badge.tsx +6 -5
  296. package/src/plugins/ibkr/trade-tab.test.tsx +2 -2
  297. package/src/plugins/ibkr/trade-tab.tsx +164 -117
  298. package/src/plugins/ibkr/trading-pane.tsx +117 -83
  299. package/src/plugins/pane-manager.test.ts +3 -1
  300. package/src/plugins/pane-manager.ts +93 -19
  301. package/src/plugins/plugin-runtime.test.tsx +176 -74
  302. package/src/plugins/plugin-runtime.tsx +228 -10
  303. package/src/plugins/prediction-markets/cache.ts +49 -0
  304. package/src/plugins/prediction-markets/categories.ts +0 -10
  305. package/src/plugins/prediction-markets/chart.test.tsx +1 -1
  306. package/src/plugins/prediction-markets/chart.tsx +21 -20
  307. package/src/plugins/prediction-markets/cli.ts +1 -0
  308. package/src/plugins/prediction-markets/controller-data.ts +135 -89
  309. package/src/plugins/prediction-markets/controller.ts +37 -108
  310. package/src/plugins/prediction-markets/detail/book.tsx +23 -22
  311. package/src/plugins/prediction-markets/detail/outcomes.tsx +33 -32
  312. package/src/plugins/prediction-markets/detail/overview.tsx +17 -12
  313. package/src/plugins/prediction-markets/detail/pane.tsx +59 -45
  314. package/src/plugins/prediction-markets/detail/rules.tsx +22 -7
  315. package/src/plugins/prediction-markets/detail/shared.tsx +3 -2
  316. package/src/plugins/prediction-markets/detail/trades.tsx +12 -11
  317. package/src/plugins/prediction-markets/detail.test.tsx +1 -1
  318. package/src/plugins/prediction-markets/keyboard.ts +1 -0
  319. package/src/plugins/prediction-markets/launch.test.ts +1 -1
  320. package/src/plugins/prediction-markets/launch.ts +1 -1
  321. package/src/plugins/prediction-markets/metrics.ts +25 -12
  322. package/src/plugins/prediction-markets/pane.test.tsx +16 -7
  323. package/src/plugins/prediction-markets/pane.tsx +180 -82
  324. package/src/plugins/prediction-markets/plugin.test.tsx +37 -45
  325. package/src/plugins/prediction-markets/services/fetch.ts +27 -10
  326. package/src/plugins/prediction-markets/services/kalshi-adapter.ts +20 -3
  327. package/src/plugins/prediction-markets/services/polymarket-adapter.ts +29 -11
  328. package/src/plugins/prediction-markets/settings.ts +0 -5
  329. package/src/plugins/prediction-markets/test-helpers.tsx +19 -44
  330. package/src/plugins/registry.test.ts +307 -0
  331. package/src/plugins/registry.ts +293 -82
  332. package/src/react/input.ts +53 -0
  333. package/src/react/plugins/plugin-slot.tsx +21 -0
  334. package/src/react/use-raf-callback.ts +44 -0
  335. package/src/renderers/electrobun/bun/application-menu-click.test.ts +41 -0
  336. package/src/renderers/electrobun/bun/application-menu-click.ts +55 -0
  337. package/src/renderers/electrobun/bun/application-menu.ts +118 -0
  338. package/src/renderers/electrobun/bun/context-menu-click.test.ts +67 -0
  339. package/src/renderers/electrobun/bun/context-menu-click.ts +61 -0
  340. package/src/renderers/electrobun/bun/context-menu-normalize.test.ts +52 -0
  341. package/src/renderers/electrobun/bun/context-menu-normalize.ts +67 -0
  342. package/src/renderers/electrobun/bun/core-capabilities.ts +328 -0
  343. package/src/renderers/electrobun/bun/desktop-workspace.test.ts +63 -0
  344. package/src/renderers/electrobun/bun/desktop-workspace.ts +162 -0
  345. package/src/renderers/electrobun/bun/index.ts +1149 -0
  346. package/src/renderers/electrobun/bun/window-frame.test.ts +40 -0
  347. package/src/renderers/electrobun/bun/window-frame.ts +46 -0
  348. package/src/renderers/electrobun/shared/protocol.ts +72 -0
  349. package/src/renderers/electrobun/view/ai-host.ts +93 -0
  350. package/src/renderers/electrobun/view/app-services.ts +348 -0
  351. package/src/renderers/electrobun/view/application-menu-bridge.ts +12 -0
  352. package/src/renderers/electrobun/view/backend-rpc.ts +173 -0
  353. package/src/renderers/electrobun/view/broker-remote-client.ts +154 -0
  354. package/src/renderers/electrobun/view/config-host.ts +35 -0
  355. package/src/renderers/electrobun/view/context-menu.test.ts +140 -0
  356. package/src/renderers/electrobun/view/context-menu.ts +164 -0
  357. package/src/renderers/electrobun/view/data-table.tsx +773 -0
  358. package/src/renderers/electrobun/view/desktop-controls.tsx +651 -0
  359. package/src/renderers/electrobun/view/desktop-window-bridge.ts +59 -0
  360. package/src/renderers/electrobun/view/desktop-window-snapshot.test.ts +57 -0
  361. package/src/renderers/electrobun/view/desktop-window-snapshot.ts +41 -0
  362. package/src/renderers/electrobun/view/dialog-host.tsx +62 -0
  363. package/src/renderers/electrobun/view/http-fetch.ts +113 -0
  364. package/src/renderers/electrobun/view/input-host.tsx +65 -0
  365. package/src/renderers/electrobun/view/key-event.test.ts +34 -0
  366. package/src/renderers/electrobun/view/key-event.ts +134 -0
  367. package/src/renderers/electrobun/view/main.tsx +104 -0
  368. package/src/renderers/electrobun/view/native-renderer.ts +110 -0
  369. package/src/renderers/electrobun/view/native-stubs/chart-kitty-support.ts +9 -0
  370. package/src/renderers/electrobun/view/native-stubs/chart-surface-manager.ts +25 -0
  371. package/src/renderers/electrobun/view/native-stubs/chart-surface-sync.ts +1 -0
  372. package/src/renderers/electrobun/view/notes-files.ts +56 -0
  373. package/src/renderers/electrobun/view/resource-store.test.ts +44 -0
  374. package/src/renderers/electrobun/view/resource-store.ts +106 -0
  375. package/src/renderers/electrobun/view/rpc-codec.ts +42 -0
  376. package/src/renderers/electrobun/view/scrollbar-activity.ts +23 -0
  377. package/src/renderers/electrobun/view/toast-host.tsx +65 -0
  378. package/src/renderers/electrobun/view/ui-host.tsx +1490 -0
  379. package/src/renderers/electrobun/view/update-host.ts +38 -0
  380. package/src/renderers/opentui/dialog-host.test.tsx +44 -0
  381. package/src/renderers/opentui/dialog-host.tsx +56 -0
  382. package/src/renderers/opentui/host.tsx +134 -0
  383. package/src/renderers/opentui/input-host.tsx +24 -0
  384. package/src/renderers/opentui/start.tsx +71 -0
  385. package/src/renderers/opentui/test-utils.tsx +76 -0
  386. package/src/renderers/opentui/toast-host.tsx +10 -0
  387. package/src/renderers/opentui/ui-host.tsx +97 -0
  388. package/src/sources/gloomberb-cloud.test.ts +288 -17
  389. package/src/sources/gloomberb-cloud.ts +176 -9
  390. package/src/sources/provider-errors.ts +1 -0
  391. package/src/sources/provider-router.test.ts +220 -81
  392. package/src/sources/provider-router.ts +294 -111
  393. package/src/sources/sec-edgar.ts +19 -6
  394. package/src/sources/yahoo-finance.test.ts +0 -5
  395. package/src/sources/yahoo-finance.ts +420 -11
  396. package/src/state/app-activity.ts +7 -7
  397. package/src/state/app-bootstrap.ts +114 -40
  398. package/src/state/app-context-selector.test.tsx +70 -1
  399. package/src/state/app-context.test.ts +71 -3
  400. package/src/state/app-context.test.tsx +14 -7
  401. package/src/state/app-context.tsx +205 -869
  402. package/src/state/config-save-scheduler.ts +35 -0
  403. package/src/state/persist-scheduler.test.ts +67 -0
  404. package/src/state/persist-scheduler.ts +69 -0
  405. package/src/state/selectors-ui.ts +55 -0
  406. package/src/state/selectors.ts +15 -71
  407. package/src/state/session-persistence.test.ts +1 -1
  408. package/src/state/ticker-refresh-queue.ts +52 -3
  409. package/src/state/use-inline-tickers.ts +18 -16
  410. package/src/state/use-quote-streaming.test.ts +0 -6
  411. package/src/state/use-quote-streaming.test.tsx +1 -1
  412. package/src/state/use-quote-streaming.ts +3 -2
  413. package/src/state/use-session-snapshot.ts +32 -17
  414. package/src/test-support/plugin-runtime.ts +149 -0
  415. package/src/theme/color-utils.ts +1 -0
  416. package/src/theme/colors.ts +16 -0
  417. package/src/types/broker.ts +16 -1
  418. package/src/types/capability-route-source.ts +23 -0
  419. package/src/types/config.test.ts +23 -0
  420. package/src/types/config.ts +145 -10
  421. package/src/types/context-menu.ts +95 -0
  422. package/src/types/data-provider.ts +34 -3
  423. package/src/types/desktop-menu.ts +13 -0
  424. package/src/types/desktop-window.ts +29 -0
  425. package/src/types/financials.ts +123 -2
  426. package/src/types/instrument.ts +1 -0
  427. package/src/types/news-source.ts +9 -0
  428. package/src/types/persistence.ts +1 -0
  429. package/src/types/plugin.ts +32 -8
  430. package/src/ui/ascii-font.ts +85 -0
  431. package/src/ui/context-menu.test.ts +94 -0
  432. package/src/ui/context-menu.tsx +433 -0
  433. package/src/ui/dialog.tsx +55 -0
  434. package/src/ui/host.tsx +361 -0
  435. package/src/ui/index.tsx +126 -0
  436. package/src/ui/toast.tsx +40 -0
  437. package/src/updater.test.ts +69 -0
  438. package/src/updater.ts +61 -18
  439. package/src/utils/api-client.test.ts +99 -9
  440. package/src/utils/api-client.ts +378 -18
  441. package/src/utils/back-navigation.ts +6 -0
  442. package/src/utils/cli-output.ts +1 -0
  443. package/src/utils/currency-units.test.ts +1 -6
  444. package/src/utils/currency-units.ts +1 -0
  445. package/src/utils/debug-log.ts +68 -0
  446. package/src/utils/dom-screenshot.ts +326 -0
  447. package/src/utils/exchange-rate-map.ts +1 -0
  448. package/src/utils/exchanges.ts +214 -0
  449. package/src/utils/format.test.ts +7 -0
  450. package/src/utils/format.ts +1 -0
  451. package/src/utils/fuzzy-search.ts +1 -0
  452. package/src/utils/html-entities.test.ts +8 -0
  453. package/src/utils/html-entities.ts +32 -0
  454. package/src/utils/http-transport.ts +11 -0
  455. package/src/utils/main-thread-monitor.ts +127 -0
  456. package/src/utils/perf-marks.ts +54 -0
  457. package/src/utils/quote-freshness.ts +27 -50
  458. package/src/utils/quote-resolution.test.ts +5 -5
  459. package/src/utils/quote-resolution.ts +0 -1
  460. package/src/utils/quote-time.ts +1 -5
  461. package/src/utils/sec.test.ts +0 -12
  462. package/src/utils/selection-clipboard.ts +14 -6
  463. package/src/utils/shortcut-labels.test.ts +20 -0
  464. package/src/utils/shortcut-labels.ts +67 -0
  465. package/src/utils/slugify.ts +1 -0
  466. package/src/utils/terminal-input-reset.ts +1 -0
  467. package/src/utils/text-wrap.ts +82 -0
  468. package/src/utils/throttled-fetch.test.ts +19 -0
  469. package/src/utils/throttled-fetch.ts +11 -9
  470. package/src/utils/ticker-list.test.ts +1 -7
  471. package/src/utils/ticker-list.ts +1 -0
  472. package/src/utils/timestamp.ts +29 -0
  473. package/src/utils/tweet-text.test.ts +13 -0
  474. package/src/utils/tweet-text.ts +18 -0
  475. package/src/utils/url.ts +11 -0
  476. package/src/version.ts +1 -1
  477. package/src/components/chart/chart-control-hint.tsx +0 -39
  478. package/src/components/detail-feed-view.test.tsx +0 -180
  479. package/src/components/detail-feed-view.tsx +0 -531
  480. package/src/components/layout/pixel-mouse-mode.test.ts +0 -122
  481. package/src/components/layout/pixel-mouse.test.ts +0 -40
  482. package/src/components/spinner.tsx +0 -2
  483. package/src/components/tab-bar.tsx +0 -17
  484. package/src/components/ui/multi-select-chips.tsx +0 -69
  485. package/src/plugins/builtin/ai/index.test.tsx +0 -139
  486. package/src/plugins/builtin/ask-ai.tsx +0 -9
  487. package/src/plugins/builtin/correlation/plugin.test.ts +0 -44
  488. package/src/plugins/builtin/correlation/settings.test.ts +0 -34
  489. package/src/plugins/builtin/econ/calendar-source.test.ts +0 -219
  490. package/src/plugins/builtin/econ/fred-client.ts +0 -79
  491. package/src/plugins/builtin/fred-settings.ts +0 -10
  492. package/src/plugins/builtin/options-availability.test.tsx +0 -179
  493. package/src/plugins/builtin/options-availability.ts +0 -158
  494. package/src/plugins/builtin/portfolio-list/metrics.test.ts +0 -61
  495. package/src/plugins/builtin/yield-curve/treasury-data.test.ts +0 -212
  496. package/src/plugins/prediction-markets/status.ts +0 -24
  497. package/src/plugins/prediction-markets/table.tsx +0 -87
  498. package/src/utils/quote-time.test.ts +0 -22
  499. package/src/utils/selection-clipboard.test.ts +0 -43
  500. package/src/utils/terminal-input-reset.test.ts +0 -50
@@ -1,13 +1,19 @@
1
- import { memo, useRef, type RefObject } from "react";
2
- import { TextAttributes, type ScrollBoxRenderable } from "@opentui/core";
1
+ import { Box, ScrollBox, Text, tickerContextMenuItems, useContextMenu, useRendererHost, useUiCapabilities } from "../ui";
2
+ import { memo, useCallback, useMemo, useRef, useState, type RefObject } from "react";
3
+ import { TextAttributes, type ScrollBoxRenderable } from "../ui";
3
4
  import { EmptyState } from "./ui";
4
5
  import { colors, hoverBg } from "../theme/colors";
6
+ import { getSharedRegistry } from "../plugins/registry";
5
7
  import type { ColumnConfig } from "../types/config";
6
8
  import type { TickerFinancials, PricePoint } from "../types/financials";
7
9
  import type { TickerRecord } from "../types/ticker";
8
10
  import { padTo } from "../utils/format";
9
11
  import { renderChart, resolveChartPalette, type StyledContent } from "./chart/chart-renderer";
10
12
  import type { ProjectedChartPoint } from "./chart/chart-data";
13
+ import { tableContentWidthProps, useMeasuredTableContentWidth } from "./ui/table-layout";
14
+ import { useViewport } from "../react/input";
15
+ import { useRafCallback } from "../react/use-raf-callback";
16
+ import { measurePerf } from "../utils/perf-marks";
11
17
 
12
18
  export interface TickerTableCell {
13
19
  text: string;
@@ -20,6 +26,12 @@ type ResolveTickerTableCell = (
20
26
  ticker: TickerRecord,
21
27
  financials: TickerFinancials | undefined,
22
28
  ) => TickerTableCell;
29
+ type TableMouseEvent = {
30
+ button?: number;
31
+ detail?: number;
32
+ preventDefault?: () => void;
33
+ stopPropagation?: () => void;
34
+ };
23
35
 
24
36
  const FLASHABLE_QUOTE_COLUMN_IDS = new Set([
25
37
  "price",
@@ -46,7 +58,7 @@ function resolveQuoteFlashColor(direction: QuoteFlashDirection, fallbackColor: s
46
58
  }
47
59
  }
48
60
 
49
- interface TickerListTableProps {
61
+ export interface TickerListTableProps {
50
62
  columns: ColumnConfig[];
51
63
  tickers: TickerRecord[];
52
64
  cursorSymbol: string | null;
@@ -67,32 +79,43 @@ interface TickerListTableProps {
67
79
  emptyTitle?: string;
68
80
  emptyHint?: string;
69
81
  showSparklines?: boolean;
82
+ virtualize?: boolean;
83
+ overscan?: number;
70
84
  }
71
85
 
86
+ const EMPTY_FLASH_SYMBOLS = new Map<string, QuoteFlashDirection>();
87
+
72
88
  const TickerListHeader = memo(function TickerListHeader({
73
89
  columns,
90
+ contentWidth,
74
91
  headerScrollRef,
75
92
  sortColumnId,
76
93
  sortDirection,
77
94
  onHeaderClick,
95
+ onSizeChange,
78
96
  }: {
79
97
  columns: ColumnConfig[];
98
+ contentWidth: number;
80
99
  headerScrollRef?: RefObject<ScrollBoxRenderable | null>;
81
100
  sortColumnId?: string | null;
82
101
  sortDirection?: "asc" | "desc";
83
102
  onHeaderClick?: (columnId: string) => void;
103
+ onSizeChange?: () => void;
84
104
  }) {
85
105
  return (
86
- <scrollbox
106
+ <ScrollBox
87
107
  ref={headerScrollRef}
108
+ width="100%"
88
109
  height={1}
110
+ backgroundColor={colors.panel}
89
111
  scrollX
90
112
  focusable={false}
113
+ onSizeChange={onSizeChange}
91
114
  >
92
- <box
115
+ <Box
93
116
  flexDirection="row"
94
117
  height={1}
95
- width="100%"
118
+ {...tableContentWidthProps(contentWidth)}
96
119
  paddingX={1}
97
120
  backgroundColor={colors.panel}
98
121
  >
@@ -101,23 +124,23 @@ const TickerListHeader = memo(function TickerListHeader({
101
124
  const indicator = isSorted ? (sortDirection === "asc" ? " \u25B2" : " \u25BC") : "";
102
125
  const labelText = column.label + indicator;
103
126
  return (
104
- <box
127
+ <Box
105
128
  key={column.id}
106
129
  width={column.width + 1}
107
130
  backgroundColor={colors.panel}
108
- onMouseDown={onHeaderClick ? (event) => {
109
- event.preventDefault();
131
+ onMouseDown={onHeaderClick ? (event: TableMouseEvent) => {
132
+ event.preventDefault?.();
110
133
  onHeaderClick(column.id);
111
134
  } : undefined}
112
135
  >
113
- <text attributes={TextAttributes.BOLD} fg={isSorted ? colors.text : colors.textDim}>
136
+ <Text attributes={TextAttributes.BOLD} fg={isSorted ? colors.text : colors.textDim}>
114
137
  {padTo(labelText, column.width, column.align)}
115
- </text>
116
- </box>
138
+ </Text>
139
+ </Box>
117
140
  );
118
141
  })}
119
- </box>
120
- </scrollbox>
142
+ </Box>
143
+ </ScrollBox>
121
144
  );
122
145
  });
123
146
 
@@ -165,8 +188,11 @@ const TickerListRow = memo(function TickerListRow({
165
188
  setCursorSymbol,
166
189
  resolveCell,
167
190
  onRowActivate,
191
+ onRowContextMenu,
192
+ openContextMenuOnMouseDown,
168
193
  showSparklines,
169
194
  priceHistory,
195
+ contentWidth,
170
196
  }: {
171
197
  columns: ColumnConfig[];
172
198
  ticker: TickerRecord;
@@ -179,8 +205,11 @@ const TickerListRow = memo(function TickerListRow({
179
205
  setCursorSymbol: (symbol: string) => void;
180
206
  resolveCell: ResolveTickerTableCell;
181
207
  onRowActivate?: (ticker: TickerRecord) => void;
208
+ onRowContextMenu: (ticker: TickerRecord, financials: TickerFinancials | undefined, event: unknown) => void;
209
+ openContextMenuOnMouseDown: boolean;
182
210
  showSparklines?: boolean;
183
211
  priceHistory?: PricePoint[];
212
+ contentWidth: number;
184
213
  }) {
185
214
  const lastActivatedAtRef = useRef<number | null>(null);
186
215
  const rowBg = isSelected ? colors.selected : isHovered ? hoverBg() : colors.bg;
@@ -190,16 +219,29 @@ const TickerListRow = memo(function TickerListRow({
190
219
  : null;
191
220
 
192
221
  return (
193
- <box
222
+ <Box
194
223
  flexDirection="row"
195
224
  height={1}
225
+ {...tableContentWidthProps(contentWidth)}
196
226
  paddingX={1}
197
227
  backgroundColor={rowBg}
228
+ data-gloom-context-menu-surface="true"
198
229
  onMouseMove={() => setHoveredIdx(index)}
199
- onMouseDown={(event) => {
200
- event.preventDefault();
230
+ onMouseDown={(event: TableMouseEvent) => {
201
231
  setCursorSymbol(ticker.metadata.ticker);
232
+ if (event.button === 2) {
233
+ if (openContextMenuOnMouseDown) {
234
+ onRowContextMenu(ticker, financials, event);
235
+ }
236
+ return;
237
+ }
238
+ event.preventDefault?.();
202
239
  if (!onRowActivate) return;
240
+ if (typeof event.detail === "number" && event.detail >= 2) {
241
+ lastActivatedAtRef.current = null;
242
+ onRowActivate(ticker);
243
+ return;
244
+ }
203
245
  const now = Date.now();
204
246
  if (lastActivatedAtRef.current != null && now - lastActivatedAtRef.current <= 350) {
205
247
  lastActivatedAtRef.current = null;
@@ -208,6 +250,10 @@ const TickerListRow = memo(function TickerListRow({
208
250
  }
209
251
  lastActivatedAtRef.current = now;
210
252
  }}
253
+ onContextMenu={(event: unknown) => {
254
+ setCursorSymbol(ticker.metadata.ticker);
255
+ onRowContextMenu(ticker, financials, event);
256
+ }}
211
257
  >
212
258
  {columns.map((column) => {
213
259
  const { text, color } = resolveCell(column, ticker, financials);
@@ -218,19 +264,19 @@ const TickerListRow = memo(function TickerListRow({
218
264
  : baseFg;
219
265
 
220
266
  return (
221
- <box key={column.id} width={column.width + 1}>
222
- <text fg={cellFg}>
267
+ <Box key={column.id} width={column.width + 1}>
268
+ <Text fg={cellFg}>
223
269
  {padTo(text, column.width, column.align)}
224
- </text>
225
- </box>
270
+ </Text>
271
+ </Box>
226
272
  );
227
273
  })}
228
274
  {sparkline && (
229
- <box width={12}>
230
- <text content={sparkline} />
231
- </box>
275
+ <Box width={12}>
276
+ <Text content={sparkline} />
277
+ </Box>
232
278
  )}
233
- </box>
279
+ </Box>
234
280
  );
235
281
  }, (previous, next) => (
236
282
  previous.columns === next.columns
@@ -244,8 +290,11 @@ const TickerListRow = memo(function TickerListRow({
244
290
  && previous.setCursorSymbol === next.setCursorSymbol
245
291
  && previous.resolveCell === next.resolveCell
246
292
  && previous.onRowActivate === next.onRowActivate
293
+ && previous.onRowContextMenu === next.onRowContextMenu
294
+ && previous.openContextMenuOnMouseDown === next.openContextMenuOnMouseDown
247
295
  && previous.showSparklines === next.showSparklines
248
296
  && previous.priceHistory === next.priceHistory
297
+ && previous.contentWidth === next.contentWidth
249
298
  ));
250
299
 
251
300
  export function TickerListTable({
@@ -269,36 +318,151 @@ export function TickerListTable({
269
318
  emptyTitle = "No tickers.",
270
319
  emptyHint = "Press Ctrl+P to add one.",
271
320
  showSparklines,
321
+ virtualize = true,
322
+ overscan = 4,
272
323
  }: TickerListTableProps) {
273
- const safeFlashSymbols = flashSymbols ?? new Map<string, QuoteFlashDirection>();
324
+ const internalHeaderScrollRef = useRef<ScrollBoxRenderable>(null);
325
+ const internalScrollRef = useRef<ScrollBoxRenderable>(null);
326
+ const effectiveHeaderScrollRef = headerScrollRef ?? internalHeaderScrollRef;
327
+ const effectiveScrollRef = scrollRef ?? internalScrollRef;
328
+ const hoveredIdxRef = useRef(hoveredIdx);
329
+ const appViewport = useViewport();
330
+ const renderer = useRendererHost();
331
+ const { showContextMenu } = useContextMenu();
332
+ const { nativeContextMenu } = useUiCapabilities();
333
+ const [scrollVersion, setScrollVersion] = useState(0);
334
+ hoveredIdxRef.current = hoveredIdx;
335
+ const safeFlashSymbols = flashSymbols ?? EMPTY_FLASH_SYMBOLS;
336
+ const tableWidth = useMemo(
337
+ () => columns.reduce((sum, column) => sum + column.width + 1, 2) + (showSparklines ? 12 : 0),
338
+ [columns, showSparklines],
339
+ );
340
+ const { contentWidth, measureContentWidth } = useMeasuredTableContentWidth(
341
+ tableWidth,
342
+ effectiveHeaderScrollRef,
343
+ effectiveScrollRef,
344
+ );
345
+ const scrollTop = virtualize ? (effectiveScrollRef.current?.scrollTop ?? 0) : 0;
346
+ const measuredViewportHeight = effectiveScrollRef.current?.viewport?.height;
347
+ const viewportHeight = virtualize
348
+ ? Math.max(
349
+ 1,
350
+ Math.min(
351
+ measuredViewportHeight ?? Math.min(tickers.length, 16),
352
+ Math.max(1, Math.ceil(appViewport.height)),
353
+ ),
354
+ )
355
+ : tickers.length;
356
+ const startIndex = virtualize ? Math.max(scrollTop - overscan, 0) : 0;
357
+ const endIndex = virtualize
358
+ ? Math.min(startIndex + viewportHeight + overscan * 2, tickers.length)
359
+ : tickers.length;
360
+ const visibleTickers = useMemo(
361
+ () => measurePerf(
362
+ "ticker-list-table.visible-rows",
363
+ () => tickers.slice(startIndex, endIndex),
364
+ {
365
+ itemCount: tickers.length,
366
+ measuredViewportHeight,
367
+ startIndex,
368
+ endIndex,
369
+ viewportHeight,
370
+ virtualize,
371
+ },
372
+ ),
373
+ [
374
+ appViewport.height,
375
+ endIndex,
376
+ measuredViewportHeight,
377
+ scrollVersion,
378
+ startIndex,
379
+ tickers,
380
+ tickers.length,
381
+ viewportHeight,
382
+ virtualize,
383
+ ],
384
+ );
385
+ const handleBodyScrollActivity = useCallback(() => {
386
+ if (virtualize) {
387
+ setScrollVersion((current) => current + 1);
388
+ }
389
+ onBodyScrollActivity?.();
390
+ }, [onBodyScrollActivity, virtualize]);
391
+ const syncHeader = useCallback(() => {
392
+ syncHeaderScroll?.();
393
+ }, [syncHeaderScroll]);
394
+ const scheduleBodyScrollActivity = useRafCallback(handleBodyScrollActivity);
395
+ const scheduleHeaderScrollSync = useRafCallback(syncHeader);
396
+ const setHoveredIdxIfChanged = useCallback((index: number | null) => {
397
+ if (hoveredIdxRef.current === index) return;
398
+ setHoveredIdx(index);
399
+ }, [setHoveredIdx]);
400
+ const handleRowContextMenu = useCallback((
401
+ ticker: TickerRecord,
402
+ financials: TickerFinancials | undefined,
403
+ event: unknown,
404
+ ) => {
405
+ const registry = getSharedRegistry() ?? null;
406
+ void showContextMenu(
407
+ {
408
+ kind: "ticker",
409
+ symbol: ticker.metadata.ticker,
410
+ ticker,
411
+ financials: financials ?? null,
412
+ },
413
+ tickerContextMenuItems({
414
+ ticker,
415
+ financials: financials ?? null,
416
+ registry,
417
+ copyText: renderer.copyText.bind(renderer),
418
+ }),
419
+ event as { preventDefault?: () => void; stopPropagation?: () => void },
420
+ );
421
+ }, [renderer, showContextMenu]);
274
422
 
275
423
  return (
276
- <>
424
+ <Box
425
+ flexDirection="column"
426
+ flexGrow={1}
427
+ flexBasis={0}
428
+ width="100%"
429
+ backgroundColor={colors.bg}
430
+ overflow="hidden"
431
+ >
277
432
  <TickerListHeader
278
433
  columns={columns}
279
- headerScrollRef={headerScrollRef}
434
+ contentWidth={contentWidth}
435
+ headerScrollRef={effectiveHeaderScrollRef}
280
436
  sortColumnId={sortColumnId}
281
437
  sortDirection={sortDirection}
282
438
  onHeaderClick={onHeaderClick}
439
+ onSizeChange={measureContentWidth}
283
440
  />
284
441
 
285
- <scrollbox
286
- ref={scrollRef}
442
+ <ScrollBox
443
+ ref={effectiveScrollRef}
444
+ width="100%"
287
445
  flexGrow={1}
446
+ flexBasis={0}
447
+ backgroundColor={colors.bg}
288
448
  scrollX
289
449
  scrollY
290
450
  focusable={false}
291
- onMouseDown={syncHeaderScroll ? () => queueMicrotask(syncHeaderScroll) : undefined}
292
- onMouseUp={onBodyScrollActivity ? () => queueMicrotask(onBodyScrollActivity) : undefined}
293
- onMouseDrag={onBodyScrollActivity ? () => queueMicrotask(onBodyScrollActivity) : undefined}
294
- onMouseScroll={onBodyScrollActivity ? () => queueMicrotask(onBodyScrollActivity) : undefined}
451
+ onMouseDown={scheduleHeaderScrollSync}
452
+ onMouseUp={scheduleBodyScrollActivity}
453
+ onMouseDrag={scheduleBodyScrollActivity}
454
+ onMouseScroll={scheduleBodyScrollActivity}
455
+ onSizeChange={measureContentWidth}
295
456
  >
296
457
  {tickers.length === 0 ? (
297
- <box paddingX={1} paddingY={1}>
458
+ <Box width="100%" paddingX={1} paddingY={1}>
298
459
  <EmptyState title={emptyTitle} hint={emptyHint} />
299
- </box>
460
+ </Box>
300
461
  ) : (
301
- tickers.map((ticker, index) => {
462
+ <>
463
+ {virtualize && startIndex > 0 && <Box height={startIndex} />}
464
+ {visibleTickers.map((ticker, visibleIndex) => {
465
+ const index = startIndex + visibleIndex;
302
466
  return (
303
467
  <TickerListRow
304
468
  key={ticker.metadata.ticker}
@@ -309,17 +473,24 @@ export function TickerListTable({
309
473
  isHovered={index === hoveredIdx && ticker.metadata.ticker !== cursorSymbol}
310
474
  financials={financialsMap.get(ticker.metadata.ticker)}
311
475
  flashDirection={safeFlashSymbols.get(ticker.metadata.ticker)}
312
- setHoveredIdx={setHoveredIdx}
476
+ setHoveredIdx={setHoveredIdxIfChanged}
313
477
  setCursorSymbol={setCursorSymbol}
314
478
  resolveCell={resolveCell}
315
479
  onRowActivate={onRowActivate}
480
+ onRowContextMenu={handleRowContextMenu}
481
+ openContextMenuOnMouseDown={nativeContextMenu !== true}
316
482
  showSparklines={showSparklines}
317
483
  priceHistory={financialsMap.get(ticker.metadata.ticker)?.priceHistory}
484
+ contentWidth={contentWidth}
318
485
  />
319
486
  );
320
- })
487
+ })}
488
+ {virtualize && endIndex < tickers.length && (
489
+ <Box height={Math.max(tickers.length - endIndex, 0)} />
490
+ )}
491
+ </>
321
492
  )}
322
- </scrollbox>
323
- </>
493
+ </ScrollBox>
494
+ </Box>
324
495
  );
325
496
  }
@@ -1,12 +1,14 @@
1
- import { TextAttributes } from "@opentui/core";
1
+ import { Box, Text, useUiHost } from "../ui";
2
+ import { TextAttributes } from "../ui";
2
3
  import { colors } from "../theme/colors";
3
- import { ListView, type ListViewItem } from "./ui/list-view";
4
+ import { ListView, type ListRowState, type ListViewItem } from "./ui/list-view";
4
5
 
5
6
  export interface ToggleListItem {
6
7
  id: string;
7
8
  label: string;
8
9
  enabled: boolean;
9
10
  description?: string;
11
+ disabled?: boolean;
10
12
  }
11
13
 
12
14
  export interface ToggleListProps {
@@ -21,6 +23,91 @@ export interface ToggleListProps {
21
23
  scrollable?: boolean;
22
24
  showSelectedDescription?: boolean;
23
25
  rowIdPrefix?: string;
26
+ rowGap?: number;
27
+ rowHeight?: number;
28
+ surface?: "framed" | "plain";
29
+ }
30
+
31
+ function DesktopToggleRow({
32
+ item,
33
+ state,
34
+ rowIdPrefix,
35
+ enabled,
36
+ }: {
37
+ item: ListViewItem;
38
+ state: ListRowState;
39
+ rowIdPrefix?: string;
40
+ enabled: boolean;
41
+ }) {
42
+ const checkboxBorder = enabled
43
+ ? colors.borderFocused
44
+ : state.selected
45
+ ? colors.textMuted
46
+ : colors.border;
47
+ const textColor = state.disabled
48
+ ? colors.textMuted
49
+ : state.selected
50
+ ? colors.text
51
+ : colors.textDim;
52
+
53
+ return (
54
+ <Box
55
+ id={rowIdPrefix ? `${rowIdPrefix}:${item.id}` : undefined}
56
+ flexDirection="row"
57
+ alignItems="center"
58
+ width="100%"
59
+ minWidth={0}
60
+ style={{
61
+ height: "100%",
62
+ gap: 10,
63
+ paddingInline: 10,
64
+ opacity: state.disabled ? 0.55 : 1,
65
+ }}
66
+ >
67
+ <Box
68
+ alignItems="center"
69
+ justifyContent="center"
70
+ backgroundColor={enabled ? colors.borderFocused : "transparent"}
71
+ style={{
72
+ width: 16,
73
+ height: 16,
74
+ minWidth: 16,
75
+ alignSelf: "center",
76
+ border: `1px solid ${checkboxBorder}`,
77
+ borderRadius: 4,
78
+ boxShadow: enabled
79
+ ? "inset 0 1px 0 rgba(255,255,255,0.28)"
80
+ : "inset 0 1px 0 rgba(255,255,255,0.05)",
81
+ }}
82
+ >
83
+ {enabled && (
84
+ <Text
85
+ fg={colors.bg}
86
+ attributes={TextAttributes.BOLD}
87
+ style={{ fontSize: 12, lineHeight: "14px", fontWeight: 800 }}
88
+ >
89
+ {"✓"}
90
+ </Text>
91
+ )}
92
+ </Box>
93
+ <Box minWidth={0} flexShrink={1}>
94
+ <Text
95
+ fg={textColor}
96
+ attributes={state.selected ? TextAttributes.BOLD : 0}
97
+ style={{
98
+ display: "block",
99
+ overflow: "hidden",
100
+ textOverflow: "ellipsis",
101
+ whiteSpace: "nowrap",
102
+ fontWeight: state.selected ? 700 : 500,
103
+ lineHeight: "16px",
104
+ }}
105
+ >
106
+ {item.label}
107
+ </Text>
108
+ </Box>
109
+ </Box>
110
+ );
24
111
  }
25
112
 
26
113
  export function ToggleList({
@@ -34,11 +121,16 @@ export function ToggleList({
34
121
  scrollable,
35
122
  showSelectedDescription = true,
36
123
  rowIdPrefix,
124
+ rowGap,
125
+ rowHeight,
126
+ surface,
37
127
  }: ToggleListProps) {
128
+ const isDesktopWeb = useUiHost().kind === "desktop-web";
38
129
  const listItems: ListViewItem[] = items.map((item) => ({
39
130
  id: item.id,
40
131
  label: item.label,
41
132
  description: item.description,
133
+ disabled: item.disabled,
42
134
  }));
43
135
 
44
136
  return (
@@ -50,12 +142,26 @@ export function ToggleList({
50
142
  height={height}
51
143
  flexGrow={flexGrow}
52
144
  scrollable={scrollable}
145
+ rowGap={rowGap ?? (isDesktopWeb ? 0 : undefined)}
146
+ rowHeight={rowHeight}
147
+ surface={surface}
53
148
  onSelect={onSelect}
54
149
  onActivate={(item) => {
55
150
  onToggle?.(item.id);
56
151
  }}
57
152
  renderRow={(item, state, index) => {
58
153
  const toggleItem = items.find((entry) => entry.id === item.id);
154
+ if (isDesktopWeb) {
155
+ return (
156
+ <DesktopToggleRow
157
+ item={item}
158
+ state={state}
159
+ rowIdPrefix={rowIdPrefix}
160
+ enabled={toggleItem?.enabled === true}
161
+ />
162
+ );
163
+ }
164
+
59
165
  const checked = toggleItem?.enabled ? "\u2713" : " ";
60
166
  const activate = (event: any) => {
61
167
  event.stopPropagation?.();
@@ -64,22 +170,22 @@ export function ToggleList({
64
170
  onToggle?.(item.id);
65
171
  };
66
172
  return (
67
- <box
173
+ <Box
68
174
  id={rowIdPrefix ? `${rowIdPrefix}:${item.id}` : undefined}
69
175
  flexDirection="row"
70
176
  onMouseDown={activate}
71
177
  >
72
- <text fg={state.selected ? colors.selectedText : colors.textDim}>
178
+ <Text fg={state.selected ? colors.selectedText : colors.textDim}>
73
179
  {state.selected ? "\u25b8 " : " "}
74
- </text>
75
- <text
180
+ </Text>
181
+ <Text
76
182
  fg={state.selected ? colors.text : colors.textDim}
77
183
  attributes={state.selected ? TextAttributes.BOLD : 0}
78
184
  onMouseDown={activate}
79
185
  >
80
186
  {`[${checked}] ${item.label}`}
81
- </text>
82
- </box>
187
+ </Text>
188
+ </Box>
83
189
  );
84
190
  }}
85
191
  />
@@ -1,4 +1,6 @@
1
- import { TextAttributes } from "@opentui/core";
1
+ import { Box, Text, useUiHost } from "../../ui";
2
+ import { TextAttributes } from "../../ui";
3
+ import { type ComponentType } from "react";
2
4
  import { colors } from "../../theme/colors";
3
5
 
4
6
  export type ButtonVariant = "primary" | "secondary" | "ghost" | "danger";
@@ -43,10 +45,25 @@ export function Button({
43
45
  shortcut,
44
46
  width,
45
47
  }: ButtonProps) {
48
+ const HostButton = useUiHost().Button as ComponentType<ButtonProps> | undefined;
49
+ if (HostButton) {
50
+ return (
51
+ <HostButton
52
+ label={label}
53
+ onPress={onPress}
54
+ variant={variant}
55
+ disabled={disabled}
56
+ active={active}
57
+ shortcut={shortcut}
58
+ width={width}
59
+ />
60
+ );
61
+ }
62
+
46
63
  const palette = resolveButtonColors(variant, active, disabled);
47
64
 
48
65
  return (
49
- <box
66
+ <Box
50
67
  width={width}
51
68
  height={1}
52
69
  flexDirection="row"
@@ -55,23 +72,14 @@ export function Button({
55
72
  if (!disabled) onPress?.();
56
73
  }}
57
74
  >
58
- <text fg={palette.fg} attributes={active ? TextAttributes.BOLD : 0}>
75
+ <Text fg={palette.fg} attributes={active ? TextAttributes.BOLD : 0}>
59
76
  {` ${label} `}
60
- </text>
77
+ </Text>
61
78
  {shortcut && (
62
- <text fg={disabled ? colors.textMuted : colors.textDim}>
79
+ <Text fg={disabled ? colors.textMuted : colors.textDim}>
63
80
  {` ${shortcut}`}
64
- </text>
81
+ </Text>
65
82
  )}
66
- </box>
83
+ </Box>
67
84
  );
68
85
  }
69
-
70
- export interface IconButtonProps extends Omit<ButtonProps, "label"> {
71
- icon: string;
72
- label?: string;
73
- }
74
-
75
- export function IconButton({ icon, label, ...props }: IconButtonProps) {
76
- return <Button label={label ? `${icon} ${label}` : icon} {...props} />;
77
- }