gloomberb 0.9.9 → 0.10.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 (519) hide show
  1. package/README.md +41 -3
  2. package/README.zh-CN.md +294 -0
  3. package/package.json +10 -4
  4. package/patches/@opentui%2Fcore@0.3.2.patch +26 -69
  5. package/src/api-client/auth.ts +2 -0
  6. package/src/api-client/index.test.ts +4 -0
  7. package/src/api-client/types.ts +3 -0
  8. package/src/app/pane-runtime/pane-settings-runtime.tsx +1 -0
  9. package/src/app/pane-template-dialog-wizard.test.tsx +36 -0
  10. package/src/app/pane-template-dialog-wizard.tsx +10 -3
  11. package/src/app/runtime/desktop-deeplink.test.ts +15 -0
  12. package/src/app/runtime/desktop-deeplink.ts +21 -6
  13. package/src/app.tsx +40 -33
  14. package/src/capabilities/service-capabilities.ts +7 -2
  15. package/src/cli/commands/automation.test.ts +202 -0
  16. package/src/cli/commands/automation.ts +153 -36
  17. package/src/cli/commands/market.ts +1 -14
  18. package/src/cli/commands/system.ts +4 -1
  19. package/src/cli/context.ts +1 -1
  20. package/src/cli/desktop-pane-shot.ts +65 -4
  21. package/src/cli/entry.ts +1 -1
  22. package/src/cli/pane-functions/capabilities.ts +532 -0
  23. package/src/cli/pane-functions/catalog.ts +39 -4
  24. package/src/cli/pane-functions/data.test.ts +17 -0
  25. package/src/cli/pane-functions/data.ts +55 -13
  26. package/src/cli/pane-functions/discovery.ts +36 -9
  27. package/src/cli/pane-functions/index.test.ts +104 -1
  28. package/src/cli/pane-functions/index.ts +64 -5
  29. package/src/cli/pane-functions/options.ts +21 -6
  30. package/src/cli/pane-functions/report.ts +735 -72
  31. package/src/cli/pane-functions/resolver.ts +70 -6
  32. package/src/cli/pane-functions/screenshot.test.ts +431 -0
  33. package/src/cli/pane-functions/screenshot.ts +952 -8
  34. package/src/components/chart/composite/column-layout.ts +215 -0
  35. package/src/components/chart/composite/composite-chart.test.tsx +630 -0
  36. package/src/components/chart/composite/composite-chart.tsx +1148 -0
  37. package/src/components/chart/composite/format.test.ts +137 -0
  38. package/src/components/chart/composite/format.ts +135 -0
  39. package/src/components/chart/composite/index.ts +38 -0
  40. package/src/components/chart/composite/interactions.test.ts +109 -0
  41. package/src/components/chart/composite/interactions.ts +247 -0
  42. package/src/components/chart/composite/price-series.test.ts +39 -0
  43. package/src/components/chart/composite/price-series.ts +78 -0
  44. package/src/components/chart/composite/rasterizer.ts +359 -0
  45. package/src/components/chart/composite/renderers.test.ts +610 -0
  46. package/src/components/chart/composite/scene.test.ts +464 -0
  47. package/src/components/chart/composite/scene.ts +553 -0
  48. package/src/components/chart/composite/text-renderer.ts +285 -0
  49. package/src/components/chart/composite/time-scale.test.ts +88 -0
  50. package/src/components/chart/composite/time-scale.ts +217 -0
  51. package/src/components/chart/composite/types.ts +141 -0
  52. package/src/components/chart/core/data.ts +81 -5
  53. package/src/components/chart/core/index.test.ts +128 -43
  54. package/src/components/chart/core/resolution.ts +0 -6
  55. package/src/components/chart/native/chart-rasterizer.ts +1 -3
  56. package/src/components/chart/native/kitty/index.test.ts +99 -120
  57. package/src/components/chart/native/kitty/manager.ts +3 -0
  58. package/src/components/chart/native/kitty/protocol.ts +4 -1
  59. package/src/components/chart/native/raster/crosshair.ts +79 -46
  60. package/src/components/chart/native/raster/types.ts +2 -11
  61. package/src/components/chart/native/surface/manager.ts +48 -3
  62. package/src/components/chart/price-axis-labels.tsx +18 -4
  63. package/src/components/command-bar/commands/direct/index.ts +25 -0
  64. package/src/components/command-bar/commands/registry.ts +9 -0
  65. package/src/components/command-bar/helpers.test.ts +4 -1
  66. package/src/components/command-bar/list/view.tsx +18 -6
  67. package/src/components/command-bar/multi-select-picker.tsx +6 -5
  68. package/src/components/command-bar/pane-settings.test.ts +141 -0
  69. package/src/components/command-bar/pane-settings.ts +61 -1
  70. package/src/components/command-bar/panel/body.tsx +7 -6
  71. package/src/components/command-bar/routes/root/command-items.ts +4 -4
  72. package/src/components/command-bar/routes/root/results.ts +10 -1
  73. package/src/components/command-bar/routes/root/selection.test.ts +54 -0
  74. package/src/components/command-bar/routes/root/selection.ts +13 -0
  75. package/src/components/command-bar/routing/actions.ts +18 -2
  76. package/src/components/command-bar/routing/list-state.ts +2 -1
  77. package/src/components/command-bar/surface/index.test.tsx +36 -4
  78. package/src/components/command-bar/surface/pane.test.tsx +93 -0
  79. package/src/components/command-bar/theme-picker.tsx +2 -1
  80. package/src/components/command-bar/view-model.ts +19 -20
  81. package/src/components/command-bar/workflow/body.tsx +2 -1
  82. package/src/components/command-bar/workflow/field-row.tsx +3 -2
  83. package/src/components/command-bar/workflow/fields.ts +4 -2
  84. package/src/components/command-bar/workflow/ops.test.ts +161 -0
  85. package/src/components/command-bar/workflow/ops.ts +20 -50
  86. package/src/components/command-bar/workflow/route-actions.test.ts +34 -0
  87. package/src/components/command-bar/workflow/route-actions.ts +4 -0
  88. package/src/components/command-bar/workflow/types.ts +1 -0
  89. package/src/components/feed-data-table/stack-view.tsx +6 -5
  90. package/src/components/index.ts +13 -1
  91. package/src/components/layout/detached-pane-shell.tsx +2 -1
  92. package/src/components/layout/floating-pane.tsx +3 -0
  93. package/src/components/layout/header.tsx +15 -8
  94. package/src/components/layout/pane/footer/index.test.tsx +22 -3
  95. package/src/components/layout/pane/footer/index.tsx +3 -1
  96. package/src/components/layout/pane/footer/model.ts +9 -2
  97. package/src/components/layout/pane/header.tsx +4 -6
  98. package/src/components/layout/pane/index.tsx +3 -0
  99. package/src/components/layout/pane/sidebar.test.tsx +100 -0
  100. package/src/components/layout/pane/sidebar.tsx +239 -0
  101. package/src/components/layout/pane/title.ts +4 -3
  102. package/src/components/layout/shell/action-menu-overlay.tsx +5 -3
  103. package/src/components/layout/shell/drag/runtime.ts +2 -0
  104. package/src/components/layout/shell/menu.test.ts +19 -0
  105. package/src/components/layout/shell/menu.ts +8 -5
  106. package/src/components/layout/shell/native/pointer-runtime.ts +4 -1
  107. package/src/components/layout/shell/pane/layers.tsx +2 -2
  108. package/src/components/layout/status-bar.tsx +8 -7
  109. package/src/components/layout/window-controls.tsx +4 -3
  110. package/src/components/metric-treemap/index.tsx +3 -2
  111. package/src/components/onboarding/onboarding-steps.tsx +30 -29
  112. package/src/components/onboarding/onboarding-wizard.tsx +5 -4
  113. package/src/components/onboarding/portfolio-step/broker-fields-panel.tsx +15 -12
  114. package/src/components/onboarding/portfolio-step/broker-setup-panel.tsx +17 -16
  115. package/src/components/onboarding/portfolio-step/broker-sync-panel.tsx +6 -5
  116. package/src/components/onboarding/portfolio-step/choose-panel.tsx +5 -4
  117. package/src/components/pane-settings-dialog/desktop.tsx +25 -3
  118. package/src/components/pane-settings-dialog/field-dialogs.tsx +5 -4
  119. package/src/components/pane-settings-dialog/tui.tsx +11 -9
  120. package/src/components/pane-settings-dialog/value.ts +8 -5
  121. package/src/components/pane-settings-dialog.test.tsx +235 -0
  122. package/src/components/pane-settings-dialog.tsx +24 -0
  123. package/src/components/pane-template-wizard.tsx +6 -2
  124. package/src/components/ticker/list-table-view.tsx +3 -2
  125. package/src/components/ui/button.tsx +3 -1
  126. package/src/components/ui/choice-dialog.tsx +6 -1
  127. package/src/components/ui/confirm-dialog.tsx +3 -2
  128. package/src/components/ui/fields.tsx +10 -1
  129. package/src/components/ui/frame.tsx +4 -1
  130. package/src/components/ui/index.ts +5 -0
  131. package/src/components/ui/inline-quick-add.tsx +104 -0
  132. package/src/components/ui/list-view.tsx +5 -4
  133. package/src/components/ui/loading.tsx +2 -1
  134. package/src/components/ui/multi-select/dialog.tsx +188 -44
  135. package/src/components/ui/popover.tsx +10 -0
  136. package/src/components/ui/shortcut-hint.tsx +2 -0
  137. package/src/components/ui/status.tsx +4 -3
  138. package/src/components/ui/tabs.tsx +19 -8
  139. package/src/components/ui/toggle.tsx +44 -3
  140. package/src/components/ui/ui.test.tsx +67 -5
  141. package/src/core/app-services.ts +4 -6
  142. package/src/core/state/session-persistence.ts +3 -2
  143. package/src/data/config/chart-settings.ts +712 -0
  144. package/src/data/config/layout.ts +24 -5
  145. package/src/data/config/store/index.test.ts +377 -14
  146. package/src/data/config/store/normalize.ts +79 -65
  147. package/src/data/fred-series.ts +137 -0
  148. package/src/i18n/index.test.ts +114 -0
  149. package/src/i18n/index.ts +147 -0
  150. package/src/i18n/ja.ts +1013 -0
  151. package/src/i18n/ko.ts +1011 -0
  152. package/src/i18n/languages.test.ts +31 -0
  153. package/src/i18n/languages.ts +72 -0
  154. package/src/i18n/zh-cn.ts +1013 -0
  155. package/src/i18n/zh-tw.ts +1013 -0
  156. package/src/plugins/builtin/account-management/footer.ts +3 -2
  157. package/src/plugins/builtin/account-management/form-components.tsx +3 -2
  158. package/src/plugins/builtin/account-management/keyboard.ts +6 -0
  159. package/src/plugins/builtin/account-management/model.ts +25 -21
  160. package/src/plugins/builtin/account-management/navigation.test.ts +27 -0
  161. package/src/plugins/builtin/account-management/navigation.ts +24 -0
  162. package/src/plugins/builtin/account-management/pane.tsx +120 -85
  163. package/src/plugins/builtin/account-management/password-dialog.tsx +10 -9
  164. package/src/plugins/builtin/ai/agent-history.ts +139 -0
  165. package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +133 -51
  166. package/src/plugins/builtin/ai/ask-ai.test.tsx +83 -22
  167. package/src/plugins/builtin/ai/index.test.ts +302 -0
  168. package/src/plugins/builtin/ai/index.tsx +374 -40
  169. package/src/plugins/builtin/ai/pane-settings.test.ts +155 -0
  170. package/src/plugins/builtin/ai/pane-settings.ts +319 -0
  171. package/src/plugins/builtin/ai/pi/credential-store.test.ts +80 -0
  172. package/src/plugins/builtin/ai/pi/credential-store.ts +138 -0
  173. package/src/plugins/builtin/ai/pi/file-store.ts +138 -0
  174. package/src/plugins/builtin/ai/pi/host.test.ts +500 -0
  175. package/src/plugins/builtin/ai/pi/host.ts +564 -0
  176. package/src/plugins/builtin/ai/pi/index.ts +5 -0
  177. package/src/plugins/builtin/ai/pi/models-store.test.ts +44 -0
  178. package/src/plugins/builtin/ai/pi/models-store.ts +141 -0
  179. package/src/plugins/builtin/ai/pi/providers.ts +68 -0
  180. package/src/plugins/builtin/ai/pi/runtime.test.ts +240 -0
  181. package/src/plugins/builtin/ai/pi/runtime.ts +635 -0
  182. package/src/plugins/builtin/ai/providers.test.ts +68 -0
  183. package/src/plugins/builtin/ai/providers.ts +163 -38
  184. package/src/plugins/builtin/ai/runner-selection.test.ts +174 -0
  185. package/src/plugins/builtin/ai/runner-selection.ts +193 -0
  186. package/src/plugins/builtin/ai/runner-selector.tsx +153 -0
  187. package/src/plugins/builtin/ai/runner.test.ts +198 -0
  188. package/src/plugins/builtin/ai/runner.ts +276 -73
  189. package/src/plugins/builtin/ai/screener/contract.test.ts +26 -73
  190. package/src/plugins/builtin/ai/screener/contract.ts +14 -52
  191. package/src/plugins/builtin/ai/screener/editor-runtime.ts +55 -5
  192. package/src/plugins/builtin/ai/screener/editor.tsx +42 -36
  193. package/src/plugins/builtin/ai/screener/footer.ts +82 -67
  194. package/src/plugins/builtin/ai/screener/keyboard.ts +27 -11
  195. package/src/plugins/builtin/ai/screener/model.test.ts +24 -0
  196. package/src/plugins/builtin/ai/screener/model.ts +9 -3
  197. package/src/plugins/builtin/ai/screener/pane.test.tsx +142 -55
  198. package/src/plugins/builtin/ai/screener/pane.tsx +95 -97
  199. package/src/plugins/builtin/ai/screener/results-view.tsx +8 -41
  200. package/src/plugins/builtin/ai/screener/runner.ts +41 -25
  201. package/src/plugins/builtin/ai/screener/tabs-bar.tsx +2 -2
  202. package/src/plugins/builtin/ai/use-runtime-providers.ts +28 -0
  203. package/src/plugins/builtin/ai/workspace/model.test.ts +227 -0
  204. package/src/plugins/builtin/ai/workspace/model.ts +308 -0
  205. package/src/plugins/builtin/ai/workspace/pane.test.tsx +197 -0
  206. package/src/plugins/builtin/ai/workspace/pane.tsx +896 -0
  207. package/src/plugins/builtin/analytics/index.test.tsx +3 -3
  208. package/src/plugins/builtin/analytics/index.tsx +3 -8
  209. package/src/plugins/builtin/broker-manager/detail.tsx +25 -24
  210. package/src/plugins/builtin/broker-manager/index.test.tsx +1 -1
  211. package/src/plugins/builtin/broker-manager/index.tsx +9 -11
  212. package/src/plugins/builtin/broker-manager/model.ts +11 -10
  213. package/src/plugins/builtin/broker-manager/pane-actions.tsx +22 -21
  214. package/src/plugins/builtin/broker-manager/table.ts +9 -8
  215. package/src/plugins/builtin/changelog/index.tsx +3 -8
  216. package/src/plugins/builtin/chart-composer/chart-spec.ts +89 -0
  217. package/src/plugins/builtin/chart-composer/cli-options.ts +98 -0
  218. package/src/plugins/builtin/chart-composer/date-window-dialog.tsx +120 -0
  219. package/src/plugins/builtin/chart-composer/editor.test.tsx +332 -0
  220. package/src/plugins/builtin/chart-composer/editor.tsx +1107 -0
  221. package/src/plugins/builtin/chart-composer/index.tsx +228 -0
  222. package/src/plugins/builtin/chart-composer/pane.tsx +645 -0
  223. package/src/plugins/builtin/chart-composer/presets.test.ts +561 -0
  224. package/src/plugins/builtin/chart-composer/presets.ts +759 -0
  225. package/src/plugins/builtin/chart-composer/quick-add.test.tsx +245 -0
  226. package/src/plugins/builtin/chart-composer/quick-add.tsx +409 -0
  227. package/src/plugins/builtin/chart-composer/semantic.ts +86 -0
  228. package/src/plugins/builtin/chart-composer/series-catalog.test.ts +65 -0
  229. package/src/plugins/builtin/chart-composer/series-catalog.ts +295 -0
  230. package/src/plugins/builtin/chart-composer/settings.test.ts +163 -0
  231. package/src/plugins/builtin/chart-composer/settings.ts +397 -0
  232. package/src/plugins/builtin/chart-composer/shortcuts.test.ts +44 -0
  233. package/src/plugins/builtin/chart-composer/shortcuts.ts +35 -0
  234. package/src/plugins/builtin/chart-composer/use-series-catalog.ts +103 -0
  235. package/src/plugins/builtin/chat/channel-labels.ts +5 -4
  236. package/src/plugins/builtin/chat/channels.ts +12 -11
  237. package/src/plugins/builtin/chat/chat.test.tsx +32 -0
  238. package/src/plugins/builtin/chat/content/composer-runtime.ts +6 -1
  239. package/src/plugins/builtin/chat/content/composer.tsx +9 -8
  240. package/src/plugins/builtin/chat/content/index.tsx +36 -1
  241. package/src/plugins/builtin/chat/content/new-dm-dialog.tsx +8 -7
  242. package/src/plugins/builtin/chat/content/transcript.tsx +9 -3
  243. package/src/plugins/builtin/chat/controller/utils.ts +10 -9
  244. package/src/plugins/builtin/chat/message/desktop.tsx +17 -10
  245. package/src/plugins/builtin/chat/message/profile-popover.test.tsx +122 -3
  246. package/src/plugins/builtin/chat/message/profile-popover.tsx +38 -1
  247. package/src/plugins/builtin/chat/message/terminal.tsx +19 -10
  248. package/src/plugins/builtin/chat/pane-state.test.ts +31 -0
  249. package/src/plugins/builtin/chat/pane-state.ts +30 -0
  250. package/src/plugins/builtin/chat/pane.tsx +27 -4
  251. package/src/plugins/builtin/chat/profile-popover.ts +62 -5
  252. package/src/plugins/builtin/chat/sidebar.test.tsx +4 -7
  253. package/src/plugins/builtin/chat/sidebar.tsx +107 -168
  254. package/src/plugins/builtin/chat/test-harness.tsx +6 -0
  255. package/src/plugins/builtin/cloud/auth-actions.tsx +5 -4
  256. package/src/plugins/builtin/cloud/plugin.tsx +150 -127
  257. package/src/plugins/builtin/composite-plugins.ts +71 -0
  258. package/src/plugins/builtin/correlation/index.test.tsx +4 -4
  259. package/src/plugins/builtin/correlation/index.tsx +3 -8
  260. package/src/plugins/builtin/earnings/index.tsx +37 -21
  261. package/src/plugins/builtin/earnings/model.test.ts +52 -0
  262. package/src/plugins/builtin/earnings/model.ts +32 -0
  263. package/src/plugins/builtin/econ/detail-view.tsx +17 -4
  264. package/src/plugins/builtin/econ/fred-cache.test.ts +57 -11
  265. package/src/plugins/builtin/econ/index.tsx +16 -20
  266. package/src/plugins/builtin/fear-greed/index.tsx +2 -8
  267. package/src/plugins/builtin/fx-matrix/index.tsx +3 -8
  268. package/src/plugins/builtin/help/components.tsx +5 -4
  269. package/src/plugins/builtin/help/index.tsx +14 -17
  270. package/src/plugins/builtin/holders/index.tsx +2 -8
  271. package/src/plugins/builtin/insider/index.tsx +2 -7
  272. package/src/plugins/builtin/kelly-sizer/index.test.tsx +2 -2
  273. package/src/plugins/builtin/kelly-sizer/index.tsx +3 -7
  274. package/src/plugins/builtin/kelly-sizer/test-support.tsx +3 -3
  275. package/src/plugins/builtin/layout-manager/index.tsx +3 -7
  276. package/src/plugins/builtin/market-heatmap/index.tsx +3 -8
  277. package/src/plugins/builtin/market-movers/index.tsx +3 -8
  278. package/src/plugins/builtin/news/index.tsx +12 -16
  279. package/src/plugins/builtin/news/wire/breaking/pane.test.tsx +3 -3
  280. package/src/plugins/builtin/news/wire/index.tsx +83 -73
  281. package/src/plugins/builtin/options/index.tsx +2 -8
  282. package/src/plugins/builtin/plugin-module.test.ts +89 -0
  283. package/src/plugins/builtin/plugin-module.ts +116 -0
  284. package/src/plugins/builtin/portfolio-list/header.tsx +4 -3
  285. package/src/plugins/builtin/portfolio-list/index.test.ts +17 -0
  286. package/src/plugins/builtin/portfolio-list/index.tsx +3 -5
  287. package/src/plugins/builtin/portfolio-list/pane/index.test.tsx +3 -3
  288. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +53 -53
  289. package/src/plugins/builtin/portfolio-list/settings.ts +5 -3
  290. package/src/plugins/builtin/portfolio-list/summary/index.tsx +4 -3
  291. package/src/plugins/builtin/research/index.tsx +2 -8
  292. package/src/plugins/builtin/sec/index.tsx +2 -8
  293. package/src/plugins/builtin/sectors/index.tsx +3 -8
  294. package/src/plugins/builtin/thirteenf/index.tsx +3 -8
  295. package/src/plugins/builtin/thirteenf/pane.test.tsx +1 -1
  296. package/src/plugins/builtin/ticker-detail/chart-tab-switch.test.tsx +49 -23
  297. package/src/plugins/builtin/ticker-detail/index.test.tsx +15 -136
  298. package/src/plugins/builtin/ticker-detail/index.tsx +2 -93
  299. package/src/plugins/builtin/ticker-detail/overview/components.tsx +6 -5
  300. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +41 -17
  301. package/src/plugins/builtin/ticker-detail/pane.tsx +4 -3
  302. package/src/plugins/builtin/ticker-detail/quote-monitor/index.test.tsx +1 -1
  303. package/src/plugins/builtin/ticker-detail/settings.ts +3 -50
  304. package/src/plugins/builtin/ticker-research-backend-plugin.ts +17 -0
  305. package/src/plugins/builtin/ticker-research-plugin.ts +27 -0
  306. package/src/plugins/builtin/tv/channels.ts +27 -0
  307. package/src/plugins/builtin/tv/index.tsx +40 -0
  308. package/src/plugins/builtin/tv/pane.tsx +286 -0
  309. package/src/plugins/builtin/tv/youtube-stream.ts +100 -0
  310. package/src/plugins/builtin/world-indices/index.tsx +3 -8
  311. package/src/plugins/builtin/yield-curve/index.tsx +8 -8
  312. package/src/plugins/catalog-backend.test.ts +21 -0
  313. package/src/plugins/catalog-backend.ts +42 -0
  314. package/src/plugins/catalog-ui.ts +6 -14
  315. package/src/plugins/ibkr/instance-selection.test.ts +0 -1
  316. package/src/plugins/ownership.ts +143 -0
  317. package/src/plugins/prediction-markets/backend-plugin.ts +19 -0
  318. package/src/plugins/prediction-markets/chart.test.tsx +7 -5
  319. package/src/plugins/prediction-markets/chart.tsx +25 -7
  320. package/src/plugins/registry/index.test.ts +51 -0
  321. package/src/plugins/registry/index.ts +8 -0
  322. package/src/plugins/registry/pane-settings.ts +1 -1
  323. package/src/plugins/runtime/index.test.tsx +2 -2
  324. package/src/remote/app-host.tsx +16 -3
  325. package/src/remote/controller.test.ts +102 -1
  326. package/src/remote/controller.ts +122 -0
  327. package/src/remote/types.ts +11 -0
  328. package/src/renderers/electrobun/bun/core-capabilities.ts +123 -27
  329. package/src/renderers/electrobun/bun/desktop/backend-requests.ts +5 -1
  330. package/src/renderers/electrobun/bun/desktop/initialization.ts +5 -1
  331. package/src/renderers/electrobun/bun/desktop/media.ts +15 -0
  332. package/src/renderers/electrobun/bun/desktop/window-style.ts +4 -1
  333. package/src/renderers/electrobun/bun/desktop/workspace/index.test.ts +32 -1
  334. package/src/renderers/electrobun/bun/desktop/workspace/index.ts +10 -0
  335. package/src/renderers/electrobun/bun/index.ts +5 -0
  336. package/src/renderers/electrobun/bun/tui-entry.ts +1 -6
  337. package/src/renderers/electrobun/view/ai-host.ts +124 -19
  338. package/src/renderers/electrobun/view/backend-rpc.ts +4 -0
  339. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +151 -39
  340. package/src/renderers/electrobun/view/desktop/popover.tsx +166 -0
  341. package/src/renderers/electrobun/view/dialog-host.test.ts +25 -0
  342. package/src/renderers/electrobun/view/dialog-host.tsx +75 -12
  343. package/src/renderers/electrobun/view/host/box.tsx +5 -0
  344. package/src/renderers/electrobun/view/host/chart-surface.tsx +10 -9
  345. package/src/renderers/electrobun/view/host/input.tsx +9 -19
  346. package/src/renderers/electrobun/view/host/media-surface.tsx +170 -0
  347. package/src/renderers/electrobun/view/host/mouse.ts +1 -0
  348. package/src/renderers/electrobun/view/host/style.ts +1 -1
  349. package/src/renderers/electrobun/view/host/tabs.tsx +4 -3
  350. package/src/renderers/electrobun/view/main.tsx +10 -3
  351. package/src/renderers/electrobun/view/styles.css +17 -0
  352. package/src/renderers/electrobun/view/ui-host.tsx +7 -0
  353. package/src/renderers/opentui/chart-surface.test.tsx +52 -6
  354. package/src/renderers/opentui/chart-surface.tsx +95 -11
  355. package/src/renderers/opentui/dialog-host.tsx +36 -5
  356. package/src/renderers/opentui/host.tsx +39 -0
  357. package/src/renderers/opentui/start.tsx +30 -0
  358. package/src/renderers/opentui/ui-host.tsx +2 -0
  359. package/src/sources/provider-router/index.test.ts +43 -0
  360. package/src/sources/provider-router/index.ts +4 -0
  361. package/src/sources/provider-router/supplemental.ts +46 -0
  362. package/src/sources/sec-edgar.test.ts +203 -2
  363. package/src/sources/sec-edgar.ts +70 -15
  364. package/src/state/app/context/index.tsx +13 -1
  365. package/src/state/app/context/selector.test.tsx +62 -1
  366. package/src/state/session-persistence.test.ts +13 -2
  367. package/src/sync/controller.test.ts +190 -3
  368. package/src/sync/controller.ts +76 -45
  369. package/src/sync/core-contributors.test.ts +121 -0
  370. package/src/sync/core-contributors.ts +104 -27
  371. package/src/sync/react.ts +6 -8
  372. package/src/sync/types.ts +3 -0
  373. package/src/time-series/alignment.ts +158 -0
  374. package/src/time-series/economic.ts +49 -0
  375. package/src/time-series/field-catalog.ts +306 -0
  376. package/src/time-series/fundamentals.test.ts +505 -0
  377. package/src/time-series/fundamentals.ts +842 -0
  378. package/src/time-series/hooks.ts +45 -0
  379. package/src/time-series/index.ts +14 -0
  380. package/src/time-series/live-quotes.test.ts +219 -0
  381. package/src/time-series/live-quotes.ts +203 -0
  382. package/src/time-series/market-resolution.ts +53 -0
  383. package/src/time-series/market.ts +125 -0
  384. package/src/time-series/reporting.ts +98 -0
  385. package/src/time-series/resolve.test.ts +1281 -0
  386. package/src/time-series/resolve.ts +783 -0
  387. package/src/time-series/spec-studies.test.ts +400 -0
  388. package/src/time-series/spec.ts +487 -0
  389. package/src/time-series/studies.ts +563 -0
  390. package/src/time-series/transforms.test.ts +105 -0
  391. package/src/time-series/transforms.ts +152 -0
  392. package/src/time-series/types.ts +162 -0
  393. package/src/time-series/use-chart-resolution.ts +166 -0
  394. package/src/types/config.ts +9 -11
  395. package/src/types/desktop-window.ts +1 -0
  396. package/src/types/financials.ts +4 -0
  397. package/src/types/media.ts +17 -0
  398. package/src/types/plugin.ts +32 -1
  399. package/src/ui/context-menu.tsx +15 -3
  400. package/src/ui/dialog.tsx +14 -2
  401. package/src/ui/host.tsx +40 -1
  402. package/src/ui/index.tsx +9 -0
  403. package/src/utils/exchanges.test.ts +13 -1
  404. package/src/utils/exchanges.ts +10 -0
  405. package/src/utils/financial-statements.test.ts +138 -0
  406. package/src/utils/financial-statements.ts +115 -8
  407. package/src/utils/format.test.ts +11 -1
  408. package/src/utils/format.ts +7 -0
  409. package/src/version.ts +1 -1
  410. package/src/components/chart/bar-chart-renderer.test.ts +0 -233
  411. package/src/components/chart/bar-chart-renderer.ts +0 -445
  412. package/src/components/chart/comparison/data.test.ts +0 -159
  413. package/src/components/chart/comparison/data.ts +0 -302
  414. package/src/components/chart/comparison/renderer.test.ts +0 -187
  415. package/src/components/chart/comparison/renderer.ts +0 -282
  416. package/src/components/chart/comparison/scene.ts +0 -178
  417. package/src/components/chart/comparison-stock-chart/axis-width.ts +0 -99
  418. package/src/components/chart/comparison-stock-chart/canvas.ts +0 -148
  419. package/src/components/chart/comparison-stock-chart/controls.ts +0 -252
  420. package/src/components/chart/comparison-stock-chart/helpers.ts +0 -101
  421. package/src/components/chart/comparison-stock-chart/interactions.ts +0 -208
  422. package/src/components/chart/comparison-stock-chart/layout-metrics.ts +0 -62
  423. package/src/components/chart/comparison-stock-chart/layout.tsx +0 -155
  424. package/src/components/chart/comparison-stock-chart/legend.tsx +0 -74
  425. package/src/components/chart/comparison-stock-chart/native-surfaces.ts +0 -89
  426. package/src/components/chart/comparison-stock-chart/render-data.ts +0 -227
  427. package/src/components/chart/comparison-stock-chart/render-output.ts +0 -140
  428. package/src/components/chart/comparison-stock-chart/selection-runtime.ts +0 -128
  429. package/src/components/chart/comparison-stock-chart/sources.ts +0 -51
  430. package/src/components/chart/comparison-stock-chart/surface-runtime.ts +0 -159
  431. package/src/components/chart/comparison-stock-chart/toolbar.tsx +0 -117
  432. package/src/components/chart/comparison-stock-chart/types.ts +0 -22
  433. package/src/components/chart/comparison-stock-chart/view.tsx +0 -572
  434. package/src/components/chart/comparison-stock-chart/viewport-runtime.ts +0 -239
  435. package/src/components/chart/comparison-stock-chart.tsx +0 -24
  436. package/src/components/chart/core/axis-measure.ts +0 -33
  437. package/src/components/chart/core/controller.test.ts +0 -157
  438. package/src/components/chart/core/controller.ts +0 -349
  439. package/src/components/chart/core/indicator-selector.tsx +0 -75
  440. package/src/components/chart/core/keyboard.ts +0 -24
  441. package/src/components/chart/core/pane-settings.test.ts +0 -52
  442. package/src/components/chart/core/pane-settings.ts +0 -141
  443. package/src/components/chart/core/scroll.test.ts +0 -64
  444. package/src/components/chart/core/scroll.ts +0 -82
  445. package/src/components/chart/core/window-viewport.test.ts +0 -69
  446. package/src/components/chart/core/window-viewport.ts +0 -162
  447. package/src/components/chart/display-cursor.ts +0 -153
  448. package/src/components/chart/indicators/bands.test.ts +0 -36
  449. package/src/components/chart/indicators/bands.ts +0 -29
  450. package/src/components/chart/indicators/moving-averages.test.ts +0 -39
  451. package/src/components/chart/indicators/moving-averages.ts +0 -42
  452. package/src/components/chart/indicators/options.test.ts +0 -32
  453. package/src/components/chart/indicators/options.ts +0 -107
  454. package/src/components/chart/indicators/oscillators.test.ts +0 -58
  455. package/src/components/chart/indicators/oscillators.ts +0 -92
  456. package/src/components/chart/native/canvas-bitmaps.ts +0 -140
  457. package/src/components/chart/native/raster/comparison-chart.ts +0 -162
  458. package/src/components/chart/native/surface/hooks.ts +0 -144
  459. package/src/components/chart/native/surface/rendering.ts +0 -211
  460. package/src/components/chart/static/bar-chart-surface.test.tsx +0 -101
  461. package/src/components/chart/static/bar-chart-surface.tsx +0 -292
  462. package/src/components/chart/stock-chart/auto.ts +0 -216
  463. package/src/components/chart/stock-chart/axis-width.ts +0 -146
  464. package/src/components/chart/stock-chart/bitmaps.ts +0 -22
  465. package/src/components/chart/stock-chart/canvas.ts +0 -202
  466. package/src/components/chart/stock-chart/controls.tsx +0 -149
  467. package/src/components/chart/stock-chart/cursor.ts +0 -60
  468. package/src/components/chart/stock-chart/data-runtime.ts +0 -128
  469. package/src/components/chart/stock-chart/footer.test.ts +0 -65
  470. package/src/components/chart/stock-chart/footer.ts +0 -303
  471. package/src/components/chart/stock-chart/geometry.ts +0 -160
  472. package/src/components/chart/stock-chart/index.test.ts +0 -532
  473. package/src/components/chart/stock-chart/index.tsx +0 -29
  474. package/src/components/chart/stock-chart/indicators.ts +0 -226
  475. package/src/components/chart/stock-chart/interaction-runtime.ts +0 -44
  476. package/src/components/chart/stock-chart/interactions.ts +0 -300
  477. package/src/components/chart/stock-chart/keyboard.ts +0 -373
  478. package/src/components/chart/stock-chart/layout-metrics.ts +0 -62
  479. package/src/components/chart/stock-chart/layout.tsx +0 -177
  480. package/src/components/chart/stock-chart/native-surfaces.ts +0 -121
  481. package/src/components/chart/stock-chart/presentation.ts +0 -67
  482. package/src/components/chart/stock-chart/renderer-switch.test.tsx +0 -263
  483. package/src/components/chart/stock-chart/rendering/bounds.ts +0 -116
  484. package/src/components/chart/stock-chart/rendering/candidates.ts +0 -145
  485. package/src/components/chart/stock-chart/rendering/data.ts +0 -370
  486. package/src/components/chart/stock-chart/rendering/invalidation.ts +0 -68
  487. package/src/components/chart/stock-chart/rendering/output.ts +0 -304
  488. package/src/components/chart/stock-chart/rendering/planning.ts +0 -122
  489. package/src/components/chart/stock-chart/rendering/projection.test.tsx +0 -98
  490. package/src/components/chart/stock-chart/rendering/projection.ts +0 -112
  491. package/src/components/chart/stock-chart/rendering/state.test.ts +0 -70
  492. package/src/components/chart/stock-chart/rendering/state.ts +0 -283
  493. package/src/components/chart/stock-chart/requests.ts +0 -337
  494. package/src/components/chart/stock-chart/resolution-support.ts +0 -96
  495. package/src/components/chart/stock-chart/resolved/runtime.ts +0 -289
  496. package/src/components/chart/stock-chart/resolved.tsx +0 -359
  497. package/src/components/chart/stock-chart/settings.ts +0 -81
  498. package/src/components/chart/stock-chart/surface-runtime.ts +0 -62
  499. package/src/components/chart/stock-chart/types.ts +0 -25
  500. package/src/components/chart/stock-chart/view.tsx +0 -155
  501. package/src/components/chart/stock-chart/viewport/effects.ts +0 -293
  502. package/src/components/chart/stock-chart/viewport/index.ts +0 -144
  503. package/src/components/chart/stock-chart/viewport/runtime.ts +0 -375
  504. package/src/components/chart/time-axis-label.tsx +0 -175
  505. package/src/plugins/builtin/ai/gloomberb-cli.ts +0 -71
  506. package/src/plugins/builtin/ai/screener/action-bar.tsx +0 -58
  507. package/src/plugins/builtin/comparison-chart/index.test.tsx +0 -848
  508. package/src/plugins/builtin/comparison-chart/index.tsx +0 -194
  509. package/src/plugins/builtin/econ/fred-cache.ts +0 -106
  510. package/src/plugins/builtin/plugin-groups.ts +0 -130
  511. package/src/plugins/builtin/ticker-detail/chart-pane.tsx +0 -91
  512. package/src/plugins/builtin/ticker-detail/chart-tab.tsx +0 -100
  513. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/content.tsx +0 -296
  514. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/data.ts +0 -62
  515. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/model.ts +0 -608
  516. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/settings.ts +0 -65
  517. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/types.ts +0 -58
  518. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph.tsx +0 -138
  519. package/src/plugins/builtin/ticker-detail/data-panes.test.ts +0 -220
