gloomberb 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (500) hide show
  1. package/README.md +91 -11
  2. package/package.json +12 -5
  3. package/src/app.tsx +414 -191
  4. package/src/architecture/import-boundaries.test.ts +118 -0
  5. package/src/assets/gloomberb-logo.png +0 -0
  6. package/src/assets/gloomberb-logo.svg +73 -0
  7. package/src/brokers/account-cache.ts +157 -0
  8. package/src/brokers/profile-form.test.ts +73 -0
  9. package/src/brokers/profile-form.ts +109 -0
  10. package/src/brokers/remote-broker-adapter.ts +91 -0
  11. package/src/brokers/sync-broker-instance.test.ts +124 -1
  12. package/src/brokers/sync-broker-instance.ts +113 -17
  13. package/src/capabilities/factories.ts +100 -0
  14. package/src/capabilities/index.ts +4 -0
  15. package/src/capabilities/registry.test.ts +137 -0
  16. package/src/capabilities/registry.ts +131 -0
  17. package/src/capabilities/service-capabilities.ts +27 -0
  18. package/src/capabilities/types.ts +84 -0
  19. package/src/cli/commands/ticker.ts +11 -3
  20. package/src/cli/context.ts +19 -13
  21. package/src/cli/helpers.ts +1 -0
  22. package/src/cli/types.ts +2 -2
  23. package/src/components/chart/chart-controller.ts +4 -24
  24. package/src/components/chart/chart-data.ts +3 -14
  25. package/src/components/chart/chart-indicator-selector.tsx +28 -6
  26. package/src/components/chart/chart-pane-settings.ts +15 -28
  27. package/src/components/chart/chart-renderer.ts +74 -99
  28. package/src/components/chart/chart-resolution.ts +10 -10
  29. package/src/components/chart/chart-scroll.test.ts +6 -5
  30. package/src/components/chart/chart-scroll.ts +2 -2
  31. package/src/components/chart/chart-types.ts +0 -1
  32. package/src/components/chart/chart-viewport.ts +2 -1
  33. package/src/components/chart/chart.test.ts +37 -0
  34. package/src/components/chart/comparison-chart-data.ts +3 -17
  35. package/src/components/chart/comparison-chart-renderer.test.ts +27 -4
  36. package/src/components/chart/comparison-chart-renderer.ts +1 -1
  37. package/src/components/chart/comparison-stock-chart.tsx +304 -108
  38. package/src/components/chart/cursor-motion.ts +3 -2
  39. package/src/components/chart/indicators/bands.test.ts +0 -14
  40. package/src/components/chart/indicators/moving-averages.test.ts +0 -15
  41. package/src/components/chart/indicators/options.ts +3 -3
  42. package/src/components/chart/indicators/oscillators.test.ts +0 -21
  43. package/src/components/chart/indicators/types.ts +1 -2
  44. package/src/components/chart/native/chart-rasterizer.ts +37 -44
  45. package/src/components/chart/native/kitty-adapter.ts +2 -5
  46. package/src/components/chart/native/kitty-manager.ts +1 -1
  47. package/src/components/chart/native/kitty-protocol.ts +0 -1
  48. package/src/components/chart/native/kitty-support.ts +1 -1
  49. package/src/components/chart/native/kitty.test.ts +30 -13
  50. package/src/components/chart/native/renderer-selection.ts +1 -1
  51. package/src/components/chart/native/surface-manager.ts +1 -1
  52. package/src/components/chart/static-chart-surface.test.tsx +51 -0
  53. package/src/components/chart/static-chart-surface.tsx +191 -0
  54. package/src/components/chart/stock-chart-renderer-switch.test.tsx +242 -35
  55. package/src/components/chart/stock-chart.tsx +438 -141
  56. package/src/components/command-bar/__snapshots__/command-bar.test.tsx.snap +4 -4
  57. package/src/components/command-bar/command-bar.test.tsx +269 -20
  58. package/src/components/command-bar/command-bar.tsx +830 -353
  59. package/src/components/command-bar/command-registry.ts +10 -20
  60. package/src/components/command-bar/helpers.test.ts +1 -1
  61. package/src/components/command-bar/helpers.ts +9 -11
  62. package/src/components/command-bar/pane-template-display.ts +4 -8
  63. package/src/components/command-bar/root-shortcuts.ts +12 -11
  64. package/src/components/command-bar/view-model.test.ts +8 -42
  65. package/src/components/command-bar/view-model.ts +8 -36
  66. package/src/components/command-bar/workflow-ops.test.ts +76 -2
  67. package/src/components/command-bar/workflow-ops.ts +40 -9
  68. package/src/components/command-bar/workflow-types.ts +2 -1
  69. package/src/components/data-table-stack-view.test.tsx +206 -0
  70. package/src/components/data-table-stack-view.tsx +152 -0
  71. package/src/components/data-table-view.test.tsx +188 -0
  72. package/src/components/data-table-view.tsx +279 -0
  73. package/src/components/feed-data-table-stack-view.test.tsx +225 -0
  74. package/src/components/feed-data-table-stack-view.tsx +365 -0
  75. package/src/components/index.ts +26 -31
  76. package/src/components/layout/detached-pane-shell.tsx +185 -0
  77. package/src/components/layout/floating-pane.tsx +87 -13
  78. package/src/components/layout/header.tsx +139 -49
  79. package/src/components/layout/pane-content.tsx +40 -0
  80. package/src/components/layout/pane-footer.test.tsx +195 -0
  81. package/src/components/layout/pane-footer.tsx +410 -0
  82. package/src/components/layout/pane-header.test.tsx +39 -0
  83. package/src/components/layout/pane-header.tsx +192 -14
  84. package/src/components/layout/pane-sizing.ts +13 -4
  85. package/src/components/layout/pane-title.ts +35 -0
  86. package/src/components/layout/pane.tsx +56 -8
  87. package/src/components/layout/shell.test.tsx +322 -19
  88. package/src/components/layout/shell.tsx +1010 -366
  89. package/src/components/layout/status-bar.test.tsx +17 -10
  90. package/src/components/layout/status-bar.tsx +367 -100
  91. package/src/components/layout/titlebar-overlay.ts +2 -0
  92. package/src/components/markdown-editor.tsx +29 -14
  93. package/src/components/markdown-text.tsx +25 -24
  94. package/src/components/onboarding/onboarding-wizard.test.tsx +8 -3
  95. package/src/components/onboarding/onboarding-wizard.tsx +347 -473
  96. package/src/components/pane-settings-dialog.tsx +521 -89
  97. package/src/components/pane-template-wizard.test.tsx +1 -1
  98. package/src/components/pane-template-wizard.tsx +38 -37
  99. package/src/components/price-selector-dialog.tsx +22 -21
  100. package/src/components/ticker-badge-list.tsx +53 -0
  101. package/src/components/ticker-badge-text.test.tsx +44 -1
  102. package/src/components/ticker-badge-text.tsx +78 -38
  103. package/src/components/ticker-badge.tsx +28 -7
  104. package/src/components/ticker-list-table-view.tsx +248 -0
  105. package/src/components/ticker-list-table.test.tsx +47 -1
  106. package/src/components/ticker-list-table.tsx +212 -41
  107. package/src/components/toggle-list.tsx +114 -8
  108. package/src/components/ui/button.tsx +24 -16
  109. package/src/components/ui/choice-dialog.tsx +89 -0
  110. package/src/components/ui/data-table.tsx +313 -114
  111. package/src/components/ui/external-link.tsx +52 -13
  112. package/src/components/ui/fields.tsx +44 -30
  113. package/src/components/ui/frame.tsx +24 -57
  114. package/src/components/ui/index.ts +15 -50
  115. package/src/components/ui/list-view.tsx +65 -25
  116. package/src/components/ui/loading.tsx +5 -60
  117. package/src/components/ui/message-composer.test.tsx +81 -0
  118. package/src/components/ui/message-composer.tsx +139 -0
  119. package/src/components/ui/multi-select-dialog.tsx +100 -32
  120. package/src/components/ui/multi-select.ts +112 -0
  121. package/src/components/ui/native-select.tsx +120 -0
  122. package/src/components/ui/page-stack-view.tsx +49 -19
  123. package/src/components/ui/remote-image.tsx +75 -0
  124. package/src/components/ui/row-value-cache.test.ts +17 -0
  125. package/src/components/ui/row-value-cache.ts +41 -0
  126. package/src/components/ui/shortcut-hint.tsx +55 -0
  127. package/src/components/ui/status.tsx +12 -67
  128. package/src/components/ui/table-layout.ts +59 -0
  129. package/src/components/ui/tabs.tsx +249 -27
  130. package/src/components/ui/toggle.tsx +19 -127
  131. package/src/components/ui/ui.test.tsx +266 -53
  132. package/src/components/use-double-click-activation.ts +7 -2
  133. package/src/components/use-external-link-footer.ts +79 -0
  134. package/src/core/app-services.ts +95 -0
  135. package/src/core/state/app-state.ts +886 -0
  136. package/src/{state → core/state}/session-persistence.ts +7 -6
  137. package/src/data/config-layout.ts +225 -0
  138. package/src/data/config-store-node.ts +390 -0
  139. package/src/data/config-store.test.ts +72 -0
  140. package/src/data/config-store.ts +35 -478
  141. package/src/data/sqlite-json.ts +2 -5
  142. package/src/data/sqlite-retry.ts +1 -0
  143. package/src/index.tsx +2 -68
  144. package/src/market-data/coordinator-subscriptions.test.ts +125 -0
  145. package/src/market-data/coordinator.test.ts +73 -4
  146. package/src/market-data/coordinator.ts +190 -24
  147. package/src/market-data/hooks.test.tsx +1 -1
  148. package/src/market-data/hooks.tsx +109 -87
  149. package/src/market-data/query-store.ts +3 -3
  150. package/src/market-data/request-types.test.ts +0 -14
  151. package/src/market-data/result-types.ts +1 -0
  152. package/src/news/aggregator.test.ts +267 -0
  153. package/src/news/aggregator.ts +452 -0
  154. package/src/news/hooks.ts +39 -0
  155. package/src/news/ticker-symbols.ts +21 -0
  156. package/src/news/types.ts +77 -0
  157. package/src/notifications/app-notifier.ts +21 -10
  158. package/src/pane-settings.test.ts +0 -2
  159. package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +124 -134
  160. package/src/plugins/builtin/ai/gloomberb-cli.ts +14 -13
  161. package/src/plugins/builtin/ai/providers.ts +1 -9
  162. package/src/plugins/builtin/ai/runner.ts +41 -5
  163. package/src/plugins/builtin/ai/screener-pane.test.tsx +37 -55
  164. package/src/plugins/builtin/ai/screener-pane.tsx +231 -276
  165. package/src/plugins/builtin/ai/settings.ts +0 -1
  166. package/src/plugins/builtin/ai/utils.ts +1 -71
  167. package/src/plugins/builtin/alerts/alert-engine.test.ts +1 -38
  168. package/src/plugins/builtin/alerts/index.test.tsx +61 -82
  169. package/src/plugins/builtin/alerts/index.tsx +263 -155
  170. package/src/plugins/builtin/alerts/types.ts +7 -0
  171. package/src/plugins/builtin/analytics/index.test.tsx +13 -8
  172. package/src/plugins/builtin/analytics/index.tsx +78 -120
  173. package/src/plugins/builtin/analytics/metrics.test.ts +0 -4
  174. package/src/plugins/builtin/ask-ai.test.tsx +55 -54
  175. package/src/plugins/builtin/broker-manager/index.test.tsx +168 -0
  176. package/src/plugins/builtin/broker-manager/index.tsx +759 -0
  177. package/src/plugins/builtin/broker-manager/model.test.ts +107 -0
  178. package/src/plugins/builtin/broker-manager/model.ts +142 -0
  179. package/src/plugins/builtin/chat-controller.test.ts +150 -0
  180. package/src/plugins/builtin/chat-controller.ts +88 -16
  181. package/src/plugins/builtin/chat.test.tsx +283 -36
  182. package/src/plugins/builtin/chat.tsx +373 -172
  183. package/src/plugins/builtin/cloud-auth-actions.tsx +72 -0
  184. package/src/plugins/builtin/cloud-tweets.tsx +896 -0
  185. package/src/plugins/builtin/comparison-chart.test.tsx +46 -57
  186. package/src/plugins/builtin/comparison-chart.tsx +11 -11
  187. package/src/plugins/builtin/correlation/compute.test.ts +0 -17
  188. package/src/plugins/builtin/correlation/index.tsx +51 -46
  189. package/src/plugins/builtin/correlation/settings.ts +1 -2
  190. package/src/plugins/builtin/debug.tsx +99 -70
  191. package/src/plugins/builtin/earnings/earnings-cache.test.ts +2 -2
  192. package/src/plugins/builtin/earnings/earnings-cache.ts +12 -3
  193. package/src/plugins/builtin/earnings/earnings-data.test.ts +42 -11
  194. package/src/plugins/builtin/earnings/earnings-data.ts +70 -5
  195. package/src/plugins/builtin/earnings/index.tsx +187 -94
  196. package/src/plugins/builtin/econ/calendar-source.ts +9 -116
  197. package/src/plugins/builtin/econ/fred-client.test.ts +1 -14
  198. package/src/plugins/builtin/econ/fred-series-map.ts +1 -0
  199. package/src/plugins/builtin/econ/index.tsx +236 -421
  200. package/src/plugins/builtin/econ/types.ts +1 -0
  201. package/src/plugins/builtin/fx-matrix/index.tsx +43 -45
  202. package/src/plugins/builtin/fx-matrix/pairs.ts +1 -0
  203. package/src/plugins/builtin/help.test.tsx +120 -60
  204. package/src/plugins/builtin/help.tsx +266 -82
  205. package/src/plugins/builtin/holders/index.test.ts +45 -0
  206. package/src/plugins/builtin/holders/index.tsx +949 -0
  207. package/src/plugins/builtin/insider/index.tsx +207 -253
  208. package/src/plugins/builtin/insider/insider-data.test.ts +1 -11
  209. package/src/plugins/builtin/insider/insider-data.ts +1 -0
  210. package/src/plugins/builtin/layout-manager.tsx +12 -40
  211. package/src/plugins/builtin/market-movers/index.tsx +70 -144
  212. package/src/plugins/builtin/market-movers/screener.test.ts +0 -18
  213. package/src/plugins/builtin/news-wire/breaking-notifications.test.ts +98 -0
  214. package/src/plugins/builtin/news-wire/breaking-notifications.ts +110 -0
  215. package/src/plugins/builtin/news-wire/breaking-pane.tsx +157 -0
  216. package/src/plugins/builtin/news-wire/categories.ts +114 -0
  217. package/src/plugins/builtin/news-wire/default-feeds.ts +3 -0
  218. package/src/plugins/builtin/news-wire/digest-store.ts +44 -0
  219. package/src/plugins/builtin/news-wire/feed-config.test.ts +90 -0
  220. package/src/plugins/builtin/news-wire/feed-config.ts +216 -0
  221. package/src/plugins/builtin/news-wire/feed-pane.tsx +21 -0
  222. package/src/plugins/builtin/news-wire/hash.ts +9 -0
  223. package/src/plugins/builtin/news-wire/index.tsx +92 -0
  224. package/src/plugins/builtin/news-wire/industry-pane.tsx +140 -0
  225. package/src/plugins/builtin/news-wire/news-detail-view.tsx +256 -0
  226. package/src/plugins/builtin/news-wire/news-footer.ts +28 -0
  227. package/src/plugins/builtin/news-wire/news-preset-pane.tsx +94 -0
  228. package/src/plugins/builtin/news-wire/news-query-presets.ts +40 -0
  229. package/src/plugins/builtin/news-wire/news-table.test.tsx +206 -0
  230. package/src/plugins/builtin/news-wire/news-table.tsx +296 -0
  231. package/src/plugins/builtin/news-wire/persisted-articles.ts +92 -0
  232. package/src/plugins/builtin/news-wire/read-state.test.ts +30 -0
  233. package/src/plugins/builtin/news-wire/read-state.ts +76 -0
  234. package/src/plugins/builtin/news-wire/rss-parser.test.ts +184 -0
  235. package/src/plugins/builtin/news-wire/rss-parser.ts +213 -0
  236. package/src/plugins/builtin/news-wire/rss-source.test.ts +159 -0
  237. package/src/plugins/builtin/news-wire/rss-source.ts +181 -0
  238. package/src/plugins/builtin/news-wire/top-pane.tsx +21 -0
  239. package/src/plugins/builtin/news.tsx +98 -25
  240. package/src/plugins/builtin/notes-files.ts +51 -10
  241. package/src/plugins/builtin/notes.tsx +332 -131
  242. package/src/plugins/builtin/options.test.tsx +185 -0
  243. package/src/plugins/builtin/options.tsx +346 -206
  244. package/src/plugins/builtin/plugin-groups.ts +92 -0
  245. package/src/plugins/builtin/portfolio-list/header.tsx +72 -37
  246. package/src/plugins/builtin/portfolio-list/index.tsx +25 -42
  247. package/src/plugins/builtin/portfolio-list/metrics.ts +1 -1
  248. package/src/plugins/builtin/portfolio-list/pane.tsx +246 -164
  249. package/src/plugins/builtin/portfolio-list/settings.ts +19 -51
  250. package/src/plugins/builtin/portfolio-list/summary.tsx +21 -16
  251. package/src/plugins/builtin/portfolio-list/table.tsx +66 -25
  252. package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.test.tsx +1 -1
  253. package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.ts +1 -1
  254. package/src/plugins/builtin/portfolio-list.test.ts +60 -1
  255. package/src/plugins/builtin/portfolio-list.test.tsx +122 -55
  256. package/src/plugins/builtin/quote-monitor.test.tsx +2 -27
  257. package/src/plugins/builtin/research/index.tsx +724 -0
  258. package/src/plugins/builtin/sec.tsx +67 -19
  259. package/src/plugins/builtin/sectors/index.tsx +42 -80
  260. package/src/plugins/builtin/sectors/sector-data.ts +1 -0
  261. package/src/plugins/builtin/ticker-detail/chart-tab.tsx +25 -39
  262. package/src/plugins/builtin/ticker-detail/financials-tab.tsx +108 -58
  263. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +383 -271
  264. package/src/plugins/builtin/ticker-detail/pane.tsx +135 -49
  265. package/src/plugins/builtin/ticker-detail/quote-monitor.tsx +43 -27
  266. package/src/plugins/builtin/ticker-detail/settings.ts +3 -6
  267. package/src/plugins/builtin/ticker-detail-chart-tab-switch.test.tsx +16 -10
  268. package/src/plugins/builtin/ticker-detail.test.tsx +156 -33
  269. package/src/plugins/builtin/ticker-surface.ts +68 -0
  270. package/src/plugins/builtin/world-indices/index.tsx +44 -82
  271. package/src/plugins/builtin/world-indices/indices.ts +1 -0
  272. package/src/plugins/builtin/yahoo.ts +55 -1
  273. package/src/plugins/builtin/yield-curve/index.tsx +87 -145
  274. package/src/plugins/builtin/yield-curve/treasury-data.ts +6 -77
  275. package/src/plugins/catalog-ui.test.ts +24 -0
  276. package/src/plugins/catalog-ui.ts +44 -0
  277. package/src/plugins/catalog.ts +2 -50
  278. package/src/plugins/ibkr/__snapshots__/trade-tab.test.tsx.snap +5 -5
  279. package/src/plugins/ibkr/account-cache.test.ts +15 -14
  280. package/src/plugins/ibkr/account-cache.ts +15 -120
  281. package/src/plugins/ibkr/broker-adapter.ts +67 -0
  282. package/src/plugins/ibkr/config.test.ts +0 -18
  283. package/src/plugins/ibkr/dialogs.tsx +14 -56
  284. package/src/plugins/ibkr/flex.test.ts +57 -2
  285. package/src/plugins/ibkr/flex.ts +116 -12
  286. package/src/plugins/ibkr/gateway-helpers.ts +8 -3
  287. package/src/plugins/ibkr/gateway-service-native.ts +2381 -0
  288. package/src/plugins/ibkr/gateway-service.test.ts +47 -9
  289. package/src/plugins/ibkr/gateway-service.ts +264 -2235
  290. package/src/plugins/ibkr/gateway-types.ts +27 -0
  291. package/src/plugins/ibkr/index.test.tsx +3 -11
  292. package/src/plugins/ibkr/index.tsx +1 -0
  293. package/src/plugins/ibkr/price-normalization.test.ts +1 -6
  294. package/src/plugins/ibkr/price-normalization.ts +1 -0
  295. package/src/plugins/ibkr/trade-badge.tsx +6 -5
  296. package/src/plugins/ibkr/trade-tab.test.tsx +2 -2
  297. package/src/plugins/ibkr/trade-tab.tsx +164 -117
  298. package/src/plugins/ibkr/trading-pane.tsx +117 -83
  299. package/src/plugins/pane-manager.test.ts +3 -1
  300. package/src/plugins/pane-manager.ts +93 -19
  301. package/src/plugins/plugin-runtime.test.tsx +176 -74
  302. package/src/plugins/plugin-runtime.tsx +228 -10
  303. package/src/plugins/prediction-markets/cache.ts +49 -0
  304. package/src/plugins/prediction-markets/categories.ts +0 -10
  305. package/src/plugins/prediction-markets/chart.test.tsx +1 -1
  306. package/src/plugins/prediction-markets/chart.tsx +21 -20
  307. package/src/plugins/prediction-markets/cli.ts +1 -0
  308. package/src/plugins/prediction-markets/controller-data.ts +135 -89
  309. package/src/plugins/prediction-markets/controller.ts +37 -108
  310. package/src/plugins/prediction-markets/detail/book.tsx +23 -22
  311. package/src/plugins/prediction-markets/detail/outcomes.tsx +33 -32
  312. package/src/plugins/prediction-markets/detail/overview.tsx +17 -12
  313. package/src/plugins/prediction-markets/detail/pane.tsx +59 -45
  314. package/src/plugins/prediction-markets/detail/rules.tsx +22 -7
  315. package/src/plugins/prediction-markets/detail/shared.tsx +3 -2
  316. package/src/plugins/prediction-markets/detail/trades.tsx +12 -11
  317. package/src/plugins/prediction-markets/detail.test.tsx +1 -1
  318. package/src/plugins/prediction-markets/keyboard.ts +1 -0
  319. package/src/plugins/prediction-markets/launch.test.ts +1 -1
  320. package/src/plugins/prediction-markets/launch.ts +1 -1
  321. package/src/plugins/prediction-markets/metrics.ts +25 -12
  322. package/src/plugins/prediction-markets/pane.test.tsx +16 -7
  323. package/src/plugins/prediction-markets/pane.tsx +180 -82
  324. package/src/plugins/prediction-markets/plugin.test.tsx +37 -45
  325. package/src/plugins/prediction-markets/services/fetch.ts +27 -10
  326. package/src/plugins/prediction-markets/services/kalshi-adapter.ts +20 -3
  327. package/src/plugins/prediction-markets/services/polymarket-adapter.ts +29 -11
  328. package/src/plugins/prediction-markets/settings.ts +0 -5
  329. package/src/plugins/prediction-markets/test-helpers.tsx +19 -44
  330. package/src/plugins/registry.test.ts +307 -0
  331. package/src/plugins/registry.ts +293 -82
  332. package/src/react/input.ts +53 -0
  333. package/src/react/plugins/plugin-slot.tsx +21 -0
  334. package/src/react/use-raf-callback.ts +44 -0
  335. package/src/renderers/electrobun/bun/application-menu-click.test.ts +41 -0
  336. package/src/renderers/electrobun/bun/application-menu-click.ts +55 -0
  337. package/src/renderers/electrobun/bun/application-menu.ts +118 -0
  338. package/src/renderers/electrobun/bun/context-menu-click.test.ts +67 -0
  339. package/src/renderers/electrobun/bun/context-menu-click.ts +61 -0
  340. package/src/renderers/electrobun/bun/context-menu-normalize.test.ts +52 -0
  341. package/src/renderers/electrobun/bun/context-menu-normalize.ts +67 -0
  342. package/src/renderers/electrobun/bun/core-capabilities.ts +328 -0
  343. package/src/renderers/electrobun/bun/desktop-workspace.test.ts +63 -0
  344. package/src/renderers/electrobun/bun/desktop-workspace.ts +162 -0
  345. package/src/renderers/electrobun/bun/index.ts +1149 -0
  346. package/src/renderers/electrobun/bun/window-frame.test.ts +40 -0
  347. package/src/renderers/electrobun/bun/window-frame.ts +46 -0
  348. package/src/renderers/electrobun/shared/protocol.ts +72 -0
  349. package/src/renderers/electrobun/view/ai-host.ts +93 -0
  350. package/src/renderers/electrobun/view/app-services.ts +348 -0
  351. package/src/renderers/electrobun/view/application-menu-bridge.ts +12 -0
  352. package/src/renderers/electrobun/view/backend-rpc.ts +173 -0
  353. package/src/renderers/electrobun/view/broker-remote-client.ts +154 -0
  354. package/src/renderers/electrobun/view/config-host.ts +35 -0
  355. package/src/renderers/electrobun/view/context-menu.test.ts +140 -0
  356. package/src/renderers/electrobun/view/context-menu.ts +164 -0
  357. package/src/renderers/electrobun/view/data-table.tsx +773 -0
  358. package/src/renderers/electrobun/view/desktop-controls.tsx +651 -0
  359. package/src/renderers/electrobun/view/desktop-window-bridge.ts +59 -0
  360. package/src/renderers/electrobun/view/desktop-window-snapshot.test.ts +57 -0
  361. package/src/renderers/electrobun/view/desktop-window-snapshot.ts +41 -0
  362. package/src/renderers/electrobun/view/dialog-host.tsx +62 -0
  363. package/src/renderers/electrobun/view/http-fetch.ts +113 -0
  364. package/src/renderers/electrobun/view/input-host.tsx +65 -0
  365. package/src/renderers/electrobun/view/key-event.test.ts +34 -0
  366. package/src/renderers/electrobun/view/key-event.ts +134 -0
  367. package/src/renderers/electrobun/view/main.tsx +104 -0
  368. package/src/renderers/electrobun/view/native-renderer.ts +110 -0
  369. package/src/renderers/electrobun/view/native-stubs/chart-kitty-support.ts +9 -0
  370. package/src/renderers/electrobun/view/native-stubs/chart-surface-manager.ts +25 -0
  371. package/src/renderers/electrobun/view/native-stubs/chart-surface-sync.ts +1 -0
  372. package/src/renderers/electrobun/view/notes-files.ts +56 -0
  373. package/src/renderers/electrobun/view/resource-store.test.ts +44 -0
  374. package/src/renderers/electrobun/view/resource-store.ts +106 -0
  375. package/src/renderers/electrobun/view/rpc-codec.ts +42 -0
  376. package/src/renderers/electrobun/view/scrollbar-activity.ts +23 -0
  377. package/src/renderers/electrobun/view/toast-host.tsx +65 -0
  378. package/src/renderers/electrobun/view/ui-host.tsx +1490 -0
  379. package/src/renderers/electrobun/view/update-host.ts +38 -0
  380. package/src/renderers/opentui/dialog-host.test.tsx +44 -0
  381. package/src/renderers/opentui/dialog-host.tsx +56 -0
  382. package/src/renderers/opentui/host.tsx +134 -0
  383. package/src/renderers/opentui/input-host.tsx +24 -0
  384. package/src/renderers/opentui/start.tsx +71 -0
  385. package/src/renderers/opentui/test-utils.tsx +76 -0
  386. package/src/renderers/opentui/toast-host.tsx +10 -0
  387. package/src/renderers/opentui/ui-host.tsx +97 -0
  388. package/src/sources/gloomberb-cloud.test.ts +288 -17
  389. package/src/sources/gloomberb-cloud.ts +176 -9
  390. package/src/sources/provider-errors.ts +1 -0
  391. package/src/sources/provider-router.test.ts +220 -81
  392. package/src/sources/provider-router.ts +294 -111
  393. package/src/sources/sec-edgar.ts +19 -6
  394. package/src/sources/yahoo-finance.test.ts +0 -5
  395. package/src/sources/yahoo-finance.ts +420 -11
  396. package/src/state/app-activity.ts +7 -7
  397. package/src/state/app-bootstrap.ts +114 -40
  398. package/src/state/app-context-selector.test.tsx +70 -1
  399. package/src/state/app-context.test.ts +71 -3
  400. package/src/state/app-context.test.tsx +14 -7
  401. package/src/state/app-context.tsx +205 -869
  402. package/src/state/config-save-scheduler.ts +35 -0
  403. package/src/state/persist-scheduler.test.ts +67 -0
  404. package/src/state/persist-scheduler.ts +69 -0
  405. package/src/state/selectors-ui.ts +55 -0
  406. package/src/state/selectors.ts +15 -71
  407. package/src/state/session-persistence.test.ts +1 -1
  408. package/src/state/ticker-refresh-queue.ts +52 -3
  409. package/src/state/use-inline-tickers.ts +18 -16
  410. package/src/state/use-quote-streaming.test.ts +0 -6
  411. package/src/state/use-quote-streaming.test.tsx +1 -1
  412. package/src/state/use-quote-streaming.ts +3 -2
  413. package/src/state/use-session-snapshot.ts +32 -17
  414. package/src/test-support/plugin-runtime.ts +149 -0
  415. package/src/theme/color-utils.ts +1 -0
  416. package/src/theme/colors.ts +16 -0
  417. package/src/types/broker.ts +16 -1
  418. package/src/types/capability-route-source.ts +23 -0
  419. package/src/types/config.test.ts +23 -0
  420. package/src/types/config.ts +145 -10
  421. package/src/types/context-menu.ts +95 -0
  422. package/src/types/data-provider.ts +34 -3
  423. package/src/types/desktop-menu.ts +13 -0
  424. package/src/types/desktop-window.ts +29 -0
  425. package/src/types/financials.ts +123 -2
  426. package/src/types/instrument.ts +1 -0
  427. package/src/types/news-source.ts +9 -0
  428. package/src/types/persistence.ts +1 -0
  429. package/src/types/plugin.ts +32 -8
  430. package/src/ui/ascii-font.ts +85 -0
  431. package/src/ui/context-menu.test.ts +94 -0
  432. package/src/ui/context-menu.tsx +433 -0
  433. package/src/ui/dialog.tsx +55 -0
  434. package/src/ui/host.tsx +361 -0
  435. package/src/ui/index.tsx +126 -0
  436. package/src/ui/toast.tsx +40 -0
  437. package/src/updater.test.ts +69 -0
  438. package/src/updater.ts +61 -18
  439. package/src/utils/api-client.test.ts +186 -6
  440. package/src/utils/api-client.ts +433 -33
  441. package/src/utils/back-navigation.ts +6 -0
  442. package/src/utils/cli-output.ts +1 -0
  443. package/src/utils/currency-units.test.ts +1 -6
  444. package/src/utils/currency-units.ts +1 -0
  445. package/src/utils/debug-log.ts +68 -0
  446. package/src/utils/dom-screenshot.ts +326 -0
  447. package/src/utils/exchange-rate-map.ts +1 -0
  448. package/src/utils/exchanges.ts +214 -0
  449. package/src/utils/format.test.ts +7 -0
  450. package/src/utils/format.ts +1 -0
  451. package/src/utils/fuzzy-search.ts +1 -0
  452. package/src/utils/html-entities.test.ts +8 -0
  453. package/src/utils/html-entities.ts +32 -0
  454. package/src/utils/http-transport.ts +11 -0
  455. package/src/utils/main-thread-monitor.ts +127 -0
  456. package/src/utils/perf-marks.ts +54 -0
  457. package/src/utils/quote-freshness.ts +27 -50
  458. package/src/utils/quote-resolution.test.ts +5 -5
  459. package/src/utils/quote-resolution.ts +0 -1
  460. package/src/utils/quote-time.ts +1 -5
  461. package/src/utils/sec.test.ts +0 -12
  462. package/src/utils/selection-clipboard.ts +14 -6
  463. package/src/utils/shortcut-labels.test.ts +20 -0
  464. package/src/utils/shortcut-labels.ts +67 -0
  465. package/src/utils/slugify.ts +1 -0
  466. package/src/utils/terminal-input-reset.ts +1 -0
  467. package/src/utils/text-wrap.ts +82 -0
  468. package/src/utils/throttled-fetch.test.ts +19 -0
  469. package/src/utils/throttled-fetch.ts +11 -9
  470. package/src/utils/ticker-list.test.ts +1 -7
  471. package/src/utils/ticker-list.ts +1 -0
  472. package/src/utils/timestamp.ts +29 -0
  473. package/src/utils/tweet-text.test.ts +13 -0
  474. package/src/utils/tweet-text.ts +18 -0
  475. package/src/utils/url.ts +11 -0
  476. package/src/version.ts +1 -1
  477. package/src/components/chart/chart-control-hint.tsx +0 -39
  478. package/src/components/detail-feed-view.test.tsx +0 -180
  479. package/src/components/detail-feed-view.tsx +0 -531
  480. package/src/components/layout/pixel-mouse-mode.test.ts +0 -122
  481. package/src/components/layout/pixel-mouse.test.ts +0 -40
  482. package/src/components/spinner.tsx +0 -2
  483. package/src/components/tab-bar.tsx +0 -17
  484. package/src/components/ui/multi-select-chips.tsx +0 -69
  485. package/src/plugins/builtin/ai/index.test.tsx +0 -139
  486. package/src/plugins/builtin/ask-ai.tsx +0 -9
  487. package/src/plugins/builtin/correlation/plugin.test.ts +0 -44
  488. package/src/plugins/builtin/correlation/settings.test.ts +0 -34
  489. package/src/plugins/builtin/econ/calendar-source.test.ts +0 -219
  490. package/src/plugins/builtin/econ/fred-client.ts +0 -79
  491. package/src/plugins/builtin/fred-settings.ts +0 -10
  492. package/src/plugins/builtin/options-availability.test.tsx +0 -179
  493. package/src/plugins/builtin/options-availability.ts +0 -158
  494. package/src/plugins/builtin/portfolio-list/metrics.test.ts +0 -61
  495. package/src/plugins/builtin/yield-curve/treasury-data.test.ts +0 -212
  496. package/src/plugins/prediction-markets/status.ts +0 -24
  497. package/src/plugins/prediction-markets/table.tsx +0 -87
  498. package/src/utils/quote-time.test.ts +0 -22
  499. package/src/utils/selection-clipboard.test.ts +0 -43
  500. package/src/utils/terminal-input-reset.test.ts +0 -50
