gloomberb 0.5.1 → 0.6.1

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 (506) hide show
  1. package/README.md +91 -11
  2. package/package.json +13 -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 +318 -158
  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 +53 -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/native/surface-visibility.ts +48 -0
  53. package/src/components/chart/static-chart-surface.test.tsx +51 -0
  54. package/src/components/chart/static-chart-surface.tsx +191 -0
  55. package/src/components/chart/stock-chart-renderer-switch.test.tsx +242 -35
  56. package/src/components/chart/stock-chart.tsx +452 -191
  57. package/src/components/command-bar/__snapshots__/command-bar.test.tsx.snap +4 -4
  58. package/src/components/command-bar/command-bar.test.tsx +269 -20
  59. package/src/components/command-bar/command-bar.tsx +830 -353
  60. package/src/components/command-bar/command-registry.ts +10 -20
  61. package/src/components/command-bar/helpers.test.ts +1 -1
  62. package/src/components/command-bar/helpers.ts +9 -11
  63. package/src/components/command-bar/pane-template-display.ts +4 -8
  64. package/src/components/command-bar/root-shortcuts.ts +12 -11
  65. package/src/components/command-bar/view-model.test.ts +8 -42
  66. package/src/components/command-bar/view-model.ts +8 -36
  67. package/src/components/command-bar/workflow-ops.test.ts +76 -2
  68. package/src/components/command-bar/workflow-ops.ts +40 -9
  69. package/src/components/command-bar/workflow-types.ts +2 -1
  70. package/src/components/data-table-stack-view.test.tsx +206 -0
  71. package/src/components/data-table-stack-view.tsx +152 -0
  72. package/src/components/data-table-view.test.tsx +188 -0
  73. package/src/components/data-table-view.tsx +279 -0
  74. package/src/components/feed-data-table-stack-view.test.tsx +225 -0
  75. package/src/components/feed-data-table-stack-view.tsx +365 -0
  76. package/src/components/index.ts +26 -31
  77. package/src/components/layout/detached-pane-shell.tsx +185 -0
  78. package/src/components/layout/floating-pane.tsx +87 -14
  79. package/src/components/layout/header.tsx +139 -49
  80. package/src/components/layout/pane-content.tsx +42 -0
  81. package/src/components/layout/pane-footer.test.tsx +195 -0
  82. package/src/components/layout/pane-footer.tsx +425 -0
  83. package/src/components/layout/pane-header.test.tsx +39 -0
  84. package/src/components/layout/pane-header.tsx +192 -14
  85. package/src/components/layout/pane-keyboard-scroll.test.tsx +226 -0
  86. package/src/components/layout/pane-sizing.ts +9 -10
  87. package/src/components/layout/pane-title.ts +35 -0
  88. package/src/components/layout/pane.tsx +52 -11
  89. package/src/components/layout/shell.test.tsx +315 -107
  90. package/src/components/layout/shell.tsx +1010 -368
  91. package/src/components/layout/status-bar.test.tsx +17 -10
  92. package/src/components/layout/status-bar.tsx +367 -100
  93. package/src/components/layout/titlebar-overlay.ts +2 -0
  94. package/src/components/markdown-editor.tsx +29 -14
  95. package/src/components/markdown-text.tsx +25 -24
  96. package/src/components/onboarding/onboarding-wizard.test.tsx +8 -3
  97. package/src/components/onboarding/onboarding-wizard.tsx +347 -473
  98. package/src/components/pane-settings-dialog.tsx +521 -89
  99. package/src/components/pane-template-wizard.test.tsx +1 -1
  100. package/src/components/pane-template-wizard.tsx +38 -37
  101. package/src/components/price-selector-dialog.tsx +22 -21
  102. package/src/components/ticker-badge-list.tsx +53 -0
  103. package/src/components/ticker-badge-text.test.tsx +44 -1
  104. package/src/components/ticker-badge-text.tsx +78 -38
  105. package/src/components/ticker-badge.tsx +28 -7
  106. package/src/components/ticker-list-table-view.tsx +248 -0
  107. package/src/components/ticker-list-table.test.tsx +47 -1
  108. package/src/components/ticker-list-table.tsx +212 -41
  109. package/src/components/toggle-list.tsx +114 -8
  110. package/src/components/ui/button.tsx +24 -16
  111. package/src/components/ui/choice-dialog.tsx +89 -0
  112. package/src/components/ui/data-table.tsx +313 -114
  113. package/src/components/ui/external-link.tsx +52 -13
  114. package/src/components/ui/fields.tsx +44 -30
  115. package/src/components/ui/frame.tsx +24 -57
  116. package/src/components/ui/index.ts +15 -50
  117. package/src/components/ui/list-view.tsx +65 -25
  118. package/src/components/ui/loading.tsx +5 -60
  119. package/src/components/ui/message-composer.test.tsx +81 -0
  120. package/src/components/ui/message-composer.tsx +139 -0
  121. package/src/components/ui/multi-select-dialog.tsx +100 -32
  122. package/src/components/ui/multi-select.ts +112 -0
  123. package/src/components/ui/native-select.tsx +120 -0
  124. package/src/components/ui/page-stack-view.tsx +49 -19
  125. package/src/components/ui/remote-image.tsx +50 -0
  126. package/src/components/ui/row-value-cache.test.ts +17 -0
  127. package/src/components/ui/row-value-cache.ts +41 -0
  128. package/src/components/ui/shortcut-hint.tsx +55 -0
  129. package/src/components/ui/status.tsx +12 -67
  130. package/src/components/ui/table-layout.ts +59 -0
  131. package/src/components/ui/tabs.tsx +249 -27
  132. package/src/components/ui/toggle.tsx +19 -127
  133. package/src/components/ui/ui.test.tsx +266 -53
  134. package/src/components/use-double-click-activation.ts +7 -2
  135. package/src/components/use-external-link-footer.ts +79 -0
  136. package/src/core/app-services.ts +95 -0
  137. package/src/core/state/app-state.ts +886 -0
  138. package/src/{state → core/state}/session-persistence.ts +7 -6
  139. package/src/data/config-layout.ts +225 -0
  140. package/src/data/config-store-node.ts +390 -0
  141. package/src/data/config-store.test.ts +72 -0
  142. package/src/data/config-store.ts +35 -478
  143. package/src/data/sqlite-json.ts +2 -5
  144. package/src/data/sqlite-retry.ts +1 -0
  145. package/src/index.tsx +2 -68
  146. package/src/market-data/coordinator-subscriptions.test.ts +125 -0
  147. package/src/market-data/coordinator.test.ts +73 -4
  148. package/src/market-data/coordinator.ts +190 -24
  149. package/src/market-data/hooks.test.tsx +1 -1
  150. package/src/market-data/hooks.tsx +109 -87
  151. package/src/market-data/query-store.ts +3 -3
  152. package/src/market-data/request-types.test.ts +0 -14
  153. package/src/market-data/result-types.ts +1 -0
  154. package/src/news/aggregator.test.ts +267 -0
  155. package/src/news/aggregator.ts +452 -0
  156. package/src/news/hooks.ts +39 -0
  157. package/src/news/ticker-symbols.ts +21 -0
  158. package/src/news/types.ts +77 -0
  159. package/src/notifications/app-notifier.ts +21 -10
  160. package/src/pane-settings.test.ts +0 -2
  161. package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +124 -134
  162. package/src/plugins/builtin/ai/gloomberb-cli.ts +14 -13
  163. package/src/plugins/builtin/ai/providers.ts +1 -9
  164. package/src/plugins/builtin/ai/runner.ts +41 -5
  165. package/src/plugins/builtin/ai/screener-pane.test.tsx +37 -55
  166. package/src/plugins/builtin/ai/screener-pane.tsx +231 -276
  167. package/src/plugins/builtin/ai/settings.ts +0 -1
  168. package/src/plugins/builtin/ai/utils.ts +1 -71
  169. package/src/plugins/builtin/alerts/alert-engine.test.ts +1 -38
  170. package/src/plugins/builtin/alerts/index.test.tsx +61 -82
  171. package/src/plugins/builtin/alerts/index.tsx +263 -155
  172. package/src/plugins/builtin/alerts/types.ts +7 -0
  173. package/src/plugins/builtin/analytics/index.test.tsx +13 -8
  174. package/src/plugins/builtin/analytics/index.tsx +78 -120
  175. package/src/plugins/builtin/analytics/metrics.test.ts +0 -4
  176. package/src/plugins/builtin/ask-ai.test.tsx +55 -54
  177. package/src/plugins/builtin/broker-manager/index.test.tsx +168 -0
  178. package/src/plugins/builtin/broker-manager/index.tsx +759 -0
  179. package/src/plugins/builtin/broker-manager/model.test.ts +107 -0
  180. package/src/plugins/builtin/broker-manager/model.ts +142 -0
  181. package/src/plugins/builtin/chat-controller.test.ts +129 -0
  182. package/src/plugins/builtin/chat-controller.ts +85 -14
  183. package/src/plugins/builtin/chat.test.tsx +283 -36
  184. package/src/plugins/builtin/chat.tsx +363 -170
  185. package/src/plugins/builtin/cloud-auth-actions.tsx +72 -0
  186. package/src/plugins/builtin/cloud-tweets.tsx +896 -0
  187. package/src/plugins/builtin/comparison-chart.test.tsx +46 -57
  188. package/src/plugins/builtin/comparison-chart.tsx +11 -11
  189. package/src/plugins/builtin/correlation/compute.test.ts +0 -17
  190. package/src/plugins/builtin/correlation/index.tsx +51 -46
  191. package/src/plugins/builtin/correlation/settings.ts +1 -2
  192. package/src/plugins/builtin/debug.tsx +99 -70
  193. package/src/plugins/builtin/earnings/earnings-cache.test.ts +2 -2
  194. package/src/plugins/builtin/earnings/earnings-cache.ts +12 -3
  195. package/src/plugins/builtin/earnings/earnings-data.test.ts +42 -11
  196. package/src/plugins/builtin/earnings/earnings-data.ts +70 -5
  197. package/src/plugins/builtin/earnings/index.tsx +187 -94
  198. package/src/plugins/builtin/econ/calendar-source.ts +9 -116
  199. package/src/plugins/builtin/econ/fred-client.test.ts +1 -14
  200. package/src/plugins/builtin/econ/fred-series-map.ts +1 -0
  201. package/src/plugins/builtin/econ/index.tsx +236 -421
  202. package/src/plugins/builtin/econ/types.ts +1 -0
  203. package/src/plugins/builtin/fx-matrix/index.tsx +43 -45
  204. package/src/plugins/builtin/fx-matrix/pairs.ts +1 -0
  205. package/src/plugins/builtin/help.test.tsx +120 -60
  206. package/src/plugins/builtin/help.tsx +266 -82
  207. package/src/plugins/builtin/holders/index.test.ts +45 -0
  208. package/src/plugins/builtin/holders/index.tsx +949 -0
  209. package/src/plugins/builtin/insider/index.tsx +207 -253
  210. package/src/plugins/builtin/insider/insider-data.test.ts +1 -11
  211. package/src/plugins/builtin/insider/insider-data.ts +1 -0
  212. package/src/plugins/builtin/layout-manager.tsx +12 -40
  213. package/src/plugins/builtin/market-movers/index.tsx +70 -144
  214. package/src/plugins/builtin/market-movers/screener.test.ts +0 -18
  215. package/src/plugins/builtin/news-wire/breaking-notifications.test.ts +98 -0
  216. package/src/plugins/builtin/news-wire/breaking-notifications.ts +110 -0
  217. package/src/plugins/builtin/news-wire/breaking-pane.tsx +157 -0
  218. package/src/plugins/builtin/news-wire/categories.ts +114 -0
  219. package/src/plugins/builtin/news-wire/default-feeds.ts +3 -0
  220. package/src/plugins/builtin/news-wire/digest-store.ts +44 -0
  221. package/src/plugins/builtin/news-wire/feed-config.test.ts +90 -0
  222. package/src/plugins/builtin/news-wire/feed-config.ts +216 -0
  223. package/src/plugins/builtin/news-wire/feed-pane.tsx +21 -0
  224. package/src/plugins/builtin/news-wire/hash.ts +9 -0
  225. package/src/plugins/builtin/news-wire/index.tsx +92 -0
  226. package/src/plugins/builtin/news-wire/industry-pane.tsx +140 -0
  227. package/src/plugins/builtin/news-wire/news-detail-view.tsx +256 -0
  228. package/src/plugins/builtin/news-wire/news-footer.ts +28 -0
  229. package/src/plugins/builtin/news-wire/news-preset-pane.tsx +94 -0
  230. package/src/plugins/builtin/news-wire/news-query-presets.ts +40 -0
  231. package/src/plugins/builtin/news-wire/news-table.test.tsx +206 -0
  232. package/src/plugins/builtin/news-wire/news-table.tsx +296 -0
  233. package/src/plugins/builtin/news-wire/persisted-articles.ts +92 -0
  234. package/src/plugins/builtin/news-wire/read-state.test.ts +30 -0
  235. package/src/plugins/builtin/news-wire/read-state.ts +76 -0
  236. package/src/plugins/builtin/news-wire/rss-parser.test.ts +184 -0
  237. package/src/plugins/builtin/news-wire/rss-parser.ts +213 -0
  238. package/src/plugins/builtin/news-wire/rss-source.test.ts +159 -0
  239. package/src/plugins/builtin/news-wire/rss-source.ts +181 -0
  240. package/src/plugins/builtin/news-wire/top-pane.tsx +21 -0
  241. package/src/plugins/builtin/news.tsx +98 -25
  242. package/src/plugins/builtin/notes-files.ts +51 -10
  243. package/src/plugins/builtin/notes.tsx +332 -131
  244. package/src/plugins/builtin/options.test.tsx +185 -0
  245. package/src/plugins/builtin/options.tsx +346 -206
  246. package/src/plugins/builtin/plugin-groups.ts +92 -0
  247. package/src/plugins/builtin/portfolio-list/header.tsx +72 -37
  248. package/src/plugins/builtin/portfolio-list/index.tsx +25 -42
  249. package/src/plugins/builtin/portfolio-list/metrics.ts +1 -1
  250. package/src/plugins/builtin/portfolio-list/pane.tsx +246 -164
  251. package/src/plugins/builtin/portfolio-list/settings.ts +19 -51
  252. package/src/plugins/builtin/portfolio-list/summary.tsx +21 -16
  253. package/src/plugins/builtin/portfolio-list/table.tsx +66 -25
  254. package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.test.tsx +1 -1
  255. package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.ts +1 -1
  256. package/src/plugins/builtin/portfolio-list.test.ts +60 -1
  257. package/src/plugins/builtin/portfolio-list.test.tsx +122 -55
  258. package/src/plugins/builtin/quote-monitor.test.tsx +2 -27
  259. package/src/plugins/builtin/research/index.tsx +724 -0
  260. package/src/plugins/builtin/sec.tsx +67 -19
  261. package/src/plugins/builtin/sectors/index.tsx +42 -80
  262. package/src/plugins/builtin/sectors/sector-data.ts +1 -0
  263. package/src/plugins/builtin/ticker-detail/chart-tab.tsx +25 -39
  264. package/src/plugins/builtin/ticker-detail/financials-tab.tsx +108 -58
  265. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +383 -271
  266. package/src/plugins/builtin/ticker-detail/pane.tsx +143 -49
  267. package/src/plugins/builtin/ticker-detail/quote-monitor.tsx +43 -27
  268. package/src/plugins/builtin/ticker-detail/settings.ts +3 -6
  269. package/src/plugins/builtin/ticker-detail-chart-tab-switch.test.tsx +82 -10
  270. package/src/plugins/builtin/ticker-detail.test.tsx +258 -33
  271. package/src/plugins/builtin/ticker-surface.ts +68 -0
  272. package/src/plugins/builtin/world-indices/index.tsx +44 -82
  273. package/src/plugins/builtin/world-indices/indices.ts +1 -0
  274. package/src/plugins/builtin/yahoo.ts +55 -1
  275. package/src/plugins/builtin/yield-curve/index.tsx +87 -145
  276. package/src/plugins/builtin/yield-curve/treasury-data.ts +6 -77
  277. package/src/plugins/catalog-ui.test.ts +24 -0
  278. package/src/plugins/catalog-ui.ts +44 -0
  279. package/src/plugins/catalog.ts +2 -50
  280. package/src/plugins/ibkr/__snapshots__/trade-tab.test.tsx.snap +5 -5
  281. package/src/plugins/ibkr/account-cache.test.ts +15 -14
  282. package/src/plugins/ibkr/account-cache.ts +15 -120
  283. package/src/plugins/ibkr/broker-adapter.ts +67 -0
  284. package/src/plugins/ibkr/config.test.ts +0 -18
  285. package/src/plugins/ibkr/dialogs.tsx +14 -56
  286. package/src/plugins/ibkr/flex.test.ts +57 -2
  287. package/src/plugins/ibkr/flex.ts +116 -12
  288. package/src/plugins/ibkr/gateway-helpers.ts +8 -3
  289. package/src/plugins/ibkr/gateway-service-native.ts +2381 -0
  290. package/src/plugins/ibkr/gateway-service.test.ts +47 -9
  291. package/src/plugins/ibkr/gateway-service.ts +264 -2235
  292. package/src/plugins/ibkr/gateway-types.ts +27 -0
  293. package/src/plugins/ibkr/index.test.tsx +3 -11
  294. package/src/plugins/ibkr/index.tsx +1 -0
  295. package/src/plugins/ibkr/price-normalization.test.ts +1 -6
  296. package/src/plugins/ibkr/price-normalization.ts +1 -0
  297. package/src/plugins/ibkr/trade-badge.tsx +6 -5
  298. package/src/plugins/ibkr/trade-tab.test.tsx +55 -3
  299. package/src/plugins/ibkr/trade-tab.tsx +166 -117
  300. package/src/plugins/ibkr/trading-pane.tsx +117 -83
  301. package/src/plugins/pane-manager.test.ts +3 -1
  302. package/src/plugins/pane-manager.ts +93 -19
  303. package/src/plugins/plugin-runtime.test.tsx +176 -74
  304. package/src/plugins/plugin-runtime.tsx +228 -10
  305. package/src/plugins/prediction-markets/cache.ts +49 -0
  306. package/src/plugins/prediction-markets/categories.ts +0 -10
  307. package/src/plugins/prediction-markets/chart.test.tsx +1 -1
  308. package/src/plugins/prediction-markets/chart.tsx +21 -20
  309. package/src/plugins/prediction-markets/cli.ts +1 -0
  310. package/src/plugins/prediction-markets/controller-data.ts +135 -89
  311. package/src/plugins/prediction-markets/controller.ts +37 -108
  312. package/src/plugins/prediction-markets/detail/book.tsx +23 -22
  313. package/src/plugins/prediction-markets/detail/outcomes.tsx +33 -32
  314. package/src/plugins/prediction-markets/detail/overview.tsx +17 -12
  315. package/src/plugins/prediction-markets/detail/pane.tsx +59 -45
  316. package/src/plugins/prediction-markets/detail/rules.tsx +22 -7
  317. package/src/plugins/prediction-markets/detail/shared.tsx +3 -2
  318. package/src/plugins/prediction-markets/detail/trades.tsx +12 -11
  319. package/src/plugins/prediction-markets/detail.test.tsx +1 -1
  320. package/src/plugins/prediction-markets/keyboard.ts +1 -0
  321. package/src/plugins/prediction-markets/launch.test.ts +1 -1
  322. package/src/plugins/prediction-markets/launch.ts +1 -1
  323. package/src/plugins/prediction-markets/metrics.ts +25 -12
  324. package/src/plugins/prediction-markets/pane.test.tsx +16 -7
  325. package/src/plugins/prediction-markets/pane.tsx +180 -82
  326. package/src/plugins/prediction-markets/plugin.test.tsx +37 -45
  327. package/src/plugins/prediction-markets/services/fetch.ts +27 -10
  328. package/src/plugins/prediction-markets/services/kalshi-adapter.ts +20 -3
  329. package/src/plugins/prediction-markets/services/polymarket-adapter.ts +29 -11
  330. package/src/plugins/prediction-markets/settings.ts +0 -5
  331. package/src/plugins/prediction-markets/test-helpers.tsx +19 -44
  332. package/src/plugins/registry.test.ts +307 -0
  333. package/src/plugins/registry.ts +293 -82
  334. package/src/react/input.ts +61 -0
  335. package/src/react/plugins/plugin-slot.tsx +21 -0
  336. package/src/react/use-raf-callback.ts +44 -0
  337. package/src/renderers/electrobun/bun/application-menu-click.test.ts +41 -0
  338. package/src/renderers/electrobun/bun/application-menu-click.ts +55 -0
  339. package/src/renderers/electrobun/bun/application-menu.ts +118 -0
  340. package/src/renderers/electrobun/bun/context-menu-click.test.ts +67 -0
  341. package/src/renderers/electrobun/bun/context-menu-click.ts +61 -0
  342. package/src/renderers/electrobun/bun/context-menu-normalize.test.ts +52 -0
  343. package/src/renderers/electrobun/bun/context-menu-normalize.ts +67 -0
  344. package/src/renderers/electrobun/bun/core-capabilities.ts +328 -0
  345. package/src/renderers/electrobun/bun/desktop-workspace.test.ts +63 -0
  346. package/src/renderers/electrobun/bun/desktop-workspace.ts +162 -0
  347. package/src/renderers/electrobun/bun/index.ts +1149 -0
  348. package/src/renderers/electrobun/bun/window-frame.test.ts +40 -0
  349. package/src/renderers/electrobun/bun/window-frame.ts +46 -0
  350. package/src/renderers/electrobun/shared/protocol.ts +72 -0
  351. package/src/renderers/electrobun/view/ai-host.ts +93 -0
  352. package/src/renderers/electrobun/view/app-services.ts +348 -0
  353. package/src/renderers/electrobun/view/application-menu-bridge.ts +12 -0
  354. package/src/renderers/electrobun/view/backend-rpc.ts +173 -0
  355. package/src/renderers/electrobun/view/broker-remote-client.ts +154 -0
  356. package/src/renderers/electrobun/view/config-host.ts +35 -0
  357. package/src/renderers/electrobun/view/context-menu.test.ts +140 -0
  358. package/src/renderers/electrobun/view/context-menu.ts +164 -0
  359. package/src/renderers/electrobun/view/data-table.tsx +773 -0
  360. package/src/renderers/electrobun/view/desktop-controls.tsx +651 -0
  361. package/src/renderers/electrobun/view/desktop-window-bridge.ts +59 -0
  362. package/src/renderers/electrobun/view/desktop-window-snapshot.test.ts +57 -0
  363. package/src/renderers/electrobun/view/desktop-window-snapshot.ts +41 -0
  364. package/src/renderers/electrobun/view/dialog-host.tsx +62 -0
  365. package/src/renderers/electrobun/view/http-fetch.ts +113 -0
  366. package/src/renderers/electrobun/view/input-host.tsx +112 -0
  367. package/src/renderers/electrobun/view/key-event.test.ts +34 -0
  368. package/src/renderers/electrobun/view/key-event.ts +134 -0
  369. package/src/renderers/electrobun/view/main.tsx +104 -0
  370. package/src/renderers/electrobun/view/native-renderer.ts +110 -0
  371. package/src/renderers/electrobun/view/native-stubs/chart-kitty-support.ts +9 -0
  372. package/src/renderers/electrobun/view/native-stubs/chart-surface-manager.ts +25 -0
  373. package/src/renderers/electrobun/view/native-stubs/chart-surface-sync.ts +1 -0
  374. package/src/renderers/electrobun/view/notes-files.ts +56 -0
  375. package/src/renderers/electrobun/view/resource-store.test.ts +44 -0
  376. package/src/renderers/electrobun/view/resource-store.ts +106 -0
  377. package/src/renderers/electrobun/view/rpc-codec.ts +42 -0
  378. package/src/renderers/electrobun/view/scrollbar-activity.ts +23 -0
  379. package/src/renderers/electrobun/view/toast-host.tsx +65 -0
  380. package/src/renderers/electrobun/view/ui-host.tsx +1509 -0
  381. package/src/renderers/electrobun/view/update-host.ts +38 -0
  382. package/src/renderers/opentui/dialog-host.test.tsx +44 -0
  383. package/src/renderers/opentui/dialog-host.tsx +56 -0
  384. package/src/renderers/opentui/host.tsx +140 -0
  385. package/src/renderers/opentui/image-loader.test.ts +25 -0
  386. package/src/renderers/opentui/image-loader.ts +105 -0
  387. package/src/renderers/opentui/image-surface.tsx +266 -0
  388. package/src/renderers/opentui/input-host.tsx +63 -0
  389. package/src/renderers/opentui/start.tsx +71 -0
  390. package/src/renderers/opentui/test-utils.tsx +76 -0
  391. package/src/renderers/opentui/toast-host.tsx +10 -0
  392. package/src/renderers/opentui/ui-host.tsx +98 -0
  393. package/src/sources/gloomberb-cloud.test.ts +288 -17
  394. package/src/sources/gloomberb-cloud.ts +176 -9
  395. package/src/sources/provider-errors.ts +1 -0
  396. package/src/sources/provider-router.test.ts +220 -81
  397. package/src/sources/provider-router.ts +294 -111
  398. package/src/sources/sec-edgar.ts +19 -6
  399. package/src/sources/yahoo-finance.test.ts +0 -5
  400. package/src/sources/yahoo-finance.ts +420 -11
  401. package/src/state/app-activity.ts +7 -7
  402. package/src/state/app-bootstrap.ts +114 -40
  403. package/src/state/app-context-selector.test.tsx +70 -1
  404. package/src/state/app-context.test.ts +71 -3
  405. package/src/state/app-context.test.tsx +14 -7
  406. package/src/state/app-context.tsx +209 -869
  407. package/src/state/config-save-scheduler.ts +35 -0
  408. package/src/state/pane-scroll-registry.tsx +254 -0
  409. package/src/state/persist-scheduler.test.ts +67 -0
  410. package/src/state/persist-scheduler.ts +69 -0
  411. package/src/state/selectors-ui.ts +55 -0
  412. package/src/state/selectors.ts +15 -71
  413. package/src/state/session-persistence.test.ts +1 -1
  414. package/src/state/ticker-refresh-queue.ts +52 -3
  415. package/src/state/use-inline-tickers.ts +18 -16
  416. package/src/state/use-quote-streaming.test.ts +0 -6
  417. package/src/state/use-quote-streaming.test.tsx +1 -1
  418. package/src/state/use-quote-streaming.ts +3 -2
  419. package/src/state/use-session-snapshot.ts +32 -17
  420. package/src/test-support/plugin-runtime.ts +149 -0
  421. package/src/theme/color-utils.ts +1 -0
  422. package/src/theme/colors.ts +16 -0
  423. package/src/types/broker.ts +16 -1
  424. package/src/types/capability-route-source.ts +23 -0
  425. package/src/types/config.test.ts +23 -0
  426. package/src/types/config.ts +145 -10
  427. package/src/types/context-menu.ts +95 -0
  428. package/src/types/data-provider.ts +34 -3
  429. package/src/types/desktop-menu.ts +13 -0
  430. package/src/types/desktop-window.ts +29 -0
  431. package/src/types/financials.ts +123 -2
  432. package/src/types/instrument.ts +1 -0
  433. package/src/types/news-source.ts +9 -0
  434. package/src/types/persistence.ts +1 -0
  435. package/src/types/plugin.ts +32 -8
  436. package/src/ui/ascii-font.ts +85 -0
  437. package/src/ui/context-menu.test.ts +94 -0
  438. package/src/ui/context-menu.tsx +433 -0
  439. package/src/ui/dialog.tsx +55 -0
  440. package/src/ui/host.tsx +364 -0
  441. package/src/ui/index.tsx +134 -0
  442. package/src/ui/toast.tsx +40 -0
  443. package/src/updater.test.ts +69 -0
  444. package/src/updater.ts +61 -18
  445. package/src/utils/api-client.test.ts +99 -9
  446. package/src/utils/api-client.ts +378 -18
  447. package/src/utils/back-navigation.ts +6 -0
  448. package/src/utils/cli-output.ts +1 -0
  449. package/src/utils/currency-units.test.ts +1 -6
  450. package/src/utils/currency-units.ts +1 -0
  451. package/src/utils/debug-log.ts +68 -0
  452. package/src/utils/dom-screenshot.ts +326 -0
  453. package/src/utils/exchange-rate-map.ts +1 -0
  454. package/src/utils/exchanges.ts +214 -0
  455. package/src/utils/format.test.ts +7 -0
  456. package/src/utils/format.ts +1 -0
  457. package/src/utils/fuzzy-search.ts +1 -0
  458. package/src/utils/html-entities.test.ts +8 -0
  459. package/src/utils/html-entities.ts +32 -0
  460. package/src/utils/http-transport.ts +11 -0
  461. package/src/utils/main-thread-monitor.ts +127 -0
  462. package/src/utils/perf-marks.ts +54 -0
  463. package/src/utils/quote-freshness.ts +27 -50
  464. package/src/utils/quote-resolution.test.ts +5 -5
  465. package/src/utils/quote-resolution.ts +0 -1
  466. package/src/utils/quote-time.ts +1 -5
  467. package/src/utils/sec.test.ts +0 -12
  468. package/src/utils/selection-clipboard.ts +14 -6
  469. package/src/utils/shortcut-labels.test.ts +20 -0
  470. package/src/utils/shortcut-labels.ts +67 -0
  471. package/src/utils/slugify.ts +1 -0
  472. package/src/utils/terminal-input-reset.ts +1 -0
  473. package/src/utils/text-wrap.ts +82 -0
  474. package/src/utils/throttled-fetch.test.ts +19 -0
  475. package/src/utils/throttled-fetch.ts +11 -9
  476. package/src/utils/ticker-list.test.ts +1 -7
  477. package/src/utils/ticker-list.ts +1 -0
  478. package/src/utils/timestamp.ts +29 -0
  479. package/src/utils/tweet-text.test.ts +13 -0
  480. package/src/utils/tweet-text.ts +18 -0
  481. package/src/utils/url.ts +11 -0
  482. package/src/version.ts +1 -1
  483. package/src/components/chart/chart-control-hint.tsx +0 -39
  484. package/src/components/detail-feed-view.test.tsx +0 -180
  485. package/src/components/detail-feed-view.tsx +0 -531
  486. package/src/components/layout/pixel-mouse-mode.test.ts +0 -122
  487. package/src/components/layout/pixel-mouse.test.ts +0 -40
  488. package/src/components/spinner.tsx +0 -2
  489. package/src/components/tab-bar.tsx +0 -17
  490. package/src/components/ui/multi-select-chips.tsx +0 -69
  491. package/src/plugins/builtin/ai/index.test.tsx +0 -139
  492. package/src/plugins/builtin/ask-ai.tsx +0 -9
  493. package/src/plugins/builtin/correlation/plugin.test.ts +0 -44
  494. package/src/plugins/builtin/correlation/settings.test.ts +0 -34
  495. package/src/plugins/builtin/econ/calendar-source.test.ts +0 -219
  496. package/src/plugins/builtin/econ/fred-client.ts +0 -79
  497. package/src/plugins/builtin/fred-settings.ts +0 -10
  498. package/src/plugins/builtin/options-availability.test.tsx +0 -179
  499. package/src/plugins/builtin/options-availability.ts +0 -158
  500. package/src/plugins/builtin/portfolio-list/metrics.test.ts +0 -61
  501. package/src/plugins/builtin/yield-curve/treasury-data.test.ts +0 -212
  502. package/src/plugins/prediction-markets/status.ts +0 -24
  503. package/src/plugins/prediction-markets/table.tsx +0 -87
  504. package/src/utils/quote-time.test.ts +0 -22
  505. package/src/utils/selection-clipboard.test.ts +0 -43
  506. package/src/utils/terminal-input-reset.test.ts +0 -50