@@ -0,0 +1,397 @@
1
+ import type { ChartResolution, TimeRange } from "../../../components/chart/core/types";
2
+ import type {
3
+ PaneSettingField,
4
+ PaneSettingOption,
5
+ PaneSettingsContext,
6
+ PaneSettingsDef,
7
+ } from "../../../types/plugin";
8
+ import type {
9
+ ChartPanelSpec,
10
+ ChartSeriesSpec,
11
+ ChartSpec,
12
+ ChartStudySpec,
13
+ SeriesStyle,
14
+ } from "../../../time-series/types";
15
+ import { isOhlcSeriesStyle } from "../../../time-series/spec";
16
+ import {
17
+ applySeriesStyle,
18
+ buildCustomChartPreset,
19
+ buildEmptyChartPreset,
20
+ buildPriceChartPreset,
21
+ chartSeriesLabel,
22
+ formatSeriesExpression,
23
+ getCompatibleSeriesStyles,
24
+ getSelectedBuiltinStudies,
25
+ getSelectedPairStudies,
26
+ setBuiltinStudies,
27
+ setPairStudies,
28
+ type BuiltinStudySelection,
29
+ type PairStudySelection,
30
+ } from "./presets";
31
+ import {
32
+ CHART_SPEC_SETTING_KEY,
33
+ parseChartSpecOr,
34
+ } from "./chart-spec";
35
+
36
+ export const CHART_RANGES: TimeRange[] = ["1D", "1W", "1M", "3M", "6M", "1Y", "5Y", "ALL"];
37
+ export const CHART_RESOLUTIONS: ChartResolution[] = [
38
+ "auto",
39
+ "1m",
40
+ "5m",
41
+ "15m",
42
+ "30m",
43
+ "45m",
44
+ "1h",
45
+ "1d",
46
+ "1wk",
47
+ "1mo",
48
+ ];
49
+
50
+ export const CHART_STUDY_OPTIONS: Array<PaneSettingOption & { value: BuiltinStudySelection }> = [
51
+ { value: "volume", label: "Volume", description: "Volume columns in a lower panel." },
52
+ { value: "sma20", label: "SMA 20", description: "20-bar simple moving average on the primary price series." },
53
+ { value: "sma50", label: "SMA 50", description: "50-bar simple moving average on the primary price series." },
54
+ { value: "sma200", label: "SMA 200", description: "200-bar simple moving average on the primary price series." },
55
+ { value: "ema20", label: "EMA 20", description: "20-bar exponential moving average on the primary price series." },
56
+ { value: "bollinger20", label: "Bollinger 20", description: "20-bar Bollinger Bands at two standard deviations." },
57
+ { value: "rsi14", label: "RSI 14", description: "14-bar Relative Strength Index in a lower panel." },
58
+ { value: "macd", label: "MACD", description: "12/26/9 MACD in a lower panel." },
59
+ ];
60
+
61
+ export const CHART_FORMULA_OPTIONS: Array<PaneSettingOption & { value: PairStudySelection }> = [
62
+ { value: "ratio", label: "Ratio", description: "First series divided by the second series." },
63
+ { value: "spread", label: "Spread", description: "First series minus the second series." },
64
+ { value: "correlation", label: "Correlation 20", description: "20-observation rolling return correlation." },
65
+ ];
66
+
67
+ export const CHART_SETTING_KEYS = {
68
+ series: "chartSeries",
69
+ indicators: "chartIndicators",
70
+ formulas: "chartFormulas",
71
+ dateWindow: "chartDateWindow",
72
+ range: "chartRange",
73
+ resolution: "chartResolution",
74
+ mode: "chartMode",
75
+ } as const;
76
+
77
+ function fallbackSpec(symbol: string | null | undefined): ChartSpec {
78
+ return symbol ? buildPriceChartPreset(symbol) : buildEmptyChartPreset();
79
+ }
80
+
81
+ function sourceKey(series: ChartSeriesSpec): string {
82
+ return formatSeriesExpression(series).toLowerCase();
83
+ }
84
+
85
+ /**
86
+ * The compact style picker is intentionally scoped to a chart with exactly one
87
+ * authored series. Multi-series styling belongs in the Series editor where the
88
+ * target is explicit, even when all but one series are currently hidden.
89
+ */
90
+ export function getChartInlineStyleTarget(spec: ChartSpec): ChartSeriesSpec | null {
91
+ return spec.series.length === 1 ? spec.series[0] ?? null : null;
92
+ }
93
+
94
+ export function getChartInlineStyles(spec: ChartSpec): SeriesStyle[] {
95
+ const target = getChartInlineStyleTarget(spec);
96
+ if (!target) return [];
97
+ const fieldId = target.source.kind === "security" ? target.source.fieldId : "";
98
+ const anotherOhlcSeriesSharesPanel = spec.series.some((series) => (
99
+ series.id !== target.id
100
+ && series.panelId === target.panelId
101
+ && isOhlcSeriesStyle(series.style)
102
+ ));
103
+ return getCompatibleSeriesStyles(fieldId).filter((style) => (
104
+ !anotherOhlcSeriesSharesPanel || !isOhlcSeriesStyle(style)
105
+ ));
106
+ }
107
+
108
+ function managedStudy(study: ChartStudySpec): boolean {
109
+ return study.id.startsWith("builtin:") || study.id.startsWith("pair:");
110
+ }
111
+
112
+ function reconcileBasePanels(
113
+ existing: readonly ChartPanelSpec[],
114
+ authored: readonly ChartPanelSpec[],
115
+ series: readonly ChartSeriesSpec[],
116
+ studies: readonly ChartStudySpec[],
117
+ ): ChartPanelSpec[] {
118
+ const requiredIds = new Set([
119
+ "main",
120
+ ...series.map((entry) => entry.panelId),
121
+ ...studies.map((entry) => entry.panelId),
122
+ ]);
123
+ const existingById = new Map(existing.map((panel) => [panel.id, panel] as const));
124
+ const authoredById = new Map(authored.map((panel) => [panel.id, panel] as const));
125
+ return [...requiredIds].map((id) => (
126
+ existingById.get(id)
127
+ ?? authoredById.get(id)
128
+ ?? { id }
129
+ ));
130
+ }
131
+
132
+ export function replaceChartSeriesFromExpression(
133
+ spec: ChartSpec,
134
+ expression: string,
135
+ ): ChartSpec {
136
+ const authored = buildCustomChartPreset(expression);
137
+ const existingBySource = new Map<string, ChartSeriesSpec[]>();
138
+ for (const series of spec.series) {
139
+ const key = sourceKey(series);
140
+ existingBySource.set(key, [...(existingBySource.get(key) ?? []), series]);
141
+ }
142
+
143
+ const series = authored.series.map((entry) => {
144
+ const matches = existingBySource.get(sourceKey(entry));
145
+ return matches?.shift() ?? entry;
146
+ });
147
+ const seriesIds = new Set(series.map((entry) => entry.id));
148
+ const customStudies = spec.studies.filter((study) => (
149
+ !managedStudy(study)
150
+ && study.inputSeriesIds.every((seriesId) => seriesIds.has(seriesId))
151
+ ));
152
+ const builtinStudies = getSelectedBuiltinStudies(spec);
153
+ const pairStudies = getSelectedPairStudies(spec);
154
+ const panels = reconcileBasePanels(spec.panels, authored.panels, series, customStudies);
155
+ const base: ChartSpec = {
156
+ ...spec,
157
+ series,
158
+ studies: customStudies,
159
+ panels,
160
+ };
161
+ return setPairStudies(setBuiltinStudies(base, builtinStudies), pairStudies);
162
+ }
163
+
164
+ function formatDateWindow(spec: ChartSpec): string {
165
+ const window = spec.viewport.dateWindow;
166
+ return window ? `${window.start} to ${window.end}` : "";
167
+ }
168
+
169
+ function parseDate(value: string): string {
170
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
171
+ throw new Error("Use YYYY-MM-DD dates.");
172
+ }
173
+ const date = new Date(`${value}T00:00:00.000Z`);
174
+ if (!Number.isFinite(date.getTime()) || date.toISOString().slice(0, 10) !== value) {
175
+ throw new Error(`"${value}" is not a valid date.`);
176
+ }
177
+ return value;
178
+ }
179
+
180
+ function parseDateWindow(value: string): { start: string; end: string } | undefined {
181
+ const trimmed = value.trim();
182
+ if (!trimmed) return undefined;
183
+ const match = trimmed.match(
184
+ /^(\d{4}-\d{2}-\d{2})\s*(?:to|through|\.\.|,|–|—)\s*(\d{4}-\d{2}-\d{2})$/i,
185
+ );
186
+ if (!match) {
187
+ throw new Error("Use YYYY-MM-DD to YYYY-MM-DD, or leave blank for the preset range.");
188
+ }
189
+ const start = parseDate(match[1]!);
190
+ const end = parseDate(match[2]!);
191
+ if (start > end) throw new Error("The start date must be before the end date.");
192
+ return { start, end };
193
+ }
194
+
195
+ function requireString(value: unknown, label: string): string {
196
+ if (typeof value !== "string") throw new Error(`${label} must be text.`);
197
+ return value;
198
+ }
199
+
200
+ function requireSelection<T extends string>(
201
+ value: unknown,
202
+ allowed: readonly T[],
203
+ label: string,
204
+ ): T[] {
205
+ if (!Array.isArray(value) || value.some((entry) => (
206
+ typeof entry !== "string" || !allowed.includes(entry as T)
207
+ ))) {
208
+ throw new Error(`Choose valid ${label.toLowerCase()} options.`);
209
+ }
210
+ return value as T[];
211
+ }
212
+
213
+ function cleanDerivedSettings(
214
+ settings: Record<string, unknown>,
215
+ spec: ChartSpec,
216
+ ): Record<string, unknown> {
217
+ const next: Record<string, unknown> = {
218
+ ...settings,
219
+ [CHART_SPEC_SETTING_KEY]: spec,
220
+ };
221
+ for (const key of Object.values(CHART_SETTING_KEYS)) delete next[key];
222
+ delete next.chartExpression;
223
+ return next;
224
+ }
225
+
226
+ export function applyChartComposerPaneSetting(
227
+ settings: Record<string, unknown>,
228
+ field: PaneSettingField,
229
+ value: unknown,
230
+ context?: Pick<PaneSettingsContext, "activeTicker">,
231
+ ): Record<string, unknown> {
232
+ const spec = parseChartSpecOr(
233
+ settings[CHART_SPEC_SETTING_KEY],
234
+ fallbackSpec(context?.activeTicker),
235
+ );
236
+ let nextSpec = spec;
237
+
238
+ switch (field.key) {
239
+ case CHART_SETTING_KEYS.series:
240
+ nextSpec = replaceChartSeriesFromExpression(spec, requireString(value, "Series"));
241
+ break;
242
+ case CHART_SETTING_KEYS.indicators:
243
+ nextSpec = setBuiltinStudies(
244
+ spec,
245
+ requireSelection(
246
+ value,
247
+ CHART_STUDY_OPTIONS.map((option) => option.value),
248
+ "indicator",
249
+ ),
250
+ );
251
+ break;
252
+ case CHART_SETTING_KEYS.formulas:
253
+ nextSpec = setPairStudies(
254
+ spec,
255
+ requireSelection(
256
+ value,
257
+ CHART_FORMULA_OPTIONS.map((option) => option.value),
258
+ "formula",
259
+ ),
260
+ );
261
+ break;
262
+ case CHART_SETTING_KEYS.dateWindow: {
263
+ const dateWindow = parseDateWindow(requireString(value, "Date window"));
264
+ nextSpec = {
265
+ ...spec,
266
+ viewport: {
267
+ ...spec.viewport,
268
+ dateWindow,
269
+ maxPoints: undefined,
270
+ },
271
+ };
272
+ break;
273
+ }
274
+ case CHART_SETTING_KEYS.range: {
275
+ const range = requireString(value, "Range") as TimeRange;
276
+ if (!CHART_RANGES.includes(range)) throw new Error("Choose a valid chart range.");
277
+ nextSpec = {
278
+ ...spec,
279
+ viewport: {
280
+ ...spec.viewport,
281
+ range,
282
+ dateWindow: undefined,
283
+ maxPoints: undefined,
284
+ },
285
+ };
286
+ break;
287
+ }
288
+ case CHART_SETTING_KEYS.resolution: {
289
+ const resolution = requireString(value, "Resolution") as ChartResolution;
290
+ if (!CHART_RESOLUTIONS.includes(resolution)) throw new Error("Choose a valid chart resolution.");
291
+ nextSpec = { ...spec, viewport: { ...spec.viewport, resolution } };
292
+ break;
293
+ }
294
+ case CHART_SETTING_KEYS.mode: {
295
+ const target = getChartInlineStyleTarget(spec);
296
+ const mode = requireString(value, "Mode") as SeriesStyle;
297
+ if (!target) {
298
+ throw new Error(spec.series.length === 0
299
+ ? "Add a series before choosing a chart style."
300
+ : "Use the Series editor to style a multi-series chart.");
301
+ }
302
+ if (!getChartInlineStyles(spec).includes(mode)) {
303
+ throw new Error(`That chart style is not compatible with ${chartSeriesLabel(target)}.`);
304
+ }
305
+ nextSpec = {
306
+ ...spec,
307
+ series: spec.series.map((series) => (
308
+ series.id === target.id ? applySeriesStyle(series, mode) : series
309
+ )),
310
+ };
311
+ break;
312
+ }
313
+ default:
314
+ return { ...settings, [field.key]: value };
315
+ }
316
+
317
+ return cleanDerivedSettings(settings, nextSpec);
318
+ }
319
+
320
+ export function buildChartComposerPaneSettingsDef(
321
+ settings: Record<string, unknown>,
322
+ activeTicker?: string | null,
323
+ ): PaneSettingsDef {
324
+ const spec = parseChartSpecOr(settings[CHART_SPEC_SETTING_KEY], fallbackSpec(activeTicker));
325
+ const inlineStyleTarget = getChartInlineStyleTarget(spec);
326
+ const modes = getChartInlineStyles(spec);
327
+
328
+ return {
329
+ title: "Chart Settings",
330
+ values: {
331
+ [CHART_SETTING_KEYS.series]: spec.series.map(formatSeriesExpression).join(", "),
332
+ [CHART_SETTING_KEYS.indicators]: getSelectedBuiltinStudies(spec),
333
+ [CHART_SETTING_KEYS.formulas]: getSelectedPairStudies(spec),
334
+ [CHART_SETTING_KEYS.dateWindow]: formatDateWindow(spec),
335
+ [CHART_SETTING_KEYS.range]: spec.viewport.range,
336
+ [CHART_SETTING_KEYS.resolution]: spec.viewport.resolution,
337
+ [CHART_SETTING_KEYS.mode]: inlineStyleTarget?.style ?? "",
338
+ },
339
+ fields: [
340
+ {
341
+ key: CHART_SETTING_KEYS.series,
342
+ label: "Series",
343
+ description: "Edit the chart sources. Press S in the pane for guided search and field suggestions.",
344
+ type: "text",
345
+ placeholder: "AAPL:price, MSFT:revenue, FRED:CPIAUCSL",
346
+ },
347
+ {
348
+ key: CHART_SETTING_KEYS.indicators,
349
+ label: "Indicators",
350
+ description: "Choose price studies and lower-panel indicators.",
351
+ type: "multi-select",
352
+ options: CHART_STUDY_OPTIONS,
353
+ },
354
+ {
355
+ key: CHART_SETTING_KEYS.formulas,
356
+ label: "Formulas",
357
+ description: "Compare two chart series with a derived formula.",
358
+ type: "multi-select",
359
+ options: CHART_FORMULA_OPTIONS,
360
+ },
361
+ {
362
+ key: CHART_SETTING_KEYS.dateWindow,
363
+ label: "Date Window",
364
+ description: "Enter YYYY-MM-DD to YYYY-MM-DD. Leave blank to use the preset range.",
365
+ type: "text",
366
+ placeholder: "2025-01-01 to 2026-01-01",
367
+ },
368
+ {
369
+ key: CHART_SETTING_KEYS.range,
370
+ label: "Range",
371
+ description: "Set the preset time range and clear any custom date window.",
372
+ type: "select",
373
+ options: CHART_RANGES.map((range) => ({ value: range, label: range })),
374
+ },
375
+ {
376
+ key: CHART_SETTING_KEYS.resolution,
377
+ label: "Resolution",
378
+ description: "Choose the observation interval, or let the chart select it automatically.",
379
+ type: "select",
380
+ options: CHART_RESOLUTIONS.map((resolution) => ({
381
+ value: resolution,
382
+ label: resolution.toUpperCase(),
383
+ })),
384
+ },
385
+ ...(inlineStyleTarget
386
+ ? [{
387
+ key: CHART_SETTING_KEYS.mode,
388
+ label: `Style (${chartSeriesLabel(inlineStyleTarget)})`,
389
+ description: `Choose how ${chartSeriesLabel(inlineStyleTarget)} is drawn.`,
390
+ type: "select" as const,
391
+ options: modes.map((mode) => ({ value: mode, label: mode.toUpperCase() })),
392
+ }]
393
+ : []),
394
+ ],
395
+ applyValue: applyChartComposerPaneSetting,
396
+ };
397
+ }
@@ -0,0 +1,44 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import type { KeyEventLike } from "../../../react/input";
3
+ import { resolveChartComposerShortcut } from "./shortcuts";
4
+
5
+ function keyEvent(
6
+ name: string,
7
+ overrides: Partial<KeyEventLike> = {},
8
+ ): KeyEventLike {
9
+ return {
10
+ key: name,
11
+ name,
12
+ sequence: name,
13
+ ctrl: false,
14
+ shift: false,
15
+ alt: false,
16
+ meta: false,
17
+ preventDefault() {},
18
+ stopPropagation() {},
19
+ ...overrides,
20
+ };
21
+ }
22
+
23
+ describe("resolveChartComposerShortcut", () => {
24
+ test("leaves shell and editable shortcuts untouched", () => {
25
+ expect(resolveChartComposerShortcut(keyEvent("w", { ctrl: true }), 8)).toBeNull();
26
+ expect(resolveChartComposerShortcut(keyEvent("w", { meta: true }), 8)).toBeNull();
27
+ expect(resolveChartComposerShortcut(keyEvent("m", { ctrl: true, shift: true }), 8)).toBeNull();
28
+ expect(resolveChartComposerShortcut(keyEvent("r", { ctrl: true, shift: true }), 8)).toBeNull();
29
+ expect(resolveChartComposerShortcut(keyEvent("w", { targetEditable: true }), 8)).toBeNull();
30
+ expect(resolveChartComposerShortcut(keyEvent("w", { defaultPrevented: true }), 8)).toBeNull();
31
+ expect(resolveChartComposerShortcut(keyEvent("w", { propagationStopped: true }), 8)).toBeNull();
32
+ });
33
+
34
+ test("matches only exact chart shortcuts", () => {
35
+ expect(resolveChartComposerShortcut(keyEvent("s"), 8)).toBe("series");
36
+ expect(resolveChartComposerShortcut(keyEvent("w"), 8)).toBe("dates");
37
+ expect(resolveChartComposerShortcut(keyEvent("m"), 8)).toBe("mode");
38
+ expect(resolveChartComposerShortcut(keyEvent("r"), 8)).toBe("resolution");
39
+ expect(resolveChartComposerShortcut(keyEvent("r", { shift: true }), 8)).toBe("reload");
40
+ expect(resolveChartComposerShortcut(keyEvent("3"), 8)).toEqual({ type: "range", index: 2 });
41
+ expect(resolveChartComposerShortcut(keyEvent("9"), 8)).toBeNull();
42
+ expect(resolveChartComposerShortcut(keyEvent("3", { alt: true }), 8)).toBeNull();
43
+ });
44
+ });
@@ -0,0 +1,35 @@
1
+ import type { KeyEventLike } from "../../../react/input";
2
+ import { isPlainKey } from "../../../utils/keyboard";
3
+
4
+ export type ChartComposerShortcut =
5
+ | "series"
6
+ | "dates"
7
+ | "mode"
8
+ | "resolution"
9
+ | "reload"
10
+ | { type: "range"; index: number };
11
+
12
+ export function resolveChartComposerShortcut(
13
+ event: KeyEventLike,
14
+ rangeCount: number,
15
+ ): ChartComposerShortcut | null {
16
+ if (event.defaultPrevented || event.propagationStopped || event.targetEditable) return null;
17
+
18
+ const exactShiftReload = event.name === "r"
19
+ && event.shift
20
+ && !event.ctrl
21
+ && !event.meta
22
+ && !event.super
23
+ && !event.alt;
24
+ if (exactShiftReload) return "reload";
25
+ if (isPlainKey(event, "s")) return "series";
26
+ if (isPlainKey(event, "w")) return "dates";
27
+ if (isPlainKey(event, "m")) return "mode";
28
+ if (isPlainKey(event, "r")) return "resolution";
29
+ if (!isPlainKey(event, event.name ?? "")) return null;
30
+
31
+ const rangeIndex = Number(event.name) - 1;
32
+ return Number.isInteger(rangeIndex) && rangeIndex >= 0 && rangeIndex < rangeCount
33
+ ? { type: "range", index: rangeIndex }
34
+ : null;
35
+ }
@@ -0,0 +1,103 @@
1
+ import { useEffect, useMemo, useState } from "react";
2
+ import { searchTickerCandidates } from "../../../tickers/search";
3
+ import { useOptionalAppSelector } from "../../../state/app/context";
4
+ import type { TickerRecord } from "../../../types/ticker";
5
+ import { getSharedRegistry } from "../../registry";
6
+ import {
7
+ analyzeSeriesSearchQuery,
8
+ buildSeriesCatalogSuggestions,
9
+ type SeriesCatalogInstrument,
10
+ type SeriesCatalogSuggestion,
11
+ } from "./series-catalog";
12
+
13
+ const EMPTY_TICKERS: ReadonlyMap<string, TickerRecord> = new Map();
14
+
15
+ export interface SeriesCatalogSearchResult {
16
+ suggestions: SeriesCatalogSuggestion[];
17
+ instruments: SeriesCatalogInstrument[];
18
+ loading: boolean;
19
+ }
20
+
21
+ /** Shared smart-series search used by both inline quick-add and the full editor. */
22
+ export function useSeriesCatalogSuggestions({
23
+ query,
24
+ defaultInstrument,
25
+ enabled,
26
+ }: {
27
+ query: string;
28
+ defaultInstrument: SeriesCatalogInstrument;
29
+ enabled: boolean;
30
+ }): SeriesCatalogSearchResult {
31
+ const tickers = useOptionalAppSelector((state) => state.tickers, EMPTY_TICKERS);
32
+ const analysis = useMemo(() => analyzeSeriesSearchQuery(query), [query]);
33
+ const [search, setSearch] = useState<{
34
+ query: string;
35
+ instruments: SeriesCatalogInstrument[];
36
+ loading: boolean;
37
+ }>({ query: "", instruments: [], loading: false });
38
+
39
+ useEffect(() => {
40
+ const instrumentQuery = analysis.instrumentQuery.trim();
41
+ if (!enabled || !instrumentQuery || analysis.directInstrument) {
42
+ setSearch({ query: "", instruments: [], loading: false });
43
+ return;
44
+ }
45
+
46
+ const registry = getSharedRegistry();
47
+ if (!registry) {
48
+ setSearch({ query: instrumentQuery, instruments: [], loading: false });
49
+ return;
50
+ }
51
+
52
+ let cancelled = false;
53
+ setSearch({ query: instrumentQuery, instruments: [], loading: true });
54
+ const timer = setTimeout(() => {
55
+ void searchTickerCandidates({
56
+ query: instrumentQuery,
57
+ tickers,
58
+ dataProvider: registry.marketData,
59
+ totalLimit: 4,
60
+ localLimit: 3,
61
+ includeOptionContracts: false,
62
+ }).then((candidates) => {
63
+ if (cancelled) return;
64
+ setSearch({
65
+ query: instrumentQuery,
66
+ instruments: candidates.map((candidate) => ({
67
+ symbol: candidate.symbol,
68
+ ...(candidate.ticker?.metadata.exchange
69
+ ? { exchange: candidate.ticker.metadata.exchange }
70
+ : candidate.result?.primaryExchange || candidate.result?.exchange
71
+ ? { exchange: candidate.result?.primaryExchange || candidate.result?.exchange }
72
+ : {}),
73
+ ...(candidate.ticker?.metadata.name || candidate.result?.name
74
+ ? { name: candidate.ticker?.metadata.name || candidate.result?.name }
75
+ : {}),
76
+ })),
77
+ loading: false,
78
+ });
79
+ }).catch(() => {
80
+ if (!cancelled) setSearch({ query: instrumentQuery, instruments: [], loading: false });
81
+ });
82
+ }, 80);
83
+
84
+ return () => {
85
+ cancelled = true;
86
+ clearTimeout(timer);
87
+ };
88
+ }, [analysis.directInstrument, analysis.instrumentQuery, enabled, tickers]);
89
+
90
+ const instruments = search.query === analysis.instrumentQuery
91
+ ? search.instruments
92
+ : [];
93
+ const suggestions = useMemo(
94
+ () => buildSeriesCatalogSuggestions(query, defaultInstrument, instruments),
95
+ [defaultInstrument, instruments, query],
96
+ );
97
+
98
+ return {
99
+ suggestions,
100
+ instruments,
101
+ loading: search.loading && search.query === analysis.instrumentQuery,
102
+ };
103
+ }
@@ -1,4 +1,5 @@
1
1
  import type { ChatChannel } from "../../../api-client";
