gloomberb 0.10.4 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (555) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +30 -3
  3. package/README.zh-CN.md +1 -1
  4. package/package.json +5 -5
  5. package/src/api-client/auth.ts +34 -0
  6. package/src/api-client/data.ts +25 -0
  7. package/src/api-client/index.test.ts +81 -0
  8. package/src/api-client/index.ts +37 -0
  9. package/src/api-client/request.test.ts +30 -0
  10. package/src/api-client/request.ts +44 -20
  11. package/src/api-client/socket-health.test.ts +79 -0
  12. package/src/api-client/socket.ts +108 -5
  13. package/src/api-client/types.ts +139 -0
  14. package/src/app/global-shortcuts.test.tsx +144 -9
  15. package/src/app/global-shortcuts.ts +33 -11
  16. package/src/app/runtime/broker-import.ts +39 -5
  17. package/src/app/runtime/startup.ts +7 -1
  18. package/src/app/runtime/update.ts +26 -1
  19. package/src/app.tsx +36 -19
  20. package/src/brokers/broker-ticker-sync.ts +3 -8
  21. package/src/brokers/sync-broker-instance.test.ts +59 -0
  22. package/src/brokers/sync-broker-instance.ts +37 -13
  23. package/src/capabilities/chart-series.ts +318 -0
  24. package/src/capabilities/factories.ts +46 -0
  25. package/src/capabilities/index.ts +1 -0
  26. package/src/capabilities/registry.test.ts +135 -0
  27. package/src/capabilities/registry.ts +31 -4
  28. package/src/capabilities/types.ts +52 -0
  29. package/src/cli/commands/overview.ts +2 -2
  30. package/src/cli/desktop-pane-shot.ts +2 -0
  31. package/src/cli/entry.ts +11 -1
  32. package/src/cli/native-smoke.ts +5 -0
  33. package/src/cli/pane-functions/catalog.ts +4 -0
  34. package/src/cli/pane-functions/data.ts +6 -1
  35. package/src/cli/pane-functions/discovery.ts +76 -10
  36. package/src/cli/pane-functions/report.test.ts +76 -0
  37. package/src/cli/pane-functions/report.ts +7 -1
  38. package/src/cli/pane-functions/resolver.ts +4 -1
  39. package/src/cli/pane-functions/screenshot.test.ts +56 -3
  40. package/src/cli/pane-functions/screenshot.ts +68 -5
  41. package/src/components/chart/composite/composite-chart.test.tsx +54 -0
  42. package/src/components/chart/composite/composite-chart.tsx +92 -15
  43. package/src/components/chart/composite/downsample.test.ts +136 -0
  44. package/src/components/chart/composite/downsample.ts +208 -0
  45. package/src/components/chart/composite/panel-series.test.ts +66 -0
  46. package/src/components/chart/composite/panel-series.ts +101 -0
  47. package/src/components/chart/multi-line/renderer.ts +1 -0
  48. package/src/components/chart/native/kitty/index.test.ts +25 -3
  49. package/src/components/chart/native/kitty/support.ts +37 -3
  50. package/src/components/chart/native/use-chart-text-fallback.ts +19 -0
  51. package/src/components/chart/static/chart/surface.tsx +43 -12
  52. package/src/components/chart/static/multi-line-chart-surface.tsx +41 -5
  53. package/src/components/command-bar/commands/direct/index.ts +18 -0
  54. package/src/components/command-bar/commands/registry.ts +14 -0
  55. package/src/components/command-bar/routes/root/article-results.test.ts +83 -0
  56. package/src/components/command-bar/routes/root/article-results.ts +55 -0
  57. package/src/components/command-bar/routes/root/command-items.ts +3 -0
  58. package/src/components/command-bar/routes/root/provider-search.ts +7 -3
  59. package/src/components/command-bar/routes/root/results.ts +38 -11
  60. package/src/components/command-bar/routes/root/runtime.ts +6 -2
  61. package/src/components/command-bar/routes/ticker-search/actions.ts +1 -1
  62. package/src/components/command-bar/routes/ticker-search/results.test.ts +34 -0
  63. package/src/components/command-bar/routes/ticker-search/results.ts +4 -6
  64. package/src/components/command-bar/routes/ticker-search/route.ts +2 -2
  65. package/src/components/command-bar/routing/list-state.ts +2 -1
  66. package/src/components/command-bar/surface/assist.test.tsx +13 -6
  67. package/src/components/command-bar/surface/index.test.tsx +65 -0
  68. package/src/components/command-bar/surface/index.tsx +35 -0
  69. package/src/components/command-bar/view-model.test.ts +43 -0
  70. package/src/components/command-bar/view-model.ts +41 -4
  71. package/src/components/command-bar/workflow/collection-actions.test.ts +94 -0
  72. package/src/components/command-bar/workflow/collection-actions.ts +14 -3
  73. package/src/components/command-bar/workflow/ops.test.ts +136 -0
  74. package/src/components/command-bar/workflow/ops.ts +71 -1
  75. package/src/components/index.ts +1 -1
  76. package/src/components/layout/header.test.tsx +35 -0
  77. package/src/components/layout/header.tsx +101 -9
  78. package/src/components/layout/pane/cycle-order.test.ts +0 -1
  79. package/src/components/layout/pane/footer/index.test.tsx +19 -19
  80. package/src/components/layout/pane/header.tsx +2 -13
  81. package/src/components/layout/shell/index.tsx +3 -0
  82. package/src/components/layout/status-bar.test.tsx +36 -1
  83. package/src/components/layout/status-bar.tsx +48 -2
  84. package/src/components/onboarding/account-step/chooser-panel.tsx +27 -50
  85. package/src/components/onboarding/account-step/form-panel.tsx +85 -29
  86. package/src/components/onboarding/account-step/index.tsx +10 -1
  87. package/src/components/onboarding/account-step/model.ts +3 -3
  88. package/src/components/onboarding/account-step/qr-panel.tsx +68 -0
  89. package/src/components/onboarding/account-step/signed-in-panel.tsx +4 -7
  90. package/src/components/onboarding/onboarding-frame.tsx +517 -0
  91. package/src/components/onboarding/onboarding-steps.tsx +0 -264
  92. package/src/components/onboarding/onboarding-wizard.test.tsx +626 -200
  93. package/src/components/onboarding/onboarding-wizard.tsx +848 -266
  94. package/src/components/onboarding/portfolio-step/broker-fields-panel.tsx +81 -51
  95. package/src/components/onboarding/portfolio-step/broker-setup-panel.tsx +14 -20
  96. package/src/components/onboarding/portfolio-step/broker-sync-panel.tsx +8 -11
  97. package/src/components/onboarding/portfolio-step/choose-panel.tsx +18 -22
  98. package/src/components/onboarding/portfolio-step/index.tsx +3 -0
  99. package/src/components/onboarding/portfolio-step/types.ts +3 -0
  100. package/src/components/onboarding/wizard-account.ts +38 -3
  101. package/src/components/onboarding/wizard-broker-sync.ts +45 -67
  102. package/src/components/onboarding/wizard-model.ts +18 -2
  103. package/src/components/ui/data-table/opentui/index.tsx +29 -9
  104. package/src/components/ui/data-table/types.ts +2 -0
  105. package/src/components/ui/external-link.tsx +4 -2
  106. package/src/components/ui/loading.tsx +1 -1
  107. package/src/components/ui/status.tsx +76 -4
  108. package/src/components/ui/table-layout.test.ts +24 -0
  109. package/src/components/ui/table-layout.ts +90 -23
  110. package/src/components/ui/tabs.tsx +86 -4
  111. package/src/core/app-services.ts +14 -3
  112. package/src/core/connection-health.test.ts +143 -0
  113. package/src/core/connection-health.ts +295 -0
  114. package/src/core/state/app/layout-reducer.ts +34 -0
  115. package/src/core/state/app/layout.ts +21 -0
  116. package/src/core/state/app/state.ts +10 -0
  117. package/src/core/state/app/types.ts +7 -1
  118. package/src/data/config/chart-settings.ts +11 -2
  119. package/src/data/config/store/index.test.ts +83 -9
  120. package/src/data/config/store/migrations.ts +16 -0
  121. package/src/data/config/store/normalize.ts +59 -1
  122. package/src/data/fred-series.ts +8 -2
  123. package/src/i18n/es.ts +126 -0
  124. package/src/i18n/ja.ts +126 -0
  125. package/src/i18n/ko.ts +126 -0
  126. package/src/i18n/zh-cn.ts +126 -0
  127. package/src/i18n/zh-tw.ts +126 -0
  128. package/src/market-data/coordinator/chart.ts +3 -2
  129. package/src/market-data/hooks.tsx +3 -2
  130. package/src/market-data/market/status.test.ts +12 -0
  131. package/src/market-data/market/status.ts +34 -17
  132. package/src/news/aggregator.test.ts +39 -0
  133. package/src/news/aggregator.ts +78 -22
  134. package/src/news/hooks.ts +4 -0
  135. package/src/notifications/app-notifier.test.ts +28 -6
  136. package/src/notifications/app-notifier.ts +24 -5
  137. package/src/plugins/broker-sync/broker-sync.test.ts +130 -0
  138. package/src/plugins/broker-sync/native-loader.ts +10 -0
  139. package/src/plugins/broker-sync/normalize.ts +271 -0
  140. package/src/plugins/broker-sync/public.ts +122 -0
  141. package/src/plugins/broker-sync/robinhood-native.ts +367 -0
  142. package/src/plugins/broker-sync/robinhood.ts +114 -0
  143. package/src/plugins/broker-sync/simplefin-native.ts +201 -0
  144. package/src/plugins/broker-sync/simplefin.ts +70 -0
  145. package/src/plugins/builtin/account-management/footer.ts +8 -19
  146. package/src/plugins/builtin/account-management/pane.tsx +13 -8
  147. package/src/plugins/builtin/ai/pi/host.ts +6 -5
  148. package/src/plugins/builtin/ai/providers.ts +1 -1
  149. package/src/plugins/builtin/ai/screener/footer.ts +0 -7
  150. package/src/plugins/builtin/ai/screener/pane.test.tsx +2 -1
  151. package/src/plugins/builtin/ai/screener/pane.tsx +3 -1
  152. package/src/plugins/builtin/ai/screener/results-view.tsx +8 -0
  153. package/src/plugins/builtin/ai/screener/runner.ts +7 -3
  154. package/src/plugins/builtin/ai/workspace/pane.tsx +13 -2
  155. package/src/plugins/builtin/alerts/alert-engine.ts +13 -0
  156. package/src/plugins/builtin/alerts/constants.ts +2 -1
  157. package/src/plugins/builtin/alerts/index.tsx +67 -36
  158. package/src/plugins/builtin/alerts/pane.tsx +17 -65
  159. package/src/plugins/builtin/analytics/index.tsx +24 -11
  160. package/src/plugins/builtin/analytics/pane-model.ts +48 -14
  161. package/src/plugins/builtin/analytics/portfolio-selection.ts +19 -0
  162. package/src/plugins/builtin/analytics/view.tsx +5 -3
  163. package/src/plugins/builtin/broker-manager/detail.tsx +7 -2
  164. package/src/plugins/builtin/broker-manager/footer.ts +4 -9
  165. package/src/plugins/builtin/broker-manager/index.tsx +1 -6
  166. package/src/plugins/builtin/buildout/detail/ui.tsx +15 -4
  167. package/src/plugins/builtin/buildout/pane/header.tsx +1 -0
  168. package/src/plugins/builtin/changelog/index.tsx +60 -45
  169. package/src/plugins/builtin/chart-composer/catalog-inventory.test.ts +97 -0
  170. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +366 -0
  171. package/src/plugins/builtin/chart-composer/chart-spec.ts +21 -1
  172. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +307 -0
  173. package/src/plugins/builtin/chart-composer/editor/controller.ts +1 -1
  174. package/src/plugins/builtin/chart-composer/index.tsx +45 -5
  175. package/src/plugins/builtin/chart-composer/pane.tsx +7 -88
  176. package/src/plugins/builtin/chart-composer/presets.test.ts +48 -0
  177. package/src/plugins/builtin/chart-composer/presets.ts +69 -3
  178. package/src/plugins/builtin/chart-composer/quick-add.tsx +8 -5
  179. package/src/plugins/builtin/chart-composer/semantic.ts +18 -14
  180. package/src/plugins/builtin/chart-composer/series-catalog.test.ts +39 -0
  181. package/src/plugins/builtin/chart-composer/series-catalog.ts +104 -3
  182. package/src/plugins/builtin/chart-composer/shortcuts.test.ts +10 -10
  183. package/src/plugins/builtin/chart-composer/shortcuts.ts +4 -12
  184. package/src/plugins/builtin/chart-composer/use-series-catalog.ts +230 -23
  185. package/src/plugins/builtin/chat/chat.test.tsx +22 -0
  186. package/src/plugins/builtin/chat/content/composer.tsx +6 -4
  187. package/src/plugins/builtin/chat/content/index.tsx +7 -0
  188. package/src/plugins/builtin/chat/content/snapshot.ts +9 -2
  189. package/src/plugins/builtin/chat/content/transcript.tsx +10 -0
  190. package/src/plugins/builtin/chat/controller/channel-actions.ts +11 -1
  191. package/src/plugins/builtin/chat/controller/channels.ts +13 -3
  192. package/src/plugins/builtin/chat/controller/index.ts +61 -7
  193. package/src/plugins/builtin/chat/controller/lifecycle.ts +3 -0
  194. package/src/plugins/builtin/chat/controller/message-loading.ts +5 -1
  195. package/src/plugins/builtin/chat/controller/messages.ts +3 -1
  196. package/src/plugins/builtin/chat/controller/notifications.ts +32 -11
  197. package/src/plugins/builtin/chat/controller/state.ts +7 -0
  198. package/src/plugins/builtin/chat/controller/view.ts +1 -0
  199. package/src/plugins/builtin/chat/controller.test.ts +117 -28
  200. package/src/plugins/builtin/cloud/auth-commands.ts +18 -0
  201. package/src/plugins/builtin/cloud/device-signin-dialog.tsx +243 -0
  202. package/src/plugins/builtin/cloud/device-signin.test.ts +217 -0
  203. package/src/plugins/builtin/cloud/device-signin.ts +240 -0
  204. package/src/plugins/builtin/cloud/plugin.tsx +7 -1
  205. package/src/plugins/builtin/cloud-tweets/table.tsx +57 -10
  206. package/src/plugins/builtin/composite-plugins.ts +39 -5
  207. package/src/plugins/builtin/congress-trades/detail.tsx +2 -4
  208. package/src/plugins/builtin/congress-trades/footer.ts +7 -16
  209. package/src/plugins/builtin/congress-trades/pane.tsx +12 -20
  210. package/src/plugins/builtin/connections/index.ts +53 -0
  211. package/src/plugins/builtin/connections/pane.test.tsx +79 -0
  212. package/src/plugins/builtin/connections/pane.tsx +258 -0
  213. package/src/plugins/builtin/correlation/index.test.tsx +2 -1
  214. package/src/plugins/builtin/correlation/index.tsx +58 -19
  215. package/src/plugins/builtin/correlation/matrix/model.ts +18 -7
  216. package/src/plugins/builtin/correlation/relationship/pane.tsx +43 -19
  217. package/src/plugins/builtin/correlation/settings.ts +1 -1
  218. package/src/plugins/builtin/credit-conditions/client.test.ts +80 -0
  219. package/src/plugins/builtin/credit-conditions/client.ts +105 -0
  220. package/src/plugins/builtin/credit-conditions/index.test.tsx +111 -0
  221. package/src/plugins/builtin/credit-conditions/index.tsx +227 -0
  222. package/src/plugins/builtin/credit-conditions/model.test.ts +43 -0
  223. package/src/plugins/builtin/credit-conditions/model.ts +64 -0
  224. package/src/plugins/builtin/debug/index.tsx +11 -6
  225. package/src/plugins/builtin/dividend-yield/client.test.ts +58 -0
  226. package/src/plugins/builtin/dividend-yield/client.ts +252 -0
  227. package/src/plugins/builtin/dividend-yield/index.tsx +61 -0
  228. package/src/plugins/builtin/dividend-yield/model.ts +83 -0
  229. package/src/plugins/builtin/dividend-yield/pane.tsx +305 -0
  230. package/src/plugins/builtin/dividend-yield/types.ts +22 -0
  231. package/src/plugins/builtin/earnings/data/cache.test.ts +10 -7
  232. package/src/plugins/builtin/earnings/data/cache.ts +23 -9
  233. package/src/plugins/builtin/earnings/index.tsx +57 -6
  234. package/src/plugins/builtin/econ/calendar-model.test.ts +37 -3
  235. package/src/plugins/builtin/econ/calendar-model.ts +32 -26
  236. package/src/plugins/builtin/econ/fred-cache.test.ts +8 -1
  237. package/src/plugins/builtin/econ/fred-series-map.ts +18 -0
  238. package/src/plugins/builtin/econ/index.test.tsx +105 -0
  239. package/src/plugins/builtin/econ/index.tsx +95 -52
  240. package/src/plugins/builtin/fear-greed/cache.test.ts +18 -3
  241. package/src/plugins/builtin/fear-greed/cache.ts +18 -6
  242. package/src/plugins/builtin/fear-greed/charts.tsx +9 -2
  243. package/src/plugins/builtin/fear-greed/format.ts +0 -9
  244. package/src/plugins/builtin/fear-greed/pane.tsx +21 -21
  245. package/src/plugins/builtin/futures/contracts.ts +100 -0
  246. package/src/plugins/builtin/futures/index.tsx +298 -0
  247. package/src/plugins/builtin/futures/model.test.ts +129 -0
  248. package/src/plugins/builtin/futures/model.ts +136 -0
  249. package/src/plugins/builtin/futures/pane.test.tsx +239 -0
  250. package/src/plugins/builtin/futures/table.test.ts +155 -0
  251. package/src/plugins/builtin/futures/table.tsx +241 -0
  252. package/src/plugins/builtin/fx-matrix/index.tsx +160 -134
  253. package/src/plugins/builtin/fx-matrix/pairs.ts +12 -1
  254. package/src/plugins/builtin/fx-matrix/pane.test.tsx +109 -0
  255. package/src/plugins/builtin/help/components.tsx +1 -30
  256. package/src/plugins/builtin/help/index.tsx +5 -25
  257. package/src/plugins/builtin/holders/pane.tsx +17 -12
  258. package/src/plugins/builtin/holders/treemap.tsx +4 -2
  259. package/src/plugins/builtin/insider/index.tsx +20 -10
  260. package/src/plugins/builtin/ipo-calendar/cache.ts +101 -0
  261. package/src/plugins/builtin/ipo-calendar/client.test.ts +31 -0
  262. package/src/plugins/builtin/ipo-calendar/client.ts +296 -0
  263. package/src/plugins/builtin/ipo-calendar/index.tsx +65 -0
  264. package/src/plugins/builtin/ipo-calendar/model.ts +145 -0
  265. package/src/plugins/builtin/ipo-calendar/pane.tsx +328 -0
  266. package/src/plugins/builtin/ipo-calendar/types.ts +19 -0
  267. package/src/plugins/builtin/kelly-sizer/index.test.tsx +3 -3
  268. package/src/plugins/builtin/kelly-sizer/model.ts +8 -1
  269. package/src/plugins/builtin/kelly-sizer/pane.tsx +33 -15
  270. package/src/plugins/builtin/kelly-sizer/view.tsx +12 -5
  271. package/src/plugins/builtin/market-halts/client.ts +114 -0
  272. package/src/plugins/builtin/market-halts/index.tsx +29 -0
  273. package/src/plugins/builtin/market-halts/model.test.ts +187 -0
  274. package/src/plugins/builtin/market-halts/model.ts +291 -0
  275. package/src/plugins/builtin/market-halts/pane.tsx +256 -0
  276. package/src/plugins/builtin/market-heatmap/data.test.ts +1 -0
  277. package/src/plugins/builtin/market-heatmap/data.ts +12 -3
  278. package/src/plugins/builtin/market-heatmap/index.tsx +131 -46
  279. package/src/plugins/builtin/market-movers/index.tsx +145 -63
  280. package/src/plugins/builtin/market-movers/model.ts +19 -3
  281. package/src/plugins/builtin/market-movers/screener.test.ts +108 -0
  282. package/src/plugins/builtin/market-movers/screener.ts +168 -10
  283. package/src/plugins/builtin/news/index.tsx +78 -99
  284. package/src/plugins/builtin/news/wire/article-search.test.ts +72 -0
  285. package/src/plugins/builtin/news/wire/article-search.ts +140 -0
  286. package/src/plugins/builtin/news/wire/breaking/pane.tsx +18 -8
  287. package/src/plugins/builtin/news/wire/categories.ts +35 -0
  288. package/src/plugins/builtin/news/wire/default-feeds.ts +47 -1
  289. package/src/plugins/builtin/news/wire/feed-config.test.ts +10 -5
  290. package/src/plugins/builtin/news/wire/index.tsx +41 -4
  291. package/src/plugins/builtin/news/wire/industry-pane.tsx +38 -19
  292. package/src/plugins/builtin/news/wire/news/detail-view.tsx +10 -5
  293. package/src/plugins/builtin/news/wire/news/footer.ts +1 -0
  294. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +15 -6
  295. package/src/plugins/builtin/news/wire/news/query-presets.ts +16 -2
  296. package/src/plugins/builtin/news/wire/news/table.test.tsx +54 -0
  297. package/src/plugins/builtin/news/wire/news/table.tsx +111 -17
  298. package/src/plugins/builtin/notes/files.test.ts +18 -0
  299. package/src/plugins/builtin/notes/files.ts +10 -4
  300. package/src/plugins/builtin/notes/index.tsx +4 -1
  301. package/src/plugins/builtin/notes/quick-notes-pane.test.tsx +182 -0
  302. package/src/plugins/builtin/notes/quick-notes-pane.tsx +74 -31
  303. package/src/plugins/builtin/notes/ticker-notes-tab.test.tsx +380 -0
  304. package/src/plugins/builtin/notes/ticker-notes-tab.tsx +97 -40
  305. package/src/plugins/builtin/options/calc-seed.test.ts +118 -0
  306. package/src/plugins/builtin/options/calc-seed.ts +52 -0
  307. package/src/plugins/builtin/options/footer.ts +4 -1
  308. package/src/plugins/builtin/options/index.tsx +22 -1
  309. package/src/plugins/builtin/options/live-quotes.ts +8 -0
  310. package/src/plugins/builtin/options/table.ts +3 -1
  311. package/src/plugins/builtin/options/view.test.tsx +5 -1
  312. package/src/plugins/builtin/options/view.tsx +112 -17
  313. package/src/plugins/builtin/options-calculator/index.tsx +35 -0
  314. package/src/plugins/builtin/options-calculator/model.test.ts +172 -0
  315. package/src/plugins/builtin/options-calculator/model.ts +273 -0
  316. package/src/plugins/builtin/options-calculator/pane.test.tsx +138 -0
  317. package/src/plugins/builtin/options-calculator/pane.tsx +218 -0
  318. package/src/plugins/builtin/plugin-module.test.ts +16 -2
  319. package/src/plugins/builtin/plugin-module.ts +5 -1
  320. package/src/plugins/builtin/polls/client.ts +57 -0
  321. package/src/plugins/builtin/polls/hooks.ts +20 -0
  322. package/src/plugins/builtin/polls/index.tsx +35 -0
  323. package/src/plugins/builtin/polls/normalize.test.ts +230 -0
  324. package/src/plugins/builtin/polls/normalize.ts +319 -0
  325. package/src/plugins/builtin/polls/pane.tsx +722 -0
  326. package/src/plugins/builtin/polls/types.ts +77 -0
  327. package/src/plugins/builtin/portfolio-list/header.tsx +15 -4
  328. package/src/plugins/builtin/portfolio-list/pane/index.tsx +7 -3
  329. package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +1 -1
  330. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -0
  331. package/src/plugins/builtin/portfolio-list/summary/index.tsx +2 -1
  332. package/src/plugins/builtin/research/analyst-pane.tsx +40 -67
  333. package/src/plugins/builtin/research/corporate-actions-pane.tsx +25 -19
  334. package/src/plugins/builtin/research/index.test.ts +6 -25
  335. package/src/plugins/builtin/research/index.tsx +11 -5
  336. package/src/plugins/builtin/research/relative-valuation-pane.tsx +86 -26
  337. package/src/plugins/builtin/scanner/denied.tsx +31 -0
  338. package/src/plugins/builtin/scanner/feed.ts +94 -0
  339. package/src/plugins/builtin/scanner/flow-model.test.ts +85 -0
  340. package/src/plugins/builtin/scanner/flow-model.ts +133 -0
  341. package/src/plugins/builtin/scanner/flow-pane.tsx +197 -0
  342. package/src/plugins/builtin/scanner/hilo-bars.tsx +121 -0
  343. package/src/plugins/builtin/scanner/hilo-model.test.ts +44 -0
  344. package/src/plugins/builtin/scanner/hilo-model.ts +75 -0
  345. package/src/plugins/builtin/scanner/hilo-pane.tsx +165 -0
  346. package/src/plugins/builtin/scanner/index.ts +109 -0
  347. package/src/plugins/builtin/scanner/waiting.tsx +11 -0
  348. package/src/plugins/builtin/sec/index.tsx +11 -7
  349. package/src/plugins/builtin/sectors/index.tsx +105 -81
  350. package/src/plugins/builtin/sectors/move-bar.tsx +43 -0
  351. package/src/plugins/builtin/sectors/sector-data.ts +18 -0
  352. package/src/plugins/builtin/sectors/sector-model.ts +13 -15
  353. package/src/plugins/builtin/shared/auto-refresh.ts +51 -0
  354. package/src/plugins/builtin/shared/cloud-upgrade.ts +29 -5
  355. package/src/plugins/builtin/shared/pane-footer.ts +16 -3
  356. package/src/plugins/builtin/shared/screener-live-quotes.test.ts +91 -0
  357. package/src/plugins/builtin/shared/screener-live-quotes.ts +134 -0
  358. package/src/plugins/builtin/shared/table-pane.ts +1 -5
  359. package/src/plugins/builtin/shared/use-quote-board.test.tsx +211 -0
  360. package/src/plugins/builtin/shared/use-quote-board.ts +195 -0
  361. package/src/plugins/builtin/short-interest/client.ts +124 -0
  362. package/src/plugins/builtin/short-interest/index.tsx +62 -0
  363. package/src/plugins/builtin/short-interest/model.ts +113 -0
  364. package/src/plugins/builtin/short-interest/pane.tsx +252 -0
  365. package/src/plugins/builtin/short-interest/types.ts +9 -0
  366. package/src/plugins/builtin/substack/article-detail.tsx +15 -1
  367. package/src/plugins/builtin/substack/pane-footer.ts +3 -22
  368. package/src/plugins/builtin/substack/pane.tsx +1 -2
  369. package/src/plugins/builtin/thirteenf/api.ts +4 -1
  370. package/src/plugins/builtin/thirteenf/model.ts +2 -0
  371. package/src/plugins/builtin/thirteenf/pane.tsx +6 -2
  372. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +10 -4
  373. package/src/plugins/builtin/ticker-detail/data-panes/provider-search.tsx +67 -11
  374. package/src/plugins/builtin/ticker-detail/financials/tab.tsx +15 -8
  375. package/src/plugins/builtin/ticker-detail/index.tsx +5 -21
  376. package/src/plugins/builtin/ticker-detail/overview/components.tsx +72 -32
  377. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +0 -10
  378. package/src/plugins/builtin/ticker-detail/pane.tsx +4 -1
  379. package/src/plugins/builtin/ticker-detail/quote-monitor/card.test.tsx +70 -0
  380. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +34 -26
  381. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +96 -49
  382. package/src/plugins/builtin/ticker-detail/research-tabs.ts +32 -0
  383. package/src/plugins/builtin/ticker-research-backend-plugin.ts +2 -9
  384. package/src/plugins/builtin/ticker-research-plugin.ts +6 -0
  385. package/src/plugins/builtin/treasury-auctions/cache.test.ts +173 -0
  386. package/src/plugins/builtin/treasury-auctions/cache.ts +104 -0
  387. package/src/plugins/builtin/treasury-auctions/client.test.ts +181 -0
  388. package/src/plugins/builtin/treasury-auctions/client.ts +151 -0
  389. package/src/plugins/builtin/treasury-auctions/index.tsx +90 -0
  390. package/src/plugins/builtin/treasury-auctions/model.test.ts +147 -0
  391. package/src/plugins/builtin/treasury-auctions/model.ts +188 -0
  392. package/src/plugins/builtin/treasury-auctions/pane.test.tsx +170 -0
  393. package/src/plugins/builtin/treasury-auctions/pane.tsx +417 -0
  394. package/src/plugins/builtin/treasury-auctions/types.ts +59 -0
  395. package/src/plugins/builtin/tv/pane.tsx +67 -7
  396. package/src/plugins/builtin/tv/youtube-embed.test.ts +84 -0
  397. package/src/plugins/builtin/tv/youtube-embed.ts +173 -0
  398. package/src/plugins/builtin/tv/youtube-stream.ts +27 -2
  399. package/src/plugins/builtin/volatility/client.ts +87 -0
  400. package/src/plugins/builtin/volatility/index.test.tsx +107 -0
  401. package/src/plugins/builtin/volatility/index.tsx +193 -0
  402. package/src/plugins/builtin/volatility/model.test.ts +66 -0
  403. package/src/plugins/builtin/volatility/model.ts +113 -0
  404. package/src/plugins/builtin/world-indices/footer.ts +43 -23
  405. package/src/plugins/builtin/world-indices/index.tsx +44 -93
  406. package/src/plugins/builtin/world-indices/indices.ts +11 -2
  407. package/src/plugins/builtin/world-indices/model.ts +17 -24
  408. package/src/plugins/builtin/world-indices/pane.test.tsx +116 -0
  409. package/src/plugins/builtin/world-indices/table.tsx +82 -37
  410. package/src/plugins/builtin/yield-curve/index.tsx +16 -7
  411. package/src/plugins/builtin/yield-curve/treasury-data.test.ts +25 -0
  412. package/src/plugins/builtin/yield-curve/treasury-data.ts +16 -0
  413. package/src/plugins/catalog-backend.ts +8 -0
  414. package/src/plugins/catalog-ui.ts +8 -0
  415. package/src/plugins/event-bus.ts +1 -0
  416. package/src/plugins/ibkr/gateway/account-loaders.ts +1 -1
  417. package/src/plugins/ibkr/trade/tab/footer.ts +9 -23
  418. package/src/plugins/ibkr/trade/tab/index.tsx +1 -5
  419. package/src/plugins/ibkr/trading/pane-view.tsx +149 -87
  420. package/src/plugins/ibkr/trading/pane.tsx +2 -2
  421. package/src/plugins/ownership.ts +3 -0
  422. package/src/plugins/pane-manager/floating.ts +8 -2
  423. package/src/plugins/pane-manager.test.ts +13 -0
  424. package/src/plugins/prediction-markets/backend-plugin.ts +2 -0
  425. package/src/plugins/prediction-markets/capability.test.ts +281 -0
  426. package/src/plugins/prediction-markets/capability.ts +171 -0
  427. package/src/plugins/prediction-markets/chart.tsx +18 -26
  428. package/src/plugins/prediction-markets/columns.ts +3 -2
  429. package/src/plugins/prediction-markets/controller/catalog.ts +15 -12
  430. package/src/plugins/prediction-markets/controller/detail.ts +4 -0
  431. package/src/plugins/prediction-markets/controller/index.ts +0 -15
  432. package/src/plugins/prediction-markets/detail/book.tsx +3 -23
  433. package/src/plugins/prediction-markets/detail/overview.tsx +0 -18
  434. package/src/plugins/prediction-markets/detail/pane.tsx +36 -12
  435. package/src/plugins/prediction-markets/detail.test.tsx +1 -1
  436. package/src/plugins/prediction-markets/index.tsx +2 -0
  437. package/src/plugins/prediction-markets/metrics.ts +28 -18
  438. package/src/plugins/prediction-markets/navigation.ts +1 -0
  439. package/src/plugins/prediction-markets/pane.test.tsx +1 -1
  440. package/src/plugins/prediction-markets/pane.tsx +0 -1
  441. package/src/plugins/prediction-markets/services/fetch.ts +3 -2
  442. package/src/plugins/prediction-markets/services/kalshi/adapter.ts +82 -23
  443. package/src/plugins/prediction-markets/services/kalshi/normalize.ts +3 -1
  444. package/src/plugins/prediction-markets/services/kalshi/types.ts +14 -0
  445. package/src/plugins/prediction-markets/services/polymarket/adapter.ts +26 -14
  446. package/src/plugins/prediction-markets/services/polymarket/detail.ts +67 -18
  447. package/src/plugins/prediction-markets/test-helpers.tsx +0 -1
  448. package/src/plugins/prediction-markets/types.ts +3 -9
  449. package/src/plugins/registry/context.ts +6 -1
  450. package/src/plugins/registry/index.test.ts +2 -0
  451. package/src/plugins/registry/index.ts +53 -3
  452. package/src/plugins/runtime/context.tsx +4 -2
  453. package/src/plugins/runtime/index.tsx +9 -0
  454. package/src/remote/controller.test.ts +17 -4
  455. package/src/renderers/electrobun/bun/application-menu/index.ts +1 -1
  456. package/src/renderers/electrobun/bun/core-capabilities.ts +2 -2
  457. package/src/renderers/electrobun/bun/desktop/capability-bridge.test.ts +64 -0
  458. package/src/renderers/electrobun/bun/desktop/capability-bridge.ts +41 -7
  459. package/src/renderers/electrobun/bun/desktop/host-requests.ts +5 -2
  460. package/src/renderers/electrobun/bun/desktop/http-fetch.test.ts +33 -0
  461. package/src/renderers/electrobun/bun/desktop/http-fetch.ts +53 -6
  462. package/src/renderers/electrobun/bun/desktop/initialization.test.ts +27 -0
  463. package/src/renderers/electrobun/bun/desktop/initialization.ts +6 -1
  464. package/src/renderers/electrobun/bun/index.ts +1 -0
  465. package/src/renderers/electrobun/shared/protocol.ts +2 -0
  466. package/src/renderers/electrobun/view/app-services.ts +25 -2
  467. package/src/renderers/electrobun/view/build-assets.ts +5 -0
  468. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +186 -65
  469. package/src/renderers/electrobun/view/cli-pane-shot-health.test.ts +25 -0
  470. package/src/renderers/electrobun/view/cli-pane-shot-health.ts +31 -0
  471. package/src/renderers/electrobun/view/data-table/index.tsx +10 -11
  472. package/src/renderers/electrobun/view/data-table/row.tsx +2 -0
  473. package/src/renderers/electrobun/view/desktop/controls.test.tsx +68 -0
  474. package/src/renderers/electrobun/view/desktop/controls.tsx +25 -1
  475. package/src/renderers/electrobun/view/host/box.test.tsx +80 -0
  476. package/src/renderers/electrobun/view/host/media-surface.tsx +70 -3
  477. package/src/renderers/electrobun/view/host/tabs.tsx +97 -5
  478. package/src/renderers/electrobun/view/host/text.tsx +5 -2
  479. package/src/renderers/electrobun/view/input-host.test.tsx +140 -0
  480. package/src/renderers/electrobun/view/input-host.tsx +4 -2
  481. package/src/renderers/electrobun/view/native-stubs/backend-rpc.ts +19 -1
  482. package/src/renderers/electrobun/view/native-stubs/broker-sync-native-loader.ts +7 -0
  483. package/src/renderers/electrobun/view/native-stubs/chart/kitty-support.ts +10 -0
  484. package/src/renderers/electrobun/view/remote/capability-invoker.test.ts +46 -0
  485. package/src/renderers/electrobun/view/remote/capability-invoker.ts +34 -13
  486. package/src/renderers/electrobun/view/remote/connection-health-backend.ts +19 -0
  487. package/src/renderers/electrobun/view/remote/connection-health.test.ts +44 -0
  488. package/src/renderers/electrobun/view/remote/connection-health.ts +49 -0
  489. package/src/renderers/electrobun/view/styles.css +5 -0
  490. package/src/renderers/opentui/dialog-host.tsx +279 -80
  491. package/src/renderers/opentui/host.tsx +15 -1
  492. package/src/renderers/opentui/terminal-media.test.ts +102 -0
  493. package/src/renderers/opentui/terminal-media.ts +142 -0
  494. package/src/renderers/opentui/test-utils.tsx +9 -6
  495. package/src/renderers/opentui/toast-host.tsx +136 -7
  496. package/src/renderers/opentui/ui-host.tsx +19 -3
  497. package/src/sources/provider-router/connection-health.test.ts +28 -0
  498. package/src/sources/provider-router/connection-health.ts +47 -0
  499. package/src/sources/provider-router/history.test.ts +53 -4
  500. package/src/sources/provider-router/history.ts +119 -14
  501. package/src/sources/provider-router/index.ts +12 -1
  502. package/src/sources/yahoo-finance/history.ts +2 -9
  503. package/src/sources/yahoo-finance/http.ts +1 -1
  504. package/src/sources/yahoo-finance/symbols.ts +1 -1
  505. package/src/sources/yahoo-finance/types.ts +19 -0
  506. package/src/state/app/context/index.test.tsx +101 -0
  507. package/src/state/app/context/index.tsx +10 -0
  508. package/src/state/config-save-scheduler.ts +1 -6
  509. package/src/state/persist-scheduler.test.ts +31 -0
  510. package/src/state/persist-scheduler.ts +21 -8
  511. package/src/sync/auth-transition.test.ts +36 -0
  512. package/src/sync/auth-transition.ts +22 -0
  513. package/src/sync/core-contributors.test.ts +58 -1
  514. package/src/sync/core-contributors.ts +39 -2
  515. package/src/sync/react.ts +9 -0
  516. package/src/test-support/plugin-runtime.ts +3 -0
  517. package/src/theme/font-scale.test.ts +14 -0
  518. package/src/theme/font-scale.ts +69 -0
  519. package/src/tickers/search/index.test.ts +284 -2
  520. package/src/tickers/search/index.ts +55 -18
  521. package/src/tickers/search/ranking.ts +143 -18
  522. package/src/tickers/search/types.ts +1 -0
  523. package/src/time-series/alignment.ts +15 -11
  524. package/src/time-series/field-catalog.ts +10 -0
  525. package/src/time-series/fundamentals.ts +19 -11
  526. package/src/time-series/hooks.ts +8 -2
  527. package/src/time-series/parsed-history-cache.ts +35 -0
  528. package/src/time-series/resolution.ts +10 -0
  529. package/src/time-series/resolve.test.ts +332 -3
  530. package/src/time-series/resolve.ts +225 -25
  531. package/src/time-series/spec-studies.test.ts +23 -0
  532. package/src/time-series/spec.ts +21 -2
  533. package/src/time-series/transforms.test.ts +76 -1
  534. package/src/time-series/transforms.ts +39 -8
  535. package/src/time-series/types.ts +9 -2
  536. package/src/time-series/use-chart-resolution.test.tsx +105 -1
  537. package/src/time-series/use-chart-resolution.ts +67 -13
  538. package/src/types/config.ts +30 -27
  539. package/src/types/media.ts +2 -0
  540. package/src/types/plugin.ts +8 -1
  541. package/src/ui/host.tsx +4 -0
  542. package/src/ui/pointer-drag.ts +12 -0
  543. package/src/ui/qr.test.ts +79 -0
  544. package/src/ui/qr.ts +82 -0
  545. package/src/updater/github-releases.test.ts +3 -3
  546. package/src/updater/github-releases.ts +1 -1
  547. package/src/updater.test.ts +117 -16
  548. package/src/updater.ts +32 -5
  549. package/src/utils/exchanges.ts +1 -0
  550. package/src/utils/external-url.test.ts +32 -0
  551. package/src/utils/external-url.ts +23 -0
  552. package/src/utils/sort-values.ts +31 -0
  553. package/src/utils/zoned-date-time.ts +67 -0
  554. package/src/components/onboarding/wizard-keyboard.ts +0 -306
  555. package/src/plugins/builtin/chart-composer/date-window-dialog.tsx +0 -120
