gloomberb 0.10.0 → 0.10.2

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 (353) hide show
  1. package/package.json +8 -1
  2. package/src/api-client/auth.ts +1 -0
  3. package/src/api-client/index.test.ts +359 -108
  4. package/src/api-client/index.ts +24 -13
  5. package/src/api-client/socket.ts +70 -35
  6. package/src/api-client/types.ts +1 -1
  7. package/src/app/language-observer.test.tsx +82 -0
  8. package/src/app/language-observer.tsx +13 -0
  9. package/src/app/pane-runtime/index.tsx +2 -2
  10. package/src/app/pane-runtime/pane-settings-runtime.tsx +2 -2
  11. package/src/app/pane-runtime/pane-template-runtime.ts +2 -2
  12. package/src/app/pane-runtime/plugin-bindings.ts +2 -2
  13. package/src/app/pane-runtime/ticker-open-runtime.ts +2 -2
  14. package/src/app/runtime/broker-import.ts +2 -2
  15. package/src/app/runtime/plugin-bindings.ts +2 -2
  16. package/src/app/runtime/startup.ts +2 -2
  17. package/src/app.tsx +16 -7
  18. package/src/brokers/account-cache.ts +6 -6
  19. package/src/brokers/broker-ticker-sync.ts +3 -3
  20. package/src/brokers/remote-broker-adapter.ts +2 -2
  21. package/src/brokers/sync-broker-instance.ts +5 -6
  22. package/src/cli/commands/api.ts +3 -6
  23. package/src/cli/commands/automation.ts +7 -15
  24. package/src/cli/commands/market.ts +26 -61
  25. package/src/cli/commands/overview.ts +34 -45
  26. package/src/cli/commands/search.ts +25 -27
  27. package/src/cli/commands/system.ts +27 -57
  28. package/src/cli/commands/ticker.ts +62 -63
  29. package/src/cli/context.test.ts +63 -0
  30. package/src/cli/context.ts +15 -1
  31. package/src/cli/index.ts +1 -1
  32. package/src/cli/pane-functions/data.ts +2 -19
  33. package/src/cli/pane-functions/index.ts +26 -25
  34. package/src/cli/pane-functions/report.ts +1 -1
  35. package/src/cli/pane-functions/screenshot.ts +3 -3
  36. package/src/cli/scoped-context.ts +41 -0
  37. package/src/components/chart/composite/composite-chart.test.tsx +615 -18
  38. package/src/components/chart/composite/composite-chart.tsx +179 -79
  39. package/src/components/chart/composite/format.test.ts +20 -7
  40. package/src/components/chart/composite/index.ts +0 -21
  41. package/src/components/chart/composite/interactions.test.ts +90 -3
  42. package/src/components/chart/composite/interactions.ts +189 -24
  43. package/src/components/chart/composite/price-series.ts +0 -2
  44. package/src/components/chart/composite/rasterizer.ts +10 -9
  45. package/src/components/chart/composite/renderers.test.ts +108 -2
  46. package/src/components/chart/composite/scene.test.ts +36 -0
  47. package/src/components/chart/composite/scene.ts +5 -2
  48. package/src/components/chart/composite/text-renderer.ts +37 -25
  49. package/src/components/chart/composite/time-axis.test.ts +243 -0
  50. package/src/components/chart/composite/time-axis.ts +608 -0
  51. package/src/components/chart/composite/types.ts +12 -1
  52. package/src/components/chart/core/data.ts +2 -159
  53. package/src/components/chart/core/date-window.ts +6 -225
  54. package/src/components/chart/core/palette.ts +1 -12
  55. package/src/components/chart/core/pointer.ts +0 -142
  56. package/src/components/chart/core/types.ts +1 -58
  57. package/src/components/chart/cursor-motion.ts +2 -4
  58. package/src/components/chart/indicators/types.ts +0 -9
  59. package/src/components/chart/market-session.ts +0 -11
  60. package/src/components/chart/native/kitty/protocol.ts +1 -1
  61. package/src/components/chart/native/raster/layout.ts +3 -10
  62. package/src/components/chart/static/chart/axis-overlays.test.tsx +64 -0
  63. package/src/components/chart/static/chart/axis-overlays.tsx +59 -14
  64. package/src/components/chart/static/chart/surface.tsx +1 -2
  65. package/src/components/command-bar/action-runtime.ts +2 -2
  66. package/src/components/command-bar/commands/direct/index.ts +1 -1
  67. package/src/components/command-bar/panel/body.tsx +2 -2
  68. package/src/components/command-bar/panel/index.tsx +11 -9
  69. package/src/components/command-bar/routes/ticker-search/actions.ts +2 -2
  70. package/src/components/command-bar/surface/index.tsx +2 -2
  71. package/src/components/command-bar/workflow/body.tsx +3 -3
  72. package/src/components/command-bar/workflow/collection-actions.ts +2 -2
  73. package/src/components/command-bar/workflow/coordinator.ts +2 -2
  74. package/src/components/command-bar/workflow/field-row.tsx +25 -11
  75. package/src/components/command-bar/workflow/tickers.ts +2 -2
  76. package/src/components/feed-data-table/stack-view.test.tsx +28 -36
  77. package/src/components/feed-data-table/stack-view.tsx +3 -1
  78. package/src/components/layout/detached-pane-shell.tsx +6 -6
  79. package/src/components/layout/header.tsx +13 -9
  80. package/src/components/layout/pane/content.test.tsx +42 -3
  81. package/src/components/layout/pane/content.tsx +3 -1
  82. package/src/components/layout/pane/footer/index.test.tsx +45 -2
  83. package/src/components/layout/pane/footer/registration.tsx +3 -1
  84. package/src/components/layout/shell/index.tsx +1 -2
  85. package/src/components/layout/shell/native/surfaces.ts +1 -1
  86. package/src/components/layout/shell/native/window-state.ts +1 -1
  87. package/src/components/layout/status-bar.tsx +12 -8
  88. package/src/components/layout/window-controls.tsx +8 -6
  89. package/src/components/onboarding/onboarding-wizard.tsx +3 -1
  90. package/src/components/pane-settings-dialog/desktop.tsx +0 -2
  91. package/src/components/pane-settings-dialog.tsx +0 -2
  92. package/src/components/pane-template-wizard.tsx +10 -9
  93. package/src/components/price-performance.tsx +2 -113
  94. package/src/components/price-sparkline/model.ts +15 -17
  95. package/src/components/price-sparkline/view.tsx +8 -7
  96. package/src/components/speedometer-gauge/desktop.tsx +2 -1
  97. package/src/components/speedometer-gauge/index.tsx +0 -2
  98. package/src/components/speedometer-gauge/terminal.tsx +24 -5
  99. package/src/components/ticker/badge/index.tsx +1 -18
  100. package/src/components/ui/data-table/opentui/index.tsx +3 -3
  101. package/src/components/ui/frame.tsx +1 -2
  102. package/src/components/ui/page-stack-view.test.tsx +126 -0
  103. package/src/components/ui/page-stack-view.tsx +24 -12
  104. package/src/components/ui/tabs.tsx +7 -3
  105. package/src/components/ui/ui.test.tsx +23 -2
  106. package/src/components/use-external-link-footer.ts +4 -2
  107. package/src/core/app-service-ports.ts +46 -0
  108. package/src/core/app-services.test.ts +66 -0
  109. package/src/core/app-services.ts +17 -15
  110. package/src/data/config/chart-settings.ts +3 -10
  111. package/src/data/config/store/index.test.ts +99 -2
  112. package/src/data/config/store/migrations.ts +278 -0
  113. package/src/data/config/store/normalize.ts +44 -186
  114. package/src/data/resource-store.ts +73 -12
  115. package/src/data/sqlite/cache.test.ts +104 -15
  116. package/src/i18n/index.test.ts +17 -0
  117. package/src/i18n/index.ts +23 -6
  118. package/src/i18n/ja.ts +13 -0
  119. package/src/i18n/ko.ts +13 -0
  120. package/src/i18n/react.ts +6 -0
  121. package/src/i18n/zh-cn.ts +13 -0
  122. package/src/i18n/zh-tw.ts +13 -0
  123. package/src/market-data/coordinator/auxiliary.ts +7 -3
  124. package/src/market-data/coordinator/chart.ts +1 -1
  125. package/src/market-data/coordinator/index.test.ts +26 -0
  126. package/src/market-data/coordinator/index.ts +11 -5
  127. package/src/market-data/coordinator/quotes.ts +4 -40
  128. package/src/market-data/coordinator/subscriptions.test.ts +89 -1
  129. package/src/market-data/hooks.tsx +47 -1
  130. package/src/market-data/market/status.ts +6 -6
  131. package/src/market-data/performance.ts +1 -1
  132. package/src/market-data/quote-subscription-target.test.ts +37 -0
  133. package/src/market-data/quote-subscription-target.ts +60 -0
  134. package/src/market-data/quotes/freshness.ts +1 -0
  135. package/src/market-data/quotes/resolution.ts +4 -0
  136. package/src/market-data/request-types.ts +3 -3
  137. package/src/news/aggregator.test.ts +97 -1
  138. package/src/news/aggregator.ts +100 -57
  139. package/src/news/hooks.test.tsx +64 -0
  140. package/src/news/hooks.ts +18 -25
  141. package/src/plugins/builtin/account-management/footer.ts +4 -2
  142. package/src/plugins/builtin/account-management/pane.tsx +9 -4
  143. package/src/plugins/builtin/ai/agent-history.ts +11 -7
  144. package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +0 -5
  145. package/src/plugins/builtin/ai/ask-ai.test.tsx +3 -4
  146. package/src/plugins/builtin/ai/index.test.ts +2 -2
  147. package/src/plugins/builtin/ai/index.tsx +10 -11
  148. package/src/plugins/builtin/ai/pane-settings.ts +1 -1
  149. package/src/plugins/builtin/ai/pi/runtime.ts +6 -5
  150. package/src/plugins/builtin/ai/providers.ts +1 -9
  151. package/src/plugins/builtin/ai/runner.ts +0 -6
  152. package/src/plugins/builtin/ai/screener/footer.ts +4 -1
  153. package/src/plugins/builtin/ai/screener/pane.test.tsx +9 -9
  154. package/src/plugins/builtin/ai/workspace/model.ts +2 -27
  155. package/src/plugins/builtin/ai/workspace/pane.tsx +2 -2
  156. package/src/plugins/builtin/analytics/metrics.ts +1 -7
  157. package/src/plugins/builtin/broker-manager/detail.tsx +3 -2
  158. package/src/plugins/builtin/broker-manager/index.tsx +4 -2
  159. package/src/plugins/builtin/chart-composer/editor/controller.ts +643 -0
  160. package/src/plugins/builtin/chart-composer/editor/desktop-view.tsx +134 -0
  161. package/src/plugins/builtin/chart-composer/editor/model.test.ts +85 -0
  162. package/src/plugins/builtin/chart-composer/editor/model.ts +292 -0
  163. package/src/plugins/builtin/chart-composer/editor/opentui-view.tsx +108 -0
  164. package/src/plugins/builtin/chart-composer/editor.tsx +65 -947
  165. package/src/plugins/builtin/chart-composer/index.tsx +0 -6
  166. package/src/plugins/builtin/chart-composer/pane.tsx +69 -36
  167. package/src/plugins/builtin/chart-composer/presets.ts +1 -19
  168. package/src/plugins/builtin/chart-composer/settings.ts +1 -1
  169. package/src/plugins/builtin/chat/content/composer-runtime.ts +1 -1
  170. package/src/plugins/builtin/chat/content/shortcuts.ts +0 -1
  171. package/src/plugins/builtin/chat/controller/fetch.ts +8 -2
  172. package/src/plugins/builtin/chat/controller/persistence.ts +2 -0
  173. package/src/plugins/builtin/chat/controller/session.test.ts +25 -0
  174. package/src/plugins/builtin/chat/controller/session.ts +2 -0
  175. package/src/plugins/builtin/chat/controller/state.ts +7 -0
  176. package/src/plugins/builtin/chat/controller.test.ts +73 -20
  177. package/src/plugins/builtin/chat/message/desktop.tsx +2 -0
  178. package/src/plugins/builtin/cloud-tweets/footer.ts +9 -28
  179. package/src/plugins/builtin/cloud-tweets/pane.tsx +0 -5
  180. package/src/plugins/builtin/cloud-tweets/table.tsx +12 -17
  181. package/src/plugins/builtin/correlation/compute.ts +4 -10
  182. package/src/plugins/builtin/econ/index.tsx +17 -19
  183. package/src/plugins/builtin/holders/types.ts +0 -1
  184. package/src/plugins/builtin/insider/index.tsx +6 -31
  185. package/src/plugins/builtin/news/index.tsx +5 -7
  186. package/src/plugins/builtin/news/wire/breaking/pane.test.tsx +6 -8
  187. package/src/plugins/builtin/news/wire/news/footer.ts +10 -11
  188. package/src/plugins/builtin/news/wire/read-state.test.ts +7 -0
  189. package/src/plugins/builtin/news/wire/read-state.ts +18 -28
  190. package/src/plugins/builtin/news/wire/rss/parser.ts +1 -11
  191. package/src/plugins/builtin/options/footer.test.ts +115 -0
  192. package/src/plugins/builtin/options/footer.ts +110 -0
  193. package/src/plugins/builtin/options/live-quotes.test.ts +238 -0
  194. package/src/plugins/builtin/options/live-quotes.ts +147 -0
  195. package/src/plugins/builtin/options/view.test.tsx +71 -1
  196. package/src/plugins/builtin/options/view.tsx +39 -24
  197. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +103 -117
  198. package/src/plugins/builtin/portfolio-list/cli/render.ts +11 -5
  199. package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +120 -135
  200. package/src/plugins/builtin/portfolio-list/header.tsx +9 -4
  201. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +6 -1
  202. package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +4 -2
  203. package/src/plugins/builtin/research/corporate-actions-pane.tsx +24 -52
  204. package/src/plugins/builtin/sec/filing-content.test.ts +41 -0
  205. package/src/plugins/builtin/sec/filing-content.ts +94 -0
  206. package/src/plugins/builtin/sec/index.tsx +21 -104
  207. package/src/plugins/builtin/sectors/sector-model.ts +1 -7
  208. package/src/plugins/builtin/shared/cloud-upgrade.ts +1 -0
  209. package/src/plugins/builtin/shared/pane-footer.ts +82 -0
  210. package/src/plugins/builtin/shared/read-state.ts +64 -3
  211. package/src/plugins/builtin/substack/read-state.ts +16 -41
  212. package/src/plugins/builtin/thirteenf/pane.tsx +24 -73
  213. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +10 -10
  214. package/src/plugins/builtin/yield-curve/index.tsx +11 -9
  215. package/src/plugins/ibkr/dialogs.tsx +3 -3
  216. package/src/plugins/ibkr/gateway/fundamentals.ts +1 -1
  217. package/src/plugins/ibkr/gateway/history-requests.ts +2 -2
  218. package/src/plugins/ibkr/gateway/history.ts +2 -2
  219. package/src/plugins/ibkr/gateway/selection.ts +2 -2
  220. package/src/plugins/ibkr/gateway/service/index.ts +2 -2
  221. package/src/plugins/ibkr/gateway/service/native.ts +2 -2
  222. package/src/plugins/plugin-persistence.ts +3 -4
  223. package/src/plugins/prediction-markets/controller/keyboard.ts +1 -1
  224. package/src/plugins/prediction-markets/controller/live-updates.ts +6 -5
  225. package/src/plugins/prediction-markets/detail/book.tsx +2 -2
  226. package/src/plugins/prediction-markets/detail/outcomes.tsx +3 -1
  227. package/src/plugins/prediction-markets/detail/pane.tsx +3 -1
  228. package/src/plugins/prediction-markets/detail/trades.tsx +3 -3
  229. package/src/plugins/registry/context.ts +3 -4
  230. package/src/plugins/registry/index.ts +5 -6
  231. package/src/react/input.test.ts +126 -3
  232. package/src/react/input.ts +120 -1
  233. package/src/remote/app-host.tsx +2 -13
  234. package/src/renderers/electrobun/bun/desktop/backend-requests.ts +44 -60
  235. package/src/renderers/electrobun/bun/desktop/capability-bridge.ts +15 -13
  236. package/src/renderers/electrobun/bun/desktop/host-requests.ts +26 -22
  237. package/src/renderers/electrobun/bun/desktop/http-fetch.ts +5 -1
  238. package/src/renderers/electrobun/bun/desktop/initialization.ts +11 -6
  239. package/src/renderers/electrobun/bun/desktop/media.ts +5 -3
  240. package/src/renderers/electrobun/bun/desktop/plugin-state.ts +21 -22
  241. package/src/renderers/electrobun/bun/desktop/window-controls.ts +3 -1
  242. package/src/renderers/electrobun/bun/desktop/workspace/requests.ts +18 -18
  243. package/src/renderers/electrobun/bun/index.ts +123 -83
  244. package/src/renderers/electrobun/shared/protocol.ts +146 -7
  245. package/src/renderers/electrobun/view/app-services.ts +5 -8
  246. package/src/renderers/electrobun/view/backend-rpc.ts +18 -8
  247. package/src/renderers/electrobun/view/broker-remote-client.ts +2 -2
  248. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +5 -22
  249. package/src/renderers/electrobun/view/config-host.ts +1 -1
  250. package/src/renderers/electrobun/view/context-menu.ts +1 -17
  251. package/src/renderers/electrobun/view/data-table/index.tsx +0 -1
  252. package/src/renderers/electrobun/view/data-table/row.tsx +0 -4
  253. package/src/renderers/electrobun/view/desktop/control-styles.ts +37 -34
  254. package/src/renderers/electrobun/view/desktop/controls.tsx +36 -33
  255. package/src/renderers/electrobun/view/desktop/list-view.tsx +15 -12
  256. package/src/renderers/electrobun/view/desktop/popover.tsx +2 -3
  257. package/src/renderers/electrobun/view/dialog-host.tsx +2 -3
  258. package/src/renderers/electrobun/view/fatal-screen.tsx +0 -1
  259. package/src/renderers/electrobun/view/host/box.tsx +0 -1
  260. package/src/renderers/electrobun/view/host/chart-surface.tsx +0 -1
  261. package/src/renderers/electrobun/view/host/input.tsx +0 -1
  262. package/src/renderers/electrobun/view/host/media-surface.tsx +0 -1
  263. package/src/renderers/electrobun/view/host/native.ts +0 -1
  264. package/src/renderers/electrobun/view/host/scroll-box.tsx +0 -1
  265. package/src/renderers/electrobun/view/host/tabs.tsx +0 -1
  266. package/src/renderers/electrobun/view/host/text.tsx +0 -1
  267. package/src/renderers/electrobun/view/http-fetch.ts +3 -11
  268. package/src/renderers/electrobun/view/input-host.tsx +10 -53
  269. package/src/renderers/electrobun/view/key-event.ts +0 -1
  270. package/src/renderers/electrobun/view/main.tsx +3 -3
  271. package/src/renderers/electrobun/view/native-renderer.ts +0 -1
  272. package/src/renderers/electrobun/view/native-stubs/backend-rpc.ts +13 -1
  273. package/src/renderers/electrobun/view/native-stubs/chart/surface-manager.ts +11 -0
  274. package/src/renderers/electrobun/view/native-stubs/native-stubs.test.ts +47 -0
  275. package/src/renderers/electrobun/view/remote/asset-data-client.ts +6 -44
  276. package/src/renderers/electrobun/view/remote/persistence.ts +6 -2
  277. package/src/renderers/electrobun/view/remote/quote-subscription-registry.test.ts +86 -0
  278. package/src/renderers/electrobun/view/remote/quote-subscription-registry.ts +72 -0
  279. package/src/renderers/electrobun/view/remote/ticker-repository.ts +2 -2
  280. package/src/renderers/electrobun/view/toast-host.tsx +0 -1
  281. package/src/renderers/electrobun/view/ui-host.tsx +1 -2
  282. package/src/renderers/electrobun/view/update-host.ts +1 -1
  283. package/src/renderers/opentui/chart-surface.tsx +2 -1
  284. package/src/renderers/opentui/dialog-host.tsx +20 -4
  285. package/src/renderers/opentui/image/loader.ts +12 -2
  286. package/src/renderers/opentui/input-host.tsx +7 -49
  287. package/src/renderers/opentui/start.tsx +2 -0
  288. package/src/sources/gloomberb-cloud/history-quality.ts +44 -0
  289. package/src/sources/gloomberb-cloud/index.test.ts +173 -0
  290. package/src/sources/gloomberb-cloud/index.ts +42 -20
  291. package/src/sources/gloomberb-cloud/normalizers.ts +9 -3
  292. package/src/sources/provider-router/cache.ts +1 -1
  293. package/src/sources/provider-router/document-routes.ts +10 -5
  294. package/src/sources/provider-router/financials.test.ts +14 -0
  295. package/src/sources/provider-router/financials.ts +6 -1
  296. package/src/sources/provider-router/history.test.ts +163 -1
  297. package/src/sources/provider-router/history.ts +278 -347
  298. package/src/sources/provider-router/index.ts +2 -2
  299. package/src/sources/provider-router/routing.test.ts +39 -0
  300. package/src/sources/provider-router/routing.ts +24 -11
  301. package/src/sources/provider-router/supplemental.ts +37 -17
  302. package/src/sources/sec-edgar/content.ts +2 -12
  303. package/src/sources/sec-edgar.ts +2 -13
  304. package/src/sources/yahoo-finance/history.ts +2 -2
  305. package/src/sources/yahoo-finance/options.ts +5 -0
  306. package/src/sources/yahoo-finance.ts +2 -2
  307. package/src/state/app/bootstrap.ts +2 -2
  308. package/src/state/app/context/index.tsx +2 -2
  309. package/src/state/hooks/quote-streaming.ts +66 -1
  310. package/src/state/hooks/session-snapshot.ts +2 -2
  311. package/src/state/selectors.ts +0 -9
  312. package/src/sync/controller.test.ts +98 -0
  313. package/src/sync/controller.ts +29 -5
  314. package/src/sync/react.ts +2 -2
  315. package/src/sync/types.ts +2 -2
  316. package/src/theme/colors.test.ts +10 -0
  317. package/src/theme/colors.ts +75 -78
  318. package/src/theme/theme-context.test.tsx +67 -0
  319. package/src/theme/theme-context.tsx +9 -5
  320. package/src/tickers/open-target.ts +2 -2
  321. package/src/tickers/search/upsert.ts +2 -2
  322. package/src/time-series/alignment.ts +0 -7
  323. package/src/time-series/chart-data.ts +145 -0
  324. package/src/time-series/date-window.ts +44 -0
  325. package/src/time-series/history-window.ts +21 -0
  326. package/src/time-series/hooks.test.ts +59 -0
  327. package/src/time-series/hooks.ts +33 -12
  328. package/src/time-series/market-resolution.ts +2 -2
  329. package/src/time-series/market.ts +1 -1
  330. package/src/time-series/range.ts +9 -0
  331. package/src/{components/chart/core → time-series}/resolution.ts +3 -102
  332. package/src/time-series/resolve.test.ts +203 -1
  333. package/src/time-series/resolve.ts +96 -18
  334. package/src/time-series/spec-studies.test.ts +15 -0
  335. package/src/time-series/spec.ts +2 -2
  336. package/src/time-series/studies.ts +5 -2
  337. package/src/time-series/types.ts +3 -1
  338. package/src/time-series/use-chart-resolution.test.tsx +358 -0
  339. package/src/time-series/use-chart-resolution.ts +46 -4
  340. package/src/types/broker.ts +2 -2
  341. package/src/types/data-provider.ts +3 -3
  342. package/src/types/financials.ts +12 -0
  343. package/src/types/plugin.ts +2 -2
  344. package/src/ui/context-menu.tsx +3 -2
  345. package/src/ui/dialog.tsx +2 -2
  346. package/src/utils/html-entities.ts +3 -5
  347. package/src/utils/main-thread-monitor.ts +1 -1
  348. package/src/utils/price-history.ts +5 -5
  349. package/src/version.ts +3 -1
  350. package/src/components/chart/core/viewport.test.ts +0 -8
  351. package/src/components/chart/core/viewport.ts +0 -24
  352. package/src/time-series/index.ts +0 -14
  353. /package/src/{components/chart/core → time-series}/resolution.test.ts +0 -0
