gloomberb 0.11.2 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (531) hide show
  1. package/README.md +7 -4
  2. package/README.zh-CN.md +4 -2
  3. package/package.json +28 -1
  4. package/src/api-client/auth.ts +10 -0
  5. package/src/api-client/data.ts +108 -1
  6. package/src/api-client/index.test.ts +127 -0
  7. package/src/api-client/index.ts +134 -1
  8. package/src/api-client/normalizers.ts +39 -0
  9. package/src/api-client/paths.ts +85 -1
  10. package/src/api-client/types.ts +220 -0
  11. package/src/app/pane-runtime/index.tsx +15 -12
  12. package/src/app/pane-runtime/layout-placement.test.ts +27 -0
  13. package/src/app/pane-runtime/layout-placement.ts +13 -0
  14. package/src/app/pane-runtime/plugin-bindings.ts +62 -0
  15. package/src/app.tsx +60 -18
  16. package/src/architecture/browser-entry-graph.test.ts +96 -0
  17. package/src/brokers/profile-form.test.ts +25 -27
  18. package/src/brokers/test-broker.ts +68 -0
  19. package/src/capabilities/chart-series.ts +3 -2
  20. package/src/cli/commands/market.ts +2 -2
  21. package/src/cli/commands/plugins.ts +39 -11
  22. package/src/cli/desktop-pane-shot.ts +285 -16
  23. package/src/cli/index.ts +1 -1
  24. package/src/cli/pane-functions/capabilities.ts +61 -31
  25. package/src/cli/pane-functions/catalog.ts +16 -4
  26. package/src/cli/pane-functions/discovery.ts +1 -0
  27. package/src/cli/pane-functions/headless.test.ts +247 -0
  28. package/src/cli/pane-functions/headless.ts +423 -0
  29. package/src/cli/pane-functions/index.test.ts +9 -0
  30. package/src/cli/pane-functions/index.ts +6 -2
  31. package/src/cli/pane-functions/options.ts +27 -2
  32. package/src/cli/pane-functions/report.ts +4 -0
  33. package/src/cli/pane-functions/resolver.ts +26 -8
  34. package/src/cli/pane-functions/screenshot.test.ts +76 -0
  35. package/src/cli/pane-functions/screenshot.ts +236 -20
  36. package/src/cli/registry.ts +0 -22
  37. package/src/cli/restore-plugins.ts +34 -0
  38. package/src/cli/result.ts +7 -10
  39. package/src/components/chart/{static/chart → composite}/axis-overlays.test.tsx +3 -3
  40. package/src/components/chart/{static/chart → composite}/axis-overlays.tsx +6 -11
  41. package/src/components/chart/{static/chart → composite}/bitmap.ts +17 -3
  42. package/src/components/chart/composite/composite-chart.test.tsx +68 -20
  43. package/src/components/chart/composite/composite-chart.tsx +301 -204
  44. package/src/components/chart/composite/format.ts +8 -2
  45. package/src/components/chart/composite/interactions.test.ts +68 -102
  46. package/src/components/chart/composite/interactions.ts +214 -264
  47. package/src/components/chart/composite/price-axis-labels.test.ts +21 -0
  48. package/src/components/chart/{price-axis-labels.tsx → composite/price-axis-labels.tsx} +9 -4
  49. package/src/components/chart/composite/scene.ts +57 -3
  50. package/src/components/chart/composite/text-renderer.ts +3 -2
  51. package/src/components/chart/composite/time-scale.ts +138 -57
  52. package/src/components/chart/composite/types.ts +25 -1
  53. package/src/components/chart/core/data.ts +9 -13
  54. package/src/components/chart/core/date-window.ts +7 -1
  55. package/src/components/chart/core/pointer.ts +3 -0
  56. package/src/components/chart/core/types.ts +0 -45
  57. package/src/components/chart/native/chart-rasterizer.ts +0 -1
  58. package/src/components/chart/native/kitty/index.test.ts +0 -188
  59. package/src/components/chart/native/raster/crosshair.ts +6 -5
  60. package/src/components/chart/native/raster/primitives.ts +0 -23
  61. package/src/components/chart/native/renderer-selection.ts +81 -31
  62. package/src/components/chart/static/{chart/surface.test.tsx → chart-surface.test.tsx} +28 -80
  63. package/src/components/chart/static/chart-surface.tsx +150 -0
  64. package/src/components/chart/static/index.ts +8 -0
  65. package/src/components/chart/static/multi-line-chart-surface.tsx +51 -166
  66. package/src/components/chart/static/scatter-chart-surface.test.ts +30 -0
  67. package/src/components/chart/static/scatter-chart-surface.tsx +135 -34
  68. package/src/components/chart/static/series.ts +49 -0
  69. package/src/components/command-bar/action-runtime.ts +1 -3
  70. package/src/components/command-bar/assist/model.test.ts +17 -2
  71. package/src/components/command-bar/assist/model.ts +40 -9
  72. package/src/components/command-bar/commands/direct/index.ts +1 -1
  73. package/src/components/command-bar/commands/direct/runtime.ts +1 -1
  74. package/src/components/command-bar/commands/registry.ts +0 -11
  75. package/src/components/command-bar/helpers.test.ts +2 -2
  76. package/src/components/command-bar/helpers.ts +2 -6
  77. package/src/components/command-bar/keyboard-shortcuts.ts +0 -11
  78. package/src/components/command-bar/list/badge.test.ts +54 -0
  79. package/src/components/command-bar/list/badge.ts +62 -0
  80. package/src/components/command-bar/list/model.test.ts +103 -0
  81. package/src/components/command-bar/list/model.ts +61 -4
  82. package/src/components/command-bar/list/view.test.tsx +113 -0
  83. package/src/components/command-bar/list/view.tsx +147 -131
  84. package/src/components/command-bar/pane-templates/workflow.ts +1 -1
  85. package/src/components/command-bar/panel/body.tsx +51 -65
  86. package/src/components/command-bar/panel/index.tsx +48 -13
  87. package/src/components/command-bar/panel/layout.test.ts +139 -0
  88. package/src/components/command-bar/panel/layout.ts +65 -28
  89. package/src/components/command-bar/panel/native-surface.ts +24 -0
  90. package/src/components/command-bar/panel/prompt-binding.ts +54 -0
  91. package/src/components/command-bar/panel/runtime.ts +6 -3
  92. package/src/components/command-bar/panel/state.ts +41 -7
  93. package/src/components/command-bar/panel/types.ts +7 -1
  94. package/src/components/command-bar/routes/root/provider-search.ts +34 -15
  95. package/src/components/command-bar/routes/root/results.test.ts +161 -0
  96. package/src/components/command-bar/routes/root/results.ts +30 -28
  97. package/src/components/command-bar/routes/root/runtime.ts +37 -20
  98. package/src/components/command-bar/routes/root/search-providers.test.tsx +224 -0
  99. package/src/components/command-bar/routes/root/search-providers.ts +205 -0
  100. package/src/components/command-bar/routes/root/selection.ts +1 -12
  101. package/src/components/command-bar/routes/root/shortcuts.test.ts +8 -8
  102. package/src/components/command-bar/routes/ticker-search/actions.ts +10 -9
  103. package/src/components/command-bar/routes/ticker-search/results.test.ts +54 -1
  104. package/src/components/command-bar/routes/ticker-search/results.ts +60 -19
  105. package/src/components/command-bar/routes/ticker-search/route.ts +1 -0
  106. package/src/components/command-bar/routing/actions.ts +1 -81
  107. package/src/components/command-bar/routing/launch-request.ts +1 -1
  108. package/src/components/command-bar/routing/list-state.ts +6 -21
  109. package/src/components/command-bar/selection-runtime.ts +1 -1
  110. package/src/components/command-bar/surface/assist.test.tsx +14 -10
  111. package/src/components/command-bar/surface/environment.ts +8 -1
  112. package/src/components/command-bar/surface/index.test.tsx +84 -68
  113. package/src/components/command-bar/surface/index.tsx +33 -36
  114. package/src/components/command-bar/surface/test-harness.tsx +15 -3
  115. package/src/components/command-bar/theme-picker.tsx +43 -15
  116. package/src/components/command-bar/view-model.test.ts +53 -8
  117. package/src/components/command-bar/view-model.ts +43 -14
  118. package/src/components/command-bar/workflow/tickers.ts +1 -0
  119. package/src/components/command-bar/workflow/types.ts +1 -1
  120. package/src/components/data-table/export.test.ts +21 -0
  121. package/src/components/data-table/export.ts +21 -0
  122. package/src/components/data-table/view.test.tsx +45 -6
  123. package/src/components/data-table/view.tsx +55 -9
  124. package/src/components/index.ts +10 -2
  125. package/src/components/input-search-bar.test.tsx +36 -2
  126. package/src/components/input-search-bar.tsx +18 -1
  127. package/src/components/layout/header.test.tsx +57 -10
  128. package/src/components/layout/header.tsx +292 -149
  129. package/src/components/layout/market-summary.test.ts +81 -0
  130. package/src/components/layout/market-summary.ts +109 -0
  131. package/src/components/layout/shell/chrome.ts +96 -1
  132. package/src/components/layout/shell/index.tsx +11 -3
  133. package/src/components/layout/shell/menu.ts +9 -0
  134. package/src/components/layout/shell/pane/actions.ts +29 -0
  135. package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
  136. package/src/components/layout/shell/shortcuts.ts +3 -0
  137. package/src/components/layout/status-bar.test.tsx +9 -8
  138. package/src/components/layout/status-bar.tsx +85 -54
  139. package/src/components/onboarding/onboarding-frame.tsx +2 -26
  140. package/src/components/price-sparkline/index.ts +61 -34
  141. package/src/components/price-sparkline/view.tsx +1 -1
  142. package/src/components/sign-in-gate.test.tsx +128 -0
  143. package/src/components/sign-in-gate.tsx +149 -0
  144. package/src/components/speedometer-gauge/desktop.tsx +2 -12
  145. package/src/components/ticker/badge/list.tsx +17 -3
  146. package/src/components/ticker/list-table-view.tsx +6 -3
  147. package/src/components/ui/data-table/index.tsx +22 -4
  148. package/src/components/ui/data-table/opentui/index.tsx +165 -120
  149. package/src/components/ui/frame.tsx +28 -0
  150. package/src/components/ui/index.ts +3 -0
  151. package/src/components/ui/native-select.tsx +20 -10
  152. package/src/components/ui/select-button.tsx +138 -0
  153. package/src/components/ui/status.tsx +2 -2
  154. package/src/components/ui/table-layout.ts +1 -1
  155. package/src/components/use-double-click-activation.ts +7 -3
  156. package/src/core/app-services.ts +3 -3
  157. package/src/data/config/chart-settings.ts +4 -4
  158. package/src/data/config/layout.ts +12 -6
  159. package/src/data/config/store/index.test.ts +97 -0
  160. package/src/data/config/store/migrations.ts +41 -0
  161. package/src/data/config/store/normalize.ts +5 -3
  162. package/src/i18n/es.ts +6 -2
  163. package/src/i18n/ja.ts +6 -2
  164. package/src/i18n/ko.ts +6 -2
  165. package/src/i18n/zh-cn.ts +6 -2
  166. package/src/i18n/zh-tw.ts +6 -2
  167. package/src/layout-marketplace/gallery-terminal.tsx +126 -96
  168. package/src/layout-marketplace/gallery.test.tsx +65 -1
  169. package/src/layout-marketplace/pane-imagery.ts +1 -0
  170. package/src/layout-marketplace/payload.test.ts +17 -0
  171. package/src/layout-marketplace/payload.ts +40 -5
  172. package/src/market-data/coordinator/index.ts +3 -2
  173. package/src/market-data/coordinator/quotes.ts +173 -66
  174. package/src/market-data/coordinator/subscriptions.test.ts +215 -37
  175. package/src/pane-settings.ts +1 -1
  176. package/src/plugins/builtin/analytics/pane-model.ts +1 -1
  177. package/src/plugins/builtin/analytics/view.tsx +1 -1
  178. package/src/plugins/builtin/broker-manager/index.test.tsx +3 -2
  179. package/src/plugins/builtin/broker-manager/model.test.ts +2 -2
  180. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +16 -2
  181. package/src/plugins/builtin/chart-composer/cli-options.ts +12 -9
  182. package/src/plugins/builtin/chart-composer/pane.tsx +22 -8
  183. package/src/plugins/builtin/chart-composer/settings.ts +2 -13
  184. package/src/plugins/builtin/chat/content/composer.tsx +8 -4
  185. package/src/plugins/builtin/cloud/auth-dialog.tsx +14 -228
  186. package/src/plugins/builtin/cloud/auth-form.tsx +277 -0
  187. package/src/plugins/builtin/cloud/plugin.tsx +1 -0
  188. package/src/plugins/builtin/composite-plugins.ts +16 -1
  189. package/src/plugins/builtin/correlation/relationship/model.ts +1 -1
  190. package/src/plugins/builtin/correlation/relationship/pane.tsx +2 -6
  191. package/src/plugins/builtin/correlation/relationship/view-model.ts +2 -2
  192. package/src/plugins/builtin/credit-conditions/index.test.tsx +12 -6
  193. package/src/plugins/builtin/credit-conditions/index.tsx +2 -21
  194. package/src/plugins/builtin/dividend-yield/index.tsx +1 -0
  195. package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
  196. package/src/plugins/builtin/earnings/index.tsx +1 -0
  197. package/src/plugins/builtin/earnings-calls/data.ts +223 -0
  198. package/src/plugins/builtin/earnings-calls/format.ts +49 -0
  199. package/src/plugins/builtin/earnings-calls/index.tsx +69 -0
  200. package/src/plugins/builtin/earnings-calls/pane.tsx +756 -0
  201. package/src/plugins/builtin/earnings-calls/prose.test.ts +36 -0
  202. package/src/plugins/builtin/earnings-calls/prose.ts +59 -0
  203. package/src/plugins/builtin/earnings-calls/transcript-view.tsx +334 -0
  204. package/src/plugins/builtin/econ/detail-view.tsx +1 -2
  205. package/src/plugins/builtin/econ/fred-series-map.ts +16 -1
  206. package/src/plugins/builtin/econ/index.tsx +3 -1
  207. package/src/plugins/builtin/econ-statistics/cache.ts +4 -0
  208. package/src/plugins/builtin/econ-statistics/client.ts +92 -0
  209. package/src/plugins/builtin/econ-statistics/defs.ts +66 -0
  210. package/src/plugins/builtin/econ-statistics/detail.tsx +154 -0
  211. package/src/plugins/builtin/econ-statistics/headless.test.ts +54 -0
  212. package/src/plugins/builtin/econ-statistics/headless.ts +171 -0
  213. package/src/plugins/builtin/econ-statistics/index.tsx +54 -0
  214. package/src/plugins/builtin/econ-statistics/pane.tsx +356 -0
  215. package/src/plugins/builtin/econ-statistics/stats.ts +522 -0
  216. package/src/plugins/builtin/econ-statistics/transform.test.ts +86 -0
  217. package/src/plugins/builtin/econ-statistics/transform.ts +85 -0
  218. package/src/plugins/builtin/econ-statistics/trend.ts +57 -0
  219. package/src/plugins/builtin/econ-statistics/view.ts +90 -0
  220. package/src/plugins/builtin/fear-greed/charts.tsx +7 -18
  221. package/src/plugins/builtin/fear-greed/data.ts +6 -1
  222. package/src/plugins/builtin/futures/index.tsx +1 -0
  223. package/src/plugins/builtin/fx-matrix/index.tsx +1 -0
  224. package/src/plugins/builtin/help/index.tsx +4 -0
  225. package/src/plugins/builtin/holders/index.tsx +1 -0
  226. package/src/plugins/builtin/insider/index.tsx +1 -0
  227. package/src/plugins/builtin/ipo-calendar/index.tsx +1 -0
  228. package/src/plugins/builtin/kelly-sizer/pane.tsx +1 -1
  229. package/src/plugins/builtin/kelly-sizer/sections.tsx +2 -3
  230. package/src/plugins/builtin/market-halts/index.tsx +1 -0
  231. package/src/plugins/builtin/market-movers/index.tsx +1 -0
  232. package/src/plugins/builtin/market-valuation/align.ts +122 -0
  233. package/src/plugins/builtin/market-valuation/cache.ts +32 -0
  234. package/src/plugins/builtin/market-valuation/chart-projection.ts +217 -0
  235. package/src/plugins/builtin/market-valuation/chart-series.ts +89 -0
  236. package/src/plugins/builtin/market-valuation/client.test.ts +141 -0
  237. package/src/plugins/builtin/market-valuation/client.ts +116 -0
  238. package/src/plugins/builtin/market-valuation/defs.ts +251 -0
  239. package/src/plugins/builtin/market-valuation/detail.tsx +159 -0
  240. package/src/plugins/builtin/market-valuation/headless.test.ts +58 -0
  241. package/src/plugins/builtin/market-valuation/headless.ts +170 -0
  242. package/src/plugins/builtin/market-valuation/index.tsx +64 -0
  243. package/src/plugins/builtin/market-valuation/indicators.ts +422 -0
  244. package/src/plugins/builtin/market-valuation/model.test.ts +350 -0
  245. package/src/plugins/builtin/market-valuation/model.ts +8 -0
  246. package/src/plugins/builtin/market-valuation/pane.test.tsx +247 -0
  247. package/src/plugins/builtin/market-valuation/pane.tsx +344 -0
  248. package/src/plugins/builtin/market-valuation/series.ts +12 -0
  249. package/src/plugins/builtin/market-valuation/settings.ts +38 -0
  250. package/src/plugins/builtin/market-valuation/sources.ts +130 -0
  251. package/src/plugins/builtin/market-valuation/trend.ts +89 -0
  252. package/src/plugins/builtin/market-valuation/view.ts +137 -0
  253. package/src/plugins/builtin/market-valuation/zone-scale/desktop.tsx +123 -0
  254. package/src/plugins/builtin/market-valuation/zone-scale/index.tsx +12 -0
  255. package/src/plugins/builtin/market-valuation/zone-scale/model.ts +60 -0
  256. package/src/plugins/builtin/market-valuation/zone-scale/terminal.tsx +94 -0
  257. package/src/plugins/builtin/news/wire/breaking/filters.test.ts +99 -0
  258. package/src/plugins/builtin/news/wire/breaking/filters.ts +110 -0
  259. package/src/plugins/builtin/news/wire/breaking/notifications.test.ts +157 -53
  260. package/src/plugins/builtin/news/wire/breaking/notifications.ts +91 -25
  261. package/src/plugins/builtin/news/wire/index.tsx +31 -44
  262. package/src/plugins/builtin/options/analytics.test.ts +83 -0
  263. package/src/plugins/builtin/options/analytics.ts +110 -0
  264. package/src/plugins/builtin/options/index.tsx +18 -2
  265. package/src/plugins/builtin/options/table.ts +121 -51
  266. package/src/plugins/builtin/options/types.ts +22 -15
  267. package/src/plugins/builtin/options/view.test.tsx +53 -1
  268. package/src/plugins/builtin/options/view.tsx +92 -41
  269. package/src/plugins/builtin/plugin-marketplace/feed.ts +80 -0
  270. package/src/plugins/builtin/plugin-marketplace/index.tsx +31 -0
  271. package/src/plugins/builtin/plugin-marketplace/model.test.ts +200 -0
  272. package/src/plugins/builtin/plugin-marketplace/model.ts +204 -0
  273. package/src/plugins/builtin/plugin-marketplace/pane.tsx +385 -0
  274. package/src/plugins/builtin/plugin-marketplace/store.ts +46 -0
  275. package/src/plugins/builtin/plugin-module.test.ts +2 -4
  276. package/src/plugins/builtin/plugin-module.ts +9 -8
  277. package/src/plugins/builtin/portfolio-list/index.tsx +1 -0
  278. package/src/plugins/builtin/research/index.tsx +4 -0
  279. package/src/plugins/builtin/research-search/command-bar-search.test.ts +81 -0
  280. package/src/plugins/builtin/research-search/command-bar-search.ts +175 -0
  281. package/src/plugins/builtin/research-search/data.ts +109 -0
  282. package/src/plugins/builtin/research-search/document-view.test.tsx +75 -0
  283. package/src/plugins/builtin/research-search/document-view.tsx +254 -0
  284. package/src/plugins/builtin/research-search/filter-bar.tsx +62 -0
  285. package/src/plugins/builtin/research-search/focus-handoff.ts +47 -0
  286. package/src/plugins/builtin/research-search/index.tsx +85 -0
  287. package/src/plugins/builtin/research-search/model.test.ts +40 -0
  288. package/src/plugins/builtin/research-search/model.ts +291 -0
  289. package/src/plugins/builtin/research-search/pane.test.tsx +241 -0
  290. package/src/plugins/builtin/research-search/pane.tsx +678 -0
  291. package/src/plugins/builtin/research-search/saved-view.tsx +171 -0
  292. package/src/plugins/builtin/research-search/snippet-text.tsx +35 -0
  293. package/src/plugins/builtin/research-search/snippet.test.ts +78 -0
  294. package/src/plugins/builtin/research-search/snippet.ts +134 -0
  295. package/src/plugins/builtin/scanner/flow-model.test.ts +11 -1
  296. package/src/plugins/builtin/scanner/flow-model.ts +29 -1
  297. package/src/plugins/builtin/scanner/flow-pane.tsx +78 -22
  298. package/src/plugins/builtin/scanner/index.ts +2 -0
  299. package/src/plugins/builtin/sec/index.tsx +1 -0
  300. package/src/plugins/builtin/sectors/index.tsx +1 -0
  301. package/src/plugins/builtin/shared/series-cache.ts +93 -0
  302. package/src/plugins/builtin/short-interest/index.tsx +1 -0
  303. package/src/plugins/builtin/short-interest/pane.tsx +1 -1
  304. package/src/plugins/builtin/thirteenf/index.tsx +1 -0
  305. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +3 -5
  306. package/src/plugins/builtin/ticker-detail/index.tsx +2 -14
  307. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +1 -1
  308. package/src/plugins/builtin/treasury-auctions/index.tsx +1 -0
  309. package/src/plugins/builtin/volatility/index.tsx +1 -1
  310. package/src/plugins/builtin/world-indices/index.tsx +1 -0
  311. package/src/plugins/builtin/world-venue-map/index.ts +27 -0
  312. package/src/plugins/builtin/world-venue-map/map.desktop.test.tsx +106 -0
  313. package/src/plugins/builtin/world-venue-map/map.tsx +481 -0
  314. package/src/plugins/builtin/world-venue-map/model.test.ts +85 -0
  315. package/src/plugins/builtin/world-venue-map/model.ts +268 -0
  316. package/src/plugins/builtin/world-venue-map/pane.tsx +300 -0
  317. package/src/plugins/builtin/world-venue-map/world-outlines.ts +2 -0
  318. package/src/plugins/builtin/yield-curve/index.tsx +1 -1
  319. package/src/plugins/bundle.test.ts +109 -0
  320. package/src/plugins/bundle.ts +139 -0
  321. package/src/plugins/catalog-backend.ts +6 -44
  322. package/src/plugins/catalog-browser.test.ts +1 -0
  323. package/src/plugins/catalog-browser.ts +12 -0
  324. package/src/plugins/catalog-manifest.test.ts +43 -0
  325. package/src/plugins/catalog-manifest.ts +92 -0
  326. package/src/plugins/catalog-ui.ts +20 -4
  327. package/src/plugins/catalog.ts +9 -2
  328. package/src/plugins/current-target.ts +28 -0
  329. package/src/plugins/host-contract.ts +37 -0
  330. package/src/plugins/host-link.ts +158 -0
  331. package/src/plugins/host-modules.ts +61 -0
  332. package/src/plugins/loader.ts +41 -22
  333. package/src/plugins/ownership.ts +1 -0
  334. package/src/plugins/prediction-markets/index.tsx +1 -0
  335. package/src/plugins/registry/context.ts +3 -0
  336. package/src/plugins/registry/contributions.ts +27 -0
  337. package/src/plugins/registry/index.test.ts +25 -0
  338. package/src/plugins/registry/index.ts +8 -0
  339. package/src/plugins/registry/pane-settings.test.ts +53 -0
  340. package/src/plugins/registry/pane-settings.ts +29 -3
  341. package/src/plugins/seed.test.ts +83 -0
  342. package/src/plugins/seed.ts +80 -0
  343. package/src/public/broker.ts +31 -0
  344. package/src/public/market-data.ts +9 -0
  345. package/src/public/pane-hooks.ts +57 -0
  346. package/src/public/public-api.test.ts +285 -0
  347. package/src/public/react.ts +62 -0
  348. package/src/public/test-support.ts +23 -0
  349. package/src/public/time-series.ts +8 -0
  350. package/src/public/utils.ts +77 -0
  351. package/src/remote/layout-helpers.ts +6 -8
  352. package/src/remote/resources.ts +2 -0
  353. package/src/remote/schema.ts +1 -0
  354. package/src/remote/semantic-tree.test.tsx +105 -0
  355. package/src/remote/semantic-tree.tsx +53 -35
  356. package/src/renderers/browser/main.tsx +6 -0
  357. package/src/renderers/browser/ui-host.tsx +11 -0
  358. package/src/renderers/cloudflare/worker.test.ts +19 -0
  359. package/src/renderers/cloudflare/worker.ts +1 -1
  360. package/src/renderers/electrobun/bun/desktop/host-requests.ts +9 -2
  361. package/src/renderers/electrobun/bun/external-plugins.ts +140 -0
  362. package/src/renderers/electrobun/bun/index.ts +6 -2
  363. package/src/renderers/electrobun/shared/protocol.ts +25 -0
  364. package/src/renderers/electrobun/view/build-assets.ts +0 -1
  365. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +51 -1
  366. package/src/renderers/electrobun/view/external-plugins.test.ts +45 -0
  367. package/src/renderers/electrobun/view/external-plugins.ts +63 -0
  368. package/src/renderers/electrobun/view/host/ascii-text.test.ts +48 -0
  369. package/src/renderers/electrobun/view/host/ascii-text.ts +11 -1
  370. package/src/renderers/electrobun/view/host/chart-surface.tsx +38 -32
  371. package/src/renderers/electrobun/view/host/mouse.ts +16 -2
  372. package/src/renderers/electrobun/view/main.tsx +27 -2
  373. package/src/renderers/electrobun/view/styles.css +5 -0
  374. package/src/renderers/electrobun/view/toast-host.tsx +18 -0
  375. package/src/renderers/electrobun/view/ui-host.tsx +3 -0
  376. package/src/renderers/opentui/chart-surface.tsx +5 -1
  377. package/src/renderers/opentui/host.tsx +20 -2
  378. package/src/renderers/opentui/interaction-performance.test.ts +79 -0
  379. package/src/renderers/opentui/interaction-performance.ts +188 -0
  380. package/src/renderers/opentui/start.tsx +26 -1
  381. package/src/renderers/opentui/test-utils.tsx +13 -0
  382. package/src/renderers/opentui/toast-host.tsx +26 -2
  383. package/src/renderers/opentui/ui-host.tsx +1 -0
  384. package/src/sources/provider-router/search.test.ts +191 -0
  385. package/src/sources/provider-router/search.ts +92 -39
  386. package/src/state/app/context/index.tsx +2 -0
  387. package/src/state/app/context/selector.test.tsx +33 -1
  388. package/src/state/hooks/quote-streaming.test.tsx +93 -0
  389. package/src/state/hooks/quote-streaming.ts +106 -30
  390. package/src/state/pane-table-export-registry.ts +50 -0
  391. package/src/theme/color-utils.ts +1 -1
  392. package/src/theme/colors.ts +50 -0
  393. package/src/theme/themes.test.ts +15 -2
  394. package/src/theme/themes.ts +19 -1
  395. package/src/tickers/search/index.test.ts +26 -0
  396. package/src/tickers/search/index.ts +42 -17
  397. package/src/tickers/search/ranking.ts +14 -0
  398. package/src/time-series/chart-data.test.ts +216 -0
  399. package/src/time-series/range.ts +12 -0
  400. package/src/time-series/resolution.test.ts +25 -26
  401. package/src/time-series/resolution.ts +44 -25
  402. package/src/time-series/resolve.ts +7 -0
  403. package/src/time-series/spec.ts +5 -5
  404. package/src/time-series/types.ts +3 -1
  405. package/src/time-series/use-chart-resolution.ts +5 -0
  406. package/src/types/config.ts +123 -3
  407. package/src/types/data-provider.ts +11 -0
  408. package/src/types/headless.ts +161 -0
  409. package/src/types/plugin.ts +104 -20
  410. package/src/ui/host.tsx +19 -1
  411. package/src/ui/index.tsx +1 -0
  412. package/src/ui/toast.tsx +8 -4
  413. package/src/utils/csv.test.ts +13 -0
  414. package/src/utils/csv.ts +37 -0
  415. package/src/utils/format.ts +35 -0
  416. package/src/utils/fuzzy-search.test.ts +94 -0
  417. package/src/utils/fuzzy-search.ts +81 -22
  418. package/src/utils/save-text-file.test.ts +24 -0
  419. package/src/utils/save-text-file.ts +36 -0
  420. package/src/utils/shortcut-labels.ts +14 -0
  421. package/src/components/chart/core/axis-format.ts +0 -157
  422. package/src/components/chart/core/index.test.ts +0 -1075
  423. package/src/components/chart/core/projection.ts +0 -357
  424. package/src/components/chart/core/render-utils.ts +0 -4
  425. package/src/components/chart/core/renderer.ts +0 -244
  426. package/src/components/chart/core/scene.ts +0 -180
  427. package/src/components/chart/core/time-axis.ts +0 -330
  428. package/src/components/chart/cursor-motion.ts +0 -47
  429. package/src/components/chart/indicators/types.ts +0 -26
  430. package/src/components/chart/market-session.ts +0 -176
  431. package/src/components/chart/multi-line/renderer.test.ts +0 -83
  432. package/src/components/chart/multi-line/renderer.ts +0 -161
  433. package/src/components/chart/multi-line/scene.ts +0 -188
  434. package/src/components/chart/native/raster/layout.ts +0 -129
  435. package/src/components/chart/native/raster/price-chart.ts +0 -244
  436. package/src/components/chart/rendering/chart-draw.ts +0 -270
  437. package/src/components/chart/rendering/geometry.ts +0 -95
  438. package/src/components/chart/rendering/pixel-buffer.ts +0 -267
  439. package/src/components/chart/scatter-chart-renderer.test.ts +0 -31
  440. package/src/components/chart/scatter-chart-renderer.ts +0 -223
  441. package/src/components/chart/static/chart/surface.tsx +0 -358
  442. package/src/components/command-bar/routes/root/article-results.test.ts +0 -83
  443. package/src/components/command-bar/routes/root/article-results.ts +0 -55
  444. package/src/plugins/builtin/news/wire/article-search.test.ts +0 -72
  445. package/src/plugins/builtin/news/wire/article-search.ts +0 -140
  446. package/src/plugins/builtin/substack/api/auth.ts +0 -185
  447. package/src/plugins/builtin/substack/api/cache.ts +0 -82
  448. package/src/plugins/builtin/substack/api/loaders.ts +0 -170
  449. package/src/plugins/builtin/substack/api/store.ts +0 -186
  450. package/src/plugins/builtin/substack/api/types.ts +0 -42
  451. package/src/plugins/builtin/substack/api.test.ts +0 -298
  452. package/src/plugins/builtin/substack/article-detail.tsx +0 -397
  453. package/src/plugins/builtin/substack/article-stack.tsx +0 -133
  454. package/src/plugins/builtin/substack/content.ts +0 -478
  455. package/src/plugins/builtin/substack/feed-tabs.tsx +0 -44
  456. package/src/plugins/builtin/substack/index.tsx +0 -49
  457. package/src/plugins/builtin/substack/login-view.tsx +0 -126
  458. package/src/plugins/builtin/substack/model.test.ts +0 -155
  459. package/src/plugins/builtin/substack/normalize.ts +0 -343
  460. package/src/plugins/builtin/substack/pane-footer.ts +0 -52
  461. package/src/plugins/builtin/substack/pane-state.ts +0 -170
  462. package/src/plugins/builtin/substack/pane.tsx +0 -547
  463. package/src/plugins/builtin/substack/read-state.ts +0 -30
  464. package/src/plugins/builtin/substack/table.ts +0 -111
  465. package/src/plugins/builtin/substack/types.ts +0 -71
  466. package/src/plugins/builtin/substack/utils.ts +0 -131
  467. package/src/plugins/builtin/ticker-detail/data-panes/provider-search.tsx +0 -229
  468. package/src/plugins/ibkr/account-cache.test.ts +0 -102
  469. package/src/plugins/ibkr/account-cache.ts +0 -24
  470. package/src/plugins/ibkr/broker-adapter.ts +0 -286
  471. package/src/plugins/ibkr/config.test.ts +0 -79
  472. package/src/plugins/ibkr/config.ts +0 -250
  473. package/src/plugins/ibkr/dialogs.tsx +0 -40
  474. package/src/plugins/ibkr/flex/index.test.ts +0 -264
  475. package/src/plugins/ibkr/flex/index.ts +0 -367
  476. package/src/plugins/ibkr/flex/statement-client.ts +0 -235
  477. package/src/plugins/ibkr/fundamental-parser.ts +0 -149
  478. package/src/plugins/ibkr/gateway/account-loaders.ts +0 -266
  479. package/src/plugins/ibkr/gateway/account-summary.ts +0 -164
  480. package/src/plugins/ibkr/gateway/client-lock.ts +0 -154
  481. package/src/plugins/ibkr/gateway/connection-events.ts +0 -114
  482. package/src/plugins/ibkr/gateway/connection.ts +0 -152
  483. package/src/plugins/ibkr/gateway/contracts.ts +0 -132
  484. package/src/plugins/ibkr/gateway/fundamentals.ts +0 -83
  485. package/src/plugins/ibkr/gateway/helpers.ts +0 -33
  486. package/src/plugins/ibkr/gateway/history-requests.ts +0 -158
  487. package/src/plugins/ibkr/gateway/history.ts +0 -120
  488. package/src/plugins/ibkr/gateway/instrument-lookup.ts +0 -117
  489. package/src/plugins/ibkr/gateway/market-data.ts +0 -345
  490. package/src/plugins/ibkr/gateway/order-execution.ts +0 -218
  491. package/src/plugins/ibkr/gateway/orders.ts +0 -63
  492. package/src/plugins/ibkr/gateway/price-normalization.test.ts +0 -44
  493. package/src/plugins/ibkr/gateway/price-normalization.ts +0 -50
  494. package/src/plugins/ibkr/gateway/quote-stream-controller.ts +0 -203
  495. package/src/plugins/ibkr/gateway/quote-stream.ts +0 -171
  496. package/src/plugins/ibkr/gateway/selection.ts +0 -51
  497. package/src/plugins/ibkr/gateway/service/index.test.ts +0 -675
  498. package/src/plugins/ibkr/gateway/service/index.ts +0 -429
  499. package/src/plugins/ibkr/gateway/service/lifecycle.ts +0 -191
  500. package/src/plugins/ibkr/gateway/service/manager.ts +0 -52
  501. package/src/plugins/ibkr/gateway/service/market-data.ts +0 -48
  502. package/src/plugins/ibkr/gateway/service/native-loader.ts +0 -12
  503. package/src/plugins/ibkr/gateway/service/native.ts +0 -543
  504. package/src/plugins/ibkr/gateway/timeouts.ts +0 -13
  505. package/src/plugins/ibkr/gateway/types.ts +0 -27
  506. package/src/plugins/ibkr/index.test.tsx +0 -162
  507. package/src/plugins/ibkr/index.tsx +0 -159
  508. package/src/plugins/ibkr/instance-selection.test.ts +0 -120
  509. package/src/plugins/ibkr/instance-selection.ts +0 -36
  510. package/src/plugins/ibkr/portfolio-performance.test.ts +0 -32
  511. package/src/plugins/ibkr/portfolio-performance.ts +0 -129
  512. package/src/plugins/ibkr/trade/badge.tsx +0 -28
  513. package/src/plugins/ibkr/trade/dialogs.tsx +0 -44
  514. package/src/plugins/ibkr/trade/preview-panel.tsx +0 -130
  515. package/src/plugins/ibkr/trade/tab/actions.tsx +0 -443
  516. package/src/plugins/ibkr/trade/tab/field-editors.tsx +0 -79
  517. package/src/plugins/ibkr/trade/tab/footer.ts +0 -53
  518. package/src/plugins/ibkr/trade/tab/header.tsx +0 -167
  519. package/src/plugins/ibkr/trade/tab/index.test.tsx +0 -178
  520. package/src/plugins/ibkr/trade/tab/index.tsx +0 -298
  521. package/src/plugins/ibkr/trade/tab/model.ts +0 -159
  522. package/src/plugins/ibkr/trade/tab/order-request.ts +0 -64
  523. package/src/plugins/ibkr/trade/tab/shortcuts.ts +0 -89
  524. package/src/plugins/ibkr/trade/tab/state-sync.ts +0 -86
  525. package/src/plugins/ibkr/trade/ticket-panel.tsx +0 -301
  526. package/src/plugins/ibkr/trade/utils.test.ts +0 -175
  527. package/src/plugins/ibkr/trade/utils.ts +0 -144
  528. package/src/plugins/ibkr/trading/pane-view.tsx +0 -237
  529. package/src/plugins/ibkr/trading/pane.tsx +0 -332
  530. package/src/plugins/ibkr/trading/state.test.ts +0 -109
  531. package/src/plugins/ibkr/trading/state.ts +0 -335