2
+ import { t } from "../../../i18n";
2
3
  import { normalizeChannelId } from "./controller/state";
3
4
 
4
5
  function normalizeMentionUsername(username: string | null | undefined): string | null {
@@ -25,7 +26,7 @@ function formatDirectChannelLabel(channel: ChatChannel, fallbackId: string) {
25
26
  : channelName;
26
27
  }
27
28
 
28
- return fallbackId.startsWith("dm:") ? "DM" : fallbackId;
29
+ return fallbackId.startsWith("dm:") ? t("DM") : fallbackId;
29
30
  }
30
31
 
31
32
  export function formatChannelLabel(channel: ChatChannel | undefined, fallbackId: string) {
@@ -34,7 +35,7 @@ export function formatChannelLabel(channel: ChatChannel | undefined, fallbackId:
34
35
  return formatDirectChannelLabel(channel, fallbackId);
35
36
  }
36
37
  if (channel.kind === "group") {
37
- return channel.name?.trim() || "Group";
38
+ return channel.name?.trim() || t("Group");
38
39
  }
39
40
  return channel.name?.trim() || fallbackId;
40
41
  }
@@ -47,7 +48,7 @@ export function formatChatPaneTitle(channel: ChatChannel | undefined, fallbackId
47
48
  if (channel?.kind === "group") {
48
49
  return formatChannelLabel(channel, normalizedFallbackId);
49
50
  }
50
- if (!channel && normalizedFallbackId.startsWith("dm:")) return "DM";
51
- if (!channel && (normalizedFallbackId.startsWith("grp:") || normalizedFallbackId.startsWith("group:"))) return "Group";
51
+ if (!channel && normalizedFallbackId.startsWith("dm:")) return t("DM");
52
+ if (!channel && (normalizedFallbackId.startsWith("grp:") || normalizedFallbackId.startsWith("group:"))) return t("Group");
52
53
  return `#${formatChannelLabel(channel, normalizedFallbackId)}`;
53
54
  }