@@ -1,11 +1,12 @@
1
- export interface ChartCursorPosition {
1
+
2
+ interface ChartCursorPosition {
2
3
  x: number | null;
3
4
  y: number | null;
4
5
  }
5
6
 
6
7
  export type ChartCursorMotionKind = "pixel" | "cell" | "discrete";
7
8
 
8
- export const CELL_CURSOR_EASING = 0.72;
9
+ const CELL_CURSOR_EASING = 0.72;
9
10
  export const CELL_CURSOR_SNAP_DISTANCE = 0.035;
10
11
 
11
12
  function sameNullableNumber(left: number | null, right: number | null, epsilon = 0): boolean {
@@ -28,20 +28,6 @@ describe("computeBollingerBands", () => {
28
28
  expect(result.middle[0].index).toBe(4);
29
29
  });
30
30
 
31
- it("returns empty for insufficient data", () => {
32
- const result = computeBollingerBands([1, 2, 3], 5, 2);
33
- expect(result.upper.length).toBe(0);
34
- expect(result.middle.length).toBe(0);
35
- expect(result.lower.length).toBe(0);
36
- });
37
-
38
- it("returns empty for empty input", () => {
39
- const result = computeBollingerBands([], 20, 2);
40
- expect(result.upper.length).toBe(0);
41
- expect(result.middle.length).toBe(0);
42
- expect(result.lower.length).toBe(0);
43
- });
44
-
45
31
  it("middle values match SMA", () => {
46
32
  // For period 5: first SMA = (10+12+11+13+15)/5 = 61/5 = 12.2
47
33
  const result = computeBollingerBands(closes, 5, 2);
@@ -15,14 +15,6 @@ describe("computeSMA", () => {
15
15
  expect(result.length).toBe(6); // 10 - 5 + 1
16
16
  });
17
17
 
18
- it("returns [] when period > data length", () => {
19
- expect(computeSMA([1, 2, 3], 5)).toEqual([]);
20
- });
21
-
22
- it("returns [] for empty input", () => {
23
- expect(computeSMA([], 5)).toEqual([]);
24
- });
25
-
26
18
  it("handles period equal to data length", () => {
27
19
  const result = computeSMA(closes, 10);
28
20
  expect(result.length).toBe(1);
@@ -44,11 +36,4 @@ describe("computeEMA", () => {
44
36
  expect(result[1].value).toBeCloseTo(12);
45
37
  });
46
38
 
47
- it("returns [] when period > data length", () => {
48
- expect(computeEMA([1, 2], 5)).toEqual([]);
49
- });
50
-
51
- it("returns [] for empty input", () => {
52
- expect(computeEMA([], 3)).toEqual([]);
53
- });
54
39
  });
@@ -12,7 +12,7 @@ export type ChartIndicatorId =
12
12
  | "ema20"
13
13
  | "bollinger20";
14
14
 
15
- export interface ChartIndicatorOption {
15
+ interface ChartIndicatorOption {
16
16
  id: ChartIndicatorId;
17
17
  label: string;
18
18
  compactLabel: string;
@@ -59,9 +59,9 @@ export const CHART_INDICATOR_OPTIONS: ChartIndicatorOption[] = [
59
59
  ];
60
60
 
61
61
  const CHART_INDICATOR_IDS = new Set<string>(CHART_INDICATOR_OPTIONS.map((option) => option.id));
62
- export const DEFAULT_CHART_INDICATOR_SELECTION: ChartIndicatorId[] = ["volume"];
62
+ const DEFAULT_CHART_INDICATOR_SELECTION: ChartIndicatorId[] = ["volume"];
63
63
 
64
- export function isChartIndicatorId(value: unknown): value is ChartIndicatorId {
64
+ function isChartIndicatorId(value: unknown): value is ChartIndicatorId {
65
65
  return typeof value === "string" && CHART_INDICATOR_IDS.has(value);
66
66
  }
67
67
 
@@ -26,14 +26,6 @@ describe("computeRSI", () => {
26
26
  }
27
27
  });
28
28
 
29
- it("returns [] for insufficient data", () => {
30
- expect(computeRSI([1, 2, 3], 14)).toEqual([]);
31
- expect(computeRSI([], 14)).toEqual([]);
32
- // Exactly period data points (need period+1)
33
- const closes = Array.from({ length: 14 }, (_, i) => i + 1);
34
- expect(computeRSI(closes, 14)).toEqual([]);
35
- });
36
-
37
29
  it("first result starts at index equal to period", () => {
38
30
  const closes = [44, 46, 44, 45, 47, 43, 44, 46, 48, 47, 45, 46, 47, 48, 50];
39
31
  const result = computeRSI(closes, 14);
@@ -63,17 +55,4 @@ describe("computeMACD", () => {
63
55
  }
64
56
  });
65
57
 
66
- it("returns empty result for insufficient data", () => {
67
- const result = computeMACD([1, 2, 3], 12, 26, 9);
68
- expect(result.macd.length).toBe(0);
69
- expect(result.signal.length).toBe(0);
70
- expect(result.histogram.length).toBe(0);
71
- });
72
-
73
- it("returns empty result for empty input", () => {
74
- const result = computeMACD([], 12, 26, 9);
75
- expect(result.macd.length).toBe(0);
76
- expect(result.signal.length).toBe(0);
77
- expect(result.histogram.length).toBe(0);
78
- });
79
58
  });
@@ -1,3 +1,4 @@
1
+
1
2
  /** A single data point for an overlay line drawn on the main chart */
2
3
  export interface OverlayPoint {
3
4
  index: number;
@@ -32,5 +33,3 @@ export interface IndicatorConfig {
32
33
  macd?: { fast: number; slow: number; signal: number } | null;
33
34
  bollinger?: { period: number; stdDev: number } | null;
34
35
  }
35
-
36
- export const DEFAULT_INDICATOR_CONFIG: IndicatorConfig = {};
@@ -1,4 +1,5 @@
1
- import type { PixelResolution } from "@opentui/core";
1
+ import { type PixelResolution } from "../../../ui";
2
+ import { measurePerf } from "../../../utils/perf-marks";
2
3
  import { computeGridLines, type ChartScene } from "../chart-renderer";
3
4
  import type { ComparisonChartScene } from "../comparison-chart-renderer";
4
5
  import type { ChartRenderMode } from "../chart-types";
@@ -98,16 +99,6 @@ function blendPixel(
98
99
  data[index + 3] = Math.round(outAlpha * 255);
99
100
  }
100
101
 
101
- function fillBackground(data: Uint8Array, width: number, height: number, color: RgbaColor) {
102
- for (let index = 0; index < width * height; index += 1) {
103
- const offset = index * 4;
104
- data[offset] = color.r;
105
- data[offset + 1] = color.g;
106
- data[offset + 2] = color.b;
107
- data[offset + 3] = color.a;
108
- }
109
- }
110
-
111
102
  function drawLine(
112
103
  data: Uint8Array,
113
104
  width: number,
@@ -475,32 +466,33 @@ function getOverlayPlotBottom(overlay: NativeCrosshairOverlay, pixelHeight: numb
475
466
  }
476
467
 
477
468
  export function renderNativeChartBase(scene: ChartScene, pixelWidth: number, pixelHeight: number): NativeChartBitmap {
478
- const pixels = new Uint8Array(pixelWidth * pixelHeight * 4);
479
- if (scene.points.length === 0 || pixelWidth <= 0 || pixelHeight <= 0) {
480
- return { width: Math.max(pixelWidth, 1), height: Math.max(pixelHeight, 1), pixels };
481
- }
469
+ return measurePerf("chart.native.base", () => {
470
+ const pixels = new Uint8Array(pixelWidth * pixelHeight * 4);
471
+ if (scene.points.length === 0 || pixelWidth <= 0 || pixelHeight <= 0) {
472
+ return { width: Math.max(pixelWidth, 1), height: Math.max(pixelHeight, 1), pixels };
473
+ }
482
474
 
483
- fillBackground(pixels, pixelWidth, pixelHeight, parseHex(scene.colors.bgColor, 1));
484
- const layout = getChartPixelLayout(scene, pixelWidth, pixelHeight);
485
- drawPriceGrid(pixels, pixelWidth, pixelHeight, scene, layout.plotTop, layout.plotBottom);
486
-
487
- switch (scene.mode) {
488
- case "area":
489
- case "line":
490
- drawLineSeries(pixels, pixelWidth, pixelHeight, scene, layout.plotTop, layout.plotBottom);
491
- break;
492
- case "candles":
493
- drawCandles(pixels, pixelWidth, pixelHeight, scene, layout.plotTop, layout.plotBottom, "candles");
494
- break;
495
- case "ohlc":
496
- drawCandles(pixels, pixelWidth, pixelHeight, scene, layout.plotTop, layout.plotBottom, "ohlc");
497
- break;
498
- }
475
+ const layout = getChartPixelLayout(scene, pixelWidth, pixelHeight);
476
+ drawPriceGrid(pixels, pixelWidth, pixelHeight, scene, layout.plotTop, layout.plotBottom);
477
+
478
+ switch (scene.mode) {
479
+ case "area":
480
+ case "line":
481
+ drawLineSeries(pixels, pixelWidth, pixelHeight, scene, layout.plotTop, layout.plotBottom);
482
+ break;
483
+ case "candles":
484
+ drawCandles(pixels, pixelWidth, pixelHeight, scene, layout.plotTop, layout.plotBottom, "candles");
485
+ break;
486
+ case "ohlc":
487
+ drawCandles(pixels, pixelWidth, pixelHeight, scene, layout.plotTop, layout.plotBottom, "ohlc");
488
+ break;
489
+ }
499
490
 
500
- drawIndicatorOverlays(pixels, pixelWidth, pixelHeight, scene, layout.plotTop, layout.plotBottom);
501
- drawVolume(pixels, pixelWidth, pixelHeight, scene, layout.volumeTop, layout.volumeBottom);
491
+ drawIndicatorOverlays(pixels, pixelWidth, pixelHeight, scene, layout.plotTop, layout.plotBottom);
492
+ drawVolume(pixels, pixelWidth, pixelHeight, scene, layout.volumeTop, layout.volumeBottom);
502
493
 
503
- return { width: pixelWidth, height: pixelHeight, pixels };
494
+ return { width: pixelWidth, height: pixelHeight, pixels };
495
+ }, { pixelWidth, pixelHeight, points: scene.points.length, mode: scene.mode });
504
496
  }
505
497
 
506
498
  interface ComparisonPathSample {
@@ -584,7 +576,6 @@ export function renderNativeComparisonChartBase(
584
576
  return { width: Math.max(pixelWidth, 1), height: Math.max(pixelHeight, 1), pixels };
585
577
  }
586
578
 
587
- fillBackground(pixels, pixelWidth, pixelHeight, parseHex(scene.colors.bgColor, 1));
588
579
  const layout = getComparisonPixelLayout(pixelWidth, pixelHeight);
589
580
  drawPriceGrid(
590
581
  pixels,
@@ -642,14 +633,16 @@ export function renderNativeCrosshairOverlay(
642
633
  pixelWidth: number,
643
634
  pixelHeight: number,
644
635
  ): NativeChartBitmap {
645
- const pixels = new Uint8Array(Math.max(pixelWidth, 1) * Math.max(pixelHeight, 1) * 4);
646
- if (pixelWidth <= 0 || pixelHeight <= 0) {
647
- return { width: Math.max(pixelWidth, 1), height: Math.max(pixelHeight, 1), pixels };
648
- }
636
+ return measurePerf("chart.native.crosshair", () => {
637
+ const pixels = new Uint8Array(Math.max(pixelWidth, 1) * Math.max(pixelHeight, 1) * 4);
638
+ if (pixelWidth <= 0 || pixelHeight <= 0) {
639
+ return { width: Math.max(pixelWidth, 1), height: Math.max(pixelHeight, 1), pixels };
640
+ }
649
641
 
650
- const plotBottom = getOverlayPlotBottom(overlay, pixelHeight);
651
- drawCrosshairOverlay(pixels, pixelWidth, pixelHeight, overlay, 0, plotBottom);
652
- return { width: pixelWidth, height: pixelHeight, pixels };
642
+ const plotBottom = getOverlayPlotBottom(overlay, pixelHeight);
643
+ drawCrosshairOverlay(pixels, pixelWidth, pixelHeight, overlay, 0, plotBottom);
644
+ return { width: pixelWidth, height: pixelHeight, pixels };
645
+ }, { pixelWidth, pixelHeight });
653
646
  }
654
647
 
655
648
  export function intersectCellRects(a: CellRect, b: CellRect): CellRect | null {
@@ -666,7 +659,7 @@ export function intersectCellRects(a: CellRect, b: CellRect): CellRect | null {
666
659
  };
667
660
  }
668
661
 
669
- export function subtractCellRect(rect: CellRect, cut: CellRect): CellRect[] {
662
+ function subtractCellRect(rect: CellRect, cut: CellRect): CellRect[] {
670
663
  const intersection = intersectCellRects(rect, cut);
671
664
  if (!intersection) return [rect];
672
665
 
@@ -715,7 +708,7 @@ export function subtractCellRect(rect: CellRect, cut: CellRect): CellRect[] {
715
708
  return fragments.filter((fragment) => fragment.width > 0 && fragment.height > 0);
716
709
  }
717
710
 
718
- export function subtractCellRects(rects: CellRect[], cut: CellRect): CellRect[] {
711
+ function subtractCellRects(rects: CellRect[], cut: CellRect): CellRect[] {
719
712
  return rects.flatMap((rect) => subtractCellRect(rect, cut));
720
713
  }
721
714
 
@@ -1,12 +1,9 @@
1
- import type { CliRenderer } from "@opentui/core";
1
+ import { type NativeRendererHost as CliRenderer } from "../../../ui";
2
2
 
3
3
  export function writeRendererRaw(renderer: CliRenderer, data: string | Uint8Array): boolean {
4
4
  if (renderer.isDestroyed) {
5
5
  return false;
6
6
  }
7
7
 
8
- const writer = (renderer as unknown as { writeOut?: (payload: string | Uint8Array) => void }).writeOut;
9
- if (typeof writer !== "function") return false;
10
- writer.call(renderer, data);
11
- return true;
8
+ return renderer.write?.(data) ?? false;
12
9
  }
@@ -1,4 +1,4 @@
1
- import type { CliRenderer } from "@opentui/core";
1
+ import { type NativeRendererHost as CliRenderer } from "../../../ui";
2
2
  import type { NativeChartBitmap, NativePlacement } from "./chart-rasterizer";
3
3
  import { writeRendererRaw } from "./kitty-adapter";
4
4
  import { encodeKittyDeleteImage, encodeKittyPlacement, encodeKittyTransmitRgba } from "./kitty-protocol";
@@ -107,4 +107,3 @@ export function encodeKittyDeleteImage(imageId: number, placementId?: number): s
107
107
  ["q", 2],
108
108
  ]));
109
109
  }
110
-
@@ -1,4 +1,4 @@
1
- import type { CliRenderer } from "@opentui/core";
1
+ import { type NativeRendererHost as CliRenderer } from "../../../ui";
2
2
  import { writeRendererRaw } from "./kitty-adapter";
3
3
  import { buildKittyGraphicsQuery } from "./kitty-protocol";
4
4
 
@@ -17,6 +17,7 @@ import { chunkBase64Payload, encodeKittyTransmitRgba } from "./kitty-protocol";
17
17
  import { resolveChartRendererState } from "./renderer-selection";
18
18
  import { computeSurfaceVisibleFragments, NativeSurfaceManager } from "./surface-manager";
19
19
  import { syncCachedNativeSurface } from "./surface-sync";
20
+ import { resolveNativeSurfaceVisibleRect, type NativeSurfaceRenderableNode } from "./surface-visibility";
20
21
 
21
22
  describe("resolveChartRendererState", () => {
22
23
  test("resolves auto and forced kitty correctly", () => {
@@ -98,8 +99,30 @@ describe("computeNativePlacement", () => {
98
99
  });
99
100
  });
100
101
 
102
+ describe("resolveNativeSurfaceVisibleRect", () => {
103
+ test("treats hidden ancestors as invisible for native graphics", () => {
104
+ const hiddenParent: NativeSurfaceRenderableNode = {
105
+ x: 0,
106
+ y: 0,
107
+ width: 80,
108
+ height: 20,
109
+ visible: false,
110
+ parent: null,
111
+ };
112
+ const child: NativeSurfaceRenderableNode = {
113
+ x: 2,
114
+ y: 3,
115
+ width: 20,
116
+ height: 8,
117
+ parent: hiddenParent,
118
+ };
119
+
120
+ expect(resolveNativeSurfaceVisibleRect(child, 100, 30)).toBeNull();
121
+ });
122
+ });
123
+
101
124
  describe("renderNativeChartBase", () => {
102
- test("fills the bitmap background opaquely", () => {
125
+ test("leaves untouched bitmap pixels transparent", () => {
103
126
  const palette = resolveChartPalette({
104
127
  bg: "#112233",
105
128
  border: "#333333",
@@ -126,9 +149,17 @@ describe("renderNativeChartBase", () => {
126
149
  expect(scene).not.toBeNull();
127
150
 
128
151
  const bitmap = renderNativeChartBase(scene!, 24, 12);
152
+ let transparentPixels = 0;
153
+ let drawnPixels = 0;
129
154
  for (let offset = 3; offset < bitmap.pixels.length; offset += 4) {
130
- expect(bitmap.pixels[offset]).toBe(0xff);
155
+ if (bitmap.pixels[offset] === 0) {
156
+ transparentPixels += 1;
157
+ } else {
158
+ drawnPixels += 1;
159
+ }
131
160
  }
161
+ expect(transparentPixels).toBeGreaterThan(0);
162
+ expect(drawnPixels).toBeGreaterThan(0);
132
163
  });
133
164
 
134
165
  test("keeps solid candle bodies opaque over the wick", () => {
@@ -277,7 +308,7 @@ describe("renderNativeChartBase", () => {
277
308
  });
278
309
 
279
310
  describe("renderNativeComparisonChartBase", () => {
280
- test("renders multi-series comparison overlays into an opaque bitmap", () => {
311
+ test("renders multi-series comparison overlays over transparent pixels", () => {
281
312
  const projection = projectComparisonChartData([
282
313
  {
283
314
  symbol: "AAPL",
@@ -302,7 +333,6 @@ describe("renderNativeComparisonChartBase", () => {
302
333
  ],
303
334
  },
304
335
  ], 12, {
305
- timeRange: "ALL",
306
336
  panOffset: 0,
307
337
  zoomLevel: 1,
308
338
  renderMode: "line",
@@ -323,17 +353,25 @@ describe("renderNativeComparisonChartBase", () => {
323
353
  expect(scene).not.toBeNull();
324
354
 
325
355
  const bitmap = renderNativeComparisonChartBase(scene!, 120, 60);
356
+ let transparentPixels = 0;
357
+ let drawnPixels = 0;
326
358
  for (let offset = 3; offset < bitmap.pixels.length; offset += 4) {
327
- expect(bitmap.pixels[offset]).toBe(0xff);
359
+ if (bitmap.pixels[offset] === 0) {
360
+ transparentPixels += 1;
361
+ } else {
362
+ drawnPixels += 1;
363
+ }
328
364
  }
365
+ expect(transparentPixels).toBeGreaterThan(0);
366
+ expect(drawnPixels).toBeGreaterThan(0);
329
367
 
330
- const brightPixels = [];
368
+ const visiblePixels = [];
331
369
  for (let offset = 0; offset < bitmap.pixels.length; offset += 4) {
332
- if (bitmap.pixels[offset] !== 17 || bitmap.pixels[offset + 1] !== 34 || bitmap.pixels[offset + 2] !== 51) {
333
- brightPixels.push(offset);
370
+ if (bitmap.pixels[offset + 3] !== 0) {
371
+ visiblePixels.push(offset);
334
372
  }
335
373
  }
336
- expect(brightPixels.length).toBeGreaterThan(0);
374
+ expect(visiblePixels.length).toBeGreaterThan(0);
337
375
  });
338
376
  });
339
377
 
@@ -457,8 +495,9 @@ describe("KittyImageManager", () => {
457
495
  test("reuses placement ids across multiple fragments and deletes the previous image on swap", () => {
458
496
  const writes: string[] = [];
459
497
  const renderer = {
460
- writeOut(payload: string | Uint8Array) {
498
+ write(payload: string | Uint8Array) {
461
499
  writes.push(typeof payload === "string" ? payload : Buffer.from(payload).toString("utf8"));
500
+ return true;
462
501
  },
463
502
  } as unknown as CliRenderer;
464
503
 
@@ -586,8 +625,9 @@ describe("NativeSurfaceManager", () => {
586
625
  resolution: { width: 1200, height: 800 },
587
626
  terminalWidth: 120,
588
627
  terminalHeight: 40,
589
- writeOut(payload: string | Uint8Array) {
628
+ write(payload: string | Uint8Array) {
590
629
  writes.push(typeof payload === "string" ? payload : Buffer.from(payload).toString("utf8"));
630
+ return true;
591
631
  },
592
632
  } as unknown as CliRenderer;
593
633
 
@@ -617,7 +657,7 @@ describe("NativeSurfaceManager", () => {
617
657
  resolution: { width: 1200, height: 800 },
618
658
  terminalWidth: 120,
619
659
  terminalHeight: 40,
620
- writeOut() {},
660
+ write() { return true; },
621
661
  } as unknown as CliRenderer;
622
662
  const manager = new NativeSurfaceManager(renderer);
623
663
  const originalRender = KittyImageManager.prototype.render;
@@ -657,7 +697,7 @@ describe("NativeSurfaceManager", () => {
657
697
  resolution: { width: 1200, height: 800 },
658
698
  terminalWidth: 120,
659
699
  terminalHeight: 40,
660
- writeOut() {},
700
+ write() { return true; },
661
701
  } as unknown as CliRenderer;
662
702
  const manager = new NativeSurfaceManager(renderer);
663
703
  const originalRender = KittyImageManager.prototype.render;
@@ -1,4 +1,4 @@
1
- import type { PixelResolution } from "@opentui/core";
1
+ import { type PixelResolution } from "../../../ui";
2
2
  import type { ChartRendererPreference, ResolvedChartRenderer } from "../chart-types";
3
3
 
4
4
  export interface ResolvedChartRendererState {
@@ -1,4 +1,4 @@
1
- import type { CliRenderer } from "@opentui/core";
1
+ import { type NativeRendererHost as CliRenderer } from "../../../ui";
2
2
  import {
3
3
  computeNativePlacements,
4
4
  excludeCellRects,
@@ -0,0 +1,48 @@
1
+ import { intersectCellRects, type CellRect } from "./chart-rasterizer";
2
+
3
+ export interface NativeSurfaceRenderableNode {
4
+ x: number;
5
+ y: number;
6
+ width: number;
7
+ height: number;
8
+ visible?: boolean;
9
+ parent: NativeSurfaceRenderableNode | null;
10
+ }
11
+
12
+ export function getRenderableCellRect(
13
+ renderable: Pick<NativeSurfaceRenderableNode, "x" | "y" | "width" | "height">,
14
+ ): CellRect {
15
+ return {
16
+ x: renderable.x,
17
+ y: renderable.y,
18
+ width: renderable.width,
19
+ height: renderable.height,
20
+ };
21
+ }
22
+
23
+ export function resolveNativeSurfaceVisibleRect(
24
+ renderable: NativeSurfaceRenderableNode | null,
25
+ terminalWidth: number,
26
+ terminalHeight: number,
27
+ ): CellRect | null {
28
+ if (!renderable) return null;
29
+
30
+ let visible: CellRect = {
31
+ x: 0,
32
+ y: 0,
33
+ width: terminalWidth,
34
+ height: terminalHeight,
35
+ };
36
+ let current: NativeSurfaceRenderableNode | null = renderable;
37
+
38
+ while (current) {
39
+ if (current.visible === false) return null;
40
+
41
+ const nextVisible = intersectCellRects(visible, getRenderableCellRect(current));
42
+ if (!nextVisible) return null;
43
+ visible = nextVisible;
44
+ current = current.parent;
45
+ }
46
+
47
+ return visible;
48
+ }
@@ -0,0 +1,51 @@
1
+ import { afterEach, describe, expect, test } from "bun:test";
2
+ import { act } from "react";
3
+ import { testRender } from "../../renderers/opentui/test-utils";
4
+ import { colors } from "../../theme/colors";
5
+ import { resolveChartPalette } from "./chart-renderer";
6
+ import { StaticChartSurface } from "./static-chart-surface";
7
+ import type { ProjectedChartPoint } from "./chart-data";
8
+
9
+ let testSetup: Awaited<ReturnType<typeof testRender>> | undefined;
10
+
11
+ afterEach(async () => {
12
+ if (!testSetup) return;
13
+ await act(async () => {
14
+ testSetup!.renderer.destroy();
15
+ });
16
+ testSetup = undefined;
17
+ });
18
+
19
+ const points: ProjectedChartPoint[] = [
20
+ { date: new Date("2026-01-01"), open: 3.5, high: 3.5, low: 3.5, close: 3.5, volume: 0 },
21
+ { date: new Date("2026-01-02"), open: 4.1, high: 4.1, low: 4.1, close: 4.1, volume: 0 },
22
+ { date: new Date("2026-01-03"), open: 4.9, high: 4.9, low: 4.9, close: 4.9, volume: 0 },
23
+ ];
24
+
25
+ describe("StaticChartSurface", () => {
26
+ test("renders unit and custom y-axis labels", async () => {
27
+ testSetup = await testRender(
28
+ <StaticChartSurface
29
+ points={points}
30
+ width={48}
31
+ height={10}
32
+ mode="line"
33
+ colors={resolveChartPalette(colors, "positive")}
34
+ yAxisLabel="Yield (%)"
35
+ yAxisColor={colors.textDim}
36
+ formatYAxisValue={(value) => `${value.toFixed(2)}%`}
37
+ />,
38
+ { width: 50, height: 12 },
39
+ );
40
+
41
+ await act(async () => {
42
+ await testSetup!.renderOnce();
43
+ await testSetup!.renderOnce();
44
+ });
45
+
46
+ const frame = testSetup.captureCharFrame();
47
+ expect(frame).toContain("Yield (%)");
48
+ expect(frame).toContain("4.90%");
49
+ expect(frame).toContain("3.50%");
50
+ });
51
+ });