@@ -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
+ });
@@ -7,3 +7,15 @@ export interface ChartDateWindow {
7
7
  }
8
8
 
9
9
  export const TIME_RANGES: TimeRange[] = ["1D", "1W", "1M", "3M", "6M", "1Y", "5Y", "ALL"];
10
+ export const CHART_RESOLUTIONS: ChartResolution[] = [
11
+ "auto",
12
+ "1m",
13
+ "5m",
14
+ "15m",
15
+ "30m",
16
+ "45m",
17
+ "1h",
18
+ "1d",
19
+ "1wk",
20
+ "1mo",
21
+ ];
@@ -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
  });
@@ -1,5 +1,5 @@
1
1
  import type { ChartResolution, TimeRange } from "./range";
2
- import { TIME_RANGES } from "./range";
2
+ import { CHART_RESOLUTIONS, TIME_RANGES } from "./range";
3
3
  import { isDateWindowWithinTimeRange } from "./date-window";
4
4
 
5
5
  export type ManualChartResolution = Exclude<ChartResolution, "auto">;
@@ -9,20 +9,8 @@ export interface ChartResolutionSupport {
9
9
  maxRange: TimeRange;
10
10
  }
11
11
 
12
- export const TIME_RANGE_ORDER: TimeRange[] = [...TIME_RANGES];
13
-
14
- const CHART_RESOLUTION_ORDER: ChartResolution[] = [
15
- "auto",
16
- "1m",
17
- "5m",
18
- "15m",
19
- "30m",
20
- "45m",
21
- "1h",
22
- "1d",
23
- "1wk",
24
- "1mo",
25
- ];
12
+ export const TIME_RANGE_ORDER = TIME_RANGES;
13
+ const CHART_RESOLUTION_ORDER = CHART_RESOLUTIONS;
26
14
 
