gloomberb 0.11.3 → 0.13.1

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 (574) hide show
  1. package/README.md +8 -4
  2. package/README.zh-CN.md +4 -2
  3. package/package.json +28 -2
  4. package/src/api-client/auth.ts +12 -1
  5. package/src/api-client/data.ts +103 -1
  6. package/src/api-client/index.test.ts +127 -0
  7. package/src/api-client/index.ts +139 -3
  8. package/src/api-client/normalizers.ts +39 -0
  9. package/src/api-client/paths.ts +85 -1
  10. package/src/api-client/research-activity.ts +61 -0
  11. package/src/api-client/types.ts +197 -0
  12. package/src/app/pane-runtime/index.tsx +15 -12
  13. package/src/app/pane-runtime/layout-placement.test.ts +27 -0
  14. package/src/app/pane-runtime/layout-placement.ts +13 -0
  15. package/src/app/pane-runtime/plugin-bindings.ts +62 -0
  16. package/src/app.tsx +60 -18
  17. package/src/architecture/browser-entry-graph.test.ts +96 -0
  18. package/src/brokers/profile-form.test.ts +25 -27
  19. package/src/brokers/test-broker.ts +68 -0
  20. package/src/cli/commands/plugins.ts +43 -14
  21. package/src/cli/desktop-pane-shot.ts +362 -24
  22. package/src/cli/index.ts +1 -1
  23. package/src/cli/pane-functions/capabilities.ts +126 -43
  24. package/src/cli/pane-functions/catalog.ts +16 -4
  25. package/src/cli/pane-functions/discovery.ts +1 -0
  26. package/src/cli/pane-functions/dom.test.ts +41 -0
  27. package/src/cli/pane-functions/dom.ts +199 -0
  28. package/src/cli/pane-functions/headless.test.ts +247 -0
  29. package/src/cli/pane-functions/headless.ts +423 -0
  30. package/src/cli/pane-functions/index.test.ts +20 -0
  31. package/src/cli/pane-functions/index.ts +10 -6
  32. package/src/cli/pane-functions/intraday-shot.test.ts +307 -0
  33. package/src/cli/pane-functions/intraday-shot.ts +334 -0
  34. package/src/cli/pane-functions/options.ts +27 -2
  35. package/src/cli/pane-functions/report.test.ts +29 -1
  36. package/src/cli/pane-functions/report.ts +69 -4
  37. package/src/cli/pane-functions/resolver.ts +26 -8
  38. package/src/cli/pane-functions/screenshot.test.ts +76 -0
  39. package/src/cli/pane-functions/screenshot.ts +428 -42
  40. package/src/cli/restore-plugins.ts +41 -0
  41. package/src/components/chart/{static/chart → composite}/axis-overlays.test.tsx +3 -3
  42. package/src/components/chart/{static/chart → composite}/axis-overlays.tsx +6 -11
  43. package/src/components/chart/{static/chart → composite}/bitmap.ts +17 -3
  44. package/src/components/chart/composite/composite-chart.test.tsx +136 -21
  45. package/src/components/chart/composite/composite-chart.tsx +378 -215
  46. package/src/components/chart/composite/format.ts +8 -2
  47. package/src/components/chart/composite/interactions.test.ts +68 -102
  48. package/src/components/chart/composite/interactions.ts +214 -264
  49. package/src/components/chart/composite/price-axis-labels.test.ts +21 -0
  50. package/src/components/chart/{price-axis-labels.tsx → composite/price-axis-labels.tsx} +9 -4
  51. package/src/components/chart/composite/rasterizer.ts +33 -0
  52. package/src/components/chart/composite/renderers.test.ts +51 -4
  53. package/src/components/chart/composite/scene.test.ts +128 -2
  54. package/src/components/chart/composite/scene.ts +149 -19
  55. package/src/components/chart/composite/text-renderer.ts +16 -2
  56. package/src/components/chart/composite/time-scale.ts +163 -62
  57. package/src/components/chart/composite/types.ts +51 -1
  58. package/src/components/chart/core/data.ts +9 -13
  59. package/src/components/chart/core/date-window.ts +7 -1
  60. package/src/components/chart/core/pointer.ts +3 -0
  61. package/src/components/chart/core/types.ts +0 -45
  62. package/src/components/chart/native/chart-rasterizer.ts +0 -1
  63. package/src/components/chart/native/kitty/index.test.ts +0 -188
  64. package/src/components/chart/native/raster/crosshair.ts +6 -5
  65. package/src/components/chart/native/raster/primitives.ts +0 -23
  66. package/src/components/chart/native/renderer-selection.ts +81 -31
  67. package/src/components/chart/static/{chart/surface.test.tsx → chart-surface.test.tsx} +28 -80
  68. package/src/components/chart/static/chart-surface.tsx +150 -0
  69. package/src/components/chart/static/index.ts +8 -0
  70. package/src/components/chart/static/multi-line-chart-surface.tsx +51 -166
  71. package/src/components/chart/static/scatter-chart-surface.test.ts +30 -0
  72. package/src/components/chart/static/scatter-chart-surface.tsx +135 -34
  73. package/src/components/chart/static/series.ts +49 -0
  74. package/src/components/command-bar/action-runtime.ts +1 -3
  75. package/src/components/command-bar/assist/model.test.ts +17 -2
  76. package/src/components/command-bar/assist/model.ts +40 -9
  77. package/src/components/command-bar/commands/direct/index.ts +1 -1
  78. package/src/components/command-bar/commands/direct/runtime.ts +1 -1
  79. package/src/components/command-bar/commands/registry.ts +0 -11
  80. package/src/components/command-bar/helpers.test.ts +2 -2
  81. package/src/components/command-bar/helpers.ts +2 -6
  82. package/src/components/command-bar/keyboard-shortcuts.ts +0 -11
  83. package/src/components/command-bar/list/badge.test.ts +54 -0
  84. package/src/components/command-bar/list/badge.ts +62 -0
  85. package/src/components/command-bar/list/model.test.ts +103 -0
  86. package/src/components/command-bar/list/model.ts +61 -4
  87. package/src/components/command-bar/list/view.test.tsx +113 -0
  88. package/src/components/command-bar/list/view.tsx +147 -131
  89. package/src/components/command-bar/pane-templates/workflow.ts +1 -1
  90. package/src/components/command-bar/panel/body.tsx +51 -65
  91. package/src/components/command-bar/panel/index.tsx +48 -13
  92. package/src/components/command-bar/panel/layout.test.ts +139 -0
  93. package/src/components/command-bar/panel/layout.ts +65 -28
  94. package/src/components/command-bar/panel/native-surface.ts +24 -0
  95. package/src/components/command-bar/panel/prompt-binding.ts +54 -0
  96. package/src/components/command-bar/panel/runtime.ts +6 -3
  97. package/src/components/command-bar/panel/state.ts +41 -7
  98. package/src/components/command-bar/panel/types.ts +7 -1
  99. package/src/components/command-bar/routes/root/provider-search.ts +34 -15
  100. package/src/components/command-bar/routes/root/results.test.ts +161 -0
  101. package/src/components/command-bar/routes/root/results.ts +30 -28
  102. package/src/components/command-bar/routes/root/runtime.ts +37 -20
  103. package/src/components/command-bar/routes/root/search-providers.test.tsx +224 -0
  104. package/src/components/command-bar/routes/root/search-providers.ts +205 -0
  105. package/src/components/command-bar/routes/root/selection.ts +1 -12
  106. package/src/components/command-bar/routes/root/shortcuts.test.ts +8 -8
  107. package/src/components/command-bar/routes/ticker-search/actions.ts +10 -9
  108. package/src/components/command-bar/routes/ticker-search/results.test.ts +54 -1
  109. package/src/components/command-bar/routes/ticker-search/results.ts +60 -19
  110. package/src/components/command-bar/routes/ticker-search/route.ts +1 -0
  111. package/src/components/command-bar/routing/actions.ts +1 -81
  112. package/src/components/command-bar/routing/launch-request.ts +1 -1
  113. package/src/components/command-bar/routing/list-state.ts +6 -21
  114. package/src/components/command-bar/selection-runtime.ts +1 -1
  115. package/src/components/command-bar/surface/assist.test.tsx +34 -19
  116. package/src/components/command-bar/surface/environment.ts +8 -1
  117. package/src/components/command-bar/surface/index.test.tsx +84 -68
  118. package/src/components/command-bar/surface/index.tsx +33 -36
  119. package/src/components/command-bar/surface/test-harness.tsx +38 -7
  120. package/src/components/command-bar/theme-picker.tsx +43 -15
  121. package/src/components/command-bar/view-model.test.ts +53 -8
  122. package/src/components/command-bar/view-model.ts +43 -14
  123. package/src/components/command-bar/workflow/tickers.ts +1 -0
  124. package/src/components/command-bar/workflow/types.ts +1 -1
  125. package/src/components/data-table/view.test.tsx +45 -6
  126. package/src/components/data-table/view.tsx +55 -9
  127. package/src/components/index.ts +10 -2
  128. package/src/components/input-search-bar.test.tsx +36 -2
  129. package/src/components/input-search-bar.tsx +18 -1
  130. package/src/components/layout/header.test.tsx +57 -10
  131. package/src/components/layout/header.tsx +292 -149
  132. package/src/components/layout/market-summary.test.ts +81 -0
  133. package/src/components/layout/market-summary.ts +109 -0
  134. package/src/components/layout/shell/chrome.ts +96 -1
  135. package/src/components/layout/shell/index.tsx +11 -3
  136. package/src/components/layout/shell/menu.ts +9 -0
  137. package/src/components/layout/shell/pane/actions.ts +29 -0
  138. package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
  139. package/src/components/layout/shell/shortcuts.ts +3 -0
  140. package/src/components/layout/status-bar.test.tsx +9 -8
  141. package/src/components/layout/status-bar.tsx +85 -54
  142. package/src/components/onboarding/account-step/chooser-panel.tsx +2 -2
  143. package/src/components/onboarding/account-step/qr-panel.tsx +2 -10
  144. package/src/components/onboarding/onboarding-frame.tsx +2 -26
  145. package/src/components/onboarding/onboarding-wizard.test.tsx +38 -12
  146. package/src/components/onboarding/onboarding-wizard.tsx +49 -14
  147. package/src/components/price-sparkline/index.ts +61 -34
  148. package/src/components/price-sparkline/view.tsx +1 -1
  149. package/src/components/sign-in-gate.test.tsx +128 -0
  150. package/src/components/sign-in-gate.tsx +149 -0
  151. package/src/components/speedometer-gauge/desktop.tsx +2 -12
  152. package/src/components/ticker/badge/list.tsx +17 -3
  153. package/src/components/ticker/list-table-view.tsx +6 -3
  154. package/src/components/ui/data-table/index.tsx +3 -2
  155. package/src/components/ui/data-table/opentui/index.tsx +165 -120
  156. package/src/components/ui/frame.tsx +28 -0
  157. package/src/components/ui/index.ts +3 -0
  158. package/src/components/ui/native-select.tsx +20 -10
  159. package/src/components/ui/select-button.tsx +138 -0
  160. package/src/components/ui/status.tsx +2 -2
  161. package/src/components/ui/table-layout.ts +1 -1
  162. package/src/components/use-double-click-activation.ts +7 -3
  163. package/src/core/app-services.ts +3 -3
  164. package/src/data/config/layout.ts +12 -6
  165. package/src/data/config/store/index.test.ts +120 -0
  166. package/src/data/config/store/migrations.ts +41 -0
  167. package/src/data/config/store/node.ts +7 -1
  168. package/src/data/config/store/normalize.ts +9 -3
  169. package/src/i18n/es.ts +6 -2
  170. package/src/i18n/ja.ts +6 -2
  171. package/src/i18n/ko.ts +6 -2
  172. package/src/i18n/zh-cn.ts +6 -2
  173. package/src/i18n/zh-tw.ts +6 -2
  174. package/src/layout-marketplace/pane-imagery.ts +1 -0
  175. package/src/layout-marketplace/payload.test.ts +17 -0
  176. package/src/layout-marketplace/payload.ts +40 -5
  177. package/src/market-data/coordinator/index.ts +3 -2
  178. package/src/market-data/coordinator/quotes.ts +173 -66
  179. package/src/market-data/coordinator/subscriptions.test.ts +215 -37
  180. package/src/pane-settings.ts +1 -1
  181. package/src/plugins/builtin/analytics/pane-model.ts +1 -1
  182. package/src/plugins/builtin/analytics/view.tsx +1 -1
  183. package/src/plugins/builtin/broker-manager/index.test.tsx +3 -2
  184. package/src/plugins/builtin/broker-manager/model.test.ts +2 -2
  185. package/src/plugins/builtin/cds/client.test.ts +20 -0
  186. package/src/plugins/builtin/cds/client.ts +13 -3
  187. package/src/plugins/builtin/cds/headless.test.ts +30 -0
  188. package/src/plugins/builtin/cds/headless.ts +113 -0
  189. package/src/plugins/builtin/cds/index.tsx +4 -0
  190. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +16 -2
  191. package/src/plugins/builtin/chart-composer/cli-options.ts +4 -1
  192. package/src/plugins/builtin/chart-composer/index.tsx +1 -1
  193. package/src/plugins/builtin/chart-composer/pane.tsx +22 -8
  194. package/src/plugins/builtin/chat/content/composer.tsx +8 -4
  195. package/src/plugins/builtin/cloud/askg/protocol.test.ts +189 -0
  196. package/src/plugins/builtin/cloud/askg/protocol.ts +274 -0
  197. package/src/plugins/builtin/cloud/auth-dialog.tsx +19 -227
  198. package/src/plugins/builtin/cloud/auth-form.tsx +277 -0
  199. package/src/plugins/builtin/cloud/device-signin-dialog.tsx +21 -8
  200. package/src/plugins/builtin/cloud/plugin.tsx +1 -0
  201. package/src/plugins/builtin/cloud/verification-panel.tsx +56 -0
  202. package/src/plugins/builtin/composite-plugins.ts +14 -1
  203. package/src/plugins/builtin/correlation/relationship/model.ts +1 -1
  204. package/src/plugins/builtin/correlation/relationship/pane.tsx +2 -6
  205. package/src/plugins/builtin/correlation/relationship/view-model.ts +2 -2
  206. package/src/plugins/builtin/credit-conditions/client.ts +1 -1
  207. package/src/plugins/builtin/credit-conditions/headless.test.ts +31 -0
  208. package/src/plugins/builtin/credit-conditions/headless.ts +55 -0
  209. package/src/plugins/builtin/credit-conditions/index.test.tsx +12 -6
  210. package/src/plugins/builtin/credit-conditions/index.tsx +6 -21
  211. package/src/plugins/builtin/dividend-yield/index.tsx +1 -0
  212. package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
  213. package/src/plugins/builtin/earnings-calls/data.ts +223 -0
  214. package/src/plugins/builtin/earnings-calls/format.ts +49 -0
  215. package/src/plugins/builtin/earnings-calls/index.tsx +69 -0
  216. package/src/plugins/builtin/earnings-calls/pane.tsx +760 -0
  217. package/src/plugins/builtin/earnings-calls/prose.test.ts +36 -0
  218. package/src/plugins/builtin/earnings-calls/prose.ts +59 -0
  219. package/src/plugins/builtin/earnings-calls/transcript-view.tsx +337 -0
  220. package/src/plugins/builtin/econ/detail-view.tsx +1 -2
  221. package/src/plugins/builtin/econ/fred-series-map.ts +16 -1
  222. package/src/plugins/builtin/econ/index.tsx +2 -1
  223. package/src/plugins/builtin/econ-statistics/cache.ts +4 -0
  224. package/src/plugins/builtin/econ-statistics/client.ts +92 -0
  225. package/src/plugins/builtin/econ-statistics/defs.ts +66 -0
  226. package/src/plugins/builtin/econ-statistics/detail.tsx +154 -0
  227. package/src/plugins/builtin/econ-statistics/headless.test.ts +54 -0
  228. package/src/plugins/builtin/econ-statistics/headless.ts +171 -0
  229. package/src/plugins/builtin/econ-statistics/index.tsx +54 -0
  230. package/src/plugins/builtin/econ-statistics/pane.tsx +356 -0
  231. package/src/plugins/builtin/econ-statistics/stats.ts +522 -0
  232. package/src/plugins/builtin/econ-statistics/transform.test.ts +86 -0
  233. package/src/plugins/builtin/econ-statistics/transform.ts +85 -0
  234. package/src/plugins/builtin/econ-statistics/trend.ts +57 -0
  235. package/src/plugins/builtin/econ-statistics/view.ts +90 -0
  236. package/src/plugins/builtin/fear-greed/charts.tsx +7 -18
  237. package/src/plugins/builtin/fear-greed/data.ts +6 -1
  238. package/src/plugins/builtin/fear-greed/headless.test.ts +53 -0
  239. package/src/plugins/builtin/fear-greed/headless.ts +84 -0
  240. package/src/plugins/builtin/fear-greed/index.tsx +4 -0
  241. package/src/plugins/builtin/help/index.tsx +4 -0
  242. package/src/plugins/builtin/holders/index.tsx +1 -0
  243. package/src/plugins/builtin/insider/index.tsx +1 -0
  244. package/src/plugins/builtin/kelly-sizer/pane.tsx +1 -1
  245. package/src/plugins/builtin/kelly-sizer/sections.tsx +2 -3
  246. package/src/plugins/builtin/market-movers/client.ts +76 -0
  247. package/src/plugins/builtin/market-movers/headless.test.ts +34 -0
  248. package/src/plugins/builtin/market-movers/headless.ts +73 -0
  249. package/src/plugins/builtin/market-movers/index.tsx +11 -51
  250. package/src/plugins/builtin/market-valuation/align.ts +122 -0
  251. package/src/plugins/builtin/market-valuation/cache.ts +32 -0
  252. package/src/plugins/builtin/market-valuation/chart-projection.ts +217 -0
  253. package/src/plugins/builtin/market-valuation/chart-series.ts +89 -0
  254. package/src/plugins/builtin/market-valuation/client.test.ts +141 -0
  255. package/src/plugins/builtin/market-valuation/client.ts +116 -0
  256. package/src/plugins/builtin/market-valuation/defs.ts +251 -0
  257. package/src/plugins/builtin/market-valuation/detail.tsx +159 -0
  258. package/src/plugins/builtin/market-valuation/headless.test.ts +58 -0
  259. package/src/plugins/builtin/market-valuation/headless.ts +170 -0
  260. package/src/plugins/builtin/market-valuation/index.tsx +64 -0
  261. package/src/plugins/builtin/market-valuation/indicators.ts +422 -0
  262. package/src/plugins/builtin/market-valuation/model.test.ts +350 -0
  263. package/src/plugins/builtin/market-valuation/model.ts +8 -0
  264. package/src/plugins/builtin/market-valuation/pane.test.tsx +247 -0
  265. package/src/plugins/builtin/market-valuation/pane.tsx +344 -0
  266. package/src/plugins/builtin/market-valuation/series.ts +12 -0
  267. package/src/plugins/builtin/market-valuation/settings.ts +38 -0
  268. package/src/plugins/builtin/market-valuation/sources.ts +130 -0
  269. package/src/plugins/builtin/market-valuation/trend.ts +89 -0
  270. package/src/plugins/builtin/market-valuation/view.ts +137 -0
  271. package/src/plugins/builtin/market-valuation/zone-scale/desktop.tsx +123 -0
  272. package/src/plugins/builtin/market-valuation/zone-scale/index.tsx +12 -0
  273. package/src/plugins/builtin/market-valuation/zone-scale/model.ts +60 -0
  274. package/src/plugins/builtin/market-valuation/zone-scale/terminal.tsx +94 -0
  275. package/src/plugins/builtin/news/wire/breaking/filters.test.ts +99 -0
  276. package/src/plugins/builtin/news/wire/breaking/filters.ts +110 -0
  277. package/src/plugins/builtin/news/wire/breaking/notifications.test.ts +157 -53
  278. package/src/plugins/builtin/news/wire/breaking/notifications.ts +91 -25
  279. package/src/plugins/builtin/news/wire/index.tsx +31 -44
  280. package/src/plugins/builtin/options/index.tsx +1 -0
  281. package/src/plugins/builtin/options/view.test.tsx +29 -1
  282. package/src/plugins/builtin/options/view.tsx +1 -22
  283. package/src/plugins/builtin/plugin-marketplace/feed.ts +80 -0
  284. package/src/plugins/builtin/plugin-marketplace/index.tsx +31 -0
  285. package/src/plugins/builtin/plugin-marketplace/model.test.ts +200 -0
  286. package/src/plugins/builtin/plugin-marketplace/model.ts +204 -0
  287. package/src/plugins/builtin/plugin-marketplace/pane.tsx +385 -0
  288. package/src/plugins/builtin/plugin-marketplace/store.ts +46 -0
  289. package/src/plugins/builtin/plugin-module.ts +9 -1
  290. package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -237
  291. package/src/plugins/builtin/research/event-model.ts +245 -0
  292. package/src/plugins/builtin/research/events-headless.test.ts +32 -0
  293. package/src/plugins/builtin/research/events-headless.ts +87 -0
  294. package/src/plugins/builtin/research/index.tsx +8 -0
  295. package/src/plugins/builtin/research-search/command-bar-search.test.ts +81 -0
  296. package/src/plugins/builtin/research-search/command-bar-search.ts +175 -0
  297. package/src/plugins/builtin/research-search/data.ts +109 -0
  298. package/src/plugins/builtin/research-search/document-view.test.tsx +75 -0
  299. package/src/plugins/builtin/research-search/document-view.tsx +254 -0
  300. package/src/plugins/builtin/research-search/filter-bar.tsx +62 -0
  301. package/src/plugins/builtin/research-search/focus-handoff.ts +47 -0
  302. package/src/plugins/builtin/research-search/index.tsx +85 -0
  303. package/src/plugins/builtin/research-search/model.test.ts +40 -0
  304. package/src/plugins/builtin/research-search/model.ts +291 -0
  305. package/src/plugins/builtin/research-search/pane.test.tsx +241 -0
  306. package/src/plugins/builtin/research-search/pane.tsx +682 -0
  307. package/src/plugins/builtin/research-search/saved-view.tsx +171 -0
  308. package/src/plugins/builtin/research-search/snippet-text.tsx +35 -0
  309. package/src/plugins/builtin/research-search/snippet.test.ts +78 -0
  310. package/src/plugins/builtin/research-search/snippet.ts +134 -0
  311. package/src/plugins/builtin/scanner/flow-model.test.ts +11 -1
  312. package/src/plugins/builtin/scanner/flow-model.ts +29 -1
  313. package/src/plugins/builtin/scanner/flow-pane.tsx +78 -22
  314. package/src/plugins/builtin/scanner/headless.test.ts +59 -0
  315. package/src/plugins/builtin/scanner/headless.ts +125 -0
  316. package/src/plugins/builtin/scanner/index.ts +4 -0
  317. package/src/plugins/builtin/sec/index.tsx +1 -0
  318. package/src/plugins/builtin/sectors/client.ts +54 -0
  319. package/src/plugins/builtin/sectors/headless.test.ts +28 -0
  320. package/src/plugins/builtin/sectors/headless.ts +80 -0
  321. package/src/plugins/builtin/sectors/index.tsx +6 -51
  322. package/src/plugins/builtin/shared/cloud-upgrade.ts +8 -4
  323. package/src/plugins/builtin/shared/series-cache.ts +93 -0
  324. package/src/plugins/builtin/short-interest/index.tsx +1 -0
  325. package/src/plugins/builtin/short-interest/pane.tsx +1 -1
  326. package/src/plugins/builtin/thirteenf/index.tsx +1 -0
  327. package/src/plugins/builtin/ticker-detail/index.tsx +0 -14
  328. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +1 -1
  329. package/src/plugins/builtin/ticker-detail/pane.tsx +14 -0
  330. package/src/plugins/builtin/treasury-auctions/headless.test.ts +34 -0
  331. package/src/plugins/builtin/treasury-auctions/headless.ts +95 -0
  332. package/src/plugins/builtin/treasury-auctions/index.tsx +4 -0
  333. package/src/plugins/builtin/volatility/client.ts +16 -3
  334. package/src/plugins/builtin/volatility/headless.test.ts +35 -0
  335. package/src/plugins/builtin/volatility/headless.ts +75 -0
  336. package/src/plugins/builtin/volatility/index.tsx +5 -1
  337. package/src/plugins/builtin/world-indices/client.ts +43 -0
  338. package/src/plugins/builtin/world-indices/headless.test.ts +32 -0
  339. package/src/plugins/builtin/world-indices/headless.ts +109 -0
  340. package/src/plugins/builtin/world-indices/index.tsx +4 -0
  341. package/src/plugins/builtin/world-venue-map/map.desktop.test.tsx +106 -0
  342. package/src/plugins/builtin/world-venue-map/map.tsx +213 -69
  343. package/src/plugins/builtin/world-venue-map/model.test.ts +32 -0
  344. package/src/plugins/builtin/world-venue-map/model.ts +134 -13
  345. package/src/plugins/builtin/yield-curve/headless.test.ts +24 -0
  346. package/src/plugins/builtin/yield-curve/headless.ts +67 -0
  347. package/src/plugins/builtin/yield-curve/index.tsx +7 -9
  348. package/src/plugins/builtin/yield-curve/treasury-data.ts +16 -6
  349. package/src/plugins/bundle.test.ts +166 -0
  350. package/src/plugins/bundle.ts +141 -0
  351. package/src/plugins/catalog-backend.test.ts +26 -0
  352. package/src/plugins/catalog-backend.ts +9 -2
  353. package/src/plugins/catalog-browser.test.ts +1 -13
  354. package/src/plugins/catalog-browser.ts +14 -0
  355. package/src/plugins/catalog-manifest.test.ts +58 -0
  356. package/src/plugins/catalog-manifest.ts +99 -0
  357. package/src/plugins/catalog-ui.ts +20 -10
  358. package/src/plugins/catalog.ts +9 -2
  359. package/src/plugins/current-target.ts +28 -0
  360. package/src/plugins/host-contract.ts +46 -0
  361. package/src/plugins/host-link.ts +158 -0
  362. package/src/plugins/host-modules.ts +73 -0
  363. package/src/plugins/loader.ts +90 -23
  364. package/src/plugins/ownership.ts +1 -0
  365. package/src/plugins/prediction-markets/index.tsx +1 -0
  366. package/src/plugins/registry/context.ts +3 -0
  367. package/src/plugins/registry/contributions.ts +27 -0
  368. package/src/plugins/registry/index.test.ts +25 -0
  369. package/src/plugins/registry/index.ts +8 -0
  370. package/src/plugins/registry/pane-settings.ts +6 -13
  371. package/src/plugins/seed.test.ts +83 -0
  372. package/src/plugins/seed.ts +83 -0
  373. package/src/public/broker.ts +37 -0
  374. package/src/public/market-data.ts +9 -0
  375. package/src/public/pane-hooks.ts +57 -0
  376. package/src/public/public-api.test.ts +285 -0
  377. package/src/public/react.ts +62 -0
  378. package/src/public/test-support.ts +23 -0
  379. package/src/public/time-series.ts +8 -0
  380. package/src/public/utils.ts +77 -0
  381. package/src/remote/layout-helpers.ts +6 -8
  382. package/src/remote/resources.ts +2 -0
  383. package/src/remote/schema.test.ts +65 -0
  384. package/src/remote/schema.ts +323 -39
  385. package/src/remote/semantic-tree.test.tsx +105 -0
  386. package/src/remote/semantic-tree.tsx +53 -35
  387. package/src/remote/types.ts +49 -2
  388. package/src/renderers/browser/app-services.ts +3 -0
  389. package/src/renderers/browser/config-host.ts +24 -8
  390. package/src/renderers/browser/deeplink-bridge.test.ts +11 -0
  391. package/src/renderers/browser/deeplink-bridge.ts +15 -3
  392. package/src/renderers/browser/main.tsx +8 -0
  393. package/src/renderers/browser/research-entry.test.ts +8 -0
  394. package/src/renderers/browser/research-entry.ts +7 -0
  395. package/src/renderers/cloudflare/worker.test.ts +19 -0
  396. package/src/renderers/cloudflare/worker.ts +1 -1
  397. package/src/renderers/electrobun/bun/desktop/backend-requests.ts +2 -2
  398. package/src/renderers/electrobun/bun/desktop/initialization.ts +10 -3
  399. package/src/renderers/electrobun/bun/external-plugins.ts +156 -0
  400. package/src/renderers/electrobun/bun/index.ts +5 -2
  401. package/src/renderers/electrobun/shared/protocol.ts +21 -0
  402. package/src/renderers/electrobun/view/build-assets.ts +0 -3
  403. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +114 -11
  404. package/src/renderers/electrobun/view/external-plugins.test.ts +45 -0
  405. package/src/renderers/electrobun/view/external-plugins.ts +63 -0
  406. package/src/renderers/electrobun/view/host/ascii-text.test.ts +48 -0
  407. package/src/renderers/electrobun/view/host/ascii-text.ts +11 -1
  408. package/src/renderers/electrobun/view/host/chart-surface.tsx +38 -32
  409. package/src/renderers/electrobun/view/host/mouse.ts +16 -2
  410. package/src/renderers/electrobun/view/main.tsx +27 -2
  411. package/src/renderers/electrobun/view/styles.css +5 -0
  412. package/src/renderers/electrobun/view/toast-host.tsx +18 -0
  413. package/src/renderers/opentui/chart-surface.tsx +5 -1
  414. package/src/renderers/opentui/host.tsx +16 -2
  415. package/src/renderers/opentui/interaction-performance.test.ts +79 -0
  416. package/src/renderers/opentui/interaction-performance.ts +188 -0
  417. package/src/renderers/opentui/start.tsx +26 -1
  418. package/src/renderers/opentui/test-utils.tsx +13 -0
  419. package/src/renderers/opentui/toast-host.tsx +26 -2
  420. package/src/renderers/opentui/ui-host.tsx +1 -0
  421. package/src/sources/provider-router/search.test.ts +191 -0
  422. package/src/sources/provider-router/search.ts +92 -39
  423. package/src/state/app/context/index.tsx +2 -0
  424. package/src/state/app/context/selector.test.tsx +33 -1
  425. package/src/state/hooks/quote-streaming.test.tsx +93 -0
  426. package/src/state/hooks/quote-streaming.ts +106 -30
  427. package/src/state/pane-table-export-registry.ts +19 -0
  428. package/src/sync/core-contributors.test.ts +111 -0
  429. package/src/sync/core-contributors.ts +155 -1
  430. package/src/theme/color-utils.ts +1 -1
  431. package/src/theme/colors.ts +50 -0
  432. package/src/theme/themes.test.ts +15 -2
  433. package/src/theme/themes.ts +19 -1
  434. package/src/tickers/search/index.test.ts +26 -0
  435. package/src/tickers/search/index.ts +42 -17
  436. package/src/tickers/search/ranking.ts +14 -0
  437. package/src/time-series/chart-data.test.ts +216 -0
  438. package/src/time-series/resolution.test.ts +25 -26
  439. package/src/time-series/resolution.ts +41 -10
  440. package/src/time-series/resolve.test.ts +118 -1
  441. package/src/time-series/resolve.ts +21 -16
  442. package/src/time-series/types.ts +3 -1
  443. package/src/time-series/use-chart-resolution.ts +5 -0
  444. package/src/types/config.ts +125 -3
  445. package/src/types/data-provider.ts +11 -0
  446. package/src/types/headless.ts +161 -0
  447. package/src/types/plugin.ts +102 -0
  448. package/src/ui/host.tsx +12 -1
  449. package/src/ui/index.tsx +1 -0
  450. package/src/ui/toast.tsx +8 -4
  451. package/src/utils/format.ts +35 -0
  452. package/src/utils/fuzzy-search.test.ts +94 -0
  453. package/src/utils/fuzzy-search.ts +81 -22
  454. package/src/utils/shortcut-labels.ts +14 -0
  455. package/src/components/chart/core/axis-format.ts +0 -157
  456. package/src/components/chart/core/index.test.ts +0 -1075
  457. package/src/components/chart/core/projection.ts +0 -357
  458. package/src/components/chart/core/render-utils.ts +0 -4
  459. package/src/components/chart/core/renderer.ts +0 -244
  460. package/src/components/chart/core/scene.ts +0 -180
  461. package/src/components/chart/core/time-axis.ts +0 -330
  462. package/src/components/chart/cursor-motion.ts +0 -47
  463. package/src/components/chart/indicators/types.ts +0 -26
  464. package/src/components/chart/market-session.ts +0 -176
  465. package/src/components/chart/multi-line/renderer.test.ts +0 -83
  466. package/src/components/chart/multi-line/renderer.ts +0 -161
  467. package/src/components/chart/multi-line/scene.ts +0 -188
  468. package/src/components/chart/native/raster/layout.ts +0 -129
  469. package/src/components/chart/native/raster/price-chart.ts +0 -244
  470. package/src/components/chart/rendering/chart-draw.ts +0 -270
  471. package/src/components/chart/rendering/geometry.ts +0 -95
  472. package/src/components/chart/rendering/pixel-buffer.ts +0 -267
  473. package/src/components/chart/scatter-chart-renderer.test.ts +0 -31
  474. package/src/components/chart/scatter-chart-renderer.ts +0 -223
  475. package/src/components/chart/static/chart/surface.tsx +0 -358
  476. package/src/components/command-bar/routes/root/article-results.test.ts +0 -83
  477. package/src/components/command-bar/routes/root/article-results.ts +0 -55
  478. package/src/plugins/broker-sync/broker-sync.test.ts +0 -188
  479. package/src/plugins/broker-sync/native-loader.ts +0 -10
  480. package/src/plugins/broker-sync/normalize.ts +0 -308
  481. package/src/plugins/broker-sync/public.ts +0 -122
  482. package/src/plugins/broker-sync/robinhood-native.ts +0 -367
  483. package/src/plugins/broker-sync/robinhood.ts +0 -114
  484. package/src/plugins/broker-sync/simplefin-native.ts +0 -201
  485. package/src/plugins/broker-sync/simplefin.ts +0 -70
  486. package/src/plugins/builtin/news/wire/article-search.test.ts +0 -72
  487. package/src/plugins/builtin/news/wire/article-search.ts +0 -140
  488. package/src/plugins/builtin/substack/api/auth.ts +0 -185
  489. package/src/plugins/builtin/substack/api/cache.ts +0 -82
  490. package/src/plugins/builtin/substack/api/loaders.ts +0 -170
  491. package/src/plugins/builtin/substack/api/store.ts +0 -186
  492. package/src/plugins/builtin/substack/api/types.ts +0 -42
  493. package/src/plugins/builtin/substack/api.test.ts +0 -298
  494. package/src/plugins/builtin/substack/article-detail.tsx +0 -397
  495. package/src/plugins/builtin/substack/article-stack.tsx +0 -133
  496. package/src/plugins/builtin/substack/content.ts +0 -478
  497. package/src/plugins/builtin/substack/feed-tabs.tsx +0 -44
  498. package/src/plugins/builtin/substack/index.tsx +0 -49
  499. package/src/plugins/builtin/substack/login-view.tsx +0 -126
  500. package/src/plugins/builtin/substack/model.test.ts +0 -155
  501. package/src/plugins/builtin/substack/normalize.ts +0 -343
  502. package/src/plugins/builtin/substack/pane-footer.ts +0 -52
  503. package/src/plugins/builtin/substack/pane-state.ts +0 -170
  504. package/src/plugins/builtin/substack/pane.tsx +0 -547
  505. package/src/plugins/builtin/substack/read-state.ts +0 -30
  506. package/src/plugins/builtin/substack/table.ts +0 -111
  507. package/src/plugins/builtin/substack/types.ts +0 -71
  508. package/src/plugins/builtin/substack/utils.ts +0 -131
  509. package/src/plugins/builtin/ticker-detail/data-panes/provider-search.tsx +0 -229
  510. package/src/plugins/ibkr/account-cache.test.ts +0 -102
  511. package/src/plugins/ibkr/account-cache.ts +0 -24
  512. package/src/plugins/ibkr/broker-adapter.ts +0 -286
  513. package/src/plugins/ibkr/config.test.ts +0 -79
  514. package/src/plugins/ibkr/config.ts +0 -250
  515. package/src/plugins/ibkr/dialogs.tsx +0 -40
  516. package/src/plugins/ibkr/flex/index.test.ts +0 -264
  517. package/src/plugins/ibkr/flex/index.ts +0 -367
  518. package/src/plugins/ibkr/flex/statement-client.ts +0 -235
  519. package/src/plugins/ibkr/fundamental-parser.ts +0 -149
  520. package/src/plugins/ibkr/gateway/account-loaders.ts +0 -266
  521. package/src/plugins/ibkr/gateway/account-summary.ts +0 -164
  522. package/src/plugins/ibkr/gateway/client-lock.ts +0 -154
  523. package/src/plugins/ibkr/gateway/connection-events.ts +0 -114
  524. package/src/plugins/ibkr/gateway/connection.ts +0 -152
  525. package/src/plugins/ibkr/gateway/contracts.ts +0 -132
  526. package/src/plugins/ibkr/gateway/fundamentals.ts +0 -83
  527. package/src/plugins/ibkr/gateway/helpers.ts +0 -33
  528. package/src/plugins/ibkr/gateway/history-requests.ts +0 -158
  529. package/src/plugins/ibkr/gateway/history.ts +0 -120
  530. package/src/plugins/ibkr/gateway/instrument-lookup.ts +0 -117
  531. package/src/plugins/ibkr/gateway/market-data.ts +0 -345
  532. package/src/plugins/ibkr/gateway/order-execution.ts +0 -218
  533. package/src/plugins/ibkr/gateway/orders.ts +0 -63
  534. package/src/plugins/ibkr/gateway/price-normalization.test.ts +0 -44
  535. package/src/plugins/ibkr/gateway/price-normalization.ts +0 -50
  536. package/src/plugins/ibkr/gateway/quote-stream-controller.ts +0 -203
  537. package/src/plugins/ibkr/gateway/quote-stream.ts +0 -171
  538. package/src/plugins/ibkr/gateway/selection.ts +0 -51
  539. package/src/plugins/ibkr/gateway/service/index.test.ts +0 -675
  540. package/src/plugins/ibkr/gateway/service/index.ts +0 -429
  541. package/src/plugins/ibkr/gateway/service/lifecycle.ts +0 -191
  542. package/src/plugins/ibkr/gateway/service/manager.ts +0 -52
  543. package/src/plugins/ibkr/gateway/service/market-data.ts +0 -48
  544. package/src/plugins/ibkr/gateway/service/native-loader.ts +0 -12
  545. package/src/plugins/ibkr/gateway/service/native.ts +0 -543
  546. package/src/plugins/ibkr/gateway/timeouts.ts +0 -13
  547. package/src/plugins/ibkr/gateway/types.ts +0 -27
  548. package/src/plugins/ibkr/index.test.tsx +0 -162
  549. package/src/plugins/ibkr/index.tsx +0 -159
  550. package/src/plugins/ibkr/instance-selection.test.ts +0 -120
  551. package/src/plugins/ibkr/instance-selection.ts +0 -36
  552. package/src/plugins/ibkr/portfolio-performance.test.ts +0 -32
  553. package/src/plugins/ibkr/portfolio-performance.ts +0 -129
  554. package/src/plugins/ibkr/trade/badge.tsx +0 -28
  555. package/src/plugins/ibkr/trade/dialogs.tsx +0 -44
  556. package/src/plugins/ibkr/trade/preview-panel.tsx +0 -130
  557. package/src/plugins/ibkr/trade/tab/actions.tsx +0 -443
  558. package/src/plugins/ibkr/trade/tab/field-editors.tsx +0 -79
  559. package/src/plugins/ibkr/trade/tab/footer.ts +0 -53
  560. package/src/plugins/ibkr/trade/tab/header.tsx +0 -167
  561. package/src/plugins/ibkr/trade/tab/index.test.tsx +0 -178
  562. package/src/plugins/ibkr/trade/tab/index.tsx +0 -298
  563. package/src/plugins/ibkr/trade/tab/model.ts +0 -159
  564. package/src/plugins/ibkr/trade/tab/order-request.ts +0 -64
  565. package/src/plugins/ibkr/trade/tab/shortcuts.ts +0 -89
  566. package/src/plugins/ibkr/trade/tab/state-sync.ts +0 -86
  567. package/src/plugins/ibkr/trade/ticket-panel.tsx +0 -301
  568. package/src/plugins/ibkr/trade/utils.test.ts +0 -175
  569. package/src/plugins/ibkr/trade/utils.ts +0 -144
  570. package/src/plugins/ibkr/trading/pane-view.tsx +0 -237
  571. package/src/plugins/ibkr/trading/pane.tsx +0 -332
  572. package/src/plugins/ibkr/trading/state.test.ts +0 -109
  573. package/src/plugins/ibkr/trading/state.ts +0 -335
  574. package/src/renderers/electrobun/view/native-stubs/broker-sync-native-loader.ts +0 -7