@@ -1,48 +1,13 @@
1
1
  import type { PricePoint } from "../../../types/financials";
2
+ export type { ChartResolution, TimeRange } from "../../../time-series/range";
2
3
 
3
- export type TimeRange = "1D" | "1W" | "1M" | "3M" | "6M" | "1Y" | "5Y" | "ALL";
4
- export type ChartResolution = "auto" | "1m" | "5m" | "15m" | "30m" | "45m" | "1h" | "1d" | "1wk" | "1mo";
5
4
  export type ChartRenderMode = "area" | "line" | "candles" | "ohlc" | "hlc";
6
5
  export type ChartAxisMode = "price" | "percent";
7
6
  export type ChartRendererPreference = "auto" | "kitty" | "braille";
8
7
  export type ResolvedChartRenderer = "kitty" | "braille";
9
- export type ComparisonChartRenderMode = "area" | "line";
10
8
  export type ChartSessionBackgroundKind = "pre" | "post";
11
9
 
12
- export interface ChartDateWindow {
13
- start: Date | null;
14
- end: Date | null;
15
- }
16
-
17
- export const TIME_RANGES: TimeRange[] = ["1D", "1W", "1M", "3M", "6M", "1Y", "5Y", "ALL"];
18
- export const CHART_RENDER_MODES: ChartRenderMode[] = ["area", "line", "candles", "ohlc", "hlc"];
19
10
  export const CHART_RENDERER_PREFERENCES: ChartRendererPreference[] = ["auto", "kitty", "braille"];