27
15
  const CHART_RESOLUTION_LABELS: Record<ChartResolution, string> = {
28
16
  auto: "AUTO",
@@ -268,15 +256,42 @@ export function isRangePresetSupported(
268
256
  return maxRange !== null && isTimeRangeAtOrBelow(range, maxRange);
269
257
  }
270
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
+ */
271
286
  export function getBestSupportedResolutionForVisibleWindow(
272
287
  window: { start: Date | null; end: Date | null } | null,
273
288
  support: readonly ChartResolutionSupport[] | ReadonlyMap<ManualChartResolution, TimeRange>,
274
289
  targetPointCount: number,
290
+ currentResolution?: ManualChartResolution | null,
275
291
  ): ManualChartResolution | null {
276
292
  if (!window?.start || !window.end) return null;
277
293
 
278
294
  const spanMs = Math.max(window.end.getTime() - window.start.getTime(), 0);
279
- const dayCount = Math.max(spanMs / CHART_RESOLUTION_STEP_MS["1d"], 1 / 24);
280
295
  const supportedResolutions = sortChartResolutions(
281
296
  CHART_RESOLUTION_ORDER
282
297
  .filter((resolution): resolution is ManualChartResolution => resolution !== "auto")
@@ -285,16 +300,20 @@ export function getBestSupportedResolutionForVisibleWindow(
285
300
  return maxRange !== null && isDateWindowWithinTimeRange(window.start!, window.end!, maxRange);
286
301
  }),
287
302
  );
288
-
289
303
  if (supportedResolutions.length === 0) return null;
290
304
 
291
- const minimumPointTarget = Math.max(targetPointCount, 1);
292
- for (const resolution of [...supportedResolutions].reverse()) {
293
- const estimatedPointCount = Math.max(dayCount * CHART_RESOLUTION_POINTS_PER_DAY[resolution], 1);
294
- if (estimatedPointCount >= minimumPointTarget) {
295
- return resolution;
296
- }
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;
297
313
  }
298
-
299
- 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;
300
319
  }