@@ -1,6 +1,7 @@
1
1
  import type { CommandResultDef, GloomPluginContext } from "../../../types/plugin";
2
2
  import type { ChatChannel, ChatChannelState } from "../../../api-client";
3
3
  import type { AppConfig } from "../../../types/config";
4
+ import { t } from "../../../i18n";
4
5
  import { chatController } from "./controller";
5
6
  import { formatChannelLabel } from "./channel-labels";
6
7
 
@@ -45,14 +46,14 @@ function conversationDetail(channel: ChatChannel): string {
45
46
  if (channel.kind === "direct") {
46
47
  const displayName = channel.dmUser?.displayName?.trim();
47
48
  const username = channel.dmUser?.username?.trim();
48
- return [displayName && username ? displayName : null, "Direct message"].filter(Boolean).join(" - ");
49
+ return [displayName && username ? displayName : null, t("Direct message")].filter(Boolean).join(" - ");
49
50
  }
50
51
  const members = (channel.members ?? [])
51
52
  .map((member) => member.username ? `@${member.username}` : member.displayName)
52
53
  .filter(Boolean)
53
54
  .slice(0, 4);
54
55
  const suffix = (channel.members?.length ?? 0) > members.length ? ` +${(channel.members?.length ?? 0) - members.length}` : "";
55
- return members.length > 0 ? `Group chat - ${members.join(", ")}${suffix}` : "Group chat";
56
+ return members.length > 0 ? `${t("Group chat")} - ${members.join(", ")}${suffix}` : t("Group chat");
56
57
  }
