gloomberb 0.10.0 → 0.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (353) hide show
  1. package/package.json +8 -1
  2. package/src/api-client/auth.ts +1 -0
  3. package/src/api-client/index.test.ts +359 -108
  4. package/src/api-client/index.ts +24 -13
  5. package/src/api-client/socket.ts +70 -35
  6. package/src/api-client/types.ts +1 -1
  7. package/src/app/language-observer.test.tsx +82 -0
  8. package/src/app/language-observer.tsx +13 -0
  9. package/src/app/pane-runtime/index.tsx +2 -2
  10. package/src/app/pane-runtime/pane-settings-runtime.tsx +2 -2
  11. package/src/app/pane-runtime/pane-template-runtime.ts +2 -2
  12. package/src/app/pane-runtime/plugin-bindings.ts +2 -2
  13. package/src/app/pane-runtime/ticker-open-runtime.ts +2 -2
  14. package/src/app/runtime/broker-import.ts +2 -2
  15. package/src/app/runtime/plugin-bindings.ts +2 -2
  16. package/src/app/runtime/startup.ts +2 -2
  17. package/src/app.tsx +16 -7
  18. package/src/brokers/account-cache.ts +6 -6
  19. package/src/brokers/broker-ticker-sync.ts +3 -3
  20. package/src/brokers/remote-broker-adapter.ts +2 -2
  21. package/src/brokers/sync-broker-instance.ts +5 -6
  22. package/src/cli/commands/api.ts +3 -6
  23. package/src/cli/commands/automation.ts +7 -15
  24. package/src/cli/commands/market.ts +26 -61
  25. package/src/cli/commands/overview.ts +34 -45
  26. package/src/cli/commands/search.ts +25 -27
  27. package/src/cli/commands/system.ts +27 -57
  28. package/src/cli/commands/ticker.ts +62 -63
  29. package/src/cli/context.test.ts +63 -0
  30. package/src/cli/context.ts +15 -1
  31. package/src/cli/index.ts +1 -1
  32. package/src/cli/pane-functions/data.ts +2 -19
  33. package/src/cli/pane-functions/index.ts +26 -25
  34. package/src/cli/pane-functions/report.ts +1 -1
  35. package/src/cli/pane-functions/screenshot.ts +3 -3
  36. package/src/cli/scoped-context.ts +41 -0
  37. package/src/components/chart/composite/composite-chart.test.tsx +615 -18
  38. package/src/components/chart/composite/composite-chart.tsx +179 -79
  39. package/src/components/chart/composite/format.test.ts +20 -7
  40. package/src/components/chart/composite/index.ts +0 -21
  41. package/src/components/chart/composite/interactions.test.ts +90 -3
  42. package/src/components/chart/composite/interactions.ts +189 -24
  43. package/src/components/chart/composite/price-series.ts +0 -2
  44. package/src/components/chart/composite/rasterizer.ts +10 -9
  45. package/src/components/chart/composite/renderers.test.ts +108 -2
  46. package/src/components/chart/composite/scene.test.ts +36 -0
  47. package/src/components/chart/composite/scene.ts +5 -2
  48. package/src/components/chart/composite/text-renderer.ts +37 -25
  49. package/src/components/chart/composite/time-axis.test.ts +243 -0
  50. package/src/components/chart/composite/time-axis.ts +608 -0
  51. package/src/components/chart/composite/types.ts +12 -1
  52. package/src/components/chart/core/data.ts +2 -159
  53. package/src/components/chart/core/date-window.ts +6 -225
  54. package/src/components/chart/core/palette.ts +1 -12
  55. package/src/components/chart/core/pointer.ts +0 -142
  56. package/src/components/chart/core/types.ts +1 -58
  57. package/src/components/chart/cursor-motion.ts +2 -4
  58. package/src/components/chart/indicators/types.ts +0 -9
  59. package/src/components/chart/market-session.ts +0 -11
  60. package/src/components/chart/native/kitty/protocol.ts +1 -1
  61. package/src/components/chart/native/raster/layout.ts +3 -10
  62. package/src/components/chart/static/chart/axis-overlays.test.tsx +64 -0
  63. package/src/components/chart/static/chart/axis-overlays.tsx +59 -14
  64. package/src/components/chart/static/chart/surface.tsx +1 -2
  65. package/src/components/command-bar/action-runtime.ts +2 -2
  66. package/src/components/command-bar/commands/direct/index.ts +1 -1
  67. package/src/components/command-bar/panel/body.tsx +2 -2
  68. package/src/components/command-bar/panel/index.tsx +11 -9
  69. package/src/components/command-bar/routes/ticker-search/actions.ts +2 -2
  70. package/src/components/command-bar/surface/index.tsx +2 -2
  71. package/src/components/command-bar/workflow/body.tsx +3 -3
  72. package/src/components/command-bar/workflow/collection-actions.ts +2 -2
  73. package/src/components/command-bar/workflow/coordinator.ts +2 -2
  74. package/src/components/command-bar/workflow/field-row.tsx +25 -11
  75. package/src/components/command-bar/workflow/tickers.ts +2 -2
  76. package/src/components/feed-data-table/stack-view.test.tsx +28 -36
  77. package/src/components/feed-data-table/stack-view.tsx +3 -1
  78. package/src/components/layout/detached-pane-shell.tsx +6 -6
  79. package/src/components/layout/header.tsx +13 -9
  80. package/src/components/layout/pane/content.test.tsx +42 -3
  81. package/src/components/layout/pane/content.tsx +3 -1
  82. package/src/components/layout/pane/footer/index.test.tsx +45 -2
  83. package/src/components/layout/pane/footer/registration.tsx +3 -1
  84. package/src/components/layout/shell/index.tsx +1 -2
  85. package/src/components/layout/shell/native/surfaces.ts +1 -1
  86. package/src/components/layout/shell/native/window-state.ts +1 -1
  87. package/src/components/layout/status-bar.tsx +12 -8
  88. package/src/components/layout/window-controls.tsx +8 -6
  89. package/src/components/onboarding/onboarding-wizard.tsx +3 -1
  90. package/src/components/pane-settings-dialog/desktop.tsx +0 -2
  91. package/src/components/pane-settings-dialog.tsx +0 -2
  92. package/src/components/pane-template-wizard.tsx +10 -9
  93. package/src/components/price-performance.tsx +2 -113
  94. package/src/components/price-sparkline/model.ts +15 -17
  95. package/src/components/price-sparkline/view.tsx +8 -7
  96. package/src/components/speedometer-gauge/desktop.tsx +2 -1
  97. package/src/components/speedometer-gauge/index.tsx +0 -2
  98. package/src/components/speedometer-gauge/terminal.tsx +24 -5
  99. package/src/components/ticker/badge/index.tsx +1 -18
  100. package/src/components/ui/data-table/opentui/index.tsx +3 -3
  101. package/src/components/ui/frame.tsx +1 -2
  102. package/src/components/ui/page-stack-view.test.tsx +126 -0
  103. package/src/components/ui/page-stack-view.tsx +24 -12
  104. package/src/components/ui/tabs.tsx +7 -3
  105. package/src/components/ui/ui.test.tsx +23 -2
  106. package/src/components/use-external-link-footer.ts +4 -2
  107. package/src/core/app-service-ports.ts +46 -0
  108. package/src/core/app-services.test.ts +66 -0
  109. package/src/core/app-services.ts +17 -15
  110. package/src/data/config/chart-settings.ts +3 -10
  111. package/src/data/config/store/index.test.ts +99 -2
  112. package/src/data/config/store/migrations.ts +278 -0
  113. package/src/data/config/store/normalize.ts +44 -186
  114. package/src/data/resource-store.ts +73 -12
  115. package/src/data/sqlite/cache.test.ts +104 -15
  116. package/src/i18n/index.test.ts +17 -0
  117. package/src/i18n/index.ts +23 -6
  118. package/src/i18n/ja.ts +13 -0
  119. package/src/i18n/ko.ts +13 -0
  120. package/src/i18n/react.ts +6 -0
  121. package/src/i18n/zh-cn.ts +13 -0
  122. package/src/i18n/zh-tw.ts +13 -0
  123. package/src/market-data/coordinator/auxiliary.ts +7 -3
  124. package/src/market-data/coordinator/chart.ts +1 -1
  125. package/src/market-data/coordinator/index.test.ts +26 -0
  126. package/src/market-data/coordinator/index.ts +11 -5
  127. package/src/market-data/coordinator/quotes.ts +4 -40
  128. package/src/market-data/coordinator/subscriptions.test.ts +89 -1
  129. package/src/market-data/hooks.tsx +47 -1
  130. package/src/market-data/market/status.ts +6 -6
  131. package/src/market-data/performance.ts +1 -1
  132. package/src/market-data/quote-subscription-target.test.ts +37 -0
  133. package/src/market-data/quote-subscription-target.ts +60 -0
  134. package/src/market-data/quotes/freshness.ts +1 -0
  135. package/src/market-data/quotes/resolution.ts +4 -0
  136. package/src/market-data/request-types.ts +3 -3
  137. package/src/news/aggregator.test.ts +97 -1
  138. package/src/news/aggregator.ts +100 -57
  139. package/src/news/hooks.test.tsx +64 -0
  140. package/src/news/hooks.ts +18 -25
  141. package/src/plugins/builtin/account-management/footer.ts +4 -2
  142. package/src/plugins/builtin/account-management/pane.tsx +9 -4
  143. package/src/plugins/builtin/ai/agent-history.ts +11 -7
  144. package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +0 -5
  145. package/src/plugins/builtin/ai/ask-ai.test.tsx +3 -4
  146. package/src/plugins/builtin/ai/index.test.ts +2 -2
  147. package/src/plugins/builtin/ai/index.tsx +10 -11
  148. package/src/plugins/builtin/ai/pane-settings.ts +1 -1
  149. package/src/plugins/builtin/ai/pi/runtime.ts +6 -5
  150. package/src/plugins/builtin/ai/providers.ts +1 -9
  151. package/src/plugins/builtin/ai/runner.ts +0 -6
  152. package/src/plugins/builtin/ai/screener/footer.ts +4 -1
  153. package/src/plugins/builtin/ai/screener/pane.test.tsx +9 -9
  154. package/src/plugins/builtin/ai/workspace/model.ts +2 -27
  155. package/src/plugins/builtin/ai/workspace/pane.tsx +2 -2
  156. package/src/plugins/builtin/analytics/metrics.ts +1 -7
  157. package/src/plugins/builtin/broker-manager/detail.tsx +3 -2
  158. package/src/plugins/builtin/broker-manager/index.tsx +4 -2
  159. package/src/plugins/builtin/chart-composer/editor/controller.ts +643 -0
  160. package/src/plugins/builtin/chart-composer/editor/desktop-view.tsx +134 -0
  161. package/src/plugins/builtin/chart-composer/editor/model.test.ts +85 -0
  162. package/src/plugins/builtin/chart-composer/editor/model.ts +292 -0
  163. package/src/plugins/builtin/chart-composer/editor/opentui-view.tsx +108 -0
  164. package/src/plugins/builtin/chart-composer/editor.tsx +65 -947
  165. package/src/plugins/builtin/chart-composer/index.tsx +0 -6
  166. package/src/plugins/builtin/chart-composer/pane.tsx +69 -36
  167. package/src/plugins/builtin/chart-composer/presets.ts +1 -19
  168. package/src/plugins/builtin/chart-composer/settings.ts +1 -1
  169. package/src/plugins/builtin/chat/content/composer-runtime.ts +1 -1
  170. package/src/plugins/builtin/chat/content/shortcuts.ts +0 -1
  171. package/src/plugins/builtin/chat/controller/fetch.ts +8 -2
  172. package/src/plugins/builtin/chat/controller/persistence.ts +2 -0
  173. package/src/plugins/builtin/chat/controller/session.test.ts +25 -0
  174. package/src/plugins/builtin/chat/controller/session.ts +2 -0
  175. package/src/plugins/builtin/chat/controller/state.ts +7 -0
  176. package/src/plugins/builtin/chat/controller.test.ts +73 -20
  177. package/src/plugins/builtin/chat/message/desktop.tsx +2 -0
  178. package/src/plugins/builtin/cloud-tweets/footer.ts +9 -28
  179. package/src/plugins/builtin/cloud-tweets/pane.tsx +0 -5
  180. package/src/plugins/builtin/cloud-tweets/table.tsx +12 -17
  181. package/src/plugins/builtin/correlation/compute.ts +4 -10
  182. package/src/plugins/builtin/econ/index.tsx +17 -19
  183. package/src/plugins/builtin/holders/types.ts +0 -1
  184. package/src/plugins/builtin/insider/index.tsx +6 -31
  185. package/src/plugins/builtin/news/index.tsx +5 -7
  186. package/src/plugins/builtin/news/wire/breaking/pane.test.tsx +6 -8
  187. package/src/plugins/builtin/news/wire/news/footer.ts +10 -11
  188. package/src/plugins/builtin/news/wire/read-state.test.ts +7 -0
  189. package/src/plugins/builtin/news/wire/read-state.ts +18 -28
  190. package/src/plugins/builtin/news/wire/rss/parser.ts +1 -11
  191. package/src/plugins/builtin/options/footer.test.ts +115 -0
  192. package/src/plugins/builtin/options/footer.ts +110 -0
  193. package/src/plugins/builtin/options/live-quotes.test.ts +238 -0
  194. package/src/plugins/builtin/options/live-quotes.ts +147 -0
  195. package/src/plugins/builtin/options/view.test.tsx +71 -1
  196. package/src/plugins/builtin/options/view.tsx +39 -24
  197. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +103 -117
  198. package/src/plugins/builtin/portfolio-list/cli/render.ts +11 -5
  199. package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +120 -135
  200. package/src/plugins/builtin/portfolio-list/header.tsx +9 -4
  201. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +6 -1
  202. package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +4 -2
  203. package/src/plugins/builtin/research/corporate-actions-pane.tsx +24 -52
  204. package/src/plugins/builtin/sec/filing-content.test.ts +41 -0
  205. package/src/plugins/builtin/sec/filing-content.ts +94 -0
  206. package/src/plugins/builtin/sec/index.tsx +21 -104
  207. package/src/plugins/builtin/sectors/sector-model.ts +1 -7
  208. package/src/plugins/builtin/shared/cloud-upgrade.ts +1 -0
  209. package/src/plugins/builtin/shared/pane-footer.ts +82 -0
  210. package/src/plugins/builtin/shared/read-state.ts +64 -3
  211. package/src/plugins/builtin/substack/read-state.ts +16 -41
  212. package/src/plugins/builtin/thirteenf/pane.tsx +24 -73
  213. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +10 -10
  214. package/src/plugins/builtin/yield-curve/index.tsx +11 -9
  215. package/src/plugins/ibkr/dialogs.tsx +3 -3
  216. package/src/plugins/ibkr/gateway/fundamentals.ts +1 -1
  217. package/src/plugins/ibkr/gateway/history-requests.ts +2 -2
  218. package/src/plugins/ibkr/gateway/history.ts +2 -2
  219. package/src/plugins/ibkr/gateway/selection.ts +2 -2
  220. package/src/plugins/ibkr/gateway/service/index.ts +2 -2
  221. package/src/plugins/ibkr/gateway/service/native.ts +2 -2
  222. package/src/plugins/plugin-persistence.ts +3 -4
  223. package/src/plugins/prediction-markets/controller/keyboard.ts +1 -1
  224. package/src/plugins/prediction-markets/controller/live-updates.ts +6 -5
  225. package/src/plugins/prediction-markets/detail/book.tsx +2 -2
  226. package/src/plugins/prediction-markets/detail/outcomes.tsx +3 -1
  227. package/src/plugins/prediction-markets/detail/pane.tsx +3 -1
  228. package/src/plugins/prediction-markets/detail/trades.tsx +3 -3
  229. package/src/plugins/registry/context.ts +3 -4
  230. package/src/plugins/registry/index.ts +5 -6
  231. package/src/react/input.test.ts +126 -3
  232. package/src/react/input.ts +120 -1
  233. package/src/remote/app-host.tsx +2 -13
  234. package/src/renderers/electrobun/bun/desktop/backend-requests.ts +44 -60
  235. package/src/renderers/electrobun/bun/desktop/capability-bridge.ts +15 -13
  236. package/src/renderers/electrobun/bun/desktop/host-requests.ts +26 -22
  237. package/src/renderers/electrobun/bun/desktop/http-fetch.ts +5 -1
  238. package/src/renderers/electrobun/bun/desktop/initialization.ts +11 -6
  239. package/src/renderers/electrobun/bun/desktop/media.ts +5 -3
  240. package/src/renderers/electrobun/bun/desktop/plugin-state.ts +21 -22
  241. package/src/renderers/electrobun/bun/desktop/window-controls.ts +3 -1
  242. package/src/renderers/electrobun/bun/desktop/workspace/requests.ts +18 -18
  243. package/src/renderers/electrobun/bun/index.ts +123 -83
  244. package/src/renderers/electrobun/shared/protocol.ts +146 -7
  245. package/src/renderers/electrobun/view/app-services.ts +5 -8
  246. package/src/renderers/electrobun/view/backend-rpc.ts +18 -8
  247. package/src/renderers/electrobun/view/broker-remote-client.ts +2 -2
  248. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +5 -22
  249. package/src/renderers/electrobun/view/config-host.ts +1 -1
  250. package/src/renderers/electrobun/view/context-menu.ts +1 -17
  251. package/src/renderers/electrobun/view/data-table/index.tsx +0 -1
  252. package/src/renderers/electrobun/view/data-table/row.tsx +0 -4
  253. package/src/renderers/electrobun/view/desktop/control-styles.ts +37 -34
  254. package/src/renderers/electrobun/view/desktop/controls.tsx +36 -33
  255. package/src/renderers/electrobun/view/desktop/list-view.tsx +15 -12
  256. package/src/renderers/electrobun/view/desktop/popover.tsx +2 -3
  257. package/src/renderers/electrobun/view/dialog-host.tsx +2 -3
  258. package/src/renderers/electrobun/view/fatal-screen.tsx +0 -1
  259. package/src/renderers/electrobun/view/host/box.tsx +0 -1
  260. package/src/renderers/electrobun/view/host/chart-surface.tsx +0 -1
  261. package/src/renderers/electrobun/view/host/input.tsx +0 -1
  262. package/src/renderers/electrobun/view/host/media-surface.tsx +0 -1
  263. package/src/renderers/electrobun/view/host/native.ts +0 -1
  264. package/src/renderers/electrobun/view/host/scroll-box.tsx +0 -1
  265. package/src/renderers/electrobun/view/host/tabs.tsx +0 -1
  266. package/src/renderers/electrobun/view/host/text.tsx +0 -1
  267. package/src/renderers/electrobun/view/http-fetch.ts +3 -11
  268. package/src/renderers/electrobun/view/input-host.tsx +10 -53
  269. package/src/renderers/electrobun/view/key-event.ts +0 -1
  270. package/src/renderers/electrobun/view/main.tsx +3 -3
  271. package/src/renderers/electrobun/view/native-renderer.ts +0 -1
  272. package/src/renderers/electrobun/view/native-stubs/backend-rpc.ts +13 -1
  273. package/src/renderers/electrobun/view/native-stubs/chart/surface-manager.ts +11 -0
  274. package/src/renderers/electrobun/view/native-stubs/native-stubs.test.ts +47 -0
  275. package/src/renderers/electrobun/view/remote/asset-data-client.ts +6 -44
  276. package/src/renderers/electrobun/view/remote/persistence.ts +6 -2
  277. package/src/renderers/electrobun/view/remote/quote-subscription-registry.test.ts +86 -0
  278. package/src/renderers/electrobun/view/remote/quote-subscription-registry.ts +72 -0
  279. package/src/renderers/electrobun/view/remote/ticker-repository.ts +2 -2
  280. package/src/renderers/electrobun/view/toast-host.tsx +0 -1
  281. package/src/renderers/electrobun/view/ui-host.tsx +1 -2
  282. package/src/renderers/electrobun/view/update-host.ts +1 -1
  283. package/src/renderers/opentui/chart-surface.tsx +2 -1
  284. package/src/renderers/opentui/dialog-host.tsx +20 -4
  285. package/src/renderers/opentui/image/loader.ts +12 -2
  286. package/src/renderers/opentui/input-host.tsx +7 -49
  287. package/src/renderers/opentui/start.tsx +2 -0
  288. package/src/sources/gloomberb-cloud/history-quality.ts +44 -0
  289. package/src/sources/gloomberb-cloud/index.test.ts +173 -0
  290. package/src/sources/gloomberb-cloud/index.ts +42 -20
  291. package/src/sources/gloomberb-cloud/normalizers.ts +9 -3
  292. package/src/sources/provider-router/cache.ts +1 -1
  293. package/src/sources/provider-router/document-routes.ts +10 -5
  294. package/src/sources/provider-router/financials.test.ts +14 -0
  295. package/src/sources/provider-router/financials.ts +6 -1
  296. package/src/sources/provider-router/history.test.ts +163 -1
  297. package/src/sources/provider-router/history.ts +278 -347
  298. package/src/sources/provider-router/index.ts +2 -2
  299. package/src/sources/provider-router/routing.test.ts +39 -0
  300. package/src/sources/provider-router/routing.ts +24 -11
  301. package/src/sources/provider-router/supplemental.ts +37 -17
  302. package/src/sources/sec-edgar/content.ts +2 -12
  303. package/src/sources/sec-edgar.ts +2 -13
  304. package/src/sources/yahoo-finance/history.ts +2 -2
  305. package/src/sources/yahoo-finance/options.ts +5 -0
  306. package/src/sources/yahoo-finance.ts +2 -2
  307. package/src/state/app/bootstrap.ts +2 -2
  308. package/src/state/app/context/index.tsx +2 -2
  309. package/src/state/hooks/quote-streaming.ts +66 -1
  310. package/src/state/hooks/session-snapshot.ts +2 -2
  311. package/src/state/selectors.ts +0 -9
  312. package/src/sync/controller.test.ts +98 -0
  313. package/src/sync/controller.ts +29 -5
  314. package/src/sync/react.ts +2 -2
  315. package/src/sync/types.ts +2 -2
  316. package/src/theme/colors.test.ts +10 -0
  317. package/src/theme/colors.ts +75 -78
  318. package/src/theme/theme-context.test.tsx +67 -0
  319. package/src/theme/theme-context.tsx +9 -5
  320. package/src/tickers/open-target.ts +2 -2
  321. package/src/tickers/search/upsert.ts +2 -2
  322. package/src/time-series/alignment.ts +0 -7
  323. package/src/time-series/chart-data.ts +145 -0
  324. package/src/time-series/date-window.ts +44 -0
  325. package/src/time-series/history-window.ts +21 -0
  326. package/src/time-series/hooks.test.ts +59 -0
  327. package/src/time-series/hooks.ts +33 -12
  328. package/src/time-series/market-resolution.ts +2 -2
  329. package/src/time-series/market.ts +1 -1
  330. package/src/time-series/range.ts +9 -0
  331. package/src/{components/chart/core → time-series}/resolution.ts +3 -102
  332. package/src/time-series/resolve.test.ts +203 -1
  333. package/src/time-series/resolve.ts +96 -18
  334. package/src/time-series/spec-studies.test.ts +15 -0
  335. package/src/time-series/spec.ts +2 -2
  336. package/src/time-series/studies.ts +5 -2
  337. package/src/time-series/types.ts +3 -1
  338. package/src/time-series/use-chart-resolution.test.tsx +358 -0
  339. package/src/time-series/use-chart-resolution.ts +46 -4
  340. package/src/types/broker.ts +2 -2
  341. package/src/types/data-provider.ts +3 -3
  342. package/src/types/financials.ts +12 -0
  343. package/src/types/plugin.ts +2 -2
  344. package/src/ui/context-menu.tsx +3 -2
  345. package/src/ui/dialog.tsx +2 -2
  346. package/src/utils/html-entities.ts +3 -5
  347. package/src/utils/main-thread-monitor.ts +1 -1
  348. package/src/utils/price-history.ts +5 -5
  349. package/src/version.ts +3 -1
  350. package/src/components/chart/core/viewport.test.ts +0 -8
  351. package/src/components/chart/core/viewport.ts +0 -24
  352. package/src/time-series/index.ts +0 -14
  353. /package/src/{components/chart/core → time-series}/resolution.test.ts +0 -0
@@ -5,6 +5,7 @@ import { instrumentFromTicker } from "../market-data/request-types";
5
5
  import { useAssetData } from "../plugins/runtime";
6
6
  import { useAppSelector } from "../state/app/context";
7
7
  import type { FredSeriesRequest } from "../data/fred-series";
8
+ import type { TickerRecord } from "../types/ticker";
8
9
  import type { ChartSpec } from "./types";
9
10
  import {
10
11
  useChartResolution,
@@ -22,24 +23,44 @@ async function loadFred(request: FredSeriesRequest) {
22
23
  );
23
24
  }
24
25
 
26
+ export function hydrateChartSpecInstruments(
27
+ spec: ChartSpec,
28
+ tickers: ReadonlyMap<string, TickerRecord>,
29
+ ): ChartSpec {
30
+ let changed = false;
31
+ const series = spec.series.map((entry) => {
32
+ if (entry.source.kind !== "security" || entry.source.instrument.exchange?.trim()) {
33
+ return entry;
34
+ }
35
+ const symbol = entry.source.instrument.symbol.trim().toUpperCase();
36
+ const instrument = instrumentFromTicker(tickers.get(symbol), symbol);
37
+ if (!instrument?.exchange) return entry;
38
+ changed = true;
39
+ return {
40
+ ...entry,
41
+ source: {
42
+ ...entry.source,
43
+ instrument: {
44
+ ...instrument,
45
+ ...entry.source.instrument,
46
+ exchange: instrument.exchange,
47
+ },
48
+ },
49
+ };
50
+ });
51
+ return changed ? { ...spec, series } : spec;
52
+ }
53
+
25
54
  export function useResolvedChartSpec(
26
55
  spec: ChartSpec,
27
56
  options: UseChartResolutionOptions = {},
28
57
  ): UseChartResolutionResult {
29
58
  const dataProvider = useAssetData();
30
59
  const tickers = useAppSelector((state) => state.tickers);
31
- const hydratedSpec = useMemo<ChartSpec>(() => ({
32
- ...spec,
33
- series: spec.series.map((entry) => {
34
- if (entry.source.kind !== "security" || entry.source.instrument.exchange) return entry;
35
- const symbol = entry.source.instrument.symbol.trim().toUpperCase();
36
- const ticker = tickers.get(symbol);
37
- const instrument = instrumentFromTicker(ticker, symbol);
38
- return instrument
39
- ? { ...entry, source: { ...entry.source, instrument: { ...instrument, ...entry.source.instrument } } }
40
- : entry;
41
- }),
42
- }), [spec, tickers]);
60
+ const hydratedSpec = useMemo(
61
+ () => hydrateChartSpecInstruments(spec, tickers),
62
+ [spec, tickers],
63
+ );
43
64
  const sources = useMemo(() => ({ dataProvider, loadFredSeries: loadFred }), [dataProvider]);
44
65
  return useChartResolution(hydratedSpec, sources, options);
45
66
  }
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  CHART_RESOLUTION_STEP_MS,
3
3
  type ManualChartResolution,
4
- } from "../components/chart/core/resolution";
4
+ } from "./resolution";
5
5
  import { isMarketFieldId } from "./field-catalog";
6
6
  import type { ChartSeriesSpec, SeriesPeriod } from "./types";
7
7
 
@@ -27,7 +27,7 @@ export function isResolutionFineEnoughForMarketPeriod(
27
27
  }
28
28
 
29
29
  /** Returns the finest fetch cadence required by explicit market periods. */
30
- export function finestExplicitMarketPeriodResolution(
30
+ function finestExplicitMarketPeriodResolution(
31
31
  series: readonly ChartSeriesSpec[],
32
32
  ): ManualChartResolution | null {
33
33
  let finest: ManualChartResolution | null = null;
@@ -41,7 +41,7 @@ interface AggregatedPricePoint {
41
41
  }
42
42
 
43
43
  /** Aggregates OHLCV correctly: first open, max high, min low, last close, summed volume. */
44
- export function aggregatePriceHistory(
44
+ function aggregatePriceHistory(
45
45
  points: readonly PricePoint[],
46
46
  period: SeriesPeriod,
47
47
  ): PricePoint[] {
@@ -0,0 +1,9 @@
1
+ export type TimeRange = "1D" | "1W" | "1M" | "3M" | "6M" | "1Y" | "5Y" | "ALL";
2
+ export type ChartResolution = "auto" | "1m" | "5m" | "15m" | "30m" | "45m" | "1h" | "1d" | "1wk" | "1mo";
3
+
4
+ export interface ChartDateWindow {
5
+ start: Date | null;
6
+ end: Date | null;
7
+ }
8
+
9
+ export const TIME_RANGES: TimeRange[] = ["1D", "1W", "1M", "3M", "6M", "1Y", "5Y", "ALL"];
@@ -1,5 +1,5 @@
1
- import type { ChartResolution, TimeRange } from "./types";
2
- import { TIME_RANGES } from "./types";
1
+ import type { ChartResolution, TimeRange } from "./range";
2
+ import { TIME_RANGES } from "./range";
3
3
  import { isDateWindowWithinTimeRange } from "./date-window";
4
4
 
5
5
  export type ManualChartResolution = Exclude<ChartResolution, "auto">;
@@ -71,10 +71,6 @@ export const CHART_RESOLUTION_STEP_MS: Record<ManualChartResolution, number> = {
71
71
  "1mo": 30 * 24 * 60 * 60_000,
72
72
  };
73
73
 
74
- export function getChartResolutionStepMs(resolution: ChartResolution): number | null {
75
- return resolution === "auto" ? null : CHART_RESOLUTION_STEP_MS[resolution];
76
- }
77
-
78
74
  const CHART_RESOLUTION_POINTS_PER_DAY: Record<ManualChartResolution, number> = {
79
75
  "1m": 390,
80
76
  "5m": 78,
@@ -88,22 +84,6 @@ const CHART_RESOLUTION_POINTS_PER_DAY: Record<ManualChartResolution, number> = {
88
84
  };
89
85
 
90
86
  const TIME_RANGE_INDEX = new Map(TIME_RANGE_ORDER.map((range, index) => [range, index]));
91
- const RANGE_PRESETS_BY_RESOLUTION = TIME_RANGE_ORDER.reduce<Record<ManualChartResolution, TimeRange[]>>((acc, range) => {
92
- const resolution = RANGE_PRESET_RESOLUTION[range];
93
- acc[resolution] ??= [];
94
- acc[resolution].push(range);
95
- return acc;
96
- }, {
97
- "1m": [],
98
- "5m": [],
99
- "15m": [],
100
- "30m": [],
101
- "45m": [],
102
- "1h": [],
103
- "1d": [],
104
- "1wk": [],
105
- "1mo": [],
106
- });
107
87
 
108
88
  function getTimeRangeIndex(range: TimeRange): number {
109
89
  return TIME_RANGE_INDEX.get(range) ?? 0;
@@ -133,14 +113,6 @@ export function getPresetResolution(range: TimeRange): ManualChartResolution {
133
113
  return RANGE_PRESET_RESOLUTION[range];
134
114
  }
135
115
 
136
- function getManualResolutionOrderIndex(resolution: ManualChartResolution): number {
137
- return CHART_RESOLUTION_ORDER.indexOf(resolution);
138
- }
139
-
140
- function getPresetBufferRange(range: TimeRange): TimeRange {
141
- return RANGE_PRELOAD_BUFFER[range];
142
- }
143
-
144
116
  export function getNextBufferRange(range: TimeRange): TimeRange {
145
117
  return RANGE_PRELOAD_BUFFER[range];
146
118
  }
@@ -201,12 +173,6 @@ export function intersectChartResolutionSupport(
201
173
  }));
202
174
  }
203
175
 
204
- export function buildChartResolutionSupportMap(
205
- support: readonly ChartResolutionSupport[],
206
- ): ReadonlyMap<ManualChartResolution, TimeRange> {
207
- return new Map(normalizeChartResolutionSupport(support).map((entry) => [entry.resolution, entry.maxRange] as const));
208
- }
209
-
210
176
  export function getSupportMaxRange(
211
177
  support: readonly ChartResolutionSupport[] | ReadonlyMap<ManualChartResolution, TimeRange>,
212
178
  resolution: ManualChartResolution,
@@ -221,11 +187,7 @@ function minTimeRange(left: TimeRange, right: TimeRange): TimeRange {
221
187
  return compareTimeRange(left, right) <= 0 ? left : right;
222
188
  }
223
189
 
224
- export function maxTimeRange(left: TimeRange, right: TimeRange): TimeRange {
225
- return compareTimeRange(left, right) >= 0 ? left : right;
226
- }
227
-
228
- export function isTimeRangeAtOrBelow(candidate: TimeRange, maxRange: TimeRange): boolean {
190
+ function isTimeRangeAtOrBelow(candidate: TimeRange, maxRange: TimeRange): boolean {
229
191
  return compareTimeRange(candidate, maxRange) <= 0;
230
192
  }
231
193
 
@@ -255,43 +217,6 @@ export function isRangePresetSupported(
255
217
  return maxRange !== null && isTimeRangeAtOrBelow(range, maxRange);
256
218
  }
257
219
 
258
- export function getWidestPresetForResolution(
259
- resolution: ManualChartResolution,
260
- maxRange: TimeRange,
261
- ): TimeRange {
262
- const presets = RANGE_PRESETS_BY_RESOLUTION[resolution] ?? [];
263
- const supported = presets.filter((range) => isTimeRangeAtOrBelow(range, maxRange));
264
- if (supported.length > 0) {
265
- return supported[supported.length - 1]!;
266
- }
267
- const fallback = TIME_RANGE_ORDER.filter((range) => isTimeRangeAtOrBelow(range, maxRange));
268
- return fallback[fallback.length - 1] ?? "1D";
269
- }
270
-
271
- export function getBestSupportedResolutionForPreset(
272
- range: TimeRange,
273
- support: readonly ChartResolutionSupport[] | ReadonlyMap<ManualChartResolution, TimeRange>,
274
- preferredResolution = getPresetResolution(range),
275
- ): ManualChartResolution | null {
276
- const supportedResolutions = CHART_RESOLUTION_ORDER
277
- .filter((resolution): resolution is ManualChartResolution => resolution !== "auto")
278
- .filter((resolution) => {
279
- const maxRange = getSupportMaxRange(support, resolution);
280
- return maxRange !== null && isTimeRangeAtOrBelow(range, maxRange);
281
- });
282
-
283
- if (supportedResolutions.length === 0) return null;
284
- if (supportedResolutions.includes(preferredResolution)) return preferredResolution;
285
-
286
- const preferredIndex = getManualResolutionOrderIndex(preferredResolution);
287
- const coarserResolution = supportedResolutions.find((resolution) => (
288
- getManualResolutionOrderIndex(resolution) > preferredIndex
289
- ));
290
- if (coarserResolution) return coarserResolution;
291
-
292
- return supportedResolutions[supportedResolutions.length - 1] ?? null;
293
- }
294
-
295
220
  export function getBestSupportedResolutionForVisibleWindow(
296
221
  window: { start: Date | null; end: Date | null } | null,
297
222
  support: readonly ChartResolutionSupport[] | ReadonlyMap<ManualChartResolution, TimeRange>,
@@ -322,27 +247,3 @@ export function getBestSupportedResolutionForVisibleWindow(
322
247
 
323
248
  return supportedResolutions[0] ?? null;
324
249
  }
325
-
326
- export function getNextFallbackResolution(
327
- range: TimeRange,
328
- currentResolution: ManualChartResolution,
329
- support: readonly ChartResolutionSupport[] | ReadonlyMap<ManualChartResolution, TimeRange>,
330
- ): ChartResolution | null {
331
- const currentIndex = getManualResolutionOrderIndex(currentResolution);
332
- const nextResolution = CHART_RESOLUTION_ORDER
333
- .filter((resolution): resolution is ManualChartResolution => resolution !== "auto")
334
- .find((resolution) => (
335
- getManualResolutionOrderIndex(resolution) > currentIndex
336
- && (() => {
337
- const maxRange = getSupportMaxRange(support, resolution);
338
- return maxRange !== null && isTimeRangeAtOrBelow(range, maxRange);
339
- })()
340
- ));
341
-
342
- return nextResolution ?? "auto";
343
- }
344
-
345
- export function getCompatibleBufferRange(presetRange: TimeRange, maxRange: TimeRange | null): TimeRange {
346
- const preloadRange = getPresetBufferRange(presetRange);
347
- return maxRange ? clampTimeRangeToMaxRange(preloadRange, maxRange) : preloadRange;
348
- }
@@ -111,6 +111,145 @@ describe("resolveChartSpecData", () => {
111
111
  ]);
112
112
  });
113
113
 
114
+ test("keeps the snapshot exchange when a newer quote omits it", async () => {
115
+ const historyExchanges: string[] = [];
116
+ const source = {
117
+ kind: "security" as const,
118
+ instrument: { symbol: "SNDK" },
119
+ fieldId: "market.ohlcv",
120
+ };
121
+ const provider = createTestDataProvider({
122
+ getTickerFinancials: async () => ({
123
+ ...emptyFinancials(),
124
+ quote: {
125
+ symbol: "SNDK",
126
+ price: 1_240,
127
+ currency: "USD",
128
+ change: 20,
129
+ changePercent: 1.64,
130
+ lastUpdated: Date.parse("2026-07-30T15:25:00Z"),
131
+ listingExchangeName: "XNAS",
132
+ },
133
+ }),
134
+ getPriceHistoryForResolution: async (_symbol, exchange) => {
135
+ historyExchanges.push(exchange);
136
+ return [{
137
+ date: new Date("2026-07-30T15:20:00Z"),
138
+ open: 1_235,
139
+ high: 1_242,
140
+ low: 1_233,
141
+ close: 1_240,
142
+ }];
143
+ },
144
+ });
145
+ const spec: ChartSpec = {
146
+ version: CHART_SPEC_VERSION,
147
+ viewport: { range: "1D", resolution: "5m" },
148
+ panels: [{ id: "main" }],
149
+ series: [{
150
+ id: "price",
151
+ source,
152
+ style: "candles",
153
+ transform: "raw",
154
+ axis: "left",
155
+ panelId: "main",
156
+ interpolation: "none",
157
+ }],
158
+ studies: [],
159
+ };
160
+
161
+ const result = await resolveChartSpecData(spec, {
162
+ dataProvider: provider,
163
+ now: new Date("2026-07-30T15:30:00Z"),
164
+ quoteOverrides: new Map([[
165
+ chartQuoteOverrideKeyForSource(source),
166
+ {
167
+ symbol: "SNDK",
168
+ price: 1_245,
169
+ currency: "USD",
170
+ change: 25,
171
+ changePercent: 2.05,
172
+ lastUpdated: Date.parse("2026-07-30T15:26:00Z"),
173
+ },
174
+ ]]),
175
+ loadFredSeries: async () => fredLoad(),
176
+ });
177
+
178
+ expect(result.errors).toEqual([]);
179
+ expect(historyExchanges).toEqual(["XNAS"]);
180
+ expect(result.series[0]?.points.at(-1)?.date.toISOString())
181
+ .toBe("2026-07-30T15:26:00.000Z");
182
+ });
183
+
184
+ test("resolves the exchange before selecting an adaptive Auto resolution", async () => {
185
+ const supportExchanges: string[] = [];
186
+ const historyRequests: Array<{ exchange: string; resolution: string }> = [];
187
+ const provider = createTestDataProvider({
188
+ getTickerFinancials: async () => ({
189
+ ...emptyFinancials(),
190
+ quote: {
191
+ symbol: "SNDK",
192
+ price: 1_240,
193
+ currency: "USD",
194
+ change: 20,
195
+ changePercent: 1.64,
196
+ lastUpdated: Date.parse("2026-07-30T15:25:00Z"),
197
+ listingExchangeName: "XNAS",
198
+ },
199
+ }),
200
+ getChartResolutionSupport: (_symbol, exchange) => {
201
+ supportExchanges.push(exchange);
202
+ return exchange === "XNAS"
203
+ ? [{ resolution: "5m", maxRange: "1W" }]
204
+ : [{ resolution: "1d", maxRange: "5Y" }];
205
+ },
206
+ getDetailedPriceHistory: async (_symbol, exchange, _start, _end, resolution) => {
207
+ historyRequests.push({ exchange, resolution });
208
+ return [{ date: new Date("2026-07-30T15:20:00Z"), close: 1_240 }];
209
+ },
210
+ });
211
+ const spec: ChartSpec = {
212
+ version: CHART_SPEC_VERSION,
213
+ viewport: { range: "1D", resolution: "auto" },
214
+ panels: [{ id: "main" }],
215
+ series: [{
216
+ id: "price",
217
+ source: {
218
+ kind: "security",
219
+ instrument: { symbol: "SNDK" },
220
+ fieldId: "market.close",
221
+ },
222
+ style: "line",
223
+ transform: "raw",
224
+ axis: "left",
225
+ panelId: "main",
226
+ interpolation: "none",
227
+ }],
228
+ studies: [],
229
+ };
230
+
231
+ const result = await resolveChartSpecData(
232
+ spec,
233
+ {
234
+ dataProvider: provider,
235
+ now: new Date("2026-07-30T15:30:00Z"),
236
+ loadFredSeries: async () => fredLoad(),
237
+ },
238
+ new ChartResolveCache(),
239
+ {
240
+ autoViewport: {
241
+ start: new Date("2026-07-30T09:30:00Z"),
242
+ end: new Date("2026-07-30T15:30:00Z"),
243
+ },
244
+ targetPointCount: 100,
245
+ },
246
+ );
247
+
248
+ expect(result.errors).toEqual([]);
249
+ expect(supportExchanges).toEqual(["XNAS"]);
250
+ expect(historyRequests).toEqual([{ exchange: "XNAS", resolution: "5m" }]);
251
+ });
252
+
114
253
  test("uses provider-default history as a valid fallback for Auto resolution", async () => {
115
254
  let genericHistoryCalls = 0;
116
255
  const provider = createTestDataProvider({
@@ -186,7 +325,7 @@ describe("resolveChartSpecData", () => {
186
325
  expect(requests).toEqual([{ range: "1W", resolution: "5m" }]);
187
326
  });
188
327
 
189
- test("adapts Auto to a zoomed historical window without changing the authored viewport", async () => {
328
+ test("keeps Auto zoom selection while loading a separately panned history window", async () => {
190
329
  const detailedRequests: Array<{
191
330
  start: string;
192
331
  end: string;
@@ -238,6 +377,10 @@ describe("resolveChartSpecData", () => {
238
377
  new ChartResolveCache(),
239
378
  {
240
379
  autoViewport: {
380
+ start: new Date("2025-03-10T00:00:00.000Z"),
381
+ end: new Date("2025-03-17T00:00:00.000Z"),
382
+ },
383
+ requestViewport: {
241
384
  start: new Date("2025-01-10T00:00:00.000Z"),
242
385
  end: new Date("2025-01-17T00:00:00.000Z"),
243
386
  },
@@ -258,6 +401,64 @@ describe("resolveChartSpecData", () => {
258
401
  });
259
402
  });
260
403
 
404
+ test("keeps percent transforms and studies defined in a panned history window", async () => {
405
+ const provider = createTestDataProvider({
406
+ getTickerFinancials: async () => emptyFinancials(),
407
+ getDetailedPriceHistory: async () => [
408
+ { date: new Date("2024-10-15T16:00:00.000Z"), close: 100 },
409
+ { date: new Date("2024-10-16T16:00:00.000Z"), close: 110 },
410
+ ],
411
+ });
412
+ const spec: ChartSpec = {
413
+ version: CHART_SPEC_VERSION,
414
+ viewport: { range: "3M", resolution: "1d" },
415
+ panels: [{ id: "main" }],
416
+ series: [{
417
+ id: "price",
418
+ source: {
419
+ kind: "security",
420
+ instrument: { symbol: "TEST", exchange: "NASDAQ" },
421
+ fieldId: "market.close",
422
+ },
423
+ style: "line",
424
+ transform: "percent",
425
+ axis: "left",
426
+ panelId: "main",
427
+ interpolation: "none",
428
+ }],
429
+ studies: [{
430
+ id: "sma",
431
+ kind: "sma",
432
+ inputSeriesIds: ["price"],
433
+ parameters: { period: 2 },
434
+ panelId: "main",
435
+ axis: "left",
436
+ }],
437
+ };
438
+
439
+ const result = await resolveChartSpecData(
440
+ spec,
441
+ {
442
+ dataProvider: provider,
443
+ now: new Date("2025-04-01T00:00:00.000Z"),
444
+ loadFredSeries: async () => fredLoad(),
445
+ },
446
+ new ChartResolveCache(),
447
+ {
448
+ requestViewport: {
449
+ start: new Date("2024-10-10T00:00:00.000Z"),
450
+ end: new Date("2024-10-17T00:00:00.000Z"),
451
+ },
452
+ },
453
+ );
454
+
455
+ expect(result.errors).toEqual([]);
456
+ expect(result.bufferedSeries?.find((series) => series.id === "price")?.points.map((point) => point.value))
457
+ .toEqual([0, 10]);
458
+ expect(result.bufferedSeries?.find((series) => series.id === "sma")?.points.map((point) => point.value))
459
+ .toEqual([5]);
460
+ });
461
+
261
462
  test("resolves unrelated price, filed fundamental, and economic series on one chart", async () => {
262
463
  const provider = createTestDataProvider({
263
464
  getTickerFinancials: async (symbol) => symbol === "MSFT"
@@ -747,6 +948,7 @@ describe("resolveChartSpecData", () => {
747
948
  expect(result.series[0]?.points.map((point) => point.value)).toEqual([15, 25]);
748
949
  expect(result.legendSeries?.find((series) => series.id === "price")?.timeBasis)
749
950
  .toMatchObject({ kind: "market", timeZone: "America/New_York" });
951
+ expect(result.timelineSeries?.map((series) => series.id)).toEqual(["price"]);
750
952
  expect(result.series[0]?.timeBasis)
751
953
  .toMatchObject({ kind: "market", timeZone: "America/New_York" });
752
954
  });