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,251 @@
1
+ import { blendHex, colors } from "../../../theme/colors";
2
+
3
+ export type ValuationRangeId = "10Y" | "25Y" | "ALL";
4
+
5
+ /** Columns of Shiller's monthly dataset the pane can chart. */
6
+ export type ShillerField =
7
+ | "price"
8
+ | "dividend"
9
+ | "earnings"
10
+ | "cpi"
11
+ | "longRate"
12
+ | "cape"
13
+ | "excessCapeYield";
14
+
15
+ /**
16
+ * Every leg resolves through the Gloom Cloud proxy. Nothing here talks to Yahoo or
17
+ * FRED directly, which is what lets the pane run in the hosted browser build.
18
+ */
19
+ export type SeriesSource =
20
+ | { kind: "fred"; seriesId: string; limit: number }
21
+ | { kind: "market-history"; symbol: string; exchange: string; startDate: string }
22
+ | { kind: "shiller"; field: ShillerField };
23
+
24
+ export interface SeriesDef {
25
+ /** Stable identity for caching and for de-duplicating legs shared by indicators. */
26
+ key: string;
27
+ /** Multiplier that brings a raw observation into billions of dollars. */
28
+ scaleToBillions: number;
29
+ source: SeriesSource;
30
+ }
31
+
32
+ export type ValuationZoneId =
33
+ | "significantly-undervalued"
34
+ | "modestly-undervalued"
35
+ | "fair"
36
+ | "modestly-overvalued"
37
+ | "significantly-overvalued";
38
+
39
+ export interface ZoneHit {
40
+ id: ValuationZoneId;
41
+ label: string;
42
+ color: string;
43
+ }
44
+
45
+ /**
46
+ * Upper edge of a valuation band, in the indicator's own units, ascending.
47
+ * `max: null` is the open top band. Bands carry their own id, so an indicator
48
+ * where a high reading means cheap (a yield spread) simply lists them in the
49
+ * opposite order rather than needing an inversion flag.
50
+ */
51
+ export interface ZoneBand {
52
+ max: number | null;
53
+ id: ValuationZoneId;
54
+ label: string;
55
+ }
56
+
57
+ export interface ZoneScaleBand {
58
+ id: ValuationZoneId;
59
+ from: number;
60
+ to: number;
61
+ color: string;
62
+ }
63
+
64
+ /** A ratio of two series, or a single series that is already the measure. */
65
+ export type IndicatorInput =
66
+ | {
67
+ kind: "ratio";
68
+ numerator: SeriesDef;
69
+ denominator: SeriesDef;
70
+ /**
71
+ * Set only when both legs are dollar amounts worth showing. A ratio of two
72
+ * index columns has no meaningful level to print, so it omits this.
73
+ */
74
+ levels?: { numeratorLabel: string; denominatorLabel: string };
75
+ }
76
+ | { kind: "direct"; series: SeriesDef };
77
+
78
+ export interface IndicatorDef {
79
+ id: string;
80
+ /** Full name for the detail heading and catalog copy. */
81
+ label: string;
82
+ /** Fits the summary table's INDICATOR column. */
83
+ shortLabel: string;
84
+ description: string;
85
+ input: IndicatorInput;
86
+ /** Multiplies the raw value: 100 renders a quotient as a percent. */
87
+ ratioScale: number;
88
+ formatValue: (value: number) => string;
89
+ /** Drives the chart's own axis and cursor formatting. "" leaves a bare number. */
90
+ axisUnit: "%" | "";
91
+ zones: readonly ZoneBand[];
92
+ zoneScale: {
93
+ min: number;
94
+ max: number;
95
+ /** Band edges, low to high, from `min` to `max`. Each band gets equal width. */
96
+ edges: readonly number[];
97
+ ticks: readonly number[];
98
+ };
99
+ /** Horizontal line drawn on the chart, e.g. Buffett's 100% parity. */
100
+ reference: { value: number; label: string } | null;
101
+ /** Y-axis gridline spacing, in the indicator's own units. */
102
+ chartGridStep: number;
103
+ /**
104
+ * Log-linear suits a level that compounds. A measure that can sit at or below
105
+ * zero, like an excess yield, needs the linear fit.
106
+ */
107
+ trendModel: "log" | "linear";
108
+ /** How old the newest observation may get before the pane flags it stale. */
109
+ staleAfterMs: number;
110
+ notes: string[];
111
+ link: { url: string; label: string } | null;
112
+ }
113
+
114
+ const MS_PER_YEAR = 365.25 * 24 * 60 * 60 * 1000;
115
+
116
+ export const RANGE_WINDOWS_MS: { readonly [K in ValuationRangeId]: number | null } = {
117
+ "10Y": 10 * MS_PER_YEAR,
118
+ "25Y": 25 * MS_PER_YEAR,
119
+ ALL: null,
120
+ };
121
+
122
+ export function zoneColor(id: ValuationZoneId): string {
123
+ switch (id) {
124
+ case "significantly-undervalued":
125
+ return blendHex(colors.positive, "#000000", 0.2);
126
+ case "modestly-undervalued":
127
+ return colors.positive;
128
+ case "fair":
129
+ return colors.textBright;
130
+ case "modestly-overvalued":
131
+ return blendHex(colors.warning, colors.negative, 0.25);
132
+ case "significantly-overvalued":
133
+ return blendHex(colors.negative, "#000000", 0.2);
134
+ default: {
135
+ const _exhaustive: never = id;
136
+ return _exhaustive;
137
+ }
138
+ }
139
+ }
140
+
141
+ /** Column-width form of the zone name for the summary table. */
142
+ export function shortZoneLabel(id: ValuationZoneId): string {
143
+ switch (id) {
144
+ case "significantly-undervalued":
145
+ return "Sig. under";
146
+ case "modestly-undervalued":
147
+ return "Under";
148
+ case "fair":
149
+ return "Fair";
150
+ case "modestly-overvalued":
151
+ return "Over";
152
+ case "significantly-overvalued":
153
+ return "Sig. over";
154
+ default: {
155
+ const _exhaustive: never = id;
156
+ return _exhaustive;
157
+ }
158
+ }
159
+ }
160
+
161
+ /**
162
+ * True when a bigger number means a more expensive market. A yield-shaped measure
163
+ * runs the other way, and lists its bands starting at overvalued to say so. The
164
+ * summary table uses this to put every row on one "expensive" scale, so a column
165
+ * can be read straight down.
166
+ */
167
+ export function higherIsExpensive(indicator: IndicatorDef): boolean {
168
+ return indicator.zones[0]?.id !== "significantly-overvalued";
169
+ }
170
+
171
+ export function classifyZone(indicator: IndicatorDef, value: number): ZoneHit {
172
+ for (const band of indicator.zones) {
173
+ if (band.max == null || value < band.max) {
174
+ return { id: band.id, label: band.label, color: zoneColor(band.id) };
175
+ }
176
+ }
177
+ const last = indicator.zones[indicator.zones.length - 1]!;
178
+ return { id: last.id, label: last.label, color: zoneColor(last.id) };
179
+ }
180
+
181
+ export function zoneScaleBands(indicator: IndicatorDef): ZoneScaleBand[] {
182
+ const { min, max } = indicator.zoneScale;
183
+ let from = min;
184
+ return indicator.zones.map((band) => {
185
+ const to = band.max == null ? max : band.max;
186
+ const entry = { id: band.id, from, to, color: zoneColor(band.id) };
187
+ from = band.max ?? from;
188
+ return entry;
189
+ });
190
+ }
191
+
192
+ /**
193
+ * Position a value on the scale as a 0..1 fraction. Each valuation band gets equal
194
+ * width so fair sits near the visual center instead of being pushed to one side by
195
+ * a long open-ended band.
196
+ */
197
+ export function zoneScaleFraction(indicator: IndicatorDef, value: number): number {
198
+ const edges = indicator.zoneScale.edges;
199
+ const bands = edges.length - 1;
200
+ if (bands <= 0) return 0;
201
+ const { min, max } = indicator.zoneScale;
202
+ const clamped = Math.max(min, Math.min(max, value));
203
+ let bandIdx = bands - 1;
204
+ for (let i = 0; i < bands; i += 1) {
205
+ if (clamped < edges[i + 1]! || i === bands - 1) {
206
+ bandIdx = i;
207
+ break;
208
+ }
209
+ }
210
+ const lo = edges[bandIdx]!;
211
+ const hi = edges[bandIdx + 1]!;
212
+ const t = hi === lo ? 0 : (clamped - lo) / (hi - lo);
213
+ return (bandIdx + t) / bands;
214
+ }
215
+
216
+ /** Inverse of zoneScaleFraction: scale position back to the indicator's units. */
217
+ export function zoneScaleValueAt(indicator: IndicatorDef, fraction: number): number {
218
+ const edges = indicator.zoneScale.edges;
219
+ const bands = edges.length - 1;
220
+ if (bands <= 0) return 0;
221
+ const pos = Math.min(bands, Math.max(0, fraction * bands));
222
+ const bandIdx = Math.min(bands - 1, Math.floor(pos));
223
+ const lo = edges[bandIdx]!;
224
+ const hi = edges[bandIdx + 1]!;
225
+ return lo + (pos - bandIdx) * (hi - lo);
226
+ }
227
+
228
+ export function zoneScaleMarkerColumn(
229
+ indicator: IndicatorDef,
230
+ value: number,
231
+ width: number,
232
+ ): number {
233
+ if (width <= 1) return 0;
234
+ return Math.round(zoneScaleFraction(indicator, value) * (width - 1));
235
+ }
236
+
237
+ export function zoneScaleColumnValue(
238
+ indicator: IndicatorDef,
239
+ column: number,
240
+ width: number,
241
+ ): number {
242
+ if (width <= 1) return 0;
243
+ return zoneScaleValueAt(indicator, column / Math.max(width - 1, 1));
244
+ }
245
+
246
+ /** Every leg an indicator needs, so callers can fetch without knowing its shape. */
247
+ export function indicatorSeries(indicator: IndicatorDef): SeriesDef[] {
248
+ return indicator.input.kind === "ratio"
249
+ ? [indicator.input.numerator, indicator.input.denominator]
250
+ : [indicator.input.series];
251
+ }
@@ -0,0 +1,159 @@
1
+ import { useMemo, useState } from "react";
2
+ import { CompositeChart } from "../../../components/chart/composite";
3
+ import { ExternalLinkText } from "../../../components/ui";
4
+ import { blendHex, colors } from "../../../theme/colors";
5
+ import { Box, Text } from "../../../ui";
6
+ import { formatNumber } from "../../../utils/format";
7
+ import { markerSeries, zoneSeriesFor } from "./chart-projection";
8
+ import type { IndicatorViewModel } from "./view";
9
+ import { ZoneColorScale } from "./zone-scale";
10
+
11
+ function formatTrillions(billions: number): string {
12
+ return `${formatNumber(billions / 1000, 1)}T`;
13
+ }
14
+
15
+ export function IndicatorDetail({
16
+ view,
17
+ width,
18
+ height,
19
+ focused = false,
20
+ }: {
21
+ view: IndicatorViewModel;
22
+ width: number;
23
+ height: number;
24
+ focused?: boolean;
25
+ }) {
26
+ const PANELS = [{ id: "main" }];
27
+ const indicator = view.indicator;
28
+ const levels = indicator.input.kind === "ratio" ? indicator.input.levels : undefined;
29
+ const [userViewport, setUserViewport] = useState<{ start: Date; end: Date } | null>(null);
30
+ const chartWidth = Math.max(24, width - 2);
31
+ const AXIS_WIDTH = 8;
32
+ // Give a tall pane a taller plot instead of leaving the space empty below.
33
+ const chartHeight = Math.max(8, Math.min(26, height - 16));
34
+
35
+ const visible = view.chart.sourcePoints;
36
+ const series = useMemo(() => {
37
+ const zones = zoneSeriesFor(indicator, visible);
38
+ const markers = [
39
+ indicator.reference
40
+ ? markerSeries(
41
+ indicator,
42
+ "reference",
43
+ indicator.reference.label,
44
+ indicator.reference.value,
45
+ colors.textDim,
46
+ visible,
47
+ )
48
+ : null,
49
+ markerSeries(
50
+ indicator,
51
+ "mean",
52
+ "mean",
53
+ view.mean,
54
+ blendHex(colors.textDim, colors.bg, 0.35),
55
+ visible,
56
+ ),
57
+ ].filter((entry) => entry != null);
58
+ return [...markers, ...zones];
59
+ }, [indicator, view.mean, visible]);
60
+
61
+ // One clean entry instead of a row per valuation band.
62
+ const legendSeries = useMemo(
63
+ () => series.filter((entry) => entry.id === "reference" || entry.id === "mean"),
64
+ [series],
65
+ );
66
+
67
+ const viewport = useMemo(() => {
68
+ if (userViewport) return userViewport;
69
+ if (visible.length < 2) return undefined;
70
+ return { start: new Date(visible[0]!.date), end: new Date(visible.at(-1)!.date) };
71
+ }, [userViewport, visible]);
72
+
73
+
74
+ return (
75
+ <Box flexDirection="column" width={width} paddingX={1} gap={1}>
76
+ {view.chart.points.length >= 2 ? (
77
+ <Box flexDirection="column" gap={0}>
78
+ <Box flexDirection="row" width={chartWidth} overflow="hidden">
79
+ <Text>{" ".repeat(AXIS_WIDTH)}</Text>
80
+ <ZoneColorScale
81
+ indicator={indicator}
82
+ value={view.current.ratio}
83
+ width={Math.max(1, chartWidth - AXIS_WIDTH)}
84
+ markerColor={view.zone.color}
85
+ />
86
+ </Box>
87
+ <CompositeChart
88
+ series={series}
89
+ legendSeries={legendSeries}
90
+ panels={PANELS}
91
+ width={chartWidth}
92
+ height={chartHeight}
93
+ focused={focused}
94
+ interactive
95
+ axisWidth={AXIS_WIDTH}
96
+ showLegend
97
+ viewport={viewport}
98
+ viewportResetKey={`${indicator.id}:${view.range}`}
99
+ onViewportChange={setUserViewport}
100
+ formatValue={(value) => indicator.formatValue(value)}
101
+ emptyMessage="Not enough chart data"
102
+ />
103
+ </Box>
104
+ ) : (
105
+ <Box height={chartHeight} justifyContent="center" alignItems="center">
106
+ <Text fg={colors.textMuted}>Not enough chart data</Text>
107
+ </Box>
108
+ )}
109
+
110
+ <Box flexDirection="column" gap={0}>
111
+ {levels && view.current.numeratorBillions != null
112
+ && view.current.denominatorBillions != null ? (
113
+ <Box flexDirection="row" height={1} overflow="hidden">
114
+ <Text fg={colors.textDim}>{`${levels.numeratorLabel} `}</Text>
115
+ <Text fg={colors.textBright}>{formatTrillions(view.current.numeratorBillions)}</Text>
116
+ <Text fg={colors.textDim}>{` ${levels.denominatorLabel} `}</Text>
117
+ <Text fg={colors.textBright}>{formatTrillions(view.current.denominatorBillions)}</Text>
118
+ {view.vintageLabel ? (
119
+ <Text fg={colors.textDim}>{` ${view.vintageLabel}`}</Text>
120
+ ) : null}
121
+ </Box>
122
+ ) : null}
123
+ <Box flexDirection="row" height={1} overflow="hidden">
124
+ <Text fg={colors.textDim}>1Y ago </Text>
125
+ <Text fg={colors.text}>
126
+ {view.ratioOneYearAgo == null ? "--" : indicator.formatValue(view.ratioOneYearAgo)}
127
+ </Text>
128
+ <Text fg={colors.textDim}>{" mean "}</Text>
129
+ <Text fg={colors.text}>{indicator.formatValue(view.mean)}</Text>
130
+ <Text fg={colors.textDim}>{" ATH "}</Text>
131
+ <Text fg={colors.text}>
132
+ {`${indicator.formatValue(view.allTimeHigh.ratio)} ${view.allTimeHigh.date}`}
133
+ </Text>
134
+ <Text fg={colors.textDim}>{" ATL "}</Text>
135
+ <Text fg={colors.text}>
136
+ {`${indicator.formatValue(view.allTimeLow.ratio)} ${view.allTimeLow.date}`}
137
+ </Text>
138
+ </Box>
139
+ </Box>
140
+
141
+ <Box flexDirection="column" gap={1} width={Math.max(1, width - 2)}>
142
+ {indicator.notes.map((note) => (
143
+ <Text key={note.slice(0, 24)} fg={colors.textDim} wrapMode="word" wrapText>
144
+ {note}
145
+ </Text>
146
+ ))}
147
+ {indicator.link ? (
148
+ <Box flexDirection="row" height={1} overflow="hidden">
149
+ <ExternalLinkText
150
+ url={indicator.link.url}
151
+ label={indicator.link.label}
152
+ color={colors.text}
153
+ />
154
+ </Box>
155
+ ) : null}
156
+ </Box>
157
+ </Box>
158
+ );
159
+ }
@@ -0,0 +1,58 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
+ import { createTestDataProvider } from "../../../test-support/data-provider";
3
+ import { createDefaultConfig } from "../../../types/config";
4
+ import type { HeadlessPaneContext, HeadlessPaneLoadArgs } from "../../../types/plugin";
5
+ import { resetValuationPersistence } from "./cache";
6
+ import { marketValuationHeadless } from "./headless";
7
+
8
+ beforeEach(resetValuationPersistence);
9
+ afterEach(resetValuationPersistence);
10
+
11
+ function loadArgs(): HeadlessPaneLoadArgs {
12
+ return {
13
+ rawArgument: "cape",
14
+ argument: "cape",
15
+ symbols: [],
16
+ options: { range: "10Y" },
17
+ };
18
+ }
19
+
20
+ describe("market valuation headless model", () => {
21
+ test("loads through the injected cloud client and produces bundle sections", async () => {
22
+ let shillerCalls = 0;
23
+ const apiClient = {
24
+ getCloudFredSeries: async () => { throw new Error("unexpected FRED request"); },
25
+ getCloudHistory: async () => { throw new Error("unexpected history request"); },
26
+ getCloudShiller: async () => {
27
+ shillerCalls += 1;
28
+ return {
29
+ observations: [
30
+ { date: "2024-01-01", price: 4800, dividend: 70, earnings: 180, cpi: 310, longRate: 4, cape: 32, excessCapeYield: 0.02 },
31
+ { date: "2025-01-01", price: 5900, dividend: 76, earnings: 200, cpi: 320, longRate: 4.2, cape: 36, excessCapeYield: 0.015 },
32
+ { date: "2026-01-01", price: 6300, dividend: 80, earnings: 220, cpi: 330, longRate: 4.4, cape: 39, excessCapeYield: 0.01 },
33
+ ],
34
+ sourceUrl: "https://example.test/shiller.xls",
35
+ fetchedAt: "2026-01-02T00:00:00Z",
36
+ };
37
+ },
38
+ } as unknown as HeadlessPaneContext["apiClient"];
39
+ const context: HeadlessPaneContext = {
40
+ marketData: createTestDataProvider(),
41
+ apiClient,
42
+ config: createDefaultConfig("/tmp/gloomberb-headless-valuation"),
43
+ signal: new AbortController().signal,
44
+ };
45
+
46
+ const result = await marketValuationHeadless.load(loadArgs(), context);
47
+
48
+ expect(shillerCalls).toBe(1);
49
+ expect(result.sections.map(({ title }) => title)).toEqual([
50
+ "Market valuation",
51
+ "Shiller CAPE",
52
+ ]);
53
+ expect(result.sections[0]).toMatchObject({
54
+ rows: [{ id: "shiller-cape", value: 39, formattedValue: "39.0" }],
55
+ });
56
+ expect(result.metadata).toMatchObject({ range: "10Y", selected: "shiller-cape" });
57
+ });
58
+ });
@@ -0,0 +1,170 @@
1
+ import type {
2
+ HeadlessBundleResult,
3
+ HeadlessPaneDefinition,
4
+ HeadlessPaneEntry,
5
+ HeadlessPaneLoadArgs,
6
+ } from "../../../types/plugin";
7
+ import { formatNumber } from "../../../utils/format";
8
+ import {
9
+ createValuationSeriesLoader,
10
+ loadValuationBundle,
11
+ type ValuationSeriesLoader,
12
+ } from "./client";
13
+ import type { ValuationRangeId } from "./defs";
14
+ import { DEFAULT_INDICATOR_ID, INDICATORS, resolveIndicatorArg } from "./indicators";
15
+ import { createCloudSourceDeps } from "./sources";
16
+ import {
17
+ formatSigma,
18
+ selectValuationViews,
19
+ type IndicatorViewModel,
20
+ type ValuationBundle,
21
+ } from "./view";
22
+
23
+ function detailEntries(view: IndicatorViewModel): HeadlessPaneEntry[] {
24
+ const format = view.indicator.formatValue;
25
+ return [
26
+ { label: "Current", value: view.current.ratio, formatted: format(view.current.ratio) },
27
+ {
28
+ label: "1Y ago",
29
+ value: view.ratioOneYearAgo,
30
+ formatted: view.ratioOneYearAgo == null ? "-" : format(view.ratioOneYearAgo),
31
+ },
32
+ { label: "Mean", value: view.mean, formatted: format(view.mean) },
33
+ {
34
+ label: "Rich percentile",
35
+ value: view.richPercentile,
36
+ formatted: formatNumber(view.richPercentile, 0),
37
+ },
38
+ {
39
+ label: "Trend deviation",
40
+ value: view.richSigma,
41
+ formatted: formatSigma(view.richSigma),
42
+ },
43
+ {
44
+ label: "All-time high",
45
+ value: { value: view.allTimeHigh.ratio, date: view.allTimeHigh.date },
46
+ formatted: `${format(view.allTimeHigh.ratio)} ${view.allTimeHigh.date}`,
47
+ },
48
+ {
49
+ label: "All-time low",
50
+ value: { value: view.allTimeLow.ratio, date: view.allTimeLow.date },
51
+ formatted: `${format(view.allTimeLow.ratio)} ${view.allTimeLow.date}`,
52
+ },
53
+ { label: "As of", value: view.asOf },
54
+ ];
55
+ }
56
+
57
+ export function projectValuationHeadlessBundle(
58
+ bundle: ValuationBundle,
59
+ range: ValuationRangeId,
60
+ selectedId: string,
61
+ ): HeadlessBundleResult {
62
+ const views = selectValuationViews(bundle, range);
63
+ const selected = views.find((view) => view.indicator.id === selectedId) ?? views[0] ?? null;
64
+ const rows = views.map((view) => ({
65
+ id: view.indicator.id,
66
+ indicator: view.indicator.label,
67
+ value: view.current.ratio,
68
+ formattedValue: view.indicator.formatValue(view.current.ratio),
69
+ zone: view.zone.label,
70
+ richPercentile: view.richPercentile,
71
+ richSigma: view.richSigma,
72
+ asOf: view.asOf,
73
+ stale: view.observationStale,
74
+ }));
75
+
76
+ return {
77
+ sections: [
78
+ {
79
+ title: "Market valuation",
80
+ columns: [
81
+ { key: "indicator", header: "Indicator" },
82
+ {
83
+ key: "value",
84
+ header: "Value",
85
+ align: "right",
86
+ format: (_value, row) => String(row.formattedValue),
87
+ },
88
+ { key: "zone", header: "Zone" },
89
+ {
90
+ key: "richPercentile",
91
+ header: "Rich %ile",
92
+ align: "right",
93
+ format: (value) => formatNumber(Number(value), 0),
94
+ },
95
+ {
96
+ key: "richSigma",
97
+ header: "Trend",
98
+ align: "right",
99
+ format: (value) => formatSigma(Number(value)),
100
+ },
101
+ { key: "asOf", header: "As of" },
102
+ ],
103
+ rows,
104
+ },
105
+ ...(selected
106
+ ? [{ title: selected.indicator.label, entries: detailEntries(selected) }]
107
+ : []),
108
+ ],
109
+ errors: bundle.errors,
110
+ metadata: {
111
+ fetchedAt: bundle.fetchedAt,
112
+ range,
113
+ selected: selected?.indicator.id ?? null,
114
+ },
115
+ };
116
+ }
117
+
118
+ export interface MarketValuationHeadlessDependencies {
119
+ loadBundle(args: HeadlessPaneLoadArgs, loader: ValuationSeriesLoader): Promise<ValuationBundle>;
120
+ }
121
+
122
+ const defaultDependencies: MarketValuationHeadlessDependencies = {
123
+ loadBundle: (args, loader) => {
124
+ const requested = typeof args.argument === "string" ? resolveIndicatorArg(args.argument) : null;
125
+ return loadValuationBundle({
126
+ loader,
127
+ ...(requested ? { indicators: [requested] } : {}),
128
+ });
129
+ },
130
+ };
131
+
132
+ export function createMarketValuationHeadless(
133
+ dependencies: MarketValuationHeadlessDependencies = defaultDependencies,
134
+ ): HeadlessPaneDefinition<"bundle"> {
135
+ return {
136
+ shape: "bundle",
137
+ argument: {
138
+ kind: "free-text",
139
+ placeholder: "indicator",
140
+ description: "Optional valuation indicator id, label, or prefix.",
141
+ optional: true,
142
+ },
143
+ options: [{
144
+ key: "range",
145
+ description: "History window used by the selected indicator view.",
146
+ type: "enum",
147
+ values: [{ value: "10Y" }, { value: "25Y" }, { value: "ALL", aliases: ["all"] }],
148
+ defaultValue: "25Y",
149
+ }],
150
+ describe: (args) => `Market Valuation | ${String(args.options.range)}`,
151
+ async load(args, ctx) {
152
+ const requested = typeof args.argument === "string" ? resolveIndicatorArg(args.argument) : null;
153
+ if (args.argument && !requested) {
154
+ throw new Error(
155
+ `Unknown valuation indicator "${String(args.argument)}". Use one of: ${INDICATORS.map(({ id }) => id).join(", ")}.`,
156
+ );
157
+ }
158
+ const range = args.options.range as ValuationRangeId;
159
+ const loader = createValuationSeriesLoader(createCloudSourceDeps(ctx.apiClient));
160
+ const bundle = await dependencies.loadBundle(args, loader);
161
+ return projectValuationHeadlessBundle(
162
+ bundle,
163
+ range,
164
+ requested?.id ?? DEFAULT_INDICATOR_ID,
165
+ );
166
+ },
167
+ };
168
+ }
169
+
170
+ export const marketValuationHeadless = createMarketValuationHeadless();
@@ -0,0 +1,64 @@
1
+ import type { PluginModule } from "../plugin-module";
2
+ import { createValuationChartSeriesCapability } from "./chart-series";
3
+ import { INDICATORS, resolveIndicatorArg } from "./indicators";
4
+ import { marketValuationHeadless } from "./headless";
5
+ import { MarketValuationPane } from "./pane";
6
+ import { buildValuationSettingsDef, VALUATION_DEFAULTS } from "./settings";
7
+
8
+ export { marketValuationHeadless } from "./headless";
9
+
10
+ const MARKET_VALUATION_PANE_ID = "market-valuation";
11
+
12
+ const INDICATOR_KEYWORDS = INDICATORS.flatMap((indicator) => [
13
+ indicator.id,
14
+ ...indicator.shortLabel.toLowerCase().split(" "),
15
+ ...indicator.label.toLowerCase().split(" "),
16
+ ]);
17
+
18
+ export const marketValuationModule: PluginModule = {
19
+ // Exposes each ratio as a chartable series, so G can overlay them on anything else.
20
+ capabilities: [createValuationChartSeriesCapability()],
21
+ panes: [{
22
+ id: MARKET_VALUATION_PANE_ID,
23
+ name: "Market Valuation",
24
+ icon: "V",
25
+ component: MarketValuationPane,
26
+ defaultPosition: "right",
27
+ defaultMode: "floating",
28
+ defaultFloatingSize: { width: 88, height: 34 },
29
+ settings: buildValuationSettingsDef(),
30
+ }],
31
+ paneTemplates: [{
32
+ id: "market-valuation-pane",
33
+ paneId: MARKET_VALUATION_PANE_ID,
34
+ label: "Market Valuation",
35
+ description: "Whole-market valuation ratios against their own history, with zones and trend deviation.",
36
+ keywords: [
37
+ "valuation",
38
+ "market cap",
39
+ "gdp",
40
+ "wilshire",
41
+ "macro",
42
+ "bubble",
43
+ "overvalued",
44
+ "undervalued",
45
+ ...INDICATOR_KEYWORDS,
46
+ ],
47
+ shortcut: {
48
+ prefix: "VAL",
49
+ argPlaceholder: "indicator",
50
+ argKind: "text",
51
+ argOptional: true,
52
+ },
53
+ headless: marketValuationHeadless,
54
+ // No wizard: VAL opens straight into the pane, where the summary rows swap
55
+ // indicators with the cursor. The argument form stays for deep links.
56
+ canCreate: () => true,
57
+ createInstance: (_context, options) => {
58
+ const indicator = resolveIndicatorArg(options?.arg);
59
+ return {
60
+ settings: { indicator: indicator?.id ?? VALUATION_DEFAULTS.indicator },
61
+ };
62
+ },
63
+ }],
64
+ };