57
58
 
58
59
  export function parseDmUsernames(value: string): string[] {
@@ -107,10 +108,10 @@ export function buildDmCommandResults(ctx: GloomPluginContext, arg: string): Com
107
108
  label,
108
109
  detail: valid
109
110
  ? usernames.length === 1
110
- ? "Start or open direct message"
111
- : "Start group chat"
112
- : "Use @username, or multiple usernames for a group chat",
113
- category: "Chat",
111
+ ? t("Start or open direct message")
112
+ : t("Start group chat")
113
+ : t("Use @username, or multiple usernames for a group chat"),
114
+ category: t("Chat"),
114
115
  right: "DM",
115
116
  disabled: !valid,
116
117
  execute: () => openDmTargetFromCommand(ctx, usernames),
@@ -122,9 +123,9 @@ export function buildDmCommandResults(ctx: GloomPluginContext, arg: string): Com
122
123
  if (conversations.length === 0) {
123
124
  return [{
124
125
  id: "empty",
125
- label: "No DMs yet",
126
- detail: "Type DM @username to start one",
127
- category: "Chat",
126
+ label: t("No DMs yet"),
127
+ detail: t("Type DM @username to start one"),
128
+ category: t("Chat"),
128
129
  right: "DM",
129
130
  disabled: true,
130
131
  execute: () => {},
@@ -135,8 +136,8 @@ export function buildDmCommandResults(ctx: GloomPluginContext, arg: string): Com
135
136
  id: `channel:${channel.id}`,
136
137
  label: formatChannelLabel(channel, channel.id),
137
138
  detail: conversationDetail(channel),
138
- category: "Conversations",
139
- right: channel.kind === "group" ? "Group" : "DM",
139
+ category: t("Conversations"),
140
+ right: channel.kind === "group" ? t("Group") : t("DM"),
140
141
  keywords: [
141
142
  channel.name,
142
143
  channel.dmUser?.username ?? "",