@@ -0,0 +1,101 @@
1
+ import type { ResolvedSeries, TimeSeriesPoint } from "../../../time-series/types";
2
+
3
+ function sameTime(left: Date | undefined, right: Date | undefined): boolean {
4
+ if (left === right) return true;
5
+ if (!left || !right) return left == right;
6
+ return left.getTime() === right.getTime();
7
+ }
8
+
9
+ function samePoint(left: TimeSeriesPoint, right: TimeSeriesPoint): boolean {
10
+ return left === right
11
+ || (
12
+ sameTime(left.date, right.date)
13
+ && sameTime(left.observedAt, right.observedAt)
14
+ && sameTime(left.availableAt, right.availableAt)
15
+ && left.value === right.value
16
+ && left.open === right.open
17
+ && left.high === right.high
18
+ && left.low === right.low
19
+ && left.close === right.close
20
+ && left.volume === right.volume
21
+ && left.periodLabel === right.periodLabel
22
+ );
23
+ }
24
+
25
+ function sameSeriesMeta(left: ResolvedSeries, right: ResolvedSeries): boolean {
26
+ return left.id === right.id
27
+ && left.label === right.label
28
+ && left.color === right.color
29
+ && left.unit === right.unit
30
+ && left.unitGroup === right.unitGroup
31
+ && left.nativeFrequency === right.nativeFrequency
32
+ && left.timestampMode === right.timestampMode
33
+ && left.dataShape === right.dataShape
34
+ && left.style === right.style
35
+ && left.transform === right.transform
36
+ && left.axis === right.axis
37
+ && left.panelId === right.panelId
38
+ && left.interpolation === right.interpolation
39
+ && left.warning === right.warning
40
+ && left.hidden === right.hidden
41
+ && left.timeBasis?.kind === right.timeBasis?.kind
42
+ && left.timeBasis?.timeZone === right.timeBasis?.timeZone
43
+ && left.timeBasis?.cadenceMs === right.timeBasis?.cadenceMs;
44
+ }
45
+
46
+ function prefixPointsEqual(left: readonly TimeSeriesPoint[], right: readonly TimeSeriesPoint[]): boolean {
47
+ if (left.length !== right.length || left.length === 0) return false;
48
+ for (let index = 0; index < left.length - 1; index += 1) {
49
+ if (!samePoint(left[index]!, right[index]!)) return false;
50
+ }
51
+ return true;
52
+ }
53
+
54
+ /**
55
+ * Reuses a previous ResolvedSeries object when a parent allocated a new wrapper
56
+ * for the same data, including live ticks that only change the last bar's
57
+ * close/time. Bitmap dirty detection keys off object identity, so this keeps
58
+ * an otherwise-stable plot from full-rerastering on every quote.
59
+ */
60
+ export function reuseResolvedSeriesIdentity(
61
+ previous: ResolvedSeries | undefined,
62
+ next: ResolvedSeries,
63
+ ): ResolvedSeries {
64
+ if (!previous) return next;
65
+ if (previous === next) return next;
66
+ if (!sameSeriesMeta(previous, next)) return next;
67
+ if (previous.points === next.points && previous.latestChangePercent === next.latestChangePercent) {
68
+ return previous;
69
+ }
70
+ if (previous.points.length !== next.points.length) return next;
71
+ const lastPrev = previous.points[previous.points.length - 1]!;
72
+ const lastNext = next.points[next.points.length - 1]!;
73
+ if (!samePoint(lastPrev, lastNext)) {
74
+ if (!prefixPointsEqual(previous.points, next.points)) return next;
75
+ previous.points[previous.points.length - 1] = lastNext;
76
+ previous.latestChangePercent = next.latestChangePercent;
77
+ return previous;
78
+ }
79
+ if (previous.points.every((point, index) => samePoint(point, next.points[index]!))) {
80
+ if (previous.latestChangePercent === next.latestChangePercent) return previous;
81
+ previous.latestChangePercent = next.latestChangePercent;
82
+ return previous;
83
+ }
84
+ return next;
85
+ }
86
+
87
+ export function reuseResolvedSeriesList(
88
+ previous: readonly ResolvedSeries[] | undefined,
89
+ next: readonly ResolvedSeries[],
90
+ ): ResolvedSeries[] {
91
+ if (!previous) return next as ResolvedSeries[];
92
+ if (previous === next) return previous as ResolvedSeries[];
93
+ const reused = next.map((series, index) => reuseResolvedSeriesIdentity(previous[index], series));
94
+ if (
95
+ reused.length === previous.length
96
+ && reused.every((series, index) => series === previous[index])
97
+ ) {
98
+ return previous as ResolvedSeries[];
99
+ }
100
+ return reused;
101
+ }
@@ -6,6 +6,7 @@ import { valueToPixelY, type MultiLineChartScene, type MultiLineProjectedPoint }
6
6
  export {
7
7
  buildMultiLineChartScene,
8
8
  resolveMultiLineCursorDate,
9
+ valueToPixelY,
9
10
  } from "./scene";