20
- export const COMPARISON_RENDER_MODES: ComparisonChartRenderMode[] = ["area", "line"];
21
-
22
- /** Number of trading days for each time range */
23
- export const RANGE_DAYS: Record<TimeRange, number> = {
24
- "1D": 1,
25
- "1W": 5,
26
- "1M": 21,
27
- "3M": 63,
28
- "6M": 126,
29
- "1Y": 252,
30
- "5Y": 1260,
31
- "ALL": Infinity,
32
- };
33
-
34
- export interface ChartViewState {
35
- presetRange: TimeRange;
36
- bufferRange: TimeRange;
37
- activePreset: TimeRange | null;
38
- dateWindow: ChartDateWindow | null;
39
- resolution: ChartResolution;
40
- panOffset: number; // data points shifted left from right edge (0 = most recent)
41
- zoomLevel: number; // 1.0 = full selected range, 2.0 = zoomed in 2x
42
- cursorX: number | null; // local plot x in cell units; may be fractional with pixel mouse
43
- cursorY: number | null; // local plot y in cell units; may be fractional with pixel mouse
44
- renderMode?: ChartRenderMode;
45
- }
46
11
 
47
12
  export interface Pixel {
48
13
  color: string;
@@ -91,28 +56,6 @@ export interface VisibleWindow {
91
56
  endIdx: number;
92
57
  }
93
58
 
94
- export interface ComparisonChartSeries {
95
- symbol: string;
96
- color: string;
97
- fillColor: string;
98
- currency?: string;
99
- points: PricePoint[];
100
- }
101
-
102
- export interface ComparisonChartViewState {
103
- presetRange: TimeRange;
104
- bufferRange: TimeRange;
105
- activePreset: TimeRange | null;
106
- dateWindow: ChartDateWindow | null;
107
- resolution: ChartResolution;
108
- panOffset: number;
109
- zoomLevel: number; // 1.0 = full selected range, 2.0 = zoomed in 2x
110
- cursorX: number | null;
111
- cursorY: number | null;
112
- renderMode?: ComparisonChartRenderMode;
113
- selectedSymbol: string | null;
114
- }
115
-
116
59
  import type { OverlayPoint, OscillatorPoint, MacdResult, BollingerResult } from "../indicators/types";
117
60
 
118
61
  export interface ChartIndicatorOverlays {
@@ -4,10 +4,8 @@ interface ChartCursorPosition {
4
4
  y: number | null;
5
5
  }
6
6
 
7
- export type ChartCursorMotionKind = "pixel" | "cell" | "discrete";
8
-
9
7
  const CELL_CURSOR_EASING = 0.72;
10
- export const CELL_CURSOR_SNAP_DISTANCE = 0.035;
8
+ const CELL_CURSOR_SNAP_DISTANCE = 0.035;
11
9
 
12
10
  function sameNullableNumber(left: number | null, right: number | null, epsilon = 0): boolean {
13
11
  if (left === right) return true;
@@ -15,7 +13,7 @@ function sameNullableNumber(left: number | null, right: number | null, epsilon =
15
13
  return Math.abs(left - right) <= epsilon;
16
14
  }
17
15
 
18
- export function sameCursorPosition(
16
+ function sameCursorPosition(
19
17
  left: ChartCursorPosition,
20
18
  right: ChartCursorPosition,
21
19
  epsilon = 0,
@@ -24,12 +24,3 @@ export interface BollingerResult {
24
24
  middle: OverlayPoint[];
25
25
  lower: OverlayPoint[];
26
26
  }
27
-
28
- /** Active indicator configuration for a chart pane */
29
- export interface IndicatorConfig {
30
- sma?: number[]; // e.g. [20, 50, 200]
31
- ema?: number[]; // e.g. [12, 26]
32
- rsi?: number | null; // e.g. 14
33
- macd?: { fast: number; slow: number; signal: number } | null;
34
- bollinger?: { period: number; stdDev: number } | null;
35
- }
@@ -136,17 +136,6 @@ export function resolveChartMarketSession(sources: readonly MarketSessionSource[
136
136
  return hasSupportedSource ? US_EXTENDED_HOURS_SESSION : null;
137
137
  }
138
138
 
139
- export function getChartMarketSessionKey(session: ChartMarketSession | null | undefined): string {
140
- if (!session) return "session:none";
141
- return [
142
- session.timeZone,
143
- session.preMarketStartMinutes,
144
- session.regularStartMinutes,
145
- session.regularEndMinutes,
146
- session.postMarketEndMinutes,
147
- ].join(":");
148
- }
149
-
150
139
  export function resolveExtendedHoursBackgroundSpans(
151
140
  values: readonly (Date | string | number)[],
152
141
  session: ChartMarketSession | null | undefined,
@@ -55,7 +55,7 @@ export function encodeKittyTransmitRgba(options: KittyTransmitOptions): string[]
55
55
  // ponytail: level 3 compresses chart pixels ~2.5x faster than the default for
56
56
  // ~40% more bytes; crosshair moves retransmit the whole plot every frame, so
57
57
  // CPU is the constraint on a local pty. Raise it if bandwidth ever is.
58
- const compressed = deflateSync(Buffer.from(options.rgba), { level: 3 });
58
+ const compressed = deflateSync(options.rgba, { level: 3 });
59
59
  const chunks = chunkBase64Payload(compressed.toString("base64"), options.chunkSize);
60
60
 
61
61
  return chunks.map((chunk, index) => {
@@ -22,15 +22,6 @@ export function getChartPixelLayout(scene: ChartScene, pixelWidth: number, pixel
22
22
  };
23
23
  }
24
24
 
25
- export function getComparisonPixelLayout(pixelWidth: number, pixelHeight: number) {
26
- return {
27
- plotLeft: 0,
28
- plotTop: 0,
29
- plotRight: Math.max(pixelWidth - 1, 0),
30
- plotBottom: Math.max(pixelHeight - 1, 0),
31
- };
32
- }
33
-
34
25
  export function projectX(index: number, count: number, left: number, right: number): number {
35
26
  if (count <= 1) return (left + right) / 2;
36
27
  return lerp(left, right, index / (count - 1));
@@ -40,7 +31,9 @@ export function isOhlcLikeMode(mode: ChartRenderMode): boolean {
40
31
  return mode === "ohlc" || mode === "hlc";
41
32
  }
42
33
 
43
- export function isHighLowMode(mode: ChartRenderMode): boolean {
34
+ export function isHighLowMode(
35
+ mode: ChartRenderMode,
36
+ ): mode is Extract<ChartRenderMode, "candles" | "ohlc" | "hlc"> {
44
37
  return mode === "candles" || isOhlcLikeMode(mode);
45
38
  }
46
39
 
@@ -0,0 +1,64 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { renderToStaticMarkup } from "react-dom/server";
3
+ import {
4
+ UiHostProvider,
5
+ type RendererHost,
6
+ type UiHost,
7
+ } from "../../../../ui";
8
+ import { WebBox } from "../../../../renderers/electrobun/view/host/box";
9
+ import { WebText } from "../../../../renderers/electrobun/view/host/text";
10
+ import { StaticXAxisLabels } from "./axis-overlays";
11
+
12
+ const renderer: RendererHost = {
13
+ requestExit() {},
14
+ async openExternal() {},
15
+ async copyText() {},
16
+ async readText() {
17
+ return "";
18
+ },
19
+ notify() {},
20
+ };
21
+
22
+ function renderAxis(kind: "opentui" | "desktop-web"): string {
23
+ const ui = {
24
+ kind,
25
+ capabilities: {
26
+ cellWidthPx: 8,
27
+ fractionalViewport: kind === "desktop-web",
28
+ },
29
+ Box: WebBox,
30
+ Text: WebText,
31
+ } as unknown as UiHost;
32
+ const fallback = "Nov 3 2025 Mar Jul 29 2026";
33
+ return renderToStaticMarkup(
34
+ <UiHostProvider ui={ui} renderer={renderer}>
35
+ <StaticXAxisLabels
36
+ labels={[fallback]}
37
+ positionedLabels={[
38
+ { label: "Nov 3 2025", ratio: 0 },
39
+ { label: "Mar", ratio: 0.5 },
40
+ { label: "Jul 29 2026", ratio: 1 },
41
+ ]}
42
+ width={80}
43
+ />
44
+ </UiHostProvider>,
45
+ );
46
+ }
47
+
48
+ describe("StaticXAxisLabels", () => {
49
+ test("positions desktop labels by chart ratio instead of font-space advances", () => {
50
+ const html = renderAxis("desktop-web");
51
+
52
+ expect(html).toContain("left:50%");
53
+ expect(html).toContain("transform:translateX(-50%)");
54
+ expect(html).toContain("right:0");
55
+ expect(html).not.toContain("Nov 3 2025 Mar");
56
+ });
57
+
58
+ test("keeps the fixed-width axis string for terminal cells", () => {
59
+ const html = renderAxis("opentui");
60
+
61
+ expect(html).toContain("Nov 3 2025 Mar");
62
+ expect(html).not.toContain("left:50%");
63
+ });
64
+ });
@@ -8,6 +8,11 @@ export interface StaticChartXMarker {
8
8
  lineChar?: string;
9
9
  }
10
10
 
11
+ export interface StaticChartXAxisLabel {
12
+ label: string;
13
+ ratio: number;
14
+ }
15
+
11
16
  export function clampRatio(value: number): number {
12
17
  if (!Number.isFinite(value)) return 0;
13
18
  return Math.max(0, Math.min(1, value));
@@ -28,6 +33,7 @@ function alignAxisLabel(label: string, width: number, index: number, count: numb
28
33
 
29
34
  export function StaticXAxisLabels({
30
35
  labels,
36
+ positionedLabels,
31
37
  width,
32
38
  color,
33
39
  cursorColumn,
@@ -37,6 +43,7 @@ export function StaticXAxisLabels({
37
43
  cursorBackgroundColor,
38
44
  }: {
39
45
  labels: string[];
46
+ positionedLabels?: readonly StaticChartXAxisLabel[];
40
47
  width: number;
41
48
  color?: string;
42
49
  cursorColumn?: number | null;
@@ -45,11 +52,17 @@ export function StaticXAxisLabels({
45
52
  cursorColor?: string;
46
53
  cursorBackgroundColor?: string;
47
54
  }) {
55
+ const uiHost = useUiHost();
48
56
  const { cellWidthPx = 8, fractionalViewport = false } = useUiCapabilities();
49
57
  const visibleLabels = labels.filter(Boolean);
50
- if (visibleLabels.length === 0 || width <= 0) return null;
51
- const baseWidth = Math.floor(width / visibleLabels.length);
52
- let remainder = width - baseWidth * visibleLabels.length;
58
+ const visiblePositionedLabels = positionedLabels?.filter((entry) => (
59
+ entry.label.length > 0 && Number.isFinite(entry.ratio)
60
+ )) ?? [];
61
+ if (visibleLabels.length === 0 && visiblePositionedLabels.length === 0) return null;
62
+ if (width <= 0) return null;
63
+ const segmentCount = Math.max(visibleLabels.length, 1);
64
+ const baseWidth = Math.floor(width / segmentCount);
65
+ let remainder = width - baseWidth * segmentCount;
53
66
  const clippedCursorLabel = cursorLabel ? cursorLabel.slice(0, width) : null;
54
67
  const cursorLabelWidth = clippedCursorLabel?.length ?? 0;
55
68
  const usePixelOverlay = fractionalViewport
@@ -83,17 +96,49 @@ export function StaticXAxisLabels({
83
96
  position="relative"
84
97
  overflow="hidden"
85
98
  >
86
- <Box width={width} height={1} flexDirection="row">
87
- {visibleLabels.map((label, index) => {
88
- const cellWidth = baseWidth + (remainder > 0 ? 1 : 0);
89
- remainder -= remainder > 0 ? 1 : 0;
90
- return (
91
- <Box key={`${label}:${index}`} width={cellWidth} overflow="hidden">
92
- <Text fg={color}>{alignAxisLabel(label, cellWidth, index, visibleLabels.length)}</Text>
93
- </Box>
94
- );
95
- })}
96
- </Box>
99
+ {uiHost.kind === "desktop-web" && visiblePositionedLabels.length > 0 ? (
100
+ <Box position="absolute" left={0} top={0} width={width} height={1}>
101
+ {visiblePositionedLabels.map((entry, index) => {
102
+ const ratio = clampRatio(entry.ratio);
103
+ const edgeStyle = ratio <= 0
104
+ ? { left: 0 }
105
+ : ratio >= 1
106
+ ? { right: 0 }
107
+ : {
108
+ left: `${ratio * 100}%`,
109
+ transform: "translateX(-50%)",
110
+ };
111
+ return (
112
+ <Text
113
+ key={`${entry.label}:${entry.ratio}:${index}`}
114
+ fg={color}
115
+ selectable={false}
116
+ style={{
117
+ position: "absolute",
118
+ top: 0,
119
+ whiteSpace: "pre",
120
+ pointerEvents: "none",
121
+ ...edgeStyle,
122
+ }}
123
+ >
124
+ {entry.label}
125
+ </Text>
126
+ );
127
+ })}
128
+ </Box>
129
+ ) : (
130
+ <Box width={width} height={1} flexDirection="row">
131
+ {visibleLabels.map((label, index) => {
132
+ const cellWidth = baseWidth + (remainder > 0 ? 1 : 0);
133
+ remainder -= remainder > 0 ? 1 : 0;
134
+ return (
135
+ <Box key={`${label}:${index}`} width={cellWidth} overflow="hidden">
136
+ <Text fg={color}>{alignAxisLabel(label, cellWidth, index, visibleLabels.length)}</Text>
137
+ </Box>
138
+ );
139
+ })}
140
+ </Box>
141
+ )}
97
142
  {clippedCursorLabel && (cursorLeftPercent !== null || cursorLeft !== null) ? (
98
143
  <Text
99
144
  fg={cursorColor}
@@ -1,6 +1,5 @@
1
1
  import { useCallback, useMemo, useRef, useState } from "react";
2
2
  import { Box, ChartSurface, Text, type BoxRenderable, type ChartSurfaceProps, useUiCapabilities } from "../../../../ui";
3
- import { colors as themeColors } from "../../../../theme/colors";
4
3
  import { useThemeColors } from "../../../../theme/theme-context";
5
4
  import { PriceAxisLabels } from "../../price-axis-labels";
6
5
  import type { ProjectedChartPoint } from "../../core/data";
@@ -102,7 +101,7 @@ export function StaticChartSurface({
102
101
  yAxisColor,
103
102
  formatYAxisValue,
104
103
  }: StaticChartSurfaceProps) {
105
- useThemeColors();
104
+ const themeColors = useThemeColors();
106
105
  const { cellWidthPx = 8, cellHeightPx = 18 } = useUiCapabilities();
107
106
  const plotRef = useRef<BoxRenderable | null>(null);
108
107
  const [cursor, setCursor] = useState<{ x: number; y: number } | null>(null);
@@ -4,7 +4,7 @@ import {
4
4
  type MutableRefObject,
5
5
  type SetStateAction,
6
6
  } from "react";
7
- import type { TickerRepository } from "../../data/ticker-repository";
7
+ import type { AppTickerRepositoryPort } from "../../core/app-service-ports";
8
8
  import type { PluginRegistry } from "../../plugins/registry";
9
9
  import type { AppAction, AppState } from "../../state/app/context";
10
10
  import type { DataProvider } from "../../types/data-provider";
@@ -45,7 +45,7 @@ interface UseCommandBarActionRuntimeOptions {
45
45
  state: AppState;
46
46
  stateRef: MutableRefObject<AppState>;
47
47
  themePickerRef: MutableRefObject<ThemePickerHandle | null>;
48
- tickerRepository: TickerRepository;
48
+ tickerRepository: AppTickerRepositoryPort;
49
49
  tickers: AppState["tickers"];
50
50
  updateTopRoute: (updater: (route: CommandBarRoute) => CommandBarRoute) => void;
51
51
  }
@@ -248,7 +248,7 @@ export function runDirectCommandAction(options: {
248
248
  persistConfig(nextConfig);
249
249
  applyLanguagePreference(next);
250
250
  const languageLabel = next === "auto" ? t(LANGUAGE_DISPLAY_NAMES.auto) : LANGUAGE_DISPLAY_NAMES[next];
251
- notify(`${t("Language")}: ${languageLabel} · ${t("Restart to apply everywhere")}`, { type: "success" });
251
+ notify(`${t("Language")}: ${languageLabel}`, { type: "success" });
252
252
  closeAll({ revertThemePreview: false });
253
253
  return;
254
254
  }
@@ -1,5 +1,4 @@
1
1
  import { Box, Text, TextAttributes } from "../../../ui";
2
- import { colors } from "../../../theme/colors";
3
2
  import { useThemeColors } from "../../../theme/theme-context";
4
3
  import { Button, Spinner } from "../../ui";
5
4
  import {
@@ -281,6 +280,7 @@ function CommandBarConfirmBody({
281
280
  queryDisplayWidth: number;
282
281
  onConfirm: () => void;
283
282
  }) {
283
+ const themeColors = useThemeColors();
284
284
  return (
285
285
  <Box flexDirection="column" height={bodyHeight} paddingX={contentPadding}>
286
286
  {route.body.map((line, index) => (
@@ -291,7 +291,7 @@ function CommandBarConfirmBody({
291
291
  <Box height={1} />
292
292
  {route.error && (
293
293
  <Box height={1}>
294
- <Text fg={colors.negative}>{truncateText(route.error, queryDisplayWidth)}</Text>
294
+ <Text fg={themeColors.negative}>{truncateText(route.error, queryDisplayWidth)}</Text>
295
295
  </Box>
296
296
  )}
297
297
  {route.pending && (
@@ -11,6 +11,7 @@ import {
11
11
  commandBarSubtleText,
12
12
  commandBarText,
13
13
  } from "../../../theme/colors";
14
+ import { useThemeColors } from "../../../theme/theme-context";
14
15
  import { CommandBarPanelBody } from "./body";
15
16
  import type { CommandBarPanelProps } from "./types";
16
17
 
@@ -68,15 +69,16 @@ export function CommandBarPanel({
68
69
  visibleListState,
69
70
  workflowScrollRef,
70
71
  }: CommandBarPanelProps) {
71
- const paletteBg = commandBarBg();
72
- const paletteHeadingText = commandBarHeadingText();
73
- const paletteHoverBg = commandBarHoverBg();
74
- const paletteSelectedBg = commandBarSelectedBg();
75
- const paletteSelectedText = commandBarSelectedText();
76
- const paletteText = commandBarText();
77
- const paletteSubtleText = commandBarSubtleText();
78
- const panelBg = nativePaneChrome ? commandBarPanelBg() : paletteBg;
79
- const inputBg = nativePaneChrome ? commandBarInputBg() : paletteBg;
72
+ const colors = useThemeColors();
73
+ const paletteBg = commandBarBg(colors);
74
+ const paletteHeadingText = commandBarHeadingText(colors);
75
+ const paletteHoverBg = commandBarHoverBg(colors);
76
+ const paletteSelectedBg = commandBarSelectedBg(colors);
77
+ const paletteSelectedText = commandBarSelectedText(colors);
78
+ const paletteText = commandBarText(colors);
79
+ const paletteSubtleText = commandBarSubtleText(colors);
80
+ const panelBg = nativePaneChrome ? commandBarPanelBg(colors) : paletteBg;
81
+ const inputBg = nativePaneChrome ? commandBarInputBg(colors) : paletteBg;
80
82
 
81
83
  useLayoutEffect(() => {
82
84
  const scrollBox = nativeListScrollRef.current;
@@ -1,6 +1,6 @@
1
1
  import { useCallback, useEffect, useRef } from "react";
2
2
  import type { AppState } from "../../../../state/app/context";
3
- import type { TickerRepository } from "../../../../data/ticker-repository";
3
+ import type { AppTickerRepositoryPort } from "../../../../core/app-service-ports";
4
4
  import type { PluginRegistry } from "../../../../plugins/registry";
5
5
  import {
6
6
  rankTickerSearchItems,
@@ -19,7 +19,7 @@ interface UseCommandBarTickerSearchActionsOptions {
19
19
  dispatch: (action: any) => void;
20
20
  focusTicker: (symbol: string, options?: { forceNewPane?: boolean }) => void;
21
21
  pluginRegistry: Pick<PluginRegistry, "events">;
22
- tickerRepository: TickerRepository;
22
+ tickerRepository: AppTickerRepositoryPort;
23
23
  tickers: AppState["tickers"];
24
24
  }
25
25
 
@@ -1,6 +1,6 @@
1
1
  import { useCallback } from "react";
2
2
  import type { DataProvider } from "../../../types/data-provider";
3
- import type { TickerRepository } from "../../../data/ticker-repository";
3
+ import type { AppTickerRepositoryPort } from "../../../core/app-service-ports";
4
4
  import type { PluginRegistry } from "../../../plugins/registry";
5
5
  import type { LayoutBounds } from "../../../plugins/pane-manager";
6
6
  import { useRouteListState } from "../routing/list-state";
@@ -16,7 +16,7 @@ import { useCommandBarActionRuntime } from "../action-runtime";
16
16
 
17
17
  interface CommandBarProps {
18
18
  dataProvider: DataProvider;
19
- tickerRepository: TickerRepository;
19
+ tickerRepository: AppTickerRepositoryPort;
20
20
  pluginRegistry: PluginRegistry;
21
21
  quitApp: () => void;
22
22
  onCheckForUpdates?: () => void | Promise<void>;
@@ -99,12 +99,12 @@ export function CommandBarWorkflowBody({
99
99
  <>
100
100
  {route.subtitle && (
101
101
  <Box height={1}>
102
- <Text fg={paletteSubtleText}>{truncateText(route.subtitle, queryDisplayWidth)}</Text>
102
+ <Text fg={paletteSubtleText}>{truncateText(t(route.subtitle), queryDisplayWidth)}</Text>
103
103
  </Box>
104
104
  )}
105
105
  {route.description?.map((line, index) => (
106
106
  <Box key={`workflow-desc:${index}`} height={1}>
107
- <Text fg={paletteSubtleText}>{truncateText(line, queryDisplayWidth)}</Text>
107
+ <Text fg={paletteSubtleText}>{truncateText(t(line), queryDisplayWidth)}</Text>
108
108
  </Box>
109
109
  ))}
110
110
  {route.subtitle || (route.description?.length ?? 0) > 0 ? <Box height={1} /> : null}
@@ -144,7 +144,7 @@ export function CommandBarWorkflowBody({
144
144
  )}
145
145
  {route.pendingLabel && route.pending && (
146
146
  <Box height={1}>
147
- <Spinner label={route.pendingLabel} />
147
+ <Spinner label={t(route.pendingLabel)} />
148
148
  </Box>
149
149
  )}
150
150
  {!nativePaneChrome && <Box flexGrow={1} />}
@@ -1,6 +1,6 @@
1
1
  import type { Dispatch } from "react";
2
2
  import type { DataProvider } from "../../../types/data-provider";
3
- import type { TickerRepository } from "../../../data/ticker-repository";
3
+ import type { AppTickerRepositoryPort } from "../../../core/app-service-ports";
4
4
  import type { PluginRegistry } from "../../../plugins/registry";
5
5
  import type { AppAction, AppState } from "../../../state/app/context";
6
6
  import {
@@ -46,7 +46,7 @@ export function createCommandBarCollectionWorkflowActions(options: {
46
46
  persistConfig: (nextConfig: AppState["config"]) => void;
47
47
  pluginRegistry: PluginRegistry;
48
48
  setActiveCollection: (collectionId: string) => void;
49
- tickerRepository: TickerRepository;
49
+ tickerRepository: AppTickerRepositoryPort;
50
50
  }): CommandBarCollectionWorkflowActions {
51
51
  const {
52
52
  activeCollectionId,
@@ -1,6 +1,6 @@
1
1
  import { useCallback, useMemo, type Dispatch, type MutableRefObject, type SetStateAction } from "react";
2
2
  import type { DataProvider } from "../../../types/data-provider";
3
- import type { TickerRepository } from "../../../data/ticker-repository";
3
+ import type { AppTickerRepositoryPort } from "../../../core/app-service-ports";
4
4
  import type { PluginRegistry } from "../../../plugins/registry";
5
5
  import type { AppAction, AppState } from "../../../state/app/context";
6
6
  import type { TickerRecord } from "../../../types/ticker";
@@ -37,7 +37,7 @@ interface UseCommandBarWorkflowCoordinatorOptions {
37
37
  setActiveCollection: (collectionId: string) => void;
38
38
  setRouteStack: Dispatch<SetStateAction<CommandBarRoute[]>>;
39
39
  stateRef: MutableRefObject<AppState>;
40
- tickerRepository: TickerRepository;
40
+ tickerRepository: AppTickerRepositoryPort;
41
41
  updateTopRoute: (updater: (route: CommandBarRoute) => CommandBarRoute) => void;
42
42
  }
43
43
 
@@ -79,6 +79,8 @@ export function CommandBarWorkflowFieldRow({
79
79
  const fieldBg = nativePaneChrome ? "transparent" : active ? inputBg : panelBg;
80
80
  const useNativeSelect = nativePaneChrome && field.type === "select";
81
81
  const fieldDescription = getWorkflowFieldDescription(field, active);
82
+ const fieldLabel = t(field.label);
83
+ const translatedFieldDescription = translateWorkflowFieldDescription(fieldDescription);
82
84
  const submitOrMoveNext = () => {
83
85
  if (isLastField) {
84
86
  void onSubmit(route);
@@ -96,7 +98,7 @@ export function CommandBarWorkflowFieldRow({
96
98
 
97
99
  useRemoteUiNode({
98
100
  role: "command-bar-field",
99
- label: field.label,
101
+ label: fieldLabel,
100
102
  disabled: route.pending,
101
103
  actions: {
102
104
  focus: focusOrOpenField,
@@ -106,12 +108,12 @@ export function CommandBarWorkflowFieldRow({
106
108
  metadata: {
107
109
  scope: "command-bar",
108
110
  surface: "workflow",
109
- routeTitle: route.title,
111
+ routeTitle: t(route.title),
110
112
  fieldId: field.id,
111
113
  fieldType: field.type,
112
114
  active,
113
115
  value: summarizeWorkflowFieldValue(field, value),
114
- description: fieldDescription,
116
+ description: translatedFieldDescription,
115
117
  },
116
118
  });
117
119
 
@@ -132,7 +134,7 @@ export function CommandBarWorkflowFieldRow({
132
134
  >
133
135
  <Box height={1}>
134
136
  <Text fg={active ? paletteText : paletteSubtleText} attributes={active ? TextAttributes.BOLD : 0}>
135
- {field.label}
137
+ {fieldLabel}
136
138
  </Text>
137
139
  </Box>
138
140
  {isWorkflowTextField(field) ? (
@@ -140,7 +142,7 @@ export function CommandBarWorkflowFieldRow({
140
142
  <NumberField
141
143
  inputRef={getWorkflowInputRef(field.id) as RefObject<InputRenderable | null>}
142
144
  value={coerceFieldString(value)}
143
- placeholder={field.placeholder}
145
+ placeholder={field.placeholder ? t(field.placeholder) : undefined}
144
146
  focused={active && !route.pending}
145
147
  variant="default"
146
148
  backgroundColor={nativePaneChrome ? inputBg : fieldBg}
@@ -175,7 +177,7 @@ export function CommandBarWorkflowFieldRow({
175
177
  />
176
178
  ) : (
177
179
  <Box flexDirection="column" paddingX={1} paddingY={0}>
178
- {buildTextareaPreviewLines(coerceFieldString(value), field.placeholder, queryDisplayWidth).map((line, index) => (
180
+ {buildTextareaPreviewLines(coerceFieldString(value), field.placeholder ? t(field.placeholder) : undefined, queryDisplayWidth).map((line, index) => (
179
181
  <Box key={`${field.id}:preview:${index}`} height={1}>
180
182
  <Text fg={coerceFieldString(value).trim() ? paletteText : paletteSubtleText}>{line || " "}</Text>
181
183
  </Box>
@@ -188,7 +190,7 @@ export function CommandBarWorkflowFieldRow({
188
190
  inputRef={getWorkflowInputRef(field.id) as RefObject<InputRenderable | null>}
189
191
  type={field.type === "password" ? "password" : "text"}
190
192
  value={coerceFieldString(value)}
191
- placeholder={field.placeholder}
193
+ placeholder={field.placeholder ? t(field.placeholder) : undefined}
192
194
  focused={active && !route.pending}
193
195
  variant="default"
194
196
  backgroundColor={nativePaneChrome ? inputBg : fieldBg}
@@ -199,7 +201,11 @@ export function CommandBarWorkflowFieldRow({
199
201
  ) : useNativeSelect ? (
200
202
  <NativeSelect
201
203
  value={coerceFieldString(value)}
202
- options={field.options}
204
+ options={field.options.map((option) => ({
205
+ ...option,
206
+ label: t(option.label),
207
+ description: option.description ? t(option.description) : undefined,
208
+ }))}
203
209
  width="100%"
204
210
  selectRef={(element) => onNativeSelectRef(field.id, element)}
205
211
  onFocus={() => onFieldFocus(field.id)}
@@ -216,14 +222,14 @@ export function CommandBarWorkflowFieldRow({
216
222
  style={nativePaneChrome ? { borderRadius: 4 } : undefined}
217
223
  >
218
224
  <Text fg={active ? paletteText : paletteSubtleText}>
219
- {truncateText(summarizeWorkflowFieldValue(field, value), queryDisplayWidth)}
225
+ {truncateText(t(summarizeWorkflowFieldValue(field, value)), queryDisplayWidth)}
220
226
  </Text>
221
227
  </Box>
222
228
  )}
223
- {fieldDescription && (
229
+ {translatedFieldDescription && (
224
230
  <Box height={1}>
225
231
  <Text fg={paletteSubtleText}>
226
- {truncateText(fieldDescription, queryDisplayWidth)}
232
+ {truncateText(translatedFieldDescription, queryDisplayWidth)}
227
233
  </Text>
228
234
  </Box>
229
235
  )}
@@ -231,6 +237,14 @@ export function CommandBarWorkflowFieldRow({
231
237
  );
232
238
  }
233
239
 
240
+ function translateWorkflowFieldDescription(description: string | null): string | null {
241
+ if (!description) return null;
242
+ const submitSuffix = " Ctrl+S submits.";
243
+ return description.endsWith(submitSuffix)
244
+ ? `${t(description.slice(0, -submitSuffix.length))} ${t(submitSuffix.trim())}`
245
+ : t(description);
246
+ }
247
+
234
248
  function buildTextareaPreviewLines(value: string, placeholder: string | undefined, queryDisplayWidth: number): string[] {
235
249
  const preview = value.trim();
236
250
  return (preview || placeholder || t("Unset"))