@@ -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
 
@@ -99,7 +99,7 @@ describe("computeNativePlacement", () => {
99
99
  });
100
100
 
101
101
  describe("renderNativeChartBase", () => {
102
- test("fills the bitmap background opaquely", () => {
102
+ test("leaves untouched bitmap pixels transparent", () => {
103
103
  const palette = resolveChartPalette({
104
104
  bg: "#112233",
105
105
  border: "#333333",
@@ -126,9 +126,17 @@ describe("renderNativeChartBase", () => {
126
126
  expect(scene).not.toBeNull();
127
127
 
128
128
  const bitmap = renderNativeChartBase(scene!, 24, 12);
129
+ let transparentPixels = 0;
130
+ let drawnPixels = 0;
129
131
  for (let offset = 3; offset < bitmap.pixels.length; offset += 4) {
130
- expect(bitmap.pixels[offset]).toBe(0xff);
132
+ if (bitmap.pixels[offset] === 0) {
133
+ transparentPixels += 1;
134
+ } else {
135
+ drawnPixels += 1;
136
+ }
131
137
  }
138
+ expect(transparentPixels).toBeGreaterThan(0);
139
+ expect(drawnPixels).toBeGreaterThan(0);
132
140
  });
133
141
 
134
142
  test("keeps solid candle bodies opaque over the wick", () => {
@@ -277,7 +285,7 @@ describe("renderNativeChartBase", () => {
277
285
  });
278
286
 
279
287
  describe("renderNativeComparisonChartBase", () => {
280
- test("renders multi-series comparison overlays into an opaque bitmap", () => {
288
+ test("renders multi-series comparison overlays over transparent pixels", () => {
281
289
  const projection = projectComparisonChartData([
282
290
  {
283
291
  symbol: "AAPL",
@@ -302,7 +310,6 @@ describe("renderNativeComparisonChartBase", () => {
302
310
  ],
303
311
  },
304
312
  ], 12, {
305
- timeRange: "ALL",
306
313
  panOffset: 0,
307
314
  zoomLevel: 1,
308
315
  renderMode: "line",
@@ -323,17 +330,25 @@ describe("renderNativeComparisonChartBase", () => {
323
330
  expect(scene).not.toBeNull();
324
331
 
325
332
  const bitmap = renderNativeComparisonChartBase(scene!, 120, 60);
333
+ let transparentPixels = 0;
334
+ let drawnPixels = 0;
326
335
  for (let offset = 3; offset < bitmap.pixels.length; offset += 4) {
327
- expect(bitmap.pixels[offset]).toBe(0xff);
336
+ if (bitmap.pixels[offset] === 0) {
337
+ transparentPixels += 1;
338
+ } else {
339
+ drawnPixels += 1;
340
+ }
328
341
  }
342
+ expect(transparentPixels).toBeGreaterThan(0);
343
+ expect(drawnPixels).toBeGreaterThan(0);
329
344
 
330
- const brightPixels = [];
345
+ const visiblePixels = [];
331
346
  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);
347
+ if (bitmap.pixels[offset + 3] !== 0) {
348
+ visiblePixels.push(offset);
334
349
  }
335
350
  }
336
- expect(brightPixels.length).toBeGreaterThan(0);
351
+ expect(visiblePixels.length).toBeGreaterThan(0);
337
352
  });
338
353
  });
339
354
 
@@ -457,8 +472,9 @@ describe("KittyImageManager", () => {
457
472
  test("reuses placement ids across multiple fragments and deletes the previous image on swap", () => {
458
473
  const writes: string[] = [];
459
474
  const renderer = {
460
- writeOut(payload: string | Uint8Array) {
475
+ write(payload: string | Uint8Array) {
461
476
  writes.push(typeof payload === "string" ? payload : Buffer.from(payload).toString("utf8"));
477
+ return true;
462
478
  },
463
479
  } as unknown as CliRenderer;
464
480
 
@@ -586,8 +602,9 @@ describe("NativeSurfaceManager", () => {
586
602
  resolution: { width: 1200, height: 800 },
587
603
  terminalWidth: 120,
588
604
  terminalHeight: 40,
589
- writeOut(payload: string | Uint8Array) {
605
+ write(payload: string | Uint8Array) {
590
606
  writes.push(typeof payload === "string" ? payload : Buffer.from(payload).toString("utf8"));
607
+ return true;
591
608
  },
592
609
  } as unknown as CliRenderer;
593
610
 
@@ -617,7 +634,7 @@ describe("NativeSurfaceManager", () => {
617
634
  resolution: { width: 1200, height: 800 },
618
635
  terminalWidth: 120,
619
636
  terminalHeight: 40,
620
- writeOut() {},
637
+ write() { return true; },
621
638
  } as unknown as CliRenderer;
622
639
  const manager = new NativeSurfaceManager(renderer);
623
640
  const originalRender = KittyImageManager.prototype.render;
@@ -657,7 +674,7 @@ describe("NativeSurfaceManager", () => {
657
674
  resolution: { width: 1200, height: 800 },
658
675
  terminalWidth: 120,
659
676
  terminalHeight: 40,
660
- writeOut() {},
677
+ write() { return true; },
661
678
  } as unknown as CliRenderer;
662
679
  const manager = new NativeSurfaceManager(renderer);
663
680
  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,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
+ });
@@ -0,0 +1,191 @@
1
+ import { useMemo } from "react";
2
+ import { Box, ChartSurface, Text, useUiCapabilities } from "../../ui";
3
+ import type { ProjectedChartPoint } from "./chart-data";
4
+ import {
5
+ buildChartScene,
6
+ computeGridLines,
7
+ formatAxisCell,
8
+ renderChart,
9
+ type RenderChartOptions,
10
+ } from "./chart-renderer";
11
+ import { renderNativeChartBase, type NativeChartBitmap } from "./native/chart-rasterizer";
12
+
13
+ export interface StaticChartSurfaceProps extends Omit<
14
+ RenderChartOptions,
15
+ "width" | "height" | "cursorX" | "cursorY" | "showVolume" | "volumeHeight"
16
+ > {
17
+ points: ProjectedChartPoint[];
18
+ width: number;
19
+ height: number;
20
+ showVolume?: boolean;
21
+ volumeHeight?: number;
22
+ showTimeAxis?: boolean;
23
+ timeAxisColor?: string;
24
+ yAxisLabel?: string;
25
+ yAxisColor?: string;
26
+ formatYAxisValue?: (value: number) => string;
27
+ }
28
+
29
+ export function StaticChartSurface({
30
+ points,
31
+ width,
32
+ height,
33
+ mode,
34
+ axisMode,
35
+ currency,
36
+ assetCategory,
37
+ colors,
38
+ timeAxisDates,
39
+ indicators,
40
+ showVolume = false,
41
+ volumeHeight = 0,
42
+ showTimeAxis = false,
43
+ timeAxisColor,
44
+ yAxisLabel,
45
+ yAxisColor,
46
+ formatYAxisValue,
47
+ }: StaticChartSurfaceProps) {
48
+ const {
49
+ canvasCharts,
50
+ cellWidthPx = 8,
51
+ cellHeightPx = 18,
52
+ pixelRatio = 1,
53
+ } = useUiCapabilities();
54
+ const timeAxisRows = showTimeAxis ? 1 : 0;
55
+ const labelRows = yAxisLabel ? 1 : 0;
56
+ const totalWidth = Math.max(1, Math.floor(width));
57
+ const totalHeight = Math.max(1, Math.floor(height));
58
+ const plotHeight = Math.max(1, totalHeight - timeAxisRows - labelRows);
59
+ const axisSourceOptions = useMemo<RenderChartOptions>(() => ({
60
+ width: totalWidth,
61
+ height: plotHeight,
62
+ showVolume,
63
+ volumeHeight,
64
+ cursorX: null,
65
+ cursorY: null,
66
+ mode,
67
+ axisMode,
68
+ currency,
69
+ assetCategory,
70
+ colors,
71
+ timeAxisDates,
72
+ indicators,
73
+ }), [
74
+ assetCategory,
75
+ axisMode,
76
+ colors,
77
+ currency,
78
+ indicators,
79
+ mode,
80
+ plotHeight,
81
+ showVolume,
82
+ timeAxisDates,
83
+ totalWidth,
84
+ volumeHeight,
85
+ ]);
86
+ const axisScene = useMemo(
87
+ () => buildChartScene(points, axisSourceOptions),
88
+ [axisSourceOptions, points],
89
+ );
90
+ const customAxisLabels = useMemo(() => {
91
+ if (!axisScene || !formatYAxisValue) return null;
92
+ return computeGridLines(axisScene.min, axisScene.max, 0, axisScene.chartRows - 1, 3)
93
+ .map((line) => ({
94
+ row: Math.max(0, Math.min(axisScene.chartRows - 1, Math.round(line.y))),
95
+ label: formatYAxisValue(line.price),
96
+ }));
97
+ }, [axisScene, formatYAxisValue]);
98
+ const axisLabels = customAxisLabels ?? [];
99
+ const axisWidth = axisLabels.length > 0
100
+ ? Math.min(Math.max(...axisLabels.map((entry) => entry.label.length), 5), 12)
101
+ : 0;
102
+ const axisGap = axisWidth > 0 ? 1 : 0;
103
+ const plotWidth = Math.max(1, totalWidth - axisWidth - axisGap);
104
+ const bitmapSize = useMemo(() => {
105
+ if (!canvasCharts) return null;
106
+ const scale = Math.max(1, pixelRatio);
107
+ return {
108
+ pixelWidth: Math.max(1, Math.round(plotWidth * cellWidthPx * scale)),
109
+ pixelHeight: Math.max(1, Math.round(plotHeight * cellHeightPx * scale)),
110
+ };
111
+ }, [canvasCharts, cellHeightPx, cellWidthPx, pixelRatio, plotHeight, plotWidth]);
112
+ const renderOptions = useMemo<RenderChartOptions>(() => ({
113
+ width: plotWidth,
114
+ height: plotHeight,
115
+ showVolume,
116
+ volumeHeight,
117
+ cursorX: null,
118
+ cursorY: null,
119
+ mode,
120
+ axisMode,
121
+ currency,
122
+ assetCategory,
123
+ colors,
124
+ timeAxisDates,
125
+ indicators,
126
+ }), [
127
+ assetCategory,
128
+ axisMode,
129
+ colors,
130
+ currency,
131
+ indicators,
132
+ mode,
133
+ plotHeight,
134
+ plotWidth,
135
+ showVolume,
136
+ timeAxisDates,
137
+ volumeHeight,
138
+ ]);
139
+ const textResult = useMemo(
140
+ () => renderChart(points, renderOptions),
141
+ [points, renderOptions],
142
+ );
143
+ const effectiveAxisLabelsByRow = useMemo(() => {
144
+ return new Map(axisLabels.map((entry) => [entry.row, entry.label] as const));
145
+ }, [axisLabels]);
146
+ const effectiveAxisWidth = axisWidth;
147
+ const effectiveAxisGap = effectiveAxisWidth > 0 ? 1 : 0;
148
+ const bitmap = useMemo<NativeChartBitmap | null>(() => {
149
+ if (!bitmapSize) return null;
150
+ const scene = buildChartScene(points, renderOptions);
151
+ if (!scene) return null;
152
+ return renderNativeChartBase(scene, bitmapSize.pixelWidth, bitmapSize.pixelHeight);
153
+ }, [bitmapSize, points, renderOptions]);
154
+
155
+ return (
156
+ <Box flexDirection="column" width={totalWidth} height={plotHeight + timeAxisRows + labelRows}>
157
+ {yAxisLabel ? (
158
+ <Box height={1}>
159
+ <Text fg={yAxisColor}>{yAxisLabel}</Text>
160
+ </Box>
161
+ ) : null}
162
+ <Box flexDirection="row" height={plotHeight}>
163
+ <ChartSurface
164
+ width={plotWidth}
165
+ height={plotHeight}
166
+ flexDirection="column"
167
+ bitmaps={bitmap ? [bitmap] : null}
168
+ >
169
+ {textResult.lines.map((line, index) => (
170
+ <Text key={index} content={line} />
171
+ ))}
172
+ </ChartSurface>
173
+ {effectiveAxisWidth > 0 ? (
174
+ <>
175
+ <Box width={effectiveAxisGap} />
176
+ <Box width={effectiveAxisWidth} height={plotHeight} flexDirection="column">
177
+ {Array.from({ length: plotHeight }, (_, row) => (
178
+ <Text key={row} fg={yAxisColor}>
179
+ {formatAxisCell(effectiveAxisLabelsByRow.get(row) ?? null, effectiveAxisWidth)}
180
+ </Text>
181
+ ))}
182
+ </Box>
183
+ </>
184
+ ) : null}
185
+ </Box>
186
+ {showTimeAxis ? (
187
+ <Text fg={timeAxisColor}>{textResult.timeLabels}</Text>
188
+ ) : null}
189
+ </Box>
190
+ );
191
+ }