10
11
  export type {
11
12
  MultiLineChartColors,
@@ -11,7 +11,11 @@ import {
11
11
  } from "../chart-rasterizer";
12
12
  import { KittyImageManager } from "./manager";
13
13
  import { chunkBase64Payload, encodeKittyTransmitRgba } from "./protocol";
14
- import { ensureKittySupport, getCachedKittySupport } from "./support";
14
+ import {
15
+ ensureKittySupport,
16
+ getCachedKittySupport,
17
+ resolveKittySupport,
18
+ } from "./support";
15
19
  import { resolveChartRendererState } from "../renderer-selection";
16
20
  import { computeSurfaceVisibleFragments, NativeSurfaceManager } from "../surface/manager";
17
21
  import { syncCachedNativeSurface } from "../surface/sync";
@@ -42,9 +46,27 @@ describe("resolveChartRendererState", () => {
42
46
  });
43
47
  });
44
48
 
49
+ describe("resolveKittySupport", () => {
50
+ const noMultiplexer = { TERM: "xterm-kitty" } as Record<string, string | undefined>;
51
+
52
+ test("never claims support behind a multiplexer, whatever it advertises", () => {
53
+ expect(resolveKittySupport({ kitty_graphics: true, multiplexer: "tmux" }, noMultiplexer)).toBe(false);
54
+ expect(resolveKittySupport({ kitty_graphics: true }, { TERM: "tmux-256color" })).toBe(false);
55
+ expect(resolveKittySupport({ kitty_graphics: true }, { TERM_PROGRAM: "tmux" })).toBe(false);
56
+ expect(resolveKittySupport({ kitty_graphics: true }, { TMUX: "/tmp/tmux-501/default,1,0" })).toBe(false);
57
+ });
58
+
59
+ test("keeps confirmed support and leaves an unanswered query undecided", () => {
60
+ expect(resolveKittySupport({ kitty_graphics: true, multiplexer: "none" }, noMultiplexer)).toBe(true);
61
+ expect(resolveKittySupport({ kitty_graphics: false }, noMultiplexer)).toBe(false);
62
+ expect(resolveKittySupport({}, noMultiplexer)).toBe(null);
63
+ expect(resolveKittySupport(null, noMultiplexer)).toBe(null);
64
+ });
65
+ });
66
+
45
67
  describe("getCachedKittySupport", () => {
46
68
  test("prefers later renderer capabilities over a failed query cache", async () => {
47
- let capabilities: { kitty_graphics?: boolean } | null = null;
69
+ let capabilities: { kitty_graphics?: boolean; multiplexer?: string } | null = null;
48
70
  const renderer = {
49
71
  isDestroyed: false,
50
72
  get capabilities() {
@@ -60,7 +82,7 @@ describe("getCachedKittySupport", () => {
60
82
  expect(await ensureKittySupport(renderer)).toBe(false);
61
83
  expect(getCachedKittySupport(renderer)).toBe(false);
62
84
 
63
- capabilities = { kitty_graphics: true };
85
+ capabilities = { kitty_graphics: true, multiplexer: "none" };
64
86
  expect(getCachedKittySupport(renderer)).toBe(true);
65
87
  });
66
88
  });
@@ -2,8 +2,9 @@ import { type NativeRendererHost as CliRenderer } from "../../../../ui";
2
2
  import { writeRendererRaw } from "./adapter";
3
3
  import { buildKittyGraphicsQuery } from "./protocol";
4
4
 
5
- interface RendererCapabilities {
5
+ export interface RendererCapabilities {
6
6
  kitty_graphics?: boolean;
7
+ multiplexer?: string;
7
8
  }
8
9
 
9
10
  interface SupportCacheEntry {
@@ -14,11 +15,42 @@ interface SupportCacheEntry {
14
15
  const supportCache = new WeakMap<CliRenderer, SupportCacheEntry>();
15
16
  const QUERY_TIMEOUT_MS = 250;
16
17
 
17
- function readKnownSupport(renderer: CliRenderer): boolean | null {
18
- const capabilities = renderer.capabilities as RendererCapabilities | null;
18
+ /**
19
+ * A multiplexer swallows the graphics query and then passes image payloads
20
+ * through as literal text, which shreds the screen. Whatever it advertises,
21
+ * kitty graphics are not usable behind one.
22
+ */
23
+ export function isMultiplexedTerminal(
24
+ capabilities: RendererCapabilities | null | undefined,
25
+ env: Record<string, string | undefined> = process.env,
26
+ ): boolean {
27
+ const reported = capabilities?.multiplexer;
28
+ // The renderer's own detection wins when it has one; the environment is only
29
+ // consulted when nothing was reported.
30
+ if (typeof reported === "string") return reported !== "none";
31
+ if (env.TMUX) return true;
32
+ const termProgram = (env.TERM_PROGRAM ?? "").toLowerCase();
33
+ if (termProgram === "tmux" || termProgram === "screen") return true;
34
+ const term = (env.TERM ?? "").toLowerCase();
35
+ return term.startsWith("tmux") || term.startsWith("screen");
36
+ }
37
+
38
+ /**
39
+ * What is known about kitty graphics before querying: `null` means undecided,
40
+ * so the query result (or its absence) decides.
41
+ */
42
+ export function resolveKittySupport(
43
+ capabilities: RendererCapabilities | null | undefined,
44
+ env: Record<string, string | undefined> = process.env,
45
+ ): boolean | null {
46
+ if (isMultiplexedTerminal(capabilities, env)) return false;
19
47
  return typeof capabilities?.kitty_graphics === "boolean" ? capabilities.kitty_graphics : null;
20
48
  }
21
49
 
50
+ function readKnownSupport(renderer: CliRenderer): boolean | null {
51
+ return resolveKittySupport(renderer.capabilities as RendererCapabilities | null);
52
+ }
53
+
22
54
  export function getCachedKittySupport(renderer: CliRenderer): boolean | null {
23
55
  const known = readKnownSupport(renderer);
24
56
  if (known !== null) return known;
@@ -54,6 +86,8 @@ export function ensureKittySupport(renderer: CliRenderer): Promise<boolean> {
54
86
  if (supported !== null) finish(supported);
55
87
  };
56
88
 
89
+ // No answer means unsupported: a terminal that speaks the protocol answers,
90
+ // and anything that swallows the query would also swallow the image.
57
91
  const timer = setTimeout(() => finish(false), QUERY_TIMEOUT_MS);
58
92
  renderer.on("capabilities", onCapabilities);
59
93
  if (!writeRendererRaw(renderer, `${buildKittyGraphicsQuery()}\x1b[c`)) {
@@ -0,0 +1,19 @@
1
+ import { useNativeRenderer } from "../../../ui";
2
+ import { useOptionalAppSelector } from "../../../state/app/context";
3
+ import type { ChartRendererPreference } from "../core/types";
4
+ import { useResolvedChartRendererState } from "./renderer-selection";
5
+
6
+ /**
7
+ * Cell-character plot text is only visible when kitty is not the active chart
8
+ * renderer. Skip rebuilding braille/ASCII fallback while the native bitmap is
9
+ * on screen.
10
+ */
11
+ export function useShowChartTextFallback(): boolean {
12
+ const renderer = useNativeRenderer();
13
+ const preferredRenderer = useOptionalAppSelector<ChartRendererPreference>(
14
+ (state) => state.config.chartPreferences.renderer,
15
+ "braille",
16
+ );
17
+ const rendererState = useResolvedChartRendererState(preferredRenderer, renderer);
18
+ return rendererState.renderer !== "kitty";
19
+ }
@@ -8,8 +8,10 @@ import {
8
8
  computeGridLines,
9
9
  renderChart,
10
10
  type RenderChartOptions,
11
+ type RenderChartResult,
11
12
  } from "../../core/renderer";
12
13
  import { renderNativeChartBase, type NativeChartBitmap } from "../../native/chart-rasterizer";
14
+ import { useShowChartTextFallback } from "../../native/use-chart-text-fallback";
13
15
  import { useStaticChartBitmapSize } from "./bitmap";
14
16
  import {
15
17
  StaticXAxisLabels,
@@ -103,6 +105,7 @@ export function StaticChartSurface({
103
105
  }: StaticChartSurfaceProps) {
104
106
  const themeColors = useThemeColors();
105
107
  const { cellWidthPx = 8, cellHeightPx = 18 } = useUiCapabilities();
108
+ const showTextFallback = useShowChartTextFallback();
106
109
  const plotRef = useRef<BoxRenderable | null>(null);
107
110
  const [cursor, setCursor] = useState<{ x: number; y: number } | null>(null);
108
111
  const xAxisRows = showTimeAxis || (xAxisLabels?.length ?? 0) > 0 ? 1 : 0;
@@ -157,13 +160,13 @@ export function StaticChartSurface({
157
160
  const axisGap = axisWidth > 0 ? 1 : 0;
158
161
  const plotWidth = Math.max(1, totalWidth - axisWidth - axisGap);
159
162
  const bitmapSize = useStaticChartBitmapSize(plotWidth, plotHeight);
160
- const renderOptions = useMemo<RenderChartOptions>(() => ({
163
+ const plotOptions = useMemo<RenderChartOptions>(() => ({
161
164
  width: plotWidth,
162
165
  height: plotHeight,
163
166
  showVolume,
164
167
  volumeHeight,
165
- cursorX: cursor?.x ?? null,
166
- cursorY: cursor?.y ?? null,
168
+ cursorX: null,
169
+ cursorY: null,
167
170
  mode,
168
171
  axisMode,
169
172
  currency,
@@ -180,15 +183,45 @@ export function StaticChartSurface({
180
183
  mode,
181
184
  plotHeight,
182
185
  plotWidth,
183
- cursor,
184
186
  showVolume,
185
187
  timeAxisDates,
186
188
  volumeHeight,
187
189
  ]);
188
- const textResult = useMemo(
189
- () => renderChart(points, renderOptions),
190
- [points, renderOptions],
190
+ const cursorOptions = useMemo<RenderChartOptions>(() => ({
191
+ ...plotOptions,
192
+ cursorX: cursor?.x ?? null,
193
+ cursorY: cursor?.y ?? null,
194
+ }), [cursor, plotOptions]);
195
+ const plotScene = useMemo(
196
+ () => buildChartScene(points, plotOptions),
197
+ [plotOptions, points],
198
+ );
199
+ const cursorScene = useMemo(
200
+ () => {
201
+ if (showTextFallback || !cursor) return plotScene;
202
+ return buildChartScene(points, cursorOptions);
203
+ },
204
+ [cursor, cursorOptions, plotScene, points, showTextFallback],
191
205
  );
206
+ const textResult = useMemo<RenderChartResult>(() => {
207
+ if (showTextFallback) return renderChart(points, cursorOptions);
208
+ return {
209
+ lines: [],
210
+ axisLabels: [],
211
+ timeLabels: cursorScene?.timeLabels ?? "",
212
+ activePoint: cursorScene?.activePoint ?? null,
213
+ priceAtCursor: cursorScene?.priceAtCursor ?? null,
214
+ crosshairPrice: cursorScene?.crosshairPrice ?? null,
215
+ dateAtCursor: cursorScene?.dateAtCursor ?? null,
216
+ changeAtCursor: cursorScene?.changeAtCursor ?? null,
217
+ changePctAtCursor: cursorScene?.changePctAtCursor ?? null,
218
+ cursorColumn: cursorScene?.cursorColumn ?? null,
219
+ cursorRow: cursorScene?.cursorRow ?? null,
220
+ axisFractionDigits: null,
221
+ priceRange: cursorScene ? cursorScene.max - cursorScene.min : null,
222
+ pixelBuffer: null,
223
+ };
224
+ }, [cursorOptions, cursorScene, points, showTextFallback]);
192
225
  const effectiveAxisLabelsByRow = useMemo(() => {
193
226
  return new Map(axisLabels.map((entry) => [entry.row, entry.label] as const));
194
227
  }, [axisLabels]);
@@ -203,11 +236,9 @@ export function StaticChartSurface({
203
236
  const effectiveAxisWidth = axisWidth;
204
237
  const effectiveAxisGap = effectiveAxisWidth > 0 ? 1 : 0;
205
238
  const bitmap = useMemo<NativeChartBitmap | null>(() => {
206
- if (!bitmapSize) return null;
207
- const scene = buildChartScene(points, renderOptions);
208
- if (!scene) return null;
209
- return renderNativeChartBase(scene, bitmapSize.pixelWidth, bitmapSize.pixelHeight);
210
- }, [bitmapSize, points, renderOptions]);
239
+ if (!bitmapSize || !plotScene) return null;
240
+ return renderNativeChartBase(plotScene, bitmapSize.pixelWidth, bitmapSize.pixelHeight);
241
+ }, [bitmapSize, plotScene]);
211
242
  const canvasCrosshair = useMemo<ChartSurfaceProps["crosshair"]>(() => {
212
243
  if (!bitmap || !cursor) return null;
213
244
  return {
@@ -1,8 +1,9 @@
1
1
  import { useCallback, useMemo, useRef } from "react";
2
- import { Box, ChartSurface, Text, type BoxRenderable } from "../../../ui";
2
+ import { Box, ChartSurface, Text, type BoxRenderable, type ChartSurfaceProps } from "../../../ui";
3
3
  import { colors } from "../../../theme/colors";
4
4
  import { computeGridLines, formatAxisCell } from "../core/renderer";
5
5
  import type { NativeChartBitmap } from "../native/chart-rasterizer";
6
+ import { useShowChartTextFallback } from "../native/use-chart-text-fallback";
6
7
  import { useStaticChartBitmapSize } from "./chart/bitmap";
7
8
  import {
8
9
  buildMultiLineChartScene,
@@ -10,6 +11,7 @@ import {
10
11
  renderMultiLineTimeAxis,
11
12
  renderNativeMultiLineChart,
12
13
  resolveMultiLineCursorDate,
14
+ valueToPixelY,
13
15
  type MultiLineChartColors,
14
16
  type MultiLineChartSeries,
15
17
  } from "../multi-line/renderer";
@@ -101,15 +103,27 @@ export function StaticMultiLineChartSurface({
101
103
  : 0;
102
104
  const axisGap = axisWidth > 0 ? 1 : 0;
103
105
  const plotWidth = Math.max(1, totalWidth - axisWidth - axisGap);
104
- const scene = useMemo(() => buildMultiLineChartScene(series, {
106
+ const plotOptions = useMemo(() => ({
105
107
  width: plotWidth,
106
108
  height: plotHeight,
107
109
  colors: chartColors,
108
110
  dates,
109
- cursorDate,
111
+ cursorDate: null as Date | null,
110
112
  yDomain,
111
- }), [chartColors, cursorDate, dates, plotHeight, plotWidth, series, yDomain]);
112
- const textLines = useMemo(() => scene ? renderMultiLineChart(scene) : [], [scene]);
113
+ }), [chartColors, dates, plotHeight, plotWidth, yDomain]);
114
+ const scene = useMemo(
115
+ () => buildMultiLineChartScene(series, plotOptions),
116
+ [plotOptions, series],
117
+ );
118
+ const cursorScene = useMemo(
119
+ () => cursorDate == null ? scene : buildMultiLineChartScene(series, { ...plotOptions, cursorDate }),
120
+ [cursorDate, plotOptions, scene, series],
121
+ );
122
+ const showTextFallback = useShowChartTextFallback();
123
+ const textLines = useMemo(() => {
124
+ if (!showTextFallback) return [];
125
+ return cursorScene ? renderMultiLineChart(cursorScene) : [];
126
+ }, [cursorScene, showTextFallback]);
113
127
  const timeLabels = useMemo(() => scene ? renderMultiLineTimeAxis(scene) : "", [scene]);
114
128
  const effectiveAxisLabelsByRow = useMemo(() => {
115
129
  return new Map(axisLabels.map((entry) => [entry.row, entry.label] as const));
@@ -119,6 +133,27 @@ export function StaticMultiLineChartSurface({
119
133
  if (!scene || !bitmapSize) return null;
120
134
  return renderNativeMultiLineChart(scene, bitmapSize.pixelWidth, bitmapSize.pixelHeight);
121
135
  }, [bitmapSize, scene]);
136
+ const canvasCrosshair = useMemo<ChartSurfaceProps["crosshair"]>(() => {
137
+ if (!bitmap || !cursorScene || cursorScene.cursorX === null) return null;
138
+ const pixelX = (cursorScene.cursorX / Math.max(cursorScene.width - 1, 1)) * Math.max(bitmap.width - 1, 0);
139
+ const cursorIndex = cursorScene.cursorIndex;
140
+ const markers = cursorIndex === null
141
+ ? []
142
+ : cursorScene.series.flatMap((item) => {
143
+ const point = item.points[cursorIndex];
144
+ if (!point) return [];
145
+ return [{
146
+ pixelY: valueToPixelY(point.value, cursorScene.min, cursorScene.max, bitmap.height),
147
+ color: item.color,
148
+ }];
149
+ });
150
+ return {
151
+ pixelX,
152
+ pixelY: markers[0]?.pixelY ?? bitmap.height / 2,
153
+ color: chartColors.crosshairColor,
154
+ markers: markers.length > 0 ? markers : undefined,
155
+ };
156
+ }, [bitmap, chartColors.crosshairColor, cursorScene]);
122
157
 
123
158
  const handleCursorEvent = useCallback((event: ChartMouseEventLike) => {
124
159
  if (!scene || !onCursorDateChange) return;
@@ -152,6 +187,7 @@ export function StaticMultiLineChartSurface({
152
187
  height={plotHeight}
153
188
  flexDirection="column"
154
189
  bitmaps={bitmap ? [bitmap] : null}
190
+ crosshair={canvasCrosshair}
155
191
  onMouseMove={handleCursorEvent}
156
192
  onMouseDown={handleCursorEvent}
157
193
  >
@@ -11,6 +11,7 @@ import {
11
11
  applyTheme,
12
12
  clearTransientThemePreview,
13
13
  } from "../../../../theme/colors";
14
+ import { clampFontSize, MIN_FONT_SIZE_PX } from "../../../../theme/font-scale";
14
15
  import type { AppAction, AppState } from "../../../../state/app/context";
15
16
  import { isManualPortfolio } from "../../../../plugins/builtin/portfolio-list/mutations";
16
17
  import { CHART_RENDERER_PREFERENCES } from "../../../chart/core/types";
@@ -262,6 +263,23 @@ export function runDirectCommandAction(options: {
262
263
  closeAll({ revertThemePreview: false });
263
264
  return;
264
265
  }
266
+ case "font-size-increase":
267
+ case "font-size-decrease": {
268
+ const delta = command.id === "font-size-increase" ? 1 : -1;
269
+ const current = clampFontSize(state.config.fontSize);
270
+ const next = clampFontSize(current + delta);
271
+ if (next === current) {
272
+ notify(`Font size already at ${next === MIN_FONT_SIZE_PX ? "minimum" : "maximum"} (${next}px)`, { type: "info" });
273
+ closeAll({ revertThemePreview: false });
274
+ return;
275
+ }
276
+ const nextConfig = { ...state.config, fontSize: next };
277
+ dispatch({ type: "SET_CONFIG", config: nextConfig });
278
+ persistConfig(nextConfig);
279
+ notify(`Font size: ${next}px`, { type: "success" });
280
+ closeAll({ revertThemePreview: false });
281
+ return;
282
+ }
265
283
  case "check-for-updates":
266
284
  void onCheckForUpdates?.();
267
285
  closeAll({ revertThemePreview: false });
@@ -173,6 +173,20 @@ export const commands: Command[] = [
173
173
  description: "Turn quote update flashing on or off",
174
174
  category: "Config",
175
175
  },
176
+ {
177
+ id: "font-size-increase",
178
+ prefix: "FONT+",
179
+ label: "Increase Font Size",
180
+ description: "Increase the app-wide font size",
181
+ category: "Config",
182
+ },
183
+ {
184
+ id: "font-size-decrease",
185
+ prefix: "FONT-",
186
+ label: "Decrease Font Size",
187
+ description: "Decrease the app-wide font size",
188
+ category: "Config",
189
+ },
176
190
  {
177
191
  id: "check-for-updates",
178
192
  prefix: "",
@@ -0,0 +1,83 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import type { NewsArticle } from "../../../../news/types";
3
+ import { buildArticleSearchResultItems } from "./article-results";
4
+ import { isArticleLookupShortcut } from "./results";
5
+
6
+ function article(title: string, source: string): NewsArticle {
7
+ return {
8
+ id: title,
9
+ title,
10
+ source,
11
+ url: `https://example.com/${title}`,
12
+ publishedAt: new Date("2026-08-14T12:00:00Z"),
13
+ topic: "general",
14
+ topics: [],
15
+ sectors: [],
16
+ categories: [],
17
+ tickers: [],
18
+ scores: { importance: 50, urgency: 0, marketImpact: 0, novelty: 0, confidence: 0 },
19
+ isBreaking: false,
20
+ isDeveloping: false,
21
+ importance: 50,
22
+ };
23
+ }
24
+
25
+ describe("buildArticleSearchResultItems", () => {
26
+ test("offers the matching article to open", () => {
27
+ const opened: string[] = [];
28
+ const items = buildArticleSearchResultItems({
29
+ articles: [
30
+ article("Iran Threatens to Close the Strait of Hormuz", "Reuters"),
31
+ article("Fed holds rates", "CNBC Top News"),
32
+ ],
33
+ query: "article on the strait",
34
+ phase: "ready",
35
+ onOpen: (item) => {
36
+ opened.push(item.id);
37
+ },
38
+ });
39
+
40
+ expect(items).toHaveLength(1);
41
+ expect(items[0]?.label).toContain("Hormuz");
42
+ expect(items[0]?.detail).toBe("Reuters");
43
+ items[0]?.action();
44
+ expect(opened).toEqual(["Iran Threatens to Close the Strait of Hormuz"]);
45
+ });
46
+
47
+ test("shows a lookup row while subscribed feeds are still loading", () => {
48
+ const items = buildArticleSearchResultItems({
49
+ articles: [],
50
+ query: "article on the strait",
51
+ phase: "loading",
52
+ onOpen: () => {},
53
+ });
54
+ expect(items.map((item) => item.label)).toEqual(["Looking up articles…"]);
55
+ });
56
+
57
+ test("returns local matches without waiting on a still-loading lookup", () => {
58
+ const items = buildArticleSearchResultItems({
59
+ articles: [article("Trump administration pauses talks", "AP")],
60
+ query: "ART trum",
61
+ phase: "loading",
62
+ onOpen: () => {},
63
+ });
64
+ expect(items.map((item) => item.label)).toEqual(["Trump administration pauses talks"]);
65
+ });
66
+ });
67
+
68
+ describe("isArticleLookupShortcut", () => {
69
+ test("treats the ART plugin command as an article lookup", () => {
70
+ expect(isArticleLookupShortcut({ kind: "none" })).toBe(false);
71
+ expect(isArticleLookupShortcut({
72
+ kind: "partial",
73
+ source: "plugin-command",
74
+ prefix: "ART",
75
+ label: "Open Article",
76
+ description: "",
77
+ argKind: "text",
78
+ argText: "hormuz",
79
+ completionQuery: null,
80
+ command: { id: "open-news-article", label: "Open Article" } as never,
81
+ })).toBe(true);
82
+ });
83
+ });
@@ -0,0 +1,55 @@
1
+ import type { NewsArticle } from "../../../../news/types";
2
+ import {
3
+ looksLikeArticleQuery,
4
+ searchNewsArticles,
5
+ } from "../../../../plugins/builtin/news/wire/article-search";
6
+ import type { ResultItem } from "../../list/model";
7
+
8
+ /**
9
+ * Builds command-bar rows for an article-lookup (`ART`) query by matching the
10
+ * typed text against the loaded news feed.
11
+ */
12
+ export function buildArticleSearchResultItems(options: {
13
+ articles: readonly NewsArticle[];
14
+ query: string;
15
+ phase?: "idle" | "loading" | "ready" | "refreshing" | "error";
16
+ onOpen: (article: NewsArticle) => void;
17
+ }): ResultItem[] {
18
+ const query = options.query.trim();
19
+ if (!query || !looksLikeArticleQuery(query)) return [];
20
+
21
+ const matches = searchNewsArticles(options.articles, query);
22
+ const items = matches.map((article) => ({
23
+ id: `article:${article.id}`,
24
+ label: article.title,
25
+ detail: article.source,
26
+ category: "Articles",
27
+ kind: "action" as const,
28
+ right: "ART",
29
+ searchText: [
30
+ article.title,
31
+ article.source,
32
+ article.summary ?? "",
33
+ ...article.topics,
34
+ ...article.categories,
35
+ "article",
36
+ "news",
37
+ "press",
38
+ ].join(" "),
39
+ action: () => options.onOpen(article),
40
+ }));
41
+
42
+ if (items.length > 0) return items;
43
+ if (options.phase === "loading" || options.phase === "idle" || options.phase === "refreshing") {
44
+ return [{
45
+ id: "article:loading",
46
+ label: "Looking up articles…",
47
+ detail: "",
48
+ category: "Articles",
49
+ kind: "info",
50
+ defaultSelectable: false,
51
+ action: () => {},
52
+ }];
53
+ }
54
+ return [];
55
+ }
@@ -142,6 +142,9 @@ export function createRootCommandItemBuilder({
142
142
  return command.description;
143
143
  case "toggle-value-flashing":
144
144
  return state.config.valueFlashingEnabled ? "Currently on" : "Currently off";
145
+ case "font-size-increase":
146
+ case "font-size-decrease":
147
+ return `Currently ${state.config.fontSize ?? 12}px`;
145
148
  default:
146
149
  return command.description;
147
150
  }
@@ -98,7 +98,7 @@ export function useRootProviderSearch(options: {
98
98
  );
99
99
  const localItems = localTickerSearchResultItems(searchQuery, { limit: 6 });
100
100
  setRootProviderResults(cachedCandidates
101
- ? mergeTickerSearchResultItems(searchQuery, localItems, buildTickerSearchResultItems(cachedCandidates, searchQuery))
101
+ ? mergeTickerSearchResultItems(searchQuery, buildTickerSearchResultItems(cachedCandidates, searchQuery), localItems)
102
102
  : null);
103
103
  setRootProviderResultsQuery(cachedCandidates ? searchQuery : null);
104
104
 
@@ -125,8 +125,8 @@ export function useRootProviderSearch(options: {
125
125
  );
126
126
  setRootProviderResults(mergeTickerSearchResultItems(
127
127
  searchQuery,
128
- localTickerSearchResultItems(searchQuery, { limit: 6 }),
129
128
  buildTickerSearchResultItems(combined, searchQuery),
129
+ localTickerSearchResultItems(searchQuery, { limit: 6 }),
130
130
  ));
131
131
  setRootProviderResultsQuery(searchQuery);
132
132
  } catch {
@@ -178,7 +178,11 @@ export function useRootProviderSearch(options: {
178
178
  rootResultItems,
179
179
  rootTickerSearchArg,
180
180
  ]);
181
- const rootSectionOrder: CommandBarSectionOrder = rootPlainTickerSearchArg ? "app-first" : "default";
181
+ const rootSectionOrder: CommandBarSectionOrder = rootPlainTickerSearchArg
182
+ ? "app-first"
183
+ : rootTickerSearchArg
184
+ ? "ranked"
185
+ : "default";
182
186
  const orderedRootResults = useMemo(
183
187
  () => orderListResults(rootResults, { sectionOrder: rootSectionOrder }),
184
188
  [rootResults, rootSectionOrder],