@@ -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
@@ -388,6 +391,9 @@ export function seedChartResolutionResult(
388
391
  loading: false,
389
392
  errors: [],
390
393
  warnings: [],
394
+ resolution: spec.viewport.resolution === "auto"
395
+ ? getPresetResolution(spec.viewport.range)
396
+ : spec.viewport.resolution,
391
397
  };
392
398
  }
393
399
 
@@ -1224,5 +1230,6 @@ export async function resolveChartSpecData(
1224
1230
  errors,
1225
1231
  warnings: [...new Set([...priorityWarnings, ...warnings])],
1226
1232
  viewport,
1233
+ resolution,
1227
1234
  };
1228
1235
  }
@@ -1,4 +1,4 @@
1
- import type { ChartResolution, TimeRange } from "./range";
1
+ import { CHART_RESOLUTIONS, TIME_RANGES, type ChartResolution, type TimeRange } from "./range";
2
2
  import { getChartResolutionLabel } from "./resolution";
3
3
  import {
4
4
  canonicalTimeSeriesFieldId,
@@ -31,8 +31,8 @@ import {
31
31
  isValidChartSeriesId,
32
32
  } from "../capabilities/chart-series";
33
33
 
34
- const TIME_RANGES = new Set<TimeRange>(["1D", "1W", "1M", "3M", "6M", "1Y", "5Y", "ALL"]);
35
- const RESOLUTIONS = new Set<ChartResolution>(["auto", "1m", "5m", "15m", "30m", "45m", "1h", "1d", "1wk", "1mo"]);
34
+ const RANGE_SET = new Set<TimeRange>(TIME_RANGES);
35
+ const RESOLUTIONS = new Set<ChartResolution>(CHART_RESOLUTIONS);
36
36
  const PERIODS = new Set<SeriesPeriod>(["auto", "daily", "weekly", "monthly", "quarterly", "annual", "ttm"]);
37
37
  const STYLES = new Set<SeriesStyle>(["line", "area", "step", "columns", "points", "candles", "ohlc", "hlc"]);
38
38
  const ECONOMIC_STYLES = new Set<SeriesStyle>(["line", "area", "step", "columns", "points"]);
@@ -306,7 +306,7 @@ export function normalizeChartSpec(value: unknown, fallback: ChartSpec = DEFAULT
306
306
  return {
307
307
  version: CHART_SPEC_VERSION,
308
308
  viewport: {
309
- range: TIME_RANGES.has(viewport?.range as TimeRange)
309
+ range: RANGE_SET.has(viewport?.range as TimeRange)
310
310
  ? viewport!.range as TimeRange
311
311
  : fallbackCopy.viewport.range,
312
312
  resolution: RESOLUTIONS.has(viewport?.resolution as ChartResolution)
@@ -340,7 +340,7 @@ export function validateChartSpec(spec: ChartSpec): ChartSpecValidationResult {
340
340
  if (spec.version !== CHART_SPEC_VERSION) {
341
341
  errors.push(issue("version", "unsupported-version", `Unsupported chart spec version ${String(spec.version)}.`));
342
342
  }
343
- if (!TIME_RANGES.has(spec.viewport.range)) errors.push(issue("viewport.range", "invalid-range", "Invalid date range."));
343
+ if (!RANGE_SET.has(spec.viewport.range)) errors.push(issue("viewport.range", "invalid-range", "Invalid date range."));
344
344
  if (!RESOLUTIONS.has(spec.viewport.resolution)) {
345
345
  errors.push(issue("viewport.resolution", "invalid-resolution", "Invalid chart resolution."));
346
346
  }
@@ -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 };
@@ -1,7 +1,7 @@
1
1
  import type { Portfolio, Watchlist } from "./ticker";
2
2
  import type { LanguagePreference } from "../i18n/languages";
3
3
 
4
- export const CURRENT_CONFIG_VERSION = 21;
4
+ export const CURRENT_CONFIG_VERSION = 22;
5
5
 
6
6
  type ChartRendererPreference = "auto" | "kitty" | "braille";
7
7
 
@@ -148,6 +148,13 @@ export interface AppConfig {
148
148
  activeLayoutIndex: number;
149
149
  brokerInstances: BrokerInstanceConfig[];
150
150
  disabledPlugins: string[];
151
+ /**
152
+ * Plugins that used to ship inside Gloomberb and have since moved to their own
153
+ * repositories. Recorded once installed, so an upgrade restores them without
154
+ * re-installing on every launch, and so a user who removes one on purpose is
155
+ * not fought with.
156
+ */
157
+ seededPlugins?: string[];
151
158
  disabledSources: string[];
152
159
  pluginConfig: Record<string, Record<string, unknown>>;
153
160
  theme: string;
@@ -264,6 +271,71 @@ const DEFAULT_HOME_LAYOUT: LayoutConfig = {
264
271
  detached: [],
265
272
  };
266
273
 
274
+ /**
275
+ * One screen for "where are we": valuation and the economy in the wide slots,
276
+ * with the faster sentiment, volatility and credit reads beside them. These stay
277
+ * separate panes because each measures a different thing on a different horizon;
278
+ * the layout is what puts them on one screen.
279
+ */
280
+ const DEFAULT_MACRO_LAYOUT: LayoutConfig = {
281
+ dockRoot: {
282
+ kind: "split",
283
+ axis: "vertical",
284
+ ratio: 0.54,
285
+ first: {
286
+ kind: "split",
287
+ axis: "horizontal",
288
+ ratio: 0.58,
289
+ first: { kind: "pane", instanceId: "market-valuation:macro" },
290
+ second: { kind: "pane", instanceId: "fear-greed:macro" },
291
+ },
292
+ second: {
293
+ kind: "split",
294
+ axis: "horizontal",
295
+ ratio: 0.58,
296
+ first: { kind: "pane", instanceId: "econ-statistics:macro" },
297
+ second: {
298
+ kind: "split",
299
+ axis: "vertical",
300
+ ratio: 0.5,
301
+ first: { kind: "pane", instanceId: "volatility-term-structure:macro" },
302
+ second: { kind: "pane", instanceId: "credit-conditions:macro" },
303
+ },
304
+ },
305
+ },
306
+ instances: [
307
+ {
308
+ instanceId: "market-valuation:macro",
309
+ paneId: "market-valuation",
310
+ binding: { kind: "none" },
311
+ settings: { indicator: "buffett", range: "25Y" },
312
+ },
313
+ {
314
+ instanceId: "econ-statistics:macro",
315
+ paneId: "econ-statistics",
316
+ binding: { kind: "none" },
317
+ settings: { stat: "cpi-yoy", range: "20Y" },
318
+ },
319
+ {
320
+ instanceId: "fear-greed:macro",
321
+ paneId: "fear-greed",
322
+ binding: { kind: "none" },
323
+ },
324
+ {
325
+ instanceId: "volatility-term-structure:macro",
326
+ paneId: "volatility-term-structure",
327
+ binding: { kind: "none" },
328
+ },
329
+ {
330
+ instanceId: "credit-conditions:macro",
331
+ paneId: "credit-conditions",
332
+ binding: { kind: "none" },
333
+ },
334
+ ],
335
+ floating: [],
336
+ detached: [],
337
+ };
338
+
267
339
  const DEFAULT_MONITOR_LAYOUT: LayoutConfig = {
268
340
  dockRoot: {
269
341
  kind: "split",
@@ -385,6 +457,14 @@ function getDockedPaneIdsFromNode(node: DockLayoutNode | null, result: string[]
385
457
  return result;
386
458
  }
387
459
 
460
+ export function getPlacedPaneInstanceIds(layout: LayoutConfig): string[] {
461
+ return [...new Set([
462
+ ...getDockedPaneIdsFromNode(layout.dockRoot),
463
+ ...layout.floating.map((entry) => entry.instanceId),
464
+ ...(layout.detached ?? []).map((entry) => entry.instanceId),
465
+ ])];
466
+ }
467
+
388
468
  export function createPaneInstanceId(paneId: string): string {
389
469
  nextPaneInstanceSeq += 1;
390
470
  return `${paneId}:${Date.now().toString(36)}${nextPaneInstanceSeq.toString(36)}`;
@@ -488,8 +568,24 @@ export function removePaneInstances(layout: LayoutConfig, instanceIds: Iterable<
488
568
  const removedIds = new Set(instanceIds);
489
569
  if (removedIds.size === 0) return layout;
490
570
 
491
- const instances = layout.instances.filter((instance) => !removedIds.has(instance.instanceId));
492
- const validInstanceIds = new Set(instances.map((instance) => instance.instanceId));
571
+ const retainedInstances = layout.instances.filter((instance) => !removedIds.has(instance.instanceId));
572
+ const validInstanceIds = new Set(retainedInstances.map((instance) => instance.instanceId));
573
+ const instances = retainedInstances
574
+ .map((instance) => {
575
+ const memory = clonePlacementMemory(instance.placementMemory);
576
+ if (!memory?.docked?.anchorInstanceId || validInstanceIds.has(memory.docked.anchorInstanceId)) {
577
+ return instance;
578
+ }
579
+ const docked = {
580
+ ...memory.docked,
581
+ anchorInstanceId: undefined,
582
+ };
583
+ const nextDocked = docked.path || docked.position ? docked : undefined;
584
+ const placementMemory = nextDocked || memory.floating || memory.detached
585
+ ? { ...memory, docked: nextDocked }
586
+ : undefined;
587
+ return { ...instance, placementMemory };
588
+ });
493
589
  const dockRoot = normalizeDockNode(layout.dockRoot, validInstanceIds, new Set<string>());
494
590
  const dockedPaneIds = new Set(getDockedPaneIdsFromNode(dockRoot));
495
591
  const detached = layout.detached ?? [];
@@ -502,6 +598,28 @@ export function removePaneInstances(layout: LayoutConfig, instanceIds: Iterable<
502
598
  };
503
599
  }
504
600
 
601
+ export function removeUnreachablePaneInstances(layout: LayoutConfig): LayoutConfig {
602
+ const instancesById = new Map(layout.instances.map((instance) => [instance.instanceId, instance] as const));
603
+ const reachableIds = new Set(getPlacedPaneInstanceIds(layout));
604
+ const pendingIds = [...reachableIds];
605
+
606
+ for (let index = 0; index < pendingIds.length; index += 1) {
607
+ const instance = instancesById.get(pendingIds[index]!);
608
+ if (instance?.binding?.kind !== "follow") continue;
609
+ const sourceId = instance.binding.sourceInstanceId;
610
+ if (!instancesById.has(sourceId) || reachableIds.has(sourceId)) continue;
611
+ reachableIds.add(sourceId);
612
+ pendingIds.push(sourceId);
613
+ }
614
+
615
+ return removePaneInstances(
616
+ layout,
617
+ layout.instances
618
+ .filter((instance) => !reachableIds.has(instance.instanceId))
619
+ .map((instance) => instance.instanceId),
620
+ );
621
+ }
622
+
505
623
  export function normalizePaneLayout(
506
624
  layout: LayoutConfig,
507
625
  options?: {
@@ -653,10 +771,12 @@ export function createDefaultConfig(dataDir: string): AppConfig {
653
771
  layouts: [
654
772
  { name: "Home", layout: cloneLayout(layout), paneState: {} },
655
773
  { name: "Monitor", layout: cloneLayout(DEFAULT_MONITOR_LAYOUT), paneState: {} },
774
+ { name: "Macro", layout: cloneLayout(DEFAULT_MACRO_LAYOUT), paneState: {} },
656
775
  ],
657
776
  activeLayoutIndex: 0,
658
777
  brokerInstances: [],
659
778
  disabledPlugins: [],
779
+ seededPlugins: [],
660
780
  disabledSources: [],
661
781
  pluginConfig: {},
662
782
  theme: "amber",