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,783 @@
1
+ import { appendLiveQuotePoint } from "../components/chart/core/data";
2
+ import {
3
+ getTimeRangeForDateWindow,
4
+ subtractTimeRange,
5
+ } from "../components/chart/core/date-window";
6
+ import {
7
+ CHART_RESOLUTION_STEP_MS,
8
+ clampTimeRangeToMaxRange,
9
+ getBestSupportedResolutionForVisibleWindow,
10
+ getNextBufferRange,
11
+ getPresetResolution,
12
+ getSupportMaxRange,
13
+ intersectChartResolutionSupport,
14
+ TIME_RANGE_ORDER,
15
+ type ChartResolutionSupport,
16
+ type ManualChartResolution,
17
+ } from "../components/chart/core/resolution";
18
+ import type { TimeRange } from "../components/chart/core/types";
19
+ import type { DataProvider, MarketDataRequestContext } from "../types/data-provider";
20
+ import type { Quote, TickerFinancials } from "../types/financials";
21
+ import type { FredSeriesLoadResult, FredSeriesRequest } from "../data/fred-series";
22
+ import { extractFredSeries } from "./economic";
23
+ import {
24
+ getTimeSeriesField,
25
+ isFundamentalFieldId,
26
+ isMarketFieldId,
27
+ } from "./field-catalog";
28
+ import {
29
+ fundamentalSeriesUsesAvailabilityFallback,
30
+ valuationSeriesUsesLiveQuote,
31
+ } from "./fundamentals";
32
+ import { extractSecuritySeries } from "./market";
33
+ import {
34
+ activeStudyInputSeriesIds,
35
+ maxStudyWarmupPoints,
36
+ resolveStudies,
37
+ } from "./studies";
38
+ import { isOhlcSeriesStyle } from "./spec";
39
+ import { applyResolvedSeriesTransform } from "./transforms";
40
+ import { clipSeriesToWindow } from "./alignment";
41
+ import { chartQuoteOverrideKeyForSource } from "./live-quotes";
42
+ import { resolutionForExplicitMarketPeriods } from "./market-resolution";
43
+ import {
44
+ canonicalExchange,
45
+ publicTickerKey,
46
+ resolveExchangeTimeZone,
47
+ } from "../utils/exchanges";
48
+ import type {
49
+ ChartResolutionResult,
50
+ ChartSeriesSpec,
51
+ ChartSpec,
52
+ ResolvedSeries,
53
+ TimeSeriesPoint,
54
+ } from "./types";
55
+
56
+ const SERIES_COLORS = [
57
+ "#4dabf7",
58
+ "#63e6be",
59
+ "#f6c85f",
60
+ "#b197fc",
61
+ "#ff8787",
62
+ "#ffa94d",
63
+ "#74c0fc",
64
+ "#e599f7",
65
+ "#8ce99a",
66
+ "#ffd43b",
67
+ ] as const;
68
+
69
+ export interface ChartResolveSources {
70
+ dataProvider: DataProvider | null;
71
+ loadFredSeries: (request: FredSeriesRequest) => Promise<FredSeriesLoadResult>;
72
+ now?: Date;
73
+ /** Latest streamed quote per security identity, layered over snapshot data. */
74
+ quoteOverrides?: ReadonlyMap<string, Quote>;
75
+ }
76
+
77
+ export interface ChartResolveOptions {
78
+ /** Runtime interaction window used only while the authored resolution is Auto. */
79
+ autoViewport?: { start: Date; end: Date } | null;
80
+ /** Approximate number of horizontal observations the current surface can use. */
81
+ targetPointCount?: number;
82
+ }
83
+
84
+ /** Raw source data retained while live quotes recompute the chart tail. */
85
+ export class ChartResolveCache {
86
+ readonly financialsByInstrument = new Map<string, Promise<TickerFinancials | null>>();
87
+ readonly priceHistoryByRequest = new Map<string, Promise<TickerFinancials["priceHistory"]>>();
88
+ readonly resolutionSupportByInstrument = new Map<string, Promise<ChartResolutionSupport[]>>();
89
+ readonly fredSeriesByRequest = new Map<string, Promise<FredSeriesLoadResult>>();
90
+ }
91
+
92
+ interface DateBounds {
93
+ start: number | null;
94
+ /** Inclusive upper bound. */
95
+ end: number | null;
96
+ }
97
+
98
+ interface PriceHistoryRequest {
99
+ bounds: DateBounds;
100
+ explicitWindow: boolean;
101
+ fallbackRange: TimeRange;
102
+ resolution: ManualChartResolution;
103
+ allowProviderDefaultFallback: boolean;
104
+ }
105
+
106
+ const DAY_MS = 24 * 60 * 60 * 1_000;
107
+ const DATE_ONLY_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
108
+
109
+ function requestContext(spec: Extract<ChartSeriesSpec["source"], { kind: "security" }>): MarketDataRequestContext {
110
+ return {
111
+ brokerId: spec.instrument.brokerId,
112
+ brokerInstanceId: spec.instrument.brokerInstanceId,
113
+ instrument: spec.instrument.instrument ?? null,
114
+ };
115
+ }
116
+
117
+ function instrumentKey(spec: Extract<ChartSeriesSpec["source"], { kind: "security" }>): string {
118
+ return chartQuoteOverrideKeyForSource(spec);
119
+ }
120
+
121
+ function instrumentLabel(spec: Extract<ChartSeriesSpec["source"], { kind: "security" }>): string {
122
+ return publicTickerKey(spec.instrument.symbol, spec.instrument.exchange);
123
+ }
124
+
125
+ function finiteDate(value: string | Date | undefined): Date | null {
126
+ if (!value) return null;
127
+ const date = value instanceof Date ? new Date(value) : new Date(value);
128
+ return Number.isFinite(date.getTime()) ? date : null;
129
+ }
130
+
131
+ function inclusiveEndDate(value: string | Date | undefined): Date | null {
132
+ const parsed = finiteDate(value);
133
+ if (!parsed) return null;
134
+ return typeof value === "string" && DATE_ONLY_PATTERN.test(value.trim())
135
+ ? new Date(parsed.getTime() + DAY_MS - 1)
136
+ : parsed;
137
+ }
138
+
139
+ function dateOnly(date: Date): string {
140
+ return date.toISOString().slice(0, 10);
141
+ }
142
+
143
+ function explicitBounds(spec: ChartSpec): DateBounds | null {
144
+ const explicitStart = finiteDate(spec.viewport.dateWindow?.start);
145
+ const explicitEnd = inclusiveEndDate(spec.viewport.dateWindow?.end);
146
+ if (!explicitStart || !explicitEnd || explicitStart.getTime() > explicitEnd.getTime()) return null;
147
+ return { start: explicitStart.getTime(), end: explicitEnd.getTime() };
148
+ }
149
+
150
+ function requestedBounds(spec: ChartSpec, latestObservation: Date): DateBounds {
151
+ const explicit = explicitBounds(spec);
152
+ if (explicit) return explicit;
153
+ return {
154
+ start: spec.viewport.range === "ALL"
155
+ ? null
156
+ : subtractTimeRange(latestObservation, spec.viewport.range).getTime(),
157
+ end: latestObservation.getTime(),
158
+ };
159
+ }
160
+
161
+ function runtimeAutoBounds(options: ChartResolveOptions): DateBounds | null {
162
+ const start = options.autoViewport?.start.getTime();
163
+ const end = options.autoViewport?.end.getTime();
164
+ return typeof start === "number"
165
+ && Number.isFinite(start)
166
+ && typeof end === "number"
167
+ && Number.isFinite(end)
168
+ && start <= end
169
+ ? { start, end }
170
+ : null;
171
+ }
172
+
173
+ function sameBounds(left: DateBounds | null, right: DateBounds | null): boolean {
174
+ return left?.start === right?.start && left?.end === right?.end;
175
+ }
176
+
177
+ function boundsRange(bounds: DateBounds): TimeRange {
178
+ if (bounds.start === null || bounds.end === null) return "ALL";
179
+ return getTimeRangeForDateWindow({
180
+ start: new Date(bounds.start),
181
+ end: new Date(bounds.end),
182
+ });
183
+ }
184
+
185
+ function requestResolution(
186
+ spec: ChartSpec,
187
+ bounds: DateBounds,
188
+ calculationSeriesIds: ReadonlySet<string>,
189
+ options: ChartResolveOptions,
190
+ sharedSupport: readonly ChartResolutionSupport[],
191
+ ): ManualChartResolution {
192
+ if (spec.viewport.resolution !== "auto") return spec.viewport.resolution;
193
+ const runtimeBounds = runtimeAutoBounds(options);
194
+ const adaptive = runtimeBounds && runtimeBounds.start !== null && runtimeBounds.end !== null
195
+ ? getBestSupportedResolutionForVisibleWindow(
196
+ { start: new Date(runtimeBounds.start), end: new Date(runtimeBounds.end) },
197
+ sharedSupport,
198
+ options.targetPointCount ?? 120,
199
+ )
200
+ : null;
201
+ const preferred = adaptive
202
+ ?? getPresetResolution(explicitBounds(spec) ? boundsRange(bounds) : spec.viewport.range);
203
+ const activeSeries = spec.series.filter((entry) => calculationSeriesIds.has(entry.id));
204
+ return resolutionForExplicitMarketPeriods(preferred, activeSeries);
205
+ }
206
+
207
+ function calculationBounds(
208
+ spec: ChartSpec,
209
+ visibleBounds: DateBounds,
210
+ resolution: ManualChartResolution,
211
+ ): DateBounds {
212
+ if (visibleBounds.start === null) return visibleBounds;
213
+ const warmupPoints = maxStudyWarmupPoints(spec.studies);
214
+
215
+ const visibleEnd = visibleBounds.end ?? visibleBounds.start;
216
+ const bufferedRange = getNextBufferRange(boundsRange(visibleBounds));
217
+ let start = bufferedRange === "ALL"
218
+ ? subtractTimeRange(new Date(visibleEnd), "ALL").getTime()
219
+ : subtractTimeRange(new Date(visibleEnd), bufferedRange).getTime();
220
+
221
+ if (warmupPoints > 0) {
222
+ // Calendar gaps and closed sessions mean N observations often span more
223
+ // than N nominal bars. The doubled point window is a bounded safety margin.
224
+ const pointWarmup = warmupPoints * CHART_RESOLUTION_STEP_MS[resolution] * 2;
225
+ start = Math.min(start, visibleBounds.start - pointWarmup);
226
+ }
227
+ return { start: Math.min(start, visibleBounds.start), end: visibleBounds.end };
228
+ }
229
+
230
+ function trailingRangeForStart(start: number | null, referenceDate: Date): TimeRange {
231
+ if (start === null) return "ALL";
232
+ for (const range of TIME_RANGE_ORDER) {
233
+ if (range === "ALL" || start >= subtractTimeRange(referenceDate, range).getTime()) return range;
234
+ }
235
+ return "ALL";
236
+ }
237
+
238
+ function exclusiveEnd(bounds: DateBounds): Date | null {
239
+ return bounds.end === null ? null : new Date(bounds.end + 1);
240
+ }
241
+
242
+ function filterPoints(points: readonly TimeSeriesPoint[], bounds: DateBounds): TimeSeriesPoint[] {
243
+ return points.filter((point) => {
244
+ const time = point.date.getTime();
245
+ return Number.isFinite(time)
246
+ && (bounds.start === null || time >= bounds.start)
247
+ && (bounds.end === null || time <= bounds.end);
248
+ });
249
+ }
250
+
251
+ function emptyFinancials(priceHistory: TickerFinancials["priceHistory"] = []): TickerFinancials {
252
+ return { annualStatements: [], quarterlyStatements: [], priceHistory };
253
+ }
254
+
255
+ function latestQuote(snapshot: Quote | undefined, override: Quote | undefined): Quote | undefined {
256
+ if (!snapshot) return override;
257
+ if (!override) return snapshot;
258
+ if (override.lastUpdated !== snapshot.lastUpdated) {
259
+ return override.lastUpdated > snapshot.lastUpdated ? override : snapshot;
260
+ }
261
+ return (override.receivedAt ?? 0) >= (snapshot.receivedAt ?? 0) ? override : snapshot;
262
+ }
263
+
264
+ function mergeHistory(
265
+ financials: TickerFinancials | null,
266
+ history: TickerFinancials["priceHistory"],
267
+ quoteOverride: Quote | undefined,
268
+ now: number,
269
+ liveBarResolution?: ManualChartResolution,
270
+ ): TickerFinancials {
271
+ const base = financials ?? emptyFinancials();
272
+ const quote = latestQuote(base.quote, quoteOverride);
273
+ const priceHistory = appendLiveQuotePoint(history, quote, liveBarResolution
274
+ ? { now, mode: "ohlc", resolution: liveBarResolution }
275
+ : { now });
276
+ return { ...base, quote, priceHistory };
277
+ }
278
+
279
+ async function loadPriceHistory(
280
+ provider: DataProvider,
281
+ source: Extract<ChartSeriesSpec["source"], { kind: "security" }>,
282
+ request: PriceHistoryRequest,
283
+ ): Promise<TickerFinancials["priceHistory"]> {
284
+ const context = requestContext(source);
285
+ const detailStart = request.bounds.start === null ? null : new Date(request.bounds.start);
286
+ const detailEnd = exclusiveEnd(request.bounds);
287
+ if (request.explicitWindow && detailStart && detailEnd && provider.getDetailedPriceHistory) {
288
+ try {
289
+ const detailed = await provider.getDetailedPriceHistory(
290
+ source.instrument.symbol,
291
+ source.instrument.exchange ?? "",
292
+ detailStart,
293
+ detailEnd,
294
+ request.resolution,
295
+ context,
296
+ );
297
+ if (detailed.length > 0) return detailed;
298
+ } catch {
299
+ // Fall through to trailing history when a provider cannot serve the exact window.
300
+ }
301
+ }
302
+ if (provider.getPriceHistoryForResolution) {
303
+ try {
304
+ const resolved = await provider.getPriceHistoryForResolution(
305
+ source.instrument.symbol,
306
+ source.instrument.exchange ?? "",
307
+ request.fallbackRange,
308
+ request.resolution,
309
+ context,
310
+ );
311
+ if (resolved.length > 0) return resolved;
312
+ } catch {
313
+ // Some providers expose the resolution API but only support a subset.
314
+ }
315
+ }
316
+ if (!request.allowProviderDefaultFallback) {
317
+ // getPriceHistory chooses its own interval, so using it here would make a
318
+ // manual interval label claim a granularity the provider did not honor.
319
+ throw new Error(
320
+ `Requested ${request.resolution} price history is unavailable for ${instrumentLabel(source)}. Choose Auto or a supported interval.`,
321
+ );
322
+ }
323
+ return provider.getPriceHistory(
324
+ source.instrument.symbol,
325
+ source.instrument.exchange ?? "",
326
+ request.fallbackRange,
327
+ context,
328
+ );
329
+ }
330
+
331
+ function baseSecuritySeries(
332
+ spec: ChartSeriesSpec,
333
+ financials: TickerFinancials,
334
+ index: number,
335
+ marketResolution?: ManualChartResolution,
336
+ ): ResolvedSeries | null {
337
+ if (spec.source.kind !== "security") return null;
338
+ const field = getTimeSeriesField(spec.source.fieldId);
339
+ if (!field) return null;
340
+ const points = extractSecuritySeries(financials, spec.source);
341
+ const symbol = instrumentLabel(spec.source);
342
+ const currency = financials.quote?.currency;
343
+ const unit = field.unit.startsWith("currency") && currency
344
+ ? field.unit.replace("currency", currency)
345
+ : field.unit;
346
+ const currencyUnitGroup = field.unit.startsWith("currency") && currency
347
+ ? `${field.unitGroup}:${currency}`
348
+ : field.unitGroup;
349
+ const marketExchange = spec.source.instrument.exchange
350
+ || financials.quote?.listingExchangeName
351
+ || financials.quote?.exchangeName;
352
+ const marketTimeZone = isMarketFieldId(spec.source.fieldId)
353
+ && canonicalExchange(marketExchange) !== "CCC"
354
+ ? resolveExchangeTimeZone(marketExchange)
355
+ : null;
356
+ return {
357
+ id: spec.id,
358
+ label: spec.label?.trim() || `${symbol} ${field.shortLabel}`,
359
+ color: spec.color ?? SERIES_COLORS[index % SERIES_COLORS.length]!,
360
+ unit,
361
+ unitGroup: currencyUnitGroup,
362
+ nativeFrequency: spec.source.period && spec.source.period !== "auto"
363
+ ? spec.source.period
364
+ : field.nativeFrequency,
365
+ timestampMode: spec.source.timestampMode,
366
+ dataShape: field.dataShape,
367
+ style: spec.style,
368
+ transform: spec.transform,
369
+ axis: spec.axis === "right" ? "right" : "left",
370
+ panelId: spec.panelId,
371
+ interpolation: spec.interpolation,
372
+ timeBasis: marketTimeZone
373
+ ? {
374
+ kind: "market",
375
+ timeZone: marketTimeZone,
376
+ cadenceMs: marketResolution
377
+ ? CHART_RESOLUTION_STEP_MS[marketResolution]
378
+ : undefined,
379
+ }
380
+ : undefined,
381
+ points,
382
+ };
383
+ }
384
+
385
+ function baseEconomicSeries(
386
+ spec: ChartSeriesSpec,
387
+ loaded: FredSeriesLoadResult,
388
+ index: number,
389
+ ): ResolvedSeries | null {
390
+ if (spec.source.kind !== "economic") return null;
391
+ const { data } = loaded;
392
+ const units = data.info?.units?.trim() || "value";
393
+ const isPercent = units.toLowerCase().includes("percent");
394
+ return {
395
+ id: spec.id,
396
+ label: spec.label?.trim() || data.info?.title?.trim() || spec.source.seriesId,
397
+ color: spec.color ?? SERIES_COLORS[index % SERIES_COLORS.length]!,
398
+ unit: isPercent ? "%" : units,
399
+ unitGroup: isPercent ? "percent" : `economic:${units.toLowerCase()}`,
400
+ nativeFrequency: "auto",
401
+ timestampMode: "period-end",
402
+ dataShape: "scalar",
403
+ style: spec.style,
404
+ transform: spec.transform,
405
+ axis: spec.axis === "right" ? "right" : "left",
406
+ panelId: spec.panelId,
407
+ interpolation: spec.interpolation,
408
+ points: extractFredSeries(data.observations, { providerId: "fred", timestampMode: "period-end" }),
409
+ warning: "FRED vintage dates are unavailable; observations use period dates.",
410
+ };
411
+ }
412
+
413
+ function staleFredWarning(loaded: FredSeriesLoadResult): string | null {
414
+ if (!loaded.stale) return null;
415
+ return `FRED refresh failed${loaded.refreshError ? ` (${loaded.refreshError})` : ""}; showing cached data fetched ${new Date(loaded.fetchedAt).toISOString().slice(0, 10)}.`;
416
+ }
417
+
418
+ function assignAxes(
419
+ series: ResolvedSeries[],
420
+ specs: readonly { id: string; axis: ChartSeriesSpec["axis"] }[],
421
+ warnings: string[],
422
+ ): ResolvedSeries[] {
423
+ const requested = new Map(specs.map((spec) => [spec.id, spec.axis] as const));
424
+ const groupsByPanel = new Map<string, Partial<Record<"left" | "right", string>>>();
425
+ return series.map((entry) => {
426
+ const groups = groupsByPanel.get(entry.panelId) ?? {};
427
+ groupsByPanel.set(entry.panelId, groups);
428
+ const preferred = requested.get(entry.id);
429
+ let axis: "left" | "right";
430
+ if (preferred === "left" || preferred === "right") {
431
+ axis = preferred;
432
+ } else if (groups.left === entry.unitGroup) {
433
+ axis = "left";
434
+ } else if (groups.right === entry.unitGroup) {
435
+ axis = "right";
436
+ } else if (!groups.left) {
437
+ axis = "left";
438
+ } else {
439
+ axis = "right";
440
+ }
441
+ if (groups[axis] && groups[axis] !== entry.unitGroup) {
442
+ warnings.push(`${entry.label} shares the ${axis} axis with a different unit; choose an explicit panel for independent scaling.`);
443
+ }
444
+ groups[axis] ??= entry.unitGroup;
445
+ return { ...entry, axis };
446
+ });
447
+ }
448
+
449
+ function prepareBaseSeriesForStudies(
450
+ series: ResolvedSeries,
451
+ bounds: DateBounds,
452
+ clipToBounds = false,
453
+ ): ResolvedSeries {
454
+ const baselineTransform = series.transform === "percent" || series.transform === "index100";
455
+ let source = series;
456
+ if (clipToBounds && bounds.start !== null && bounds.end !== null) {
457
+ source = clipSeriesToWindow(series, new Date(bounds.start), new Date(bounds.end));
458
+ } else if (clipToBounds) {
459
+ source = { ...series, points: filterPoints(series.points, bounds) };
460
+ }
461
+ return applyResolvedSeriesTransform(
462
+ source,
463
+ source.transform,
464
+ baselineTransform ? { baseline: scalarBaseline(series, bounds) } : undefined,
465
+ );
466
+ }
467
+
468
+ function rawCalculationSeries(series: ResolvedSeries, bounds: DateBounds): ResolvedSeries {
469
+ if (bounds.start !== null && bounds.end !== null) {
470
+ return clipSeriesToWindow(series, new Date(bounds.start), new Date(bounds.end));
471
+ }
472
+ return { ...series, points: filterPoints(series.points, bounds) };
473
+ }
474
+
475
+ function scalarBaseline(series: ResolvedSeries, bounds: DateBounds): number | null {
476
+ const points = filterPoints(series.points, bounds);
477
+ for (const point of points) {
478
+ const value = typeof point.value === "number" && Number.isFinite(point.value)
479
+ ? point.value
480
+ : typeof point.close === "number" && Number.isFinite(point.close)
481
+ ? point.close
482
+ : null;
483
+ if (value !== null && value !== 0) return value;
484
+ }
485
+ return null;
486
+ }
487
+
488
+ function studyForOutput(
489
+ outputId: string,
490
+ studies: readonly ChartSpec["studies"][number][],
491
+ ): ChartSpec["studies"][number] | undefined {
492
+ return studies
493
+ .filter((study) => outputId === study.id || outputId.startsWith(`${study.id}:`))
494
+ .sort((left, right) => right.id.length - left.id.length)[0];
495
+ }
496
+
497
+ function applyStudyPresentationTransforms(
498
+ outputs: ResolvedSeries[],
499
+ studies: readonly ChartSpec["studies"][number][],
500
+ rawSeries: readonly ResolvedSeries[],
501
+ visibleBounds: DateBounds,
502
+ ): ResolvedSeries[] {
503
+ const rawById = new Map(rawSeries.map((series) => [series.id, series] as const));
504
+ return outputs.map((output) => {
505
+ const study = studyForOutput(output.id, studies);
506
+ if (!study || (study.kind !== "sma" && study.kind !== "ema" && study.kind !== "bollinger")) {
507
+ return output;
508
+ }
509
+ const input = rawById.get(study.inputSeriesIds[0] ?? "");
510
+ if (!input || input.transform === "raw") return output;
511
+ const baseline = input.transform === "percent" || input.transform === "index100"
512
+ ? scalarBaseline(input, visibleBounds)
513
+ : undefined;
514
+ return applyResolvedSeriesTransform(output, input.transform, { baseline });
515
+ });
516
+ }
517
+
518
+ export async function resolveChartSpecData(
519
+ spec: ChartSpec,
520
+ sources: ChartResolveSources,
521
+ cache = new ChartResolveCache(),
522
+ options: ChartResolveOptions = {},
523
+ ): Promise<ChartResolutionResult> {
524
+ const errors: string[] = [];
525
+ const warnings: string[] = [];
526
+ const priorityWarnings: string[] = [];
527
+ const baseSeriesIds = new Set(spec.series.map((entry) => entry.id));
528
+ const visibleSeriesIds = new Set(spec.series
529
+ .filter((entry) => entry.visible !== false)
530
+ .map((entry) => entry.id));
531
+ const calculationSeriesIds = activeStudyInputSeriesIds(spec.studies);
532
+ visibleSeriesIds.forEach((id) => calculationSeriesIds.add(id));
533
+ // Keep the first authored market series loaded as the deterministic shared
534
+ // session anchor even when the user temporarily hides its marks.
535
+ const primaryMarketSeries = spec.series.find((entry) => (
536
+ entry.source.kind === "security" && isMarketFieldId(entry.source.fieldId)
537
+ ));
538
+ if (primaryMarketSeries) calculationSeriesIds.add(primaryMarketSeries.id);
539
+ if (!sources.dataProvider && spec.series.some((entry) => (
540
+ calculationSeriesIds.has(entry.id) && entry.source.kind === "security"
541
+ ))) {
542
+ return { series: [], loading: false, errors: ["Market data is unavailable."], warnings };
543
+ }
544
+
545
+ const referenceNow = sources.now ?? new Date();
546
+ const initialVisibleBounds = requestedBounds(spec, referenceNow);
547
+
548
+ const loadFinancials = (source: Extract<ChartSeriesSpec["source"], { kind: "security" }>) => {
549
+ const key = instrumentKey(source);
550
+ let pending = cache.financialsByInstrument.get(key);
551
+ if (!pending) {
552
+ pending = sources.dataProvider!
553
+ .getTickerFinancials(
554
+ source.instrument.symbol,
555
+ source.instrument.exchange ?? "",
556
+ requestContext(source),
557
+ )
558
+ .catch(() => null);
559
+ cache.financialsByInstrument.set(key, pending);
560
+ }
561
+ return pending;
562
+ };
563
+ const loadResolutionSupport = (
564
+ source: Extract<ChartSeriesSpec["source"], { kind: "security" }>,
565
+ ) => {
566
+ const provider = sources.dataProvider!;
567
+ if (!provider.getChartResolutionSupport) return Promise.resolve([]);
568
+ const key = `${provider.id}|${instrumentKey(source)}`;
569
+ let pending = cache.resolutionSupportByInstrument.get(key);
570
+ if (!pending) {
571
+ pending = Promise.resolve(provider.getChartResolutionSupport(
572
+ source.instrument.symbol,
573
+ source.instrument.exchange ?? "",
574
+ requestContext(source),
575
+ )).catch(() => []);
576
+ cache.resolutionSupportByInstrument.set(key, pending);
577
+ }
578
+ return pending;
579
+ };
580
+
581
+ const runtimeBounds = spec.viewport.resolution === "auto"
582
+ ? runtimeAutoBounds(options)
583
+ : null;
584
+ const activeMarketSources = [...new Map(spec.series.flatMap((entry) => (
585
+ calculationSeriesIds.has(entry.id)
586
+ && entry.source.kind === "security"
587
+ && isMarketFieldId(entry.source.fieldId)
588
+ ? [[instrumentKey(entry.source), entry.source] as const]
589
+ : []
590
+ ))).values()];
591
+ const sharedSupport = runtimeBounds && activeMarketSources.length > 0
592
+ ? intersectChartResolutionSupport(await Promise.all(
593
+ activeMarketSources.map((source) => loadResolutionSupport(source)),
594
+ ))
595
+ : [];
596
+ const initialResolution = requestResolution(
597
+ spec,
598
+ initialVisibleBounds,
599
+ calculationSeriesIds,
600
+ options,
601
+ sharedSupport,
602
+ );
603
+ const requestVisibleBounds = runtimeBounds ?? initialVisibleBounds;
604
+ const initialCalculationBounds = calculationBounds(
605
+ spec,
606
+ requestVisibleBounds,
607
+ initialResolution,
608
+ );
609
+ const hasExplicitWindow = explicitBounds(spec) !== null
610
+ || (runtimeBounds !== null && !sameBounds(runtimeBounds, initialVisibleBounds));
611
+
612
+ const loadHistory = async (
613
+ source: Extract<ChartSeriesSpec["source"], { kind: "security" }>,
614
+ all = false,
615
+ ) => {
616
+ const requestedFallbackRange = all
617
+ ? "ALL"
618
+ : trailingRangeForStart(initialCalculationBounds.start, referenceNow);
619
+ const support = await loadResolutionSupport(source);
620
+ const maxRange = getSupportMaxRange(support, initialResolution);
621
+ const fallbackRange = maxRange
622
+ ? clampTimeRangeToMaxRange(requestedFallbackRange, maxRange)
623
+ : requestedFallbackRange;
624
+ const request: PriceHistoryRequest = {
625
+ bounds: initialCalculationBounds,
626
+ explicitWindow: hasExplicitWindow,
627
+ fallbackRange,
628
+ resolution: initialResolution,
629
+ allowProviderDefaultFallback: spec.viewport.resolution === "auto",
630
+ };
631
+ const key = [
632
+ instrumentKey(source),
633
+ request.resolution,
634
+ request.fallbackRange,
635
+ ...(request.explicitWindow
636
+ ? [request.bounds.start ?? "open", request.bounds.end ?? "open"]
637
+ : []),
638
+ ].join("|");
639
+ let pending = cache.priceHistoryByRequest.get(key);
640
+ if (!pending) {
641
+ pending = loadPriceHistory(sources.dataProvider!, source, request);
642
+ cache.priceHistoryByRequest.set(key, pending);
643
+ }
644
+ return pending;
645
+ };
646
+
647
+ const loadEconomicSeries = (request: FredSeriesRequest) => {
648
+ const key = `${request.seriesId.trim().toUpperCase()}|${request.startDate}|${request.sortOrder}`;
649
+ let pending = cache.fredSeriesByRequest.get(key);
650
+ if (!pending) {
651
+ pending = sources.loadFredSeries(request);
652
+ cache.fredSeriesByRequest.set(key, pending);
653
+ }
654
+ return pending;
655
+ };
656
+
657
+ const loaded = await Promise.all(spec.series.map(async (seriesSpec, index) => {
658
+ if (!calculationSeriesIds.has(seriesSpec.id)) return null;
659
+ try {
660
+ if (seriesSpec.source.kind === "economic") {
661
+ const request: FredSeriesRequest = {
662
+ seriesId: seriesSpec.source.seriesId,
663
+ startDate: initialCalculationBounds.start === null
664
+ ? "1900-01-01"
665
+ : dateOnly(new Date(initialCalculationBounds.start)),
666
+ sortOrder: "asc",
667
+ };
668
+ const fred = await loadEconomicSeries(request);
669
+ const result = baseEconomicSeries(seriesSpec, fred, index);
670
+ const freshnessWarning = staleFredWarning(fred);
671
+ if (result && freshnessWarning) priorityWarnings.push(`${result.label}: ${freshnessWarning}`);
672
+ return result;
673
+ }
674
+
675
+ const source = seriesSpec.source;
676
+ const marketField = isMarketFieldId(source.fieldId);
677
+ const quoteDerivedValuation = valuationSeriesUsesLiveQuote(source.fieldId);
678
+ const needsHistory = marketField || quoteDerivedValuation;
679
+ const [financials, history] = await Promise.all([
680
+ loadFinancials(source),
681
+ needsHistory ? loadHistory(source, quoteDerivedValuation) : Promise.resolve(null),
682
+ ]);
683
+ const quoteOverride = marketField || quoteDerivedValuation
684
+ ? sources.quoteOverrides?.get(chartQuoteOverrideKeyForSource(source))
685
+ : undefined;
686
+ const liveBarResolution = isOhlcSeriesStyle(seriesSpec.style) ? initialResolution : undefined;
687
+ const merged = history
688
+ ? mergeHistory(financials, history, quoteOverride, referenceNow.getTime(), liveBarResolution)
689
+ : quoteOverride && financials
690
+ ? { ...financials, quote: latestQuote(financials.quote, quoteOverride) }
691
+ : financials;
692
+ if (!merged) throw new Error(`No financial data is available for ${instrumentLabel(source)}.`);
693
+ const result = baseSecuritySeries(seriesSpec, merged, index, initialResolution);
694
+ if (!result) throw new Error(`Unknown field ${source.fieldId}.`);
695
+ if (isFundamentalFieldId(source.fieldId) && fundamentalSeriesUsesAvailabilityFallback(merged, source)) {
696
+ result.warning = "Publication dates are unavailable for some observations; period-end dates are used as a fallback.";
697
+ }
698
+ return result;
699
+ } catch (error) {
700
+ errors.push(`${seriesSpec.label ?? seriesSpec.id}: ${error instanceof Error ? error.message : String(error)}`);
701
+ return null;
702
+ }
703
+ }));
704
+
705
+ const rawSeries = loaded.filter((entry): entry is ResolvedSeries => !!entry);
706
+ // Preset ranges describe a window ending at the requested reference time,
707
+ // even when the newest filing or economic observation lags that endpoint.
708
+ // Re-anchoring to the latest observation both mislabels the range and asks
709
+ // providers for a different window than the one ultimately rendered.
710
+ const bounds = initialVisibleBounds;
711
+ const resolution = initialResolution;
712
+ const studyBounds = initialCalculationBounds;
713
+ const baseSeries = rawSeries
714
+ .filter((entry) => visibleSeriesIds.has(entry.id))
715
+ .map((entry) => prepareBaseSeriesForStudies(entry, bounds));
716
+ const calculationSeries = rawSeries.map((entry) => rawCalculationSeries(entry, studyBounds));
717
+ let resolved = baseSeries;
718
+
719
+ // Study outputs are appended by the pure engine before the final viewport clip.
720
+ if (spec.studies.length > 0) {
721
+ const studyResult = resolveStudies(calculationSeries, spec.studies);
722
+ resolved = [
723
+ ...resolved,
724
+ ...applyStudyPresentationTransforms(studyResult.series, spec.studies, rawSeries, bounds),
725
+ ];
726
+ warnings.push(...studyResult.warnings);
727
+ errors.push(...studyResult.errors);
728
+ }
729
+
730
+ const bufferedSeries = assignAxes(resolved, [...spec.series, ...spec.studies], warnings);
731
+ resolved = bufferedSeries.map((entry) => (
732
+ bounds.start !== null && bounds.end !== null
733
+ ? clipSeriesToWindow(entry, new Date(bounds.start), new Date(bounds.end))
734
+ : { ...entry, points: filterPoints(entry.points, bounds) }
735
+ ));
736
+ if (spec.viewport.maxPoints !== undefined) {
737
+ resolved = resolved.map((entry) => ({
738
+ ...entry,
739
+ points: entry.points.slice(-spec.viewport.maxPoints!),
740
+ }));
741
+ }
742
+ const resolvedById = new Map(resolved.map((entry) => [entry.id, entry] as const));
743
+ const hiddenBaseSeries = rawSeries
744
+ .filter((entry) => !visibleSeriesIds.has(entry.id))
745
+ .map((entry) => prepareBaseSeriesForStudies(entry, bounds, true));
746
+ const hiddenBaseById = new Map(hiddenBaseSeries.map((entry) => [entry.id, entry] as const));
747
+ const legendSeries = [
748
+ ...spec.series.flatMap((seriesSpec) => {
749
+ const entry = resolvedById.get(seriesSpec.id) ?? hiddenBaseById.get(seriesSpec.id);
750
+ return entry ? [entry] : [];
751
+ }),
752
+ ...resolved.filter((entry) => !baseSeriesIds.has(entry.id)),
753
+ ];
754
+ for (const entry of resolved) {
755
+ if (entry.warning) warnings.push(`${entry.label}: ${entry.warning}`);
756
+ if (entry.points.length === 0) warnings.push(`${entry.label}: no observations in the selected date range.`);
757
+ }
758
+ for (const panel of spec.panels) {
759
+ if (panel.scale !== "log") continue;
760
+ const hiddenCount = resolved
761
+ .filter((entry) => entry.panelId === panel.id)
762
+ .flatMap((entry) => entry.points)
763
+ .filter((point) => typeof point.value === "number" && Number.isFinite(point.value) && point.value <= 0)
764
+ .length;
765
+ if (hiddenCount > 0) {
766
+ warnings.push(`${panel.label ?? panel.id}: ${hiddenCount} non-positive observation${hiddenCount === 1 ? " is" : "s are"} hidden on the logarithmic scale.`);
767
+ }
768
+ }
769
+
770
+ const exposeViewport = hasExplicitWindow || spec.viewport.maxPoints === undefined;
771
+ const viewport = exposeViewport && bounds.start !== null && bounds.end !== null
772
+ ? { start: new Date(bounds.start), end: new Date(bounds.end) }
773
+ : undefined;
774
+ return {
775
+ series: resolved,
776
+ legendSeries,
777
+ ...(spec.viewport.maxPoints === undefined ? { bufferedSeries } : {}),
778
+ loading: false,
779
+ errors,
780
+ warnings: [...new Set([...priorityWarnings, ...warnings])],
781
+ viewport,
782
+ };
783
+ }