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,712 @@
1
+ import type { ChartResolution, TimeRange } from "../../components/chart/core/types";
2
+ import type { LayoutConfig, PaneBinding } from "../../types/config";
3
+ import type {
4
+ ChartPanelSpec,
5
+ ChartSeriesSpec,
6
+ ChartSpec,
7
+ ChartStudySpec,
8
+ SeriesPeriod,
9
+ SeriesStyle,
10
+ } from "../../time-series/types";
11
+ import { CHART_SPEC_VERSION } from "../../time-series/types";
12
+ import { getTimeSeriesField } from "../../time-series/field-catalog";
13
+ import {
14
+ coerceSeriesTransformForStyle,
15
+ normalizeChartSpec,
16
+ validateChartSpec,
17
+ } from "../../time-series/spec";
18
+
19
+ const CHART_RANGES = new Set<TimeRange>(["1D", "1W", "1M", "3M", "6M", "1Y", "5Y", "ALL"]);
20
+ const CHART_RESOLUTIONS = new Set<ChartResolution>(["auto", "1m", "5m", "15m", "30m", "45m", "1h", "1d", "1wk", "1mo"]);
21
+ const PRICE_RENDER_MODES = new Set<SeriesStyle>(["area", "line", "candles", "ohlc", "hlc"]);
22
+ const LEGACY_INDICATOR_IDS = new Set<LegacyChartIndicatorId>([
23
+ "volume",
24
+ "sma20",
25
+ "sma50",
26
+ "sma200",
27
+ "ema20",
28
+ "bollinger20",
29
+ ]);
30
+ const LEGACY_GRAPH_PLUGIN_IDS = ["ticker-detail", "ticker-research", "company-research"] as const;
31
+ const LEGACY_GRAPH_STATE_KEYS = new Set([
32
+ "period",
33
+ "chartKind",
34
+ "metric",
35
+ "periods",
36
+ "detailPeriod",
37
+ "detailChartKind",
38
+ "detailMetric",
39
+ "selectedIdx",
40
+ "hiddenSeriesIds",
41
+ ]);
42
+
43
+ export type LegacyChartIndicatorId =
44
+ | "volume"
45
+ | "sma20"
46
+ | "sma50"
47
+ | "sma200"
48
+ | "ema20"
49
+ | "bollinger20";
50
+
51
+ export interface LegacyChartIndicatorSelection {
52
+ ids: LegacyChartIndicatorId[];
53
+ /** A stored selection replaced pane indicator configuration in the old UI. */
54
+ explicit: boolean;
55
+ }
56
+
57
+ export interface LegacyChartMigrationContext {
58
+ defaultRenderMode?: SeriesStyle;
59
+ indicatorSelection?: LegacyChartIndicatorSelection;
60
+ }
61
+
62
+ function record(value: unknown): Record<string, unknown> | null {
63
+ return value !== null && typeof value === "object" && !Array.isArray(value)
64
+ ? value as Record<string, unknown>
65
+ : null;
66
+ }
67
+
68
+ function hasOwn(value: Record<string, unknown> | undefined, key: string): boolean {
69
+ return !!value && Object.prototype.hasOwnProperty.call(value, key);
70
+ }
71
+
72
+ function strings(value: unknown): string[] {
73
+ if (!Array.isArray(value)) return [];
74
+ return [...new Set(value
75
+ .filter((entry): entry is string => typeof entry === "string")
76
+ .map((entry) => entry.trim().toUpperCase())
77
+ .filter(Boolean))];
78
+ }
79
+
80
+ function symbolList(settings: Record<string, unknown> | undefined, binding: PaneBinding | undefined): string[] {
81
+ const stored = strings(settings?.symbols);
82
+ if (stored.length > 0) return stored;
83
+ if (typeof settings?.symbol === "string" && settings.symbol.trim()) {
84
+ return [settings.symbol.trim().toUpperCase()];
85
+ }
86
+ return binding?.kind === "fixed" && binding.symbol.trim()
87
+ ? [binding.symbol.trim().toUpperCase()]
88
+ : [];
89
+ }
90
+
91
+ function range(value: unknown, fallback: TimeRange): TimeRange {
92
+ return CHART_RANGES.has(value as TimeRange) ? value as TimeRange : fallback;
93
+ }
94
+
95
+ function resolution(value: unknown, fallback: ChartResolution): ChartResolution {
96
+ return CHART_RESOLUTIONS.has(value as ChartResolution) ? value as ChartResolution : fallback;
97
+ }
98
+
99
+ function makeSpec(
100
+ series: ChartSeriesSpec[],
101
+ viewport: ChartSpec["viewport"],
102
+ ): ChartSpec {
103
+ return {
104
+ version: CHART_SPEC_VERSION,
105
+ viewport,
106
+ panels: [{ id: "main", height: 1, scale: "linear" }],
107
+ series,
108
+ studies: [],
109
+ };
110
+ }
111
+
112
+ function securitySeries(
113
+ symbol: string,
114
+ index: number,
115
+ options: {
116
+ fieldId: string;
117
+ style: ChartSeriesSpec["style"];
118
+ transform?: ChartSeriesSpec["transform"];
119
+ period?: SeriesPeriod;
120
+ timestampMode?: "available-at" | "period-end";
121
+ },
122
+ ): ChartSeriesSpec {
123
+ return {
124
+ id: `${symbol.toLowerCase()}-${options.fieldId.replace(/[^a-z0-9]+/gi, "-")}-${index + 1}`,
125
+ source: {
126
+ kind: "security",
127
+ instrument: { symbol },
128
+ fieldId: options.fieldId,
129
+ period: options.period,
130
+ timestampMode: options.timestampMode,
131
+ },
132
+ label: symbol,
133
+ style: options.style,
134
+ transform: options.transform ?? "raw",
135
+ axis: "auto",
136
+ panelId: "main",
137
+ interpolation: options.style === "columns" || options.fieldId.startsWith("market.")
138
+ ? "none"
139
+ : "step-after",
140
+ visible: true,
141
+ };
142
+ }
143
+
144
+ function priceStyle(value: unknown, fallback: SeriesStyle = "area"): SeriesStyle {
145
+ return PRICE_RENDER_MODES.has(value as SeriesStyle) ? value as SeriesStyle : fallback;
146
+ }
147
+
148
+ function parsedChartSpec(value: unknown): ChartSpec | null {
149
+ let decoded = value;
150
+ if (typeof value === "string") {
151
+ try {
152
+ decoded = JSON.parse(value);
153
+ } catch {
154
+ return null;
155
+ }
156
+ }
157
+ if (!record(decoded)) return null;
158
+ const spec = normalizeChartSpec(decoded);
159
+ return validateChartSpec(spec).valid ? spec : null;
160
+ }
161
+
162
+ function panelForStudy(panelId: string): ChartPanelSpec {
163
+ if (panelId === "volume") return { id: panelId, label: "Volume", height: 0.24 };
164
+ if (panelId === "rsi" || panelId === "macd") {
165
+ return { id: panelId, label: panelId.toUpperCase(), height: 0.28 };
166
+ }
167
+ return { id: panelId };
168
+ }
169
+
170
+ function reconcilePanels(spec: ChartSpec, studies: readonly ChartStudySpec[]): ChartPanelSpec[] {
171
+ const requiredIds = new Set([
172
+ "main",
173
+ ...spec.series.map((series) => series.panelId),
174
+ ...studies.map((study) => study.panelId),
175
+ ]);
176
+ const retained = spec.panels.filter((panel) => requiredIds.has(panel.id));
177
+ const retainedIds = new Set(retained.map((panel) => panel.id));
178
+ return [
179
+ ...retained,
180
+ ...[...requiredIds].filter((id) => !retainedIds.has(id)).map(panelForStudy),
181
+ ];
182
+ }
183
+
184
+ function indicatorStudy(
185
+ id: string,
186
+ inputSeriesId: string,
187
+ kind: ChartStudySpec["kind"],
188
+ parameters: Record<string, number>,
189
+ panelId = "main",
190
+ ): ChartStudySpec {
191
+ return {
192
+ id,
193
+ kind,
194
+ inputSeriesIds: [inputSeriesId],
195
+ parameters,
196
+ panelId,
197
+ axis: "auto",
198
+ };
199
+ }
200
+
201
+ function studiesForSelection(
202
+ ids: readonly LegacyChartIndicatorId[],
203
+ inputSeriesId: string,
204
+ ): ChartStudySpec[] {
205
+ const selected = new Set(ids);
206
+ return [
207
+ ...(selected.has("volume")
208
+ ? [indicatorStudy(`builtin:volume:${inputSeriesId}`, inputSeriesId, "volume", {}, "volume")]
209
+ : []),
210
+ ...(selected.has("sma20")
211
+ ? [indicatorStudy(`builtin:sma20:${inputSeriesId}`, inputSeriesId, "sma", { period: 20 })]
212
+ : []),
213
+ ...(selected.has("sma50")
214
+ ? [indicatorStudy(`builtin:sma50:${inputSeriesId}`, inputSeriesId, "sma", { period: 50 })]
215
+ : []),
216
+ ...(selected.has("sma200")
217
+ ? [indicatorStudy(`builtin:sma200:${inputSeriesId}`, inputSeriesId, "sma", { period: 200 })]
218
+ : []),
219
+ ...(selected.has("ema20")
220
+ ? [indicatorStudy(`builtin:ema20:${inputSeriesId}`, inputSeriesId, "ema", { period: 20 })]
221
+ : []),
222
+ ...(selected.has("bollinger20")
223
+ ? [indicatorStudy(`builtin:bollinger20:${inputSeriesId}`, inputSeriesId, "bollinger", { period: 20, stdDev: 2 })]
224
+ : []),
225
+ ];
226
+ }
227
+
228
+ function positiveIntegers(value: unknown): number[] {
229
+ if (!Array.isArray(value)) return [];
230
+ return [...new Set(value.filter((entry): entry is number => (
231
+ typeof entry === "number" && Number.isFinite(entry) && entry > 0
232
+ )).map((entry) => Math.floor(entry)))];
233
+ }
234
+
235
+ function studiesForIndicatorConfig(value: unknown, inputSeriesId: string): ChartStudySpec[] {
236
+ const config = record(value);
237
+ if (!config) return [];
238
+ const studies: ChartStudySpec[] = [];
239
+ for (const period of positiveIntegers(config.sma)) {
240
+ const builtin = period === 20 || period === 50 || period === 200;
241
+ studies.push(indicatorStudy(
242
+ builtin ? `builtin:sma${period}:${inputSeriesId}` : `migrated:sma:${period}:${inputSeriesId}`,
243
+ inputSeriesId,
244
+ "sma",
245
+ { period },
246
+ ));
247
+ }
248
+ for (const period of positiveIntegers(config.ema)) {
249
+ studies.push(indicatorStudy(
250
+ period === 20 ? `builtin:ema20:${inputSeriesId}` : `migrated:ema:${period}:${inputSeriesId}`,
251
+ inputSeriesId,
252
+ "ema",
253
+ { period },
254
+ ));
255
+ }
256
+ const bollinger = record(config.bollinger);
257
+ if (typeof bollinger?.period === "number" && bollinger.period > 0) {
258
+ const period = Math.floor(bollinger.period);
259
+ const stdDev = typeof bollinger.stdDev === "number" && bollinger.stdDev > 0 ? bollinger.stdDev : 2;
260
+ studies.push(indicatorStudy(
261
+ period === 20 && stdDev === 2
262
+ ? `builtin:bollinger20:${inputSeriesId}`
263
+ : `migrated:bollinger:${period}:${inputSeriesId}`,
264
+ inputSeriesId,
265
+ "bollinger",
266
+ { period, stdDev },
267
+ ));
268
+ }
269
+ if (typeof config.rsi === "number" && config.rsi > 0) {
270
+ const period = Math.floor(config.rsi);
271
+ studies.push(indicatorStudy(
272
+ period === 14 ? `builtin:rsi14:${inputSeriesId}` : `migrated:rsi:${period}:${inputSeriesId}`,
273
+ inputSeriesId,
274
+ "rsi",
275
+ { period },
276
+ "rsi",
277
+ ));
278
+ }
279
+ const macd = record(config.macd);
280
+ if (
281
+ typeof macd?.fast === "number" && macd.fast > 0
282
+ && typeof macd.slow === "number" && macd.slow > 0
283
+ && typeof macd.signal === "number" && macd.signal > 0
284
+ ) {
285
+ const parameters = {
286
+ fast: Math.floor(macd.fast),
287
+ slow: Math.floor(macd.slow),
288
+ signal: Math.floor(macd.signal),
289
+ };
290
+ const standard = parameters.fast === 12 && parameters.slow === 26 && parameters.signal === 9;
291
+ studies.push(indicatorStudy(
292
+ standard ? `builtin:macd:${inputSeriesId}` : `migrated:macd:${inputSeriesId}`,
293
+ inputSeriesId,
294
+ "macd",
295
+ parameters,
296
+ "macd",
297
+ ));
298
+ }
299
+ return studies;
300
+ }
301
+
302
+ function applyLegacyIndicators(
303
+ spec: ChartSpec,
304
+ selection: LegacyChartIndicatorSelection | undefined,
305
+ indicatorConfig: unknown,
306
+ ): ChartSpec {
307
+ const input = spec.series.find((series) => (
308
+ series.source.kind === "security"
309
+ && (series.source.fieldId === "market.ohlcv" || series.source.fieldId === "market.close")
310
+ ));
311
+ if (!input || (!selection && !record(indicatorConfig))) return spec;
312
+
313
+ const selectedStudies = selection ? studiesForSelection(selection.ids, input.id) : [];
314
+ const configuredStudies = selection?.explicit ? [] : studiesForIndicatorConfig(indicatorConfig, input.id);
315
+ const migratedStudies = [...selectedStudies, ...configuredStudies].filter((study, index, all) => (
316
+ all.findIndex((candidate) => candidate.id === study.id) === index
317
+ ));
318
+ const retainedStudies = spec.studies.filter((study) => (
319
+ !study.id.startsWith("builtin:") && !study.id.startsWith("migrated:")
320
+ ));
321
+ const studies = [...retainedStudies, ...migratedStudies];
322
+ return { ...spec, studies, panels: reconcilePanels(spec, studies) };
323
+ }
324
+
325
+ function legacyPriceSpec(
326
+ symbol: string,
327
+ settings: Record<string, unknown> | undefined,
328
+ context: LegacyChartMigrationContext,
329
+ ): ChartSpec {
330
+ const style = priceStyle(settings?.chartRenderMode, priceStyle(context.defaultRenderMode, "area"));
331
+ const transform = coerceSeriesTransformForStyle(
332
+ style,
333
+ settings?.chartAxisMode === "percent" ? "percent" : "raw",
334
+ );
335
+ const spec = makeSpec(
336
+ [securitySeries(symbol, 0, { fieldId: "market.ohlcv", style, transform })],
337
+ {
338
+ range: range(settings?.chartRangePreset, "5Y"),
339
+ resolution: resolution(settings?.chartResolution, "auto"),
340
+ },
341
+ );
342
+ return applyLegacyIndicators(spec, context.indicatorSelection, settings?.indicators);
343
+ }
344
+
345
+ function hasLegacyPriceSettings(settings: Record<string, unknown> | undefined): boolean {
346
+ return ["chartAxisMode", "chartRangePreset", "chartResolution", "chartRenderMode", "indicators"]
347
+ .some((key) => hasOwn(settings, key));
348
+ }
349
+
350
+ function stripKeys(
351
+ settings: Record<string, unknown> | undefined,
352
+ keys: readonly string[],
353
+ ): Record<string, unknown> {
354
+ const retained = { ...(settings ?? {}) };
355
+ for (const key of keys) delete retained[key];
356
+ return retained;
357
+ }
358
+
359
+ export function migrateChartPaneSettings(
360
+ paneId: string,
361
+ binding: PaneBinding | undefined,
362
+ settings: Record<string, unknown> | undefined,
363
+ context: LegacyChartMigrationContext = {},
364
+ ): Record<string, unknown> | undefined {
365
+ if (paneId === "chart-composer") {
366
+ const retained = stripKeys(settings, [
367
+ "chartAxisMode",
368
+ "chartRangePreset",
369
+ "chartResolution",
370
+ "chartRenderMode",
371
+ "indicators",
372
+ "axisMode",
373
+ "rangePreset",
374
+ "symbols",
375
+ "symbolsText",
376
+ "symbol",
377
+ "chartKind",
378
+ "metric",
379
+ "period",
380
+ "periods",
381
+ ]);
382
+ const spec = parsedChartSpec(retained.chartSpec);
383
+ if (spec && context.indicatorSelection) {
384
+ retained.chartSpec = applyLegacyIndicators(spec, context.indicatorSelection, undefined);
385
+ }
386
+ return Object.keys(retained).length > 0 ? retained : undefined;
387
+ }
388
+ const symbols = symbolList(settings, binding);
389
+
390
+ if (paneId === "ticker-research" || paneId === "ticker-detail") {
391
+ const retained = stripKeys(settings, [
392
+ "chartAxisMode",
393
+ "chartRangePreset",
394
+ "chartResolution",
395
+ "chartRenderMode",
396
+ "indicators",
397
+ "detailPeriod",
398
+ "detailChartKind",
399
+ "detailMetric",
400
+ ]);
401
+ if (retained.lockedTabId === "fundamental-graphs") retained.lockedTabId = "chart";
402
+ const existingSpec = parsedChartSpec(retained.chartSpec);
403
+ if (existingSpec && context.indicatorSelection) {
404
+ retained.chartSpec = applyLegacyIndicators(existingSpec, context.indicatorSelection, undefined);
405
+ } else if (
406
+ !existingSpec
407
+ && symbols[0]
408
+ && (hasLegacyPriceSettings(settings) || context.indicatorSelection?.explicit === true)
409
+ ) {
410
+ retained.chartSpec = legacyPriceSpec(symbols[0], settings, context);
411
+ }
412
+ return Object.keys(retained).length > 0 ? retained : undefined;
413
+ }
414
+
415
+ if (paneId === "ticker-chart") {
416
+ if (symbols.length === 0) return undefined;
417
+ return {
418
+ chartSpec: legacyPriceSpec(symbols[0]!, settings, context),
419
+ };
420
+ }
421
+
422
+ if (paneId === "comparison-chart") {
423
+ if (symbols.length === 0) return undefined;
424
+ const transform = settings?.axisMode === "price" ? "raw" : "percent";
425
+ const style = priceStyle(context.defaultRenderMode, "area") === "line" ? "line" : "area";
426
+ return {
427
+ chartSpec: makeSpec(
428
+ symbols.map((symbol, index) => securitySeries(symbol, index, {
429
+ fieldId: "market.close",
430
+ style,
431
+ transform,
432
+ })),
433
+ {
434
+ range: range(settings?.rangePreset, "1Y"),
435
+ resolution: resolution(settings?.chartResolution, "1d"),
436
+ },
437
+ ),
438
+ };
439
+ }
440
+
441
+ if (paneId === "fundamental-graph") {
442
+ if (symbols.length === 0) return undefined;
443
+ const valuation = settings?.chartKind === "valuation";
444
+ const metric = typeof settings?.metric === "string"
445
+ ? settings.metric
446
+ : valuation ? "priceSales" : "totalRevenue";
447
+ const period: SeriesPeriod = settings?.period === "quarterly" ? "quarterly" : "annual";
448
+ return {
449
+ chartSpec: makeSpec(
450
+ symbols.map((symbol, index) => securitySeries(symbol, index, {
451
+ fieldId: `${valuation ? "valuation" : "fundamental"}.${metric}`,
452
+ style: valuation ? "line" : "columns",
453
+ period,
454
+ timestampMode: valuation ? "available-at" : "period-end",
455
+ })),
456
+ {
457
+ range: "ALL",
458
+ resolution: "auto",
459
+ maxPoints: typeof settings?.periods === "number" && Number.isFinite(settings.periods)
460
+ ? Math.max(1, Math.min(40, Math.floor(settings.periods)))
461
+ : undefined,
462
+ },
463
+ ),
464
+ };
465
+ }
466
+
467
+ return settings;
468
+ }
469
+
470
+ function normalizedLegacyIndicatorIds(value: unknown): LegacyChartIndicatorId[] {
471
+ if (!Array.isArray(value)) return [];
472
+ const selected = new Set(value.filter((entry): entry is LegacyChartIndicatorId => (
473
+ typeof entry === "string" && LEGACY_INDICATOR_IDS.has(entry as LegacyChartIndicatorId)
474
+ )));
475
+ return ["volume", "sma20", "sma50", "sma200", "ema20", "bollinger20"]
476
+ .filter((id): id is LegacyChartIndicatorId => selected.has(id as LegacyChartIndicatorId));
477
+ }
478
+
479
+ export function extractLegacyChartIndicatorSelection(
480
+ pluginConfig: Record<string, Record<string, unknown>>,
481
+ includeImplicitDefault: boolean,
482
+ ): LegacyChartIndicatorSelection | undefined {
483
+ const state = pluginConfig["ticker-research"]
484
+ ?? pluginConfig["ticker-detail"]
485
+ ?? pluginConfig["company-research"];
486
+ const hasStoredSelection = !!state && hasOwn(state, "chartIndicators");
487
+ if (!hasStoredSelection && !includeImplicitDefault) return undefined;
488
+
489
+ if (!Array.isArray(state?.chartIndicators)) {
490
+ return { ids: ["volume"], explicit: false };
491
+ }
492
+ const ids = normalizedLegacyIndicatorIds(state.chartIndicators);
493
+ return state.chartIndicatorsVersion === 2
494
+ ? { ids, explicit: true }
495
+ : { ids: normalizedLegacyIndicatorIds(["volume", ...ids]), explicit: true };
496
+ }
497
+
498
+ export function hasLegacyChartIndicatorConfig(
499
+ pluginConfig: Record<string, Record<string, unknown>>,
500
+ ): boolean {
501
+ return ["ticker-research", "ticker-detail", "company-research"].some((pluginId) => {
502
+ const state = pluginConfig[pluginId];
503
+ return hasOwn(state, "chartIndicators") || hasOwn(state, "chartIndicatorsVersion");
504
+ });
505
+ }
506
+
507
+ export function stripLegacyChartPluginConfig(
508
+ pluginConfig: Record<string, Record<string, unknown>>,
509
+ ): Record<string, Record<string, unknown>> {
510
+ const result: Record<string, Record<string, unknown>> = {};
511
+ for (const [pluginId, state] of Object.entries(pluginConfig)) {
512
+ if (pluginId !== "ticker-research" && pluginId !== "ticker-detail" && pluginId !== "company-research") {
513
+ result[pluginId] = { ...state };
514
+ continue;
515
+ }
516
+ const retained = { ...state };
517
+ delete retained.chartIndicators;
518
+ delete retained.chartIndicatorsVersion;
519
+ if (Object.keys(retained).length > 0) result[pluginId] = retained;
520
+ }
521
+ return result;
522
+ }
523
+
524
+ function chartSymbols(spec: ChartSpec | null, binding: PaneBinding | undefined, paneState: Record<string, unknown>): string[] {
525
+ const stored = spec?.series.flatMap((series) => (
526
+ series.source.kind === "security" ? [series.source.instrument.symbol] : []
527
+ )) ?? [];
528
+ if (stored.length > 0) return [...new Set(stored)];
529
+ if (binding?.kind === "fixed" && binding.symbol.trim()) return [binding.symbol.trim().toUpperCase()];
530
+ return typeof paneState.cursorSymbol === "string" && paneState.cursorSymbol.trim()
531
+ ? [paneState.cursorSymbol.trim().toUpperCase()]
532
+ : [];
533
+ }
534
+
535
+ function legacyGraphField(
536
+ kindValue: unknown,
537
+ metricValue: unknown,
538
+ fallbackSpec?: ChartSpec | null,
539
+ ): { fieldId: string; valuation: boolean } {
540
+ const kind = kindValue === "valuation" ? "valuation" : "fundamental";
541
+ if (typeof metricValue === "string") {
542
+ const fundamentalId = `fundamental.${metricValue}`;
543
+ const valuationId = `valuation.${metricValue}`;
544
+ if (getTimeSeriesField(fundamentalId)) return { fieldId: fundamentalId, valuation: false };
545
+ if (getTimeSeriesField(valuationId)) return { fieldId: valuationId, valuation: true };
546
+ }
547
+ const fallbackFieldId = fallbackSpec?.series.find((series) => (
548
+ series.source.kind === "security"
549
+ && (series.source.fieldId.startsWith("fundamental.") || series.source.fieldId.startsWith("valuation."))
550
+ ))?.source;
551
+ if (fallbackFieldId?.kind === "security") {
552
+ return {
553
+ fieldId: fallbackFieldId.fieldId,
554
+ valuation: fallbackFieldId.fieldId.startsWith("valuation."),
555
+ };
556
+ }
557
+ return kind === "valuation"
558
+ ? { fieldId: "valuation.priceSales", valuation: true }
559
+ : { fieldId: "fundamental.totalRevenue", valuation: false };
560
+ }
561
+
562
+ function legacyPeriod(value: unknown, fallbackSpec?: ChartSpec | null): SeriesPeriod {
563
+ if (value === "annual" || value === "quarterly") return value;
564
+ const fallback = fallbackSpec?.series.find((series) => series.source.kind === "security")?.source;
565
+ return fallback?.kind === "security" && (fallback.period === "annual" || fallback.period === "quarterly")
566
+ ? fallback.period
567
+ : "quarterly";
568
+ }
569
+
570
+ function periodLimit(value: unknown): number | undefined {
571
+ return typeof value === "number" && Number.isFinite(value) && value > 0
572
+ ? Math.max(1, Math.min(40, Math.floor(value)))
573
+ : undefined;
574
+ }
575
+
576
+ function buildLegacyGraphSpec(
577
+ symbols: readonly string[],
578
+ state: Record<string, unknown>,
579
+ detail: boolean,
580
+ fallbackSpec?: ChartSpec | null,
581
+ ): ChartSpec {
582
+ const kindKey = detail ? "detailChartKind" : "chartKind";
583
+ const metricKey = detail ? "detailMetric" : "metric";
584
+ const periodKey = detail ? "detailPeriod" : "period";
585
+ const field = legacyGraphField(state[kindKey], state[metricKey], fallbackSpec);
586
+ const period = legacyPeriod(state[periodKey], fallbackSpec);
587
+ const hidden = new Set(strings(state.hiddenSeriesIds));
588
+ const series = symbols.map((symbol, index) => ({
589
+ ...securitySeries(symbol, index, {
590
+ fieldId: field.fieldId,
591
+ style: field.valuation ? "line" : "columns",
592
+ period,
593
+ timestampMode: field.valuation ? "available-at" : "period-end",
594
+ }),
595
+ visible: !hidden.has(symbol),
596
+ }));
597
+ return makeSpec(series, {
598
+ range: "ALL",
599
+ resolution: "auto",
600
+ maxPoints: detail
601
+ ? undefined
602
+ : hasOwn(state, "periods") ? periodLimit(state.periods) : fallbackSpec?.viewport.maxPoints,
603
+ });
604
+ }
605
+
606
+ function mergedLegacyGraphState(paneState: Record<string, unknown>): Record<string, unknown> {
607
+ const pluginState = record(paneState.pluginState);
608
+ const merged: Record<string, unknown> = {};
609
+ for (const pluginId of LEGACY_GRAPH_PLUGIN_IDS) {
610
+ Object.assign(merged, record(pluginState?.[pluginId]) ?? {});
611
+ }
612
+ return merged;
613
+ }
614
+
615
+ function originalPaneSettings(value: unknown, instanceId: string): Record<string, unknown> | null {
616
+ const layout = record(value);
617
+ if (!Array.isArray(layout?.instances)) return null;
618
+ const instance = layout.instances.find((entry) => record(entry)?.instanceId === instanceId);
619
+ return record(record(instance)?.settings);
620
+ }
621
+
622
+ function stripLegacyGraphPaneState(
623
+ paneState: Record<string, unknown>,
624
+ migrateActiveTab: boolean,
625
+ ): Record<string, unknown> {
626
+ const retained = { ...paneState };
627
+ if (migrateActiveTab && retained.activeTabId === "fundamental-graphs") retained.activeTabId = "chart";
628
+ const pluginState = record(retained.pluginState);
629
+ if (!pluginState) return retained;
630
+
631
+ const nextPluginState: Record<string, unknown> = { ...pluginState };
632
+ for (const pluginId of LEGACY_GRAPH_PLUGIN_IDS) {
633
+ const state = record(nextPluginState[pluginId]);
634
+ if (!state) continue;
635
+ const nextState = Object.fromEntries(
636
+ Object.entries(state).filter(([key]) => !LEGACY_GRAPH_STATE_KEYS.has(key)),
637
+ );
638
+ if (Object.keys(nextState).length > 0) nextPluginState[pluginId] = nextState;
639
+ else delete nextPluginState[pluginId];
640
+ }
641
+ if (Object.keys(nextPluginState).length > 0) retained.pluginState = nextPluginState;
642
+ else delete retained.pluginState;
643
+ return retained;
644
+ }
645
+
646
+ /**
647
+ * Fold chart-owned runtime state into the unified pane spec. Saved layout state
648
+ * is the only old persistence layer that carried graph metric/period choices.
649
+ */
650
+ export function migrateLegacyChartSavedPaneState(
651
+ layout: LayoutConfig,
652
+ paneState: Record<string, Record<string, unknown>> | undefined,
653
+ originalLayout?: unknown,
654
+ ): { layout: LayoutConfig; paneState: Record<string, Record<string, unknown>> | undefined } {
655
+ if (!paneState) return { layout, paneState };
656
+ let nextLayout = layout;
657
+ let layoutChanged = false;
658
+ const nextPaneState: Record<string, Record<string, unknown>> = {};
659
+
660
+ for (const [instanceId, state] of Object.entries(paneState)) {
661
+ const instanceIndex = layout.instances.findIndex((instance) => instance.instanceId === instanceId);
662
+ const instance = layout.instances[instanceIndex];
663
+ if (!instance || (instance.paneId !== "chart-composer" && instance.paneId !== "ticker-research")) {
664
+ nextPaneState[instanceId] = state;
665
+ continue;
666
+ }
667
+
668
+ const legacyState = mergedLegacyGraphState(state);
669
+ const hasAnyLegacyGraphState = [...LEGACY_GRAPH_STATE_KEYS]
670
+ .some((key) => hasOwn(legacyState, key));
671
+ const hasStandaloneState = ["period", "chartKind", "metric", "periods", "hiddenSeriesIds"]
672
+ .some((key) => hasOwn(legacyState, key));
673
+ const hasDetailState = ["detailPeriod", "detailChartKind", "detailMetric"]
674
+ .some((key) => hasOwn(legacyState, key));
675
+ const originalSettings = originalPaneSettings(originalLayout, instanceId);
676
+ const graphWasActive = state.activeTabId === "fundamental-graphs"
677
+ || originalSettings?.lockedTabId === "fundamental-graphs";
678
+ const useStandaloneState = instance.paneId === "chart-composer" && hasStandaloneState;
679
+ const useDetailState = instance.paneId === "ticker-research" && graphWasActive;
680
+ const currentSpec = parsedChartSpec(instance.settings?.chartSpec);
681
+ const symbols = chartSymbols(currentSpec, instance.binding, state);
682
+
683
+ if ((useStandaloneState || useDetailState) && symbols.length > 0) {
684
+ const migratedSpec = buildLegacyGraphSpec(symbols, legacyState, useDetailState, currentSpec);
685
+ if (!layoutChanged) {
686
+ nextLayout = {
687
+ ...layout,
688
+ instances: [...layout.instances],
689
+ floating: [...layout.floating],
690
+ detached: [...layout.detached],
691
+ };
692
+ layoutChanged = true;
693
+ }
694
+ nextLayout.instances[instanceIndex] = {
695
+ ...instance,
696
+ settings: { ...(instance.settings ?? {}), chartSpec: migratedSpec },
697
+ };
698
+ }
699
+
700
+ const shouldStripGraphState = hasAnyLegacyGraphState || graphWasActive;
701
+ nextPaneState[instanceId] = shouldStripGraphState
702
+ ? stripLegacyGraphPaneState(state, true)
703
+ : state.activeTabId === "fundamental-graphs"
704
+ ? { ...state, activeTabId: "chart" }
705
+ : state;
706
+ }
707
+
708
+ return {
709
+ layout: nextLayout,
710
+ paneState: Object.keys(nextPaneState).length > 0 ? nextPaneState : undefined,
711
+ };
712
+ }