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,217 @@
1
+ import type { ProjectedChartPoint } from "../../../components/chart/core/data";
2
+ import { blendHex, colors } from "../../../theme/colors";
3
+ import type { RatioPoint } from "./align";
4
+ import type { ResolvedSeries, TimeSeriesPoint } from "../../../time-series/types";
5
+ import {
6
+ classifyZone,
7
+ shortZoneLabel,
8
+ zoneColor,
9
+ type IndicatorDef,
10
+ type ValuationZoneId,
11
+ } from "./defs";
12
+
13
+ export interface ChartMarkerLine {
14
+ value: number;
15
+ label: string;
16
+ }
17
+
18
+ interface ChartReferenceLine {
19
+ value: number;
20
+ color: string;
21
+ }
22
+
23
+ export interface ValuationChartProjection {
24
+ points: ProjectedChartPoint[];
25
+ /** The ratio points behind the projection, for the interactive chart. */
26
+ sourcePoints: RatioPoint[];
27
+ referenceLines: ChartReferenceLine[];
28
+ yDomain: { min: number; max: number };
29
+ yearLabels: string[];
30
+ lineColors: string[];
31
+ /** Captions the pane positions against the plot; the renderer draws lines only. */
32
+ markers: ChartMarkerLine[];
33
+ }
34
+
35
+ /** Snaps to whole grid steps, and opens a floor when the measure can go negative. */
36
+ export function niceDomain(
37
+ dataMin: number,
38
+ dataMax: number,
39
+ step: number,
40
+ ): { min: number; max: number } {
41
+ const span = step > 0 ? step : 1;
42
+ const hi = Number.isFinite(dataMax) && dataMax > 0 ? dataMax : 0;
43
+ const lo = Number.isFinite(dataMin) ? Math.min(0, dataMin) : 0;
44
+ return {
45
+ min: lo < 0 ? Math.floor(lo / span) * span : 0,
46
+ max: Math.max(span, Math.ceil(hi / span) * span),
47
+ };
48
+ }
49
+
50
+ export function meanRatio(points: readonly RatioPoint[]): number {
51
+ if (points.length === 0) return 0;
52
+ let total = 0;
53
+ for (const point of points) total += point.ratio;
54
+ return total / points.length;
55
+ }
56
+
57
+ /**
58
+ * @param visible points inside the selected range, which is what gets drawn
59
+ * @param mean full-history average, so the anchor does not move when the range changes
60
+ */
61
+ export function projectChart(
62
+ indicator: IndicatorDef,
63
+ visible: readonly RatioPoint[],
64
+ mean: number,
65
+ ): ValuationChartProjection {
66
+ const points: ProjectedChartPoint[] = visible.map((p) => ({
67
+ date: new Date(p.date),
68
+ open: p.ratio,
69
+ high: p.ratio,
70
+ low: p.ratio,
71
+ close: p.ratio,
72
+ volume: 0,
73
+ }));
74
+
75
+ let yMax = -Infinity;
76
+ let yMin = Infinity;
77
+ for (const point of visible) {
78
+ yMax = Math.max(yMax, point.ratio);
79
+ yMin = Math.min(yMin, point.ratio);
80
+ }
81
+ if (!Number.isFinite(yMax)) yMax = 0;
82
+ if (!Number.isFinite(yMin)) yMin = 0;
83
+ if (indicator.reference) {
84
+ yMax = Math.max(yMax, indicator.reference.value);
85
+ yMin = Math.min(yMin, indicator.reference.value);
86
+ }
87
+ yMax = Math.max(yMax, mean);
88
+ yMin = Math.min(yMin, mean);
89
+
90
+ const markers: ChartMarkerLine[] = [];
91
+ const referenceLines: ChartReferenceLine[] = [];
92
+ if (indicator.reference) {
93
+ referenceLines.push({ value: indicator.reference.value, color: colors.textDim });
94
+ markers.push({ value: indicator.reference.value, label: indicator.reference.label });
95
+ }
96
+ if (Number.isFinite(mean)) {
97
+ referenceLines.push({ value: mean, color: blendHex(colors.textDim, colors.bg, 0.35) });
98
+ markers.push({ value: mean, label: "mean" });
99
+ }
100
+
101
+ return {
102
+ points,
103
+ sourcePoints: [...visible],
104
+ referenceLines,
105
+ yDomain: niceDomain(yMin, yMax, indicator.chartGridStep),
106
+ yearLabels: chartYearLabels(points),
107
+ lineColors: visible.map((point) => classifyZone(indicator, point.ratio).color),
108
+ markers,
109
+ };
110
+ }
111
+
112
+ export function chartYearLabels(points: readonly ProjectedChartPoint[], maxLabels = 8): string[] {
113
+ if (points.length === 0) return [];
114
+ const years: string[] = [];
115
+ for (const point of points) {
116
+ const year = String(point.date.getFullYear());
117
+ if (years[years.length - 1] !== year) years.push(year);
118
+ }
119
+ if (years.length <= maxLabels) return years;
120
+
121
+ const start = Number(years[0]);
122
+ const end = Number(years[years.length - 1]);
123
+ let step = 5;
124
+ while (Math.floor((end - start) / step) + 1 > maxLabels) {
125
+ step = step === 5 ? 10 : step * 2;
126
+ if (step > 100) break;
127
+ }
128
+
129
+ const picked: string[] = [years[0]!];
130
+ for (const year of years) {
131
+ if (Number(year) % step !== 0) continue;
132
+ if (picked[picked.length - 1] !== year) picked.push(year);
133
+ }
134
+ const last = years[years.length - 1]!;
135
+ if (picked[picked.length - 1] !== last) picked.push(last);
136
+ return picked;
137
+ }
138
+
139
+ /**
140
+ * CompositeChart draws one colour per series, so the zone gradient is carried by
141
+ * one series per zone that holds nulls everywhere the ratio sits in another band.
142
+ * A run keeps its neighbours' boundary points so the segments join without gaps.
143
+ */
144
+ export function zoneSeriesFor(
145
+ indicator: IndicatorDef,
146
+ points: readonly RatioPoint[],
147
+ ): ResolvedSeries[] {
148
+ const byZone = new Map<ValuationZoneId, TimeSeriesPoint[]>();
149
+ const zones = points.map((point) => classifyZone(indicator, point.ratio));
150
+ for (const band of indicator.zones) {
151
+ if (!byZone.has(band.id)) byZone.set(band.id, []);
152
+ }
153
+
154
+ points.forEach((point, index) => {
155
+ const date = new Date(point.date);
156
+ const here = zones[index]!.id;
157
+ const before = zones[index - 1]?.id;
158
+ const after = zones[index + 1]?.id;
159
+ for (const [zoneId, series] of byZone) {
160
+ // Carry a boundary point into the neighbouring run so the line is continuous.
161
+ const belongs = zoneId === here || zoneId === before || zoneId === after;
162
+ series.push({ date, observedAt: date, value: belongs ? point.ratio : null });
163
+ }
164
+ });
165
+
166
+ const built: ResolvedSeries[] = [];
167
+ for (const [zoneId, series] of byZone) {
168
+ if (!series.some((entry) => entry.value != null)) continue;
169
+ built.push({
170
+ id: `zone:${zoneId}`,
171
+ label: shortZoneLabel(zoneId),
172
+ color: zoneColor(zoneId),
173
+ unit: indicator.axisUnit,
174
+ unitGroup: indicator.axisUnit === "%" ? "valuation-percent" : "valuation",
175
+ nativeFrequency: "daily",
176
+ dataShape: "scalar",
177
+ style: "line",
178
+ transform: "raw",
179
+ axis: "left",
180
+ panelId: "main",
181
+ interpolation: "none",
182
+ points: series,
183
+ });
184
+ }
185
+ return built;
186
+ }
187
+
188
+ /** Flat line at a fixed level, for parity, replacement cost, or the sample mean. */
189
+ export function markerSeries(
190
+ indicator: IndicatorDef,
191
+ id: string,
192
+ label: string,
193
+ value: number,
194
+ color: string,
195
+ points: readonly RatioPoint[],
196
+ ): ResolvedSeries | null {
197
+ if (points.length === 0 || !Number.isFinite(value)) return null;
198
+ const ends = [points[0]!, points[points.length - 1]!].map((point) => {
199
+ const date = new Date(point.date);
200
+ return { date, observedAt: date, value };
201
+ });
202
+ return {
203
+ id,
204
+ label,
205
+ color,
206
+ unit: indicator.axisUnit,
207
+ unitGroup: indicator.axisUnit === "%" ? "valuation-percent" : "valuation",
208
+ nativeFrequency: "daily",
209
+ dataShape: "scalar",
210
+ style: "line",
211
+ transform: "raw",
212
+ axis: "left",
213
+ panelId: "main",
214
+ interpolation: "none",
215
+ points: ends,
216
+ };
217
+ }
@@ -0,0 +1,89 @@
1
+ import { chartSeriesProvider } from "../../../capabilities";
2
+ import type { ChartSeriesCatalogItem, ChartSeriesCapability } from "../../../capabilities";
3
+ import { colors } from "../../../theme/colors";
4
+ import type { ResolvedSeries, TimeSeriesPoint } from "../../../time-series/types";
5
+ import { buildValuationSeries } from "./align";
6
+ import { defaultValuationSeriesLoader, type ValuationSeriesLoader } from "./client";
7
+ import { indicatorSeries, type IndicatorDef } from "./defs";
8
+ import { findIndicator, INDICATORS } from "./indicators";
9
+ import type { DatedSeries } from "./series";
10
+
11
+ export const MARKET_VALUATION_CAPABILITY_ID = "market-valuation";
12
+
13
+ function catalogItem(indicator: IndicatorDef): ChartSeriesCatalogItem {
14
+ return {
15
+ seriesId: indicator.id,
16
+ label: indicator.label,
17
+ description: indicator.description,
18
+ detail: "Market valuation",
19
+ style: "line",
20
+ };
21
+ }
22
+
23
+ function matches(indicator: IndicatorDef, query: string): boolean {
24
+ const haystack = `${indicator.id} ${indicator.label} ${indicator.shortLabel} ${indicator.description}`;
25
+ return haystack.toLowerCase().includes(query);
26
+ }
27
+
28
+ /**
29
+ * A ratio is not a series anyone can fetch, so the pane's own computation is what
30
+ * makes it chartable. Exposing it here lets G overlay, say, the Buffett indicator
31
+ * against CPI without the pane being involved.
32
+ */
33
+ export function createValuationChartSeriesCapability(
34
+ loader: ValuationSeriesLoader = defaultValuationSeriesLoader,
35
+ ): ChartSeriesCapability {
36
+ return chartSeriesProvider({
37
+ id: MARKET_VALUATION_CAPABILITY_ID,
38
+ name: "Market Valuation",
39
+ provider: {
40
+ catalog: ({ limit }) => INDICATORS.slice(0, limit ?? INDICATORS.length).map(catalogItem),
41
+ search: ({ query, limit }) => {
42
+ const needle = query?.trim().toLowerCase();
43
+ const hits = needle
44
+ ? INDICATORS.filter((indicator) => matches(indicator, needle))
45
+ : [...INDICATORS];
46
+ return hits.slice(0, limit ?? hits.length).map(catalogItem);
47
+ },
48
+ resolve: async ({ seriesId }) => resolveValuationSeries(seriesId, loader),
49
+ },
50
+ });
51
+ }
52
+
53
+ export async function resolveValuationSeries(
54
+ seriesId: string,
55
+ loader: ValuationSeriesLoader = defaultValuationSeriesLoader,
56
+ ): Promise<ResolvedSeries> {
57
+ const indicator = findIndicator(seriesId);
58
+ if (indicator.id !== seriesId.trim().toLowerCase()) {
59
+ throw new Error(`Unknown valuation series ${seriesId}`);
60
+ }
61
+
62
+ const legs = new Map<string, DatedSeries>();
63
+ await Promise.all(indicatorSeries(indicator).map(async (def) => {
64
+ legs.set(def.key, await loader(def));
65
+ }));
66
+
67
+ const built = buildValuationSeries(indicator, legs);
68
+ const points: TimeSeriesPoint[] = built.points.map((point) => {
69
+ const date = new Date(point.date);
70
+ return { date, observedAt: date, value: point.ratio };
71
+ });
72
+
73
+ return {
74
+ id: `market-valuation:${indicator.id}`,
75
+ label: indicator.label,
76
+ color: colors.textBright,
77
+ // Percent-scaled ratios share an axis with each other, never with a price.
78
+ unit: indicator.ratioScale === 100 ? "%" : "x",
79
+ unitGroup: indicator.ratioScale === 100 ? "valuation-percent" : "valuation-ratio",
80
+ nativeFrequency: "daily",
81
+ dataShape: "scalar",
82
+ style: "line",
83
+ transform: "raw",
84
+ axis: "left",
85
+ panelId: "main",
86
+ interpolation: "none",
87
+ points,
88
+ };
89
+ }
@@ -0,0 +1,141 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
+ import { MemoryPluginPersistence } from "../../../test-support/plugin-persistence";
3
+ import { attachValuationPersistence, resetValuationPersistence } from "./cache";
4
+ import { loadValuationBundle, requiredSeries, type ValuationSeriesLoader } from "./client";
5
+ import { BUFFETT_INDICATOR, INDICATORS, SHILLER_CAPE, TOBINS_Q } from "./indicators";
6
+ import type { DatedObservation, DatedSeries } from "./series";
7
+ import { createSourceLoader, shillerObservations } from "./sources";
8
+
9
+ function obs(values: Array<[string, number]>): DatedObservation[] {
10
+ return values.map(([date, value]) => ({ date, value }));
11
+ }
12
+
13
+ const LEGS: Record<string, DatedObservation[]> = {
14
+ W5000: obs([["2024-01-02", 40_000], ["2025-01-02", 45_000]]),
15
+ GDP: obs([["2024-01-01", 25_000], ["2025-01-01", 26_000]]),
16
+ M2SL: obs([["2024-01-01", 20_000], ["2025-01-01", 21_000]]),
17
+ NCBEILQ027S: obs([["2024-01-01", 60_000_000], ["2025-01-01", 64_000_000]]),
18
+ TNWMVBSNNCB: obs([["2024-01-01", 40_000_000], ["2025-01-01", 41_000_000]]),
19
+ BOGZ1FL153064486Q: obs([["2024-01-01", 38], ["2025-01-01", 45.8]]),
20
+ BOGZ1FL663067003Q: obs([["2024-01-01", 600000], ["2025-01-01", 650000]]),
21
+ CPROFIT: obs([["2024-01-01", 3400], ["2025-01-01", 4800]]),
22
+ SHILLER_CAPE: obs([["2024-01-01", 33.2], ["2025-01-01", 38.1]]),
23
+ SHILLER_ECY: obs([["2024-01-01", 0.021], ["2025-01-01", 0.013]]),
24
+ SHILLER_DIVIDEND: obs([["2024-01-01", 70], ["2025-01-01", 78]]),
25
+ SHILLER_PRICE: obs([["2024-01-01", 4800], ["2025-01-01", 6000]]),
26
+ };
27
+
28
+ const everyLeg: ValuationSeriesLoader = async (def) => {
29
+ const observations = LEGS[def.key];
30
+ if (!observations) throw new Error(`unexpected ${def.key}`);
31
+ return { seriesId: def.key, observations, provenance: "fred" } satisfies DatedSeries;
32
+ };
33
+
34
+ beforeEach(() => {
35
+ resetValuationPersistence();
36
+ attachValuationPersistence(new MemoryPluginPersistence());
37
+ });
38
+ afterEach(resetValuationPersistence);
39
+
40
+ describe("requiredSeries", () => {
41
+ test("fetches a leg shared by two indicators only once", () => {
42
+ // Buffett and Cap/M2 share the Wilshire leg.
43
+ const keys = requiredSeries(INDICATORS).map((def) => def.key);
44
+ expect(keys.filter((key) => key === "W5000")).toHaveLength(1);
45
+ expect(new Set(keys).size).toBe(keys.length);
46
+ });
47
+ });
48
+
49
+ describe("loadValuationBundle", () => {
50
+ test("builds every registered indicator", async () => {
51
+ const bundle = await loadValuationBundle({ loader: everyLeg });
52
+ expect(bundle.builds.map((build) => build.indicator.id))
53
+ .toEqual(INDICATORS.map((indicator) => indicator.id));
54
+ });
55
+
56
+ test("one broken leg only drops the indicators that need it", async () => {
57
+ const bundle = await loadValuationBundle({
58
+ loader: async (def) => {
59
+ if (def.key === "W5000") throw new Error("history unavailable");
60
+ return everyLeg(def);
61
+ },
62
+ });
63
+ const ids = bundle.builds.map((build) => build.indicator.id);
64
+ expect(ids).not.toContain("buffett");
65
+ expect(ids).not.toContain("market-cap-m2");
66
+ expect(ids).toContain("shiller-cape");
67
+ expect(bundle.errors.join(" ")).toContain("W5000");
68
+ });
69
+
70
+ test("throws when nothing builds", async () => {
71
+ await expect(loadValuationBundle({
72
+ loader: async () => { throw new Error("offline"); },
73
+ })).rejects.toThrow("offline");
74
+ });
75
+ });
76
+
77
+ describe("createSourceLoader", () => {
78
+ test("fetches the Shiller dataset once for every column that needs it", async () => {
79
+ let shillerCalls = 0;
80
+ const loader = createSourceLoader({
81
+ loadFred: async () => obs([["2024-01-01", 1]]),
82
+ loadMarketHistory: async () => obs([["2024-01-01", 1]]),
83
+ loadShiller: async () => {
84
+ shillerCalls += 1;
85
+ return {
86
+ observations: [
87
+ { date: "2026-08-01", price: 7600, dividend: 81, earnings: null, cpi: 333, longRate: 4.75, cape: 41.2, excessCapeYield: 0.0097 },
88
+ ],
89
+ sourceUrl: "https://example.test/ie_data.xls",
90
+ fetchedAt: "2026-09-02T00:00:00.000Z",
91
+ };
92
+ },
93
+ });
94
+
95
+ const shillerLegs = requiredSeries(INDICATORS)
96
+ .filter((def) => def.source.kind === "shiller");
97
+ expect(shillerLegs.length).toBeGreaterThan(1);
98
+ const loaded = await Promise.all(shillerLegs.map((def) => loader(def)));
99
+ expect(shillerCalls).toBe(1);
100
+ expect(loaded.every((entry) => entry.observations.length === 1)).toBe(true);
101
+ });
102
+
103
+ test("routes each source kind to its own transport", async () => {
104
+ const seen: string[] = [];
105
+ const loader = createSourceLoader({
106
+ loadFred: async (seriesId) => { seen.push(`fred:${seriesId}`); return obs([["2024-01-01", 1]]); },
107
+ loadMarketHistory: async (symbol) => { seen.push(`history:${symbol}`); return obs([["2024-01-01", 1]]); },
108
+ loadShiller: async () => { throw new Error("not needed"); },
109
+ });
110
+ if (BUFFETT_INDICATOR.input.kind !== "ratio") throw new Error("expected a ratio");
111
+ await loader(BUFFETT_INDICATOR.input.numerator);
112
+ await loader(BUFFETT_INDICATOR.input.denominator);
113
+ if (TOBINS_Q.input.kind !== "ratio") throw new Error("expected a ratio");
114
+ await loader(TOBINS_Q.input.numerator);
115
+ expect(seen).toEqual(["history:^W5000", "fred:GDP", "fred:NCBEILQ027S"]);
116
+ });
117
+ });
118
+
119
+ describe("shillerObservations", () => {
120
+ test("keeps a column's nulls rather than dropping the month", () => {
121
+ const observations = shillerObservations({
122
+ observations: [
123
+ { date: "2026-07-01", price: 7481, dividend: null, earnings: null, cpi: 333, longRate: 4.6, cape: 40.6, excessCapeYield: 0.0118 },
124
+ { date: "2026-08-01", price: 7600, dividend: 81, earnings: null, cpi: 333, longRate: 4.75, cape: 41.2, excessCapeYield: 0.0097 },
125
+ ],
126
+ sourceUrl: "x",
127
+ fetchedAt: "y",
128
+ }, "dividend");
129
+ expect(observations.map((entry) => entry.value)).toEqual([null, 81]);
130
+ });
131
+
132
+ test("throws when a column is entirely empty", () => {
133
+ expect(() => shillerObservations({
134
+ observations: [
135
+ { date: "2026-08-01", price: 7600, dividend: null, earnings: null, cpi: null, longRate: null, cape: null, excessCapeYield: null },
136
+ ],
137
+ sourceUrl: "x",
138
+ fetchedAt: "y",
139
+ }, "earnings")).toThrow("no earnings observations");
140
+ });
141
+ });
@@ -0,0 +1,116 @@
1
+ import { buildValuationSeries } from "./align";
2
+ import { getCachedSeries, loadCachedSeries } from "./cache";
3
+ import { indicatorSeries, type IndicatorDef, type SeriesDef } from "./defs";
4
+ import { INDICATORS } from "./indicators";
5
+ import type { DatedSeries } from "./series";
6
+ import {
7
+ cloudSourceDeps,
8
+ createSourceLoader,
9
+ provenanceFor,
10
+ type ValuationSourceDeps,
11
+ } from "./sources";
12
+ import { fitIndicatorTrend } from "./trend";
13
+ import type { IndicatorBuild, ValuationBundle } from "./view";
14
+
15
+ export type ValuationSeriesLoader = (def: SeriesDef) => Promise<DatedSeries>;
16
+
17
+ /** Every distinct leg the registry needs, so shared series are fetched once. */
18
+ export function requiredSeries(
19
+ indicators: readonly IndicatorDef[] = INDICATORS,
20
+ ): SeriesDef[] {
21
+ const seen = new Map<string, SeriesDef>();
22
+ for (const indicator of indicators) {
23
+ for (const def of indicatorSeries(indicator)) {
24
+ if (!seen.has(def.key)) seen.set(def.key, def);
25
+ }
26
+ }
27
+ return [...seen.values()];
28
+ }
29
+
30
+ /** Cache-first around the cloud sources; exported so Bun-side tooling can reuse it. */
31
+ export function createValuationSeriesLoader(deps: ValuationSourceDeps): ValuationSeriesLoader {
32
+ const cloudLoader = createSourceLoader(deps);
33
+ return async (def) => ({
34
+ seriesId: def.key,
35
+ observations: await loadCachedSeries(def.key, async () => (await cloudLoader(def)).observations),
36
+ provenance: provenanceFor(def),
37
+ });
38
+ }
39
+
40
+ export const defaultValuationSeriesLoader = createValuationSeriesLoader(cloudSourceDeps);
41
+
42
+ /** Builds whatever the on-disk cache can already answer, for an instant first paint. */
43
+ export function getCachedValuationBundle(
44
+ indicators: readonly IndicatorDef[] = INDICATORS,
45
+ ): ValuationBundle | null {
46
+ const legs = new Map<string, DatedSeries>();
47
+ for (const def of requiredSeries(indicators)) {
48
+ const cached = getCachedSeries(def.key, { allowExpired: true });
49
+ if (!cached) continue;
50
+ legs.set(def.key, {
51
+ seriesId: def.key,
52
+ observations: cached.observations,
53
+ provenance: provenanceFor(def),
54
+ });
55
+ }
56
+ const builds = buildIndicators(legs, [], indicators);
57
+ return builds.length > 0 ? { builds, errors: [], fetchedAt: Date.now() } : null;
58
+ }
59
+
60
+ function buildIndicators(
61
+ legs: Map<string, DatedSeries>,
62
+ errors: string[],
63
+ indicators: readonly IndicatorDef[],
64
+ ): IndicatorBuild[] {
65
+ const builds: IndicatorBuild[] = [];
66
+ for (const indicator of indicators) {
67
+ if (indicatorSeries(indicator).some((def) => !legs.has(def.key))) continue;
68
+ try {
69
+ const series = buildValuationSeries(indicator, legs);
70
+ builds.push({
71
+ indicator,
72
+ series,
73
+ trend: fitIndicatorTrend(indicator, series.points),
74
+ });
75
+ } catch (error) {
76
+ errors.push(`${indicator.label}: ${error instanceof Error ? error.message : String(error)}`);
77
+ }
78
+ }
79
+ return builds;
80
+ }
81
+
82
+ function summarizeErrors(errors: readonly string[]): string {
83
+ if (errors.length === 0) return "Market valuation data unavailable";
84
+ const reasons = new Set(errors.map((entry) => entry.replace(/^[^:]+:\s*/, "")));
85
+ const reason = reasons.size === 1 ? [...reasons][0]! : `${errors.length} series failed`;
86
+ return errors.length > 1 ? `${reason} (${errors.length} series)` : reason;
87
+ }
88
+
89
+ export async function loadValuationBundle(options?: {
90
+ loader?: ValuationSeriesLoader;
91
+ indicators?: readonly IndicatorDef[];
92
+ }): Promise<ValuationBundle> {
93
+ const loader = options?.loader ?? defaultValuationSeriesLoader;
94
+ const indicators = options?.indicators ?? INDICATORS;
95
+ const errors: string[] = [];
96
+ const legs = new Map<string, DatedSeries>();
97
+
98
+ const settled = await Promise.all(requiredSeries(indicators).map(async (def) => {
99
+ try {
100
+ return { def, data: await loader(def) };
101
+ } catch (error) {
102
+ return {
103
+ def,
104
+ error: `${def.key}: ${error instanceof Error ? error.message : String(error)}`,
105
+ };
106
+ }
107
+ }));
108
+ for (const outcome of settled) {
109
+ if ("error" in outcome && outcome.error) errors.push(outcome.error);
110
+ if ("data" in outcome && outcome.data) legs.set(outcome.def.key, outcome.data);
111
+ }
112
+
113
+ const builds = buildIndicators(legs, errors, indicators);
114
+ if (builds.length === 0) throw new Error(summarizeErrors(errors));
115
+ return { builds, errors, fetchedAt: Date.now() };
116
+ }