@@ -0,0 +1,216 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { appendLiveQuotePoint } from "./chart-data";
3
+ import type { PricePoint, Quote } from "../types/financials";
4
+
5
+ function quoteFixture(overrides: Partial<Quote> = {}): Quote {
6
+ return {
7
+ symbol: "INTC",
8
+ price: 129,
9
+ currency: "USD",
10
+ change: 0,
11
+ changePercent: 0,
12
+ lastUpdated: Date.parse("2026-05-15T20:30:00Z"),
13
+ listingExchangeName: "NASDAQ",
14
+ marketState: "REGULAR",
15
+ ...overrides,
16
+ };
17
+ }
18
+
19
+ describe("appendLiveQuotePoint", () => {
20
+ test("extends coarse chart histories with a fresh quote tail", () => {
21
+ const history: PricePoint[] = [
22
+ { date: new Date("2026-05-04T00:00:00Z"), close: 56 },
23
+ { date: new Date("2026-05-11T00:00:00Z"), close: 68 },
24
+ ];
25
+
26
+ const extended = appendLiveQuotePoint(
27
+ history,
28
+ quoteFixture(),
29
+ { now: Date.parse("2026-05-15T21:00:00Z") },
30
+ );
31
+
32
+ expect(extended).toHaveLength(3);
33
+ expect(extended.at(-1)).toEqual({
34
+ date: new Date("2026-05-15T20:30:00Z"),
35
+ close: 129,
36
+ });
37
+ });
38
+
39
+ test("merges a quote into the active OHLC bucket", () => {
40
+ const history: PricePoint[] = [
41
+ {
42
+ date: new Date("2026-05-15T20:25:00Z"),
43
+ open: 124,
44
+ high: 130,
45
+ low: 122,
46
+ close: 126,
47
+ volume: 1_000,
48
+ },
49
+ ];
50
+
51
+ const extended = appendLiveQuotePoint(
52
+ history,
53
+ quoteFixture({ lastUpdated: Date.parse("2026-05-15T20:29:00Z") }),
54
+ {
55
+ now: Date.parse("2026-05-15T20:30:00Z"),
56
+ mode: "ohlc",
57
+ resolution: "5m",
58
+ },
59
+ );
60
+
61
+ expect(extended).toHaveLength(1);
62
+ expect(extended[0]).toEqual({
63
+ date: new Date("2026-05-15T20:25:00Z"),
64
+ open: 124,
65
+ high: 130,
66
+ low: 122,
67
+ close: 129,
68
+ volume: 1_000,
69
+ });
70
+ });
71
+
72
+ test("seeds a new OHLC bucket from the live price instead of prior-bar extremes", () => {
73
+ const history: PricePoint[] = [
74
+ {
75
+ date: new Date("2026-05-15T20:25:00Z"),
76
+ open: 124,
77
+ high: 180,
78
+ low: 80,
79
+ close: 126,
80
+ volume: 1_000,
81
+ },
82
+ ];
83
+
84
+ const extended = appendLiveQuotePoint(
85
+ history,
86
+ quoteFixture({ lastUpdated: Date.parse("2026-05-15T20:30:00Z") }),
87
+ {
88
+ now: Date.parse("2026-05-15T20:31:00Z"),
89
+ mode: "ohlc",
90
+ resolution: "5m",
91
+ },
92
+ );
93
+
94
+ expect(extended).toHaveLength(2);
95
+ expect(extended[1]).toEqual({
96
+ date: new Date("2026-05-15T20:30:00Z"),
97
+ open: 129,
98
+ high: 129,
99
+ low: 129,
100
+ close: 129,
101
+ });
102
+ });
103
+
104
+ test("treats calendar-month bars as variable-length buckets", () => {
105
+ const history: PricePoint[] = [{
106
+ date: new Date("2026-01-01T00:00:00Z"),
107
+ open: 120,
108
+ high: 140,
109
+ low: 110,
110
+ close: 125,
111
+ }];
112
+
113
+ const endOfJanuary = appendLiveQuotePoint(
114
+ history,
115
+ quoteFixture({ lastUpdated: Date.parse("2026-01-31T20:30:00Z") }),
116
+ {
117
+ now: Date.parse("2026-01-31T20:31:00Z"),
118
+ mode: "ohlc",
119
+ resolution: "1mo",
120
+ },
121
+ );
122
+ const startOfFebruary = appendLiveQuotePoint(
123
+ history,
124
+ quoteFixture({ lastUpdated: Date.parse("2026-02-01T20:30:00Z") }),
125
+ {
126
+ now: Date.parse("2026-02-01T20:31:00Z"),
127
+ mode: "ohlc",
128
+ resolution: "1mo",
129
+ },
130
+ );
131
+
132
+ expect(endOfJanuary).toHaveLength(1);
133
+ expect(endOfJanuary[0]?.close).toBe(129);
134
+ expect(startOfFebruary).toHaveLength(2);
135
+ expect(startOfFebruary[1]).toMatchObject({
136
+ open: 129,
137
+ high: 129,
138
+ low: 129,
139
+ close: 129,
140
+ });
141
+ });
142
+
143
+ test("uses the active extended-hours price for the live tail", () => {
144
+ const history: PricePoint[] = [
145
+ { date: new Date("2026-05-15T19:30:00Z"), close: 128 },
146
+ ];
147
+
148
+ const extended = appendLiveQuotePoint(
149
+ history,
150
+ quoteFixture({
151
+ marketState: "POST",
152
+ postMarketPrice: 131,
153
+ lastUpdated: Date.parse("2026-05-15T21:10:00Z"),
154
+ }),
155
+ { now: Date.parse("2026-05-15T21:15:00Z") },
156
+ );
157
+
158
+ expect(extended.at(-1)?.close).toBe(131);
159
+ });
160
+
161
+ test("does not bridge a missing intraday history window with one synthetic candle", () => {
162
+ const history: PricePoint[] = [
163
+ { date: new Date("2026-07-22T14:49:00Z"), close: 347.73 },
164
+ { date: new Date("2026-07-22T14:50:00Z"), close: 347.76 },
165
+ { date: new Date("2026-07-22T14:51:00Z"), close: 347.68 },
166
+ ];
167
+
168
+ const extended = appendLiveQuotePoint(
169
+ history,
170
+ quoteFixture({
171
+ symbol: "GOOG",
172
+ price: 346.27,
173
+ lastUpdated: Date.parse("2026-07-22T18:52:00Z"),
174
+ }),
175
+ Date.parse("2026-07-22T18:53:00Z"),
176
+ );
177
+
178
+ expect(extended).toBe(history);
179
+ });
180
+
181
+ test("does not append a live quote tail with a likely unit mismatch", () => {
182
+ const history: PricePoint[] = [
183
+ { date: new Date("2026-05-18T00:00:00Z"), close: 405 },
184
+ { date: new Date("2026-05-22T00:00:00Z"), close: 379 },
185
+ ];
186
+
187
+ const extended = appendLiveQuotePoint(
188
+ history,
189
+ quoteFixture({
190
+ symbol: "FTC",
191
+ currency: "GBP",
192
+ price: 3.79,
193
+ lastUpdated: Date.parse("2026-05-22T16:39:00Z"),
194
+ }),
195
+ { now: Date.parse("2026-05-22T16:45:00Z") },
196
+ );
197
+
198
+ expect(extended).toBe(history);
199
+ });
200
+
201
+ test("does not append stale quotes from an older active session", () => {
202
+ const history: PricePoint[] = [
203
+ { date: new Date("2026-05-11T00:00:00Z"), close: 68 },
204
+ ];
205
+
206
+ const extended = appendLiveQuotePoint(
207
+ history,
208
+ quoteFixture({
209
+ lastUpdated: Date.parse("2026-05-08T20:00:00Z"),
210
+ }),
211
+ { now: Date.parse("2026-05-15T15:00:00Z") },
212
+ );
213
+
214
+ expect(extended).toBe(history);
215
+ });
216
+ });
@@ -44,7 +44,7 @@ describe("chart-resolution", () => {
44
44
  expect(sortChartResolutions(["1wk", "auto", "15m", "1d"])).toEqual(["auto", "15m", "1d", "1wk"]);
45
45
  });
46
46
 
47
- test("picks a denser supported resolution as the visible window narrows", () => {
47
+ test("picks the supported resolution whose bar count sits nearest the target", () => {
48
48
  const support = [
49
49
  { resolution: "1m", maxRange: "1D" },
50
50
  { resolution: "5m", maxRange: "1W" },
@@ -53,31 +53,30 @@ describe("chart-resolution", () => {
53
53
  { resolution: "1d", maxRange: "5Y" },
54
54
  { resolution: "1wk", maxRange: "ALL" },
55
55
  ] as const;
56
-
57
- expect(getBestSupportedResolutionForVisibleWindow({
58
- start: new Date("2021-01-01T00:00:00Z"),
59
- end: new Date("2026-01-01T00:00:00Z"),
60
- }, support, 100)).toBe("1wk");
61
-
62
- expect(getBestSupportedResolutionForVisibleWindow({
63
- start: new Date("2025-01-01T00:00:00Z"),
64
- end: new Date("2026-01-01T00:00:00Z"),
65
- }, support, 100)).toBe("1d");
66
-
67
- expect(getBestSupportedResolutionForVisibleWindow({
68
- start: new Date("2025-12-01T00:00:00Z"),
69
- end: new Date("2026-01-01T00:00:00Z"),
70
- }, support, 100)).toBe("1h");
71
-
72
- expect(getBestSupportedResolutionForVisibleWindow({
73
- start: new Date("2025-12-25T00:00:00Z"),
74
- end: new Date("2026-01-01T00:00:00Z"),
75
- }, support, 100)).toBe("15m");
76
-
77
- expect(getBestSupportedResolutionForVisibleWindow({
78
- start: new Date("2026-01-01T00:00:00Z"),
79
- end: new Date("2026-01-02T00:00:00Z"),
80
- }, support, 100)).toBe("1m");
56
+ const pick = (start: string, end: string, current?: "5m" | "15m") => (
57
+ getBestSupportedResolutionForVisibleWindow(
58
+ { start: new Date(start), end: new Date(end) },
59
+ support,
60
+ 230,
61
+ current,
62
+ )
63
+ );
64
+
65
+ expect(pick("2021-01-01T00:00:00Z", "2026-01-01T00:00:00Z")).toBe("1wk");
66
+ expect(pick("2025-01-01T00:00:00Z", "2026-01-01T00:00:00Z")).toBe("1d");
67
+ // Five months of daily bars beat 1h bars packed three to a pixel.
68
+ expect(pick("2025-08-01T00:00:00Z", "2026-01-01T00:00:00Z")).toBe("1d");
69
+ expect(pick("2025-12-01T00:00:00Z", "2026-01-01T00:00:00Z")).toBe("1h");
70
+ // Two weeks hold ten sessions: 15m bars, not the 780 5m bars of a
71
+ // calendar-day estimate.
72
+ expect(pick("2025-12-18T00:00:00Z", "2026-01-01T00:00:00Z")).toBe("15m");
73
+ expect(pick("2025-12-29T00:00:00Z", "2026-01-01T00:00:00Z")).toBe("5m");
74
+ expect(pick("2026-01-01T00:00:00Z", "2026-01-02T00:00:00Z")).toBe("1m");
75
+
76
+ // A modest zoom keeps the resolution on screen; a large one still switches.
77
+ expect(pick("2025-12-26T09:36:00Z", "2026-01-01T00:00:00Z", "15m")).toBe("15m");
78
+ expect(pick("2025-12-29T00:00:00Z", "2026-01-01T00:00:00Z", "15m")).toBe("5m");
79
+ expect(pick("2025-12-15T00:00:00Z", "2026-01-01T00:00:00Z", "5m")).toBe("15m");
81
80
 
82
81
  expect(CHART_RESOLUTION_STEP_MS["1h"]).toBe(60 * 60_000);
83
82
  });
@@ -256,15 +256,42 @@ export function isRangePresetSupported(
256
256
  return maxRange !== null && isTimeRangeAtOrBelow(range, maxRange);
257
257
  }
258
258
 
259
+ const INTRADAY_TRADING_DAY_RATIO = 5 / 7;
260
+ /** Bars packed tighter than the target cost more than bars spread wider. */
261
+ const DENSE_SCORE_PENALTY = 1.5;
262
+ /** Score margin a candidate must win by before the active resolution changes. */
263
+ const RESOLUTION_SWITCH_HYSTERESIS = 0.35;
264
+
265
+ function estimateVisiblePointCount(resolution: ManualChartResolution, spanMs: number): number {
266
+ const calendarDays = Math.max(spanMs / CHART_RESOLUTION_STEP_MS["1d"], 1 / 24);
267
+ // Intraday bars only exist on trading days, so a multi-day window holds
268
+ // about five sessions a week. Anything up to a day is one session.
269
+ const days = isIntradayResolution(resolution) && calendarDays > 1
270
+ ? Math.max(calendarDays * INTRADAY_TRADING_DAY_RATIO, 1)
271
+ : calendarDays;
272
+ return Math.max(days * CHART_RESOLUTION_POINTS_PER_DAY[resolution], 1);
273
+ }
274
+
275
+ function resolutionFitScore(estimatedPointCount: number, targetPointCount: number): number {
276
+ const ratio = estimatedPointCount / targetPointCount;
277
+ return ratio > 1 ? Math.log(ratio) * DENSE_SCORE_PENALTY : -Math.log(ratio);
278
+ }
279
+
280
+ /**
281
+ * Picks the supported resolution whose bar count sits closest to the target
282
+ * on a log scale, leaning toward fewer bars. A current resolution is kept
283
+ * until another candidate is clearly better, so a small zoom does not swap
284
+ * the whole data set.
285
+ */
259
286
  export function getBestSupportedResolutionForVisibleWindow(
260
287
  window: { start: Date | null; end: Date | null } | null,
261
288
  support: readonly ChartResolutionSupport[] | ReadonlyMap<ManualChartResolution, TimeRange>,
262
289
  targetPointCount: number,
290
+ currentResolution?: ManualChartResolution | null,
263
291
  ): ManualChartResolution | null {
264
292
  if (!window?.start || !window.end) return null;
265
293
 
266
294
  const spanMs = Math.max(window.end.getTime() - window.start.getTime(), 0);
267
- const dayCount = Math.max(spanMs / CHART_RESOLUTION_STEP_MS["1d"], 1 / 24);
268
295
  const supportedResolutions = sortChartResolutions(
269
296
  CHART_RESOLUTION_ORDER
270
297
  .filter((resolution): resolution is ManualChartResolution => resolution !== "auto")
@@ -273,16 +300,20 @@ export function getBestSupportedResolutionForVisibleWindow(
273
300
  return maxRange !== null && isDateWindowWithinTimeRange(window.start!, window.end!, maxRange);
274
301
  }),
275
302
  );
276
-
277
303
  if (supportedResolutions.length === 0) return null;
278
304
 
279
- const minimumPointTarget = Math.max(targetPointCount, 1);
280
- for (const resolution of [...supportedResolutions].reverse()) {
281
- const estimatedPointCount = Math.max(dayCount * CHART_RESOLUTION_POINTS_PER_DAY[resolution], 1);
282
- if (estimatedPointCount >= minimumPointTarget) {
283
- return resolution;
284
- }
305
+ const target = Math.max(targetPointCount, 1);
306
+ const scores = new Map(supportedResolutions.map((resolution) => [
307
+ resolution,
308
+ resolutionFitScore(estimateVisiblePointCount(resolution, spanMs), target),
309
+ ] as const));
310
+ let best = supportedResolutions[0]!;
311
+ for (const resolution of supportedResolutions) {
312
+ if (scores.get(resolution)! <= scores.get(best)!) best = resolution;
285
313
  }
286
-
287
- return supportedResolutions[0] ?? null;
314
+ if (currentResolution && scores.has(currentResolution)) {
315
+ const currentScore = scores.get(currentResolution)!;
316
+ if (currentScore <= scores.get(best)! + RESOLUTION_SWITCH_HYSTERESIS) return currentResolution;
317
+ }
318
+ return best;
288
319
  }
@@ -3,7 +3,12 @@ import type { FredSeriesData, FredSeriesLoadResult } from "../data/fred-series";
3
3
  import { createTestDataProvider } from "../test-support/data-provider";
4
4
  import type { TickerFinancials } from "../types/financials";
5
5
  import { CHART_SPEC_VERSION, type ChartSpec } from "./types";
6
- import { ChartResolveCache, mergePriceHistoryWindows, resolveChartSpecData } from "./resolve";
6
+ import {
7
+ ChartResolveCache,
8
+ mergePriceHistoryWindows,
9
+ resolveChartSpecData,
10
+ seedChartResolutionResult,
11
+ } from "./resolve";
7
12
  import { chartQuoteOverrideKeyForSource } from "./live-quotes";
8
13
  import { chartSeriesSourceKey } from "../capabilities";
9
14
  import { buildCustomChartPreset } from "../plugins/builtin/chart-composer/presets";
@@ -26,6 +31,118 @@ const fredLoad = (
26
31
  });
27
32
 
28
33
  describe("resolveChartSpecData", () => {
34
+ test("seeds study series so their panels survive the wait for real data", async () => {
35
+ const date = new Date("2026-01-05T00:00:00.000Z");
36
+ const seeded = seedChartResolutionResult(
37
+ {
38
+ version: CHART_SPEC_VERSION,
39
+ viewport: { range: "1M", resolution: "auto" },
40
+ panels: [{ id: "main" }, { id: "volume" }],
41
+ series: [{
42
+ id: "price",
43
+ source: {
44
+ kind: "security",
45
+ instrument: { symbol: "TEST", exchange: "NASDAQ" },
46
+ fieldId: "market.ohlcv",
47
+ },
48
+ style: "candles",
49
+ transform: "raw",
50
+ axis: "left",
51
+ panelId: "main",
52
+ interpolation: "none",
53
+ }],
54
+ studies: [{
55
+ id: "volume",
56
+ kind: "volume",
57
+ inputSeriesIds: ["price"],
58
+ parameters: {},
59
+ panelId: "volume",
60
+ axis: "left",
61
+ }],
62
+ },
63
+ new Map([[
64
+ chartQuoteOverrideKeyForSource({
65
+ kind: "security",
66
+ instrument: { symbol: "TEST", exchange: "NASDAQ" },
67
+ fieldId: "market.ohlcv",
68
+ }),
69
+ [{ date, open: 10, high: 12, low: 9, close: 11, volume: 5_000 }],
70
+ ]]),
71
+ );
72
+
73
+ const volume = seeded?.series.find((entry) => entry.panelId === "volume");
74
+ expect(volume?.points.map((point) => point.value)).toEqual([5_000]);
75
+ expect(seeded?.bufferedSeries).toBe(seeded?.series);
76
+ });
77
+
78
+ test("keeps study output over the whole buffered history its base series carries", async () => {
79
+ const history = Array.from({ length: 900 }, (_, day) => {
80
+ const date = new Date(Date.UTC(2024, 0, 1) + day * 86_400_000);
81
+ return date.getUTCDay() === 0 || date.getUTCDay() === 6
82
+ ? null
83
+ : { date, open: 100, high: 101, low: 99, close: 100 + (day % 7), volume: 1_000 + day };
84
+ }).filter((point): point is NonNullable<typeof point> => point !== null);
85
+ const provider = createTestDataProvider({
86
+ getTickerFinancials: async () => emptyFinancials(),
87
+ getChartResolutionSupport: () => [{ resolution: "1d", maxRange: "ALL" }],
88
+ getPriceHistoryForResolution: async () => history,
89
+ getDetailedPriceHistory: async () => history,
90
+ });
91
+ const spec: ChartSpec = {
92
+ version: CHART_SPEC_VERSION,
93
+ viewport: { range: "1Y", resolution: "1d" },
94
+ panels: [{ id: "main" }, { id: "volume", label: "Volume", height: 0.24 }],
95
+ series: [{
96
+ id: "price",
97
+ source: {
98
+ kind: "security",
99
+ instrument: { symbol: "TEST", exchange: "NASDAQ" },
100
+ fieldId: "market.close",
101
+ },
102
+ style: "line",
103
+ transform: "raw",
104
+ axis: "left",
105
+ panelId: "main",
106
+ interpolation: "none",
107
+ }],
108
+ studies: [{
109
+ id: "volume",
110
+ kind: "volume",
111
+ inputSeriesIds: ["price"],
112
+ parameters: {},
113
+ panelId: "volume",
114
+ axis: "left",
115
+ }],
116
+ };
117
+ const sources = {
118
+ dataProvider: provider,
119
+ now: new Date("2026-06-15T00:00:00.000Z"),
120
+ loadFredSeries: async () => fredLoad(),
121
+ };
122
+ const cache = new ChartResolveCache();
123
+
124
+ await resolveChartSpecData(spec, sources, cache);
125
+ // Panning back accumulates older bars into the buffer. The study has to
126
+ // follow, or it empties out wherever the base series has already been.
127
+ const panned = await resolveChartSpecData(spec, sources, cache, {
128
+ requestViewport: {
129
+ start: new Date("2024-02-01T00:00:00.000Z"),
130
+ end: new Date("2024-08-01T00:00:00.000Z"),
131
+ },
132
+ });
133
+
134
+ const span = (id: string) => {
135
+ const entry = panned.bufferedSeries?.find((candidate) => candidate.id === id);
136
+ return entry && entry.points.length > 0
137
+ ? {
138
+ first: entry.points[0]!.date.toISOString(),
139
+ last: entry.points.at(-1)!.date.toISOString(),
140
+ }
141
+ : null;
142
+ };
143
+ expect(span("volume")).toEqual(span("price"));
144
+ });
145
+
29
146
  test("routes futures and Treasury aliases through the existing market and FRED pipelines", async () => {
30
147
  const marketRequests: string[] = [];
31
148
  const fredRequests: string[] = [];
@@ -98,6 +98,8 @@ export interface ChartResolveOptions {
98
98
  requestViewport?: { start: Date; end: Date } | null;
99
99
  /** Approximate number of horizontal observations the current surface can use. */
100
100
  targetPointCount?: number;
101
+ /** Resolution currently on screen, kept through small Auto zooms. */
102
+ currentResolution?: ManualChartResolution | null;
101
103
  }
102
104
 
103
105
  /** Raw source data retained while live quotes recompute the chart tail. */
@@ -290,6 +292,7 @@ function requestResolution(
290
292
  { start: new Date(runtimeBounds.start), end: new Date(runtimeBounds.end) },
291
293
  sharedSupport,
292
294
  options.targetPointCount ?? 120,
295
+ options.currentResolution,
293
296
  )
294
297
  : null;
295
298
  const preferred = adaptive
@@ -381,13 +384,22 @@ export function seedChartResolutionResult(
381
384
  if (resolved?.points.length) series.push(resolved);
382
385
  });
383
386
  if (series.length === 0) return null;
387
+ // Studies ride along with the seed. Without them the chart briefly drops to
388
+ // base series alone whenever it falls back here, and every panel a study
389
+ // owns loses its rows until the first real resolve lands.
390
+ const seeded = spec.studies.length > 0
391
+ ? [...series, ...resolveStudies(series, spec.studies).series]
392
+ : series;
384
393
  return {
385
- series,
386
- legendSeries: series,
387
- bufferedSeries: series,
394
+ series: seeded,
395
+ legendSeries: seeded,
396
+ bufferedSeries: seeded,
388
397
  loading: false,
389
398
  errors: [],
390
399
  warnings: [],
400
+ resolution: spec.viewport.resolution === "auto"
401
+ ? getPresetResolution(spec.viewport.range)
402
+ : spec.viewport.resolution,
391
403
  };
392
404
  }
393
405
 
@@ -799,13 +811,6 @@ function prepareBaseSeriesForStudies(
799
811
  );
800
812
  }
801
813
 
802
- function rawCalculationSeries(series: ResolvedSeries, bounds: DateBounds): ResolvedSeries {
803
- if (bounds.start !== null && bounds.end !== null) {
804
- return clipSeriesToWindow(series, new Date(bounds.start), new Date(bounds.end));
805
- }
806
- return { ...series, points: filterPoints(series.points, bounds) };
807
- }
808
-
809
814
  function scalarBaseline(series: ResolvedSeries, bounds: DateBounds): number | null {
810
815
  const points = filterPoints(series.points, bounds);
811
816
  for (const point of points) {
@@ -1142,15 +1147,14 @@ export async function resolveChartSpecData(
1142
1147
  ? requestVisibleBounds
1143
1148
  : followLatestMarketObservation(initialVisibleBounds, rawSeries);
1144
1149
  const resolution = initialResolution;
1145
- const studyBounds = bounds.end !== null
1146
- && initialCalculationBounds.end !== null
1147
- && bounds.end > initialCalculationBounds.end
1148
- ? { ...initialCalculationBounds, end: bounds.end }
1149
- : initialCalculationBounds;
1150
1150
  const baseSeries = rawSeries
1151
1151
  .filter((entry) => visibleSeriesIds.has(entry.id))
1152
1152
  .map((entry) => prepareBaseSeriesForStudies(entry, bounds, false, requestVisibleBounds));
1153
- const calculationSeries = rawSeries.map((entry) => rawCalculationSeries(entry, studyBounds));
1153
+ // Studies run over the same loaded history their base series carries. Clipping
1154
+ // them to the requested window instead left a study with no observations
1155
+ // wherever the accumulated buffer had already been panned past, so a study's
1156
+ // panel emptied out mid-pan and only refilled once the next fetch landed.
1157
+ const calculationSeries = rawSeries;
1154
1158
  let resolved = baseSeries;
1155
1159
 
1156
1160
  // Study outputs are appended by the pure engine before the final viewport clip.
@@ -1224,5 +1228,6 @@ export async function resolveChartSpecData(
1224
1228
  errors,
1225
1229
  warnings: [...new Set([...priorityWarnings, ...warnings])],
1226
1230
  viewport,
1231
+ resolution,
1227
1232
  };
1228
1233
  }
@@ -1,5 +1,5 @@
1
1
  import type { ChartResolution, TimeRange } from "./range";
2
- import type { ChartResolutionSupport } from "./resolution";
2
+ import type { ChartResolutionSupport, ManualChartResolution } from "./resolution";
3
3
  import type { InstrumentRef } from "../market-data/request-types";
4
4
 
5
5
  export const CHART_SPEC_VERSION = 2 as const;
@@ -175,4 +175,6 @@ export interface ChartResolutionResult {
175
175
  warnings: string[];
176
176
  /** Effective inclusive bounds used to clip the resolved chart data. */
177
177
  viewport?: { start: Date; end: Date };
178
+ /** Bar resolution the loaded market history was fetched at. */
179
+ resolution?: ManualChartResolution;
178
180
  }
@@ -35,6 +35,7 @@ export interface UseChartResolutionOptions {
35
35
  autoViewport?: ChartResolveOptions["autoViewport"];
36
36
  requestViewport?: ChartResolveOptions["requestViewport"];
37
37
  targetPointCount?: number;
38
+ currentResolution?: ChartResolveOptions["currentResolution"];
38
39
  }
39
40
 
40
41
  const DEFAULT_QUOTE_POLL_INTERVAL_MS = 60_000;
@@ -146,10 +147,14 @@ export function useChartResolution(
146
147
  ? { start: new Date(requestViewportStart), end: new Date(requestViewportEnd) }
147
148
  : null;
148
149
  const adaptiveTargetPointCount = validAutoViewport ? options.targetPointCount : undefined;
150
+ // Read at resolve time only: hysteresis breaks ties when the viewport moves,
151
+ // and echoing the chosen resolution back must not trigger another resolve.
152
+ const currentResolution = validAutoViewport ? options.currentResolution ?? null : null;
149
153
  const resolveOptions: ChartResolveOptions = {
150
154
  autoViewport: validAutoViewport,
151
155
  requestViewport: validRequestViewport,
152
156
  targetPointCount: adaptiveTargetPointCount,
157
+ currentResolution,
153
158
  };
154
159
  const latestRequestRef = useRef({ spec, sources, options: resolveOptions });
155
160
  latestRequestRef.current = { spec, sources, options: resolveOptions };