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,36 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { splitFigures, splitSentences } from "./prose";
3
+
4
+ describe("splitFigures", () => {
5
+ test("picks out money, percentages and quantities, leaves years and labels", () => {
6
+ const runs = splitFigures(
7
+ "Revenue grew 20% to $1.2 billion in 2026, or 150 bps above Q2 guidance of $900 million to $1 billion, with 2,500 hires.",
8
+ );
9
+ expect(runs.filter((run) => run.figure).map((run) => run.text)).toEqual([
10
+ "20%",
11
+ "$1.2 billion",
12
+ "150 bps",
13
+ "$900 million",
14
+ "$1 billion",
15
+ "2,500",
16
+ ]);
17
+ // Everything is accounted for, nothing dropped or duplicated.
18
+ expect(runs.map((run) => run.text).join("")).toBe(
19
+ "Revenue grew 20% to $1.2 billion in 2026, or 150 bps above Q2 guidance of $900 million to $1 billion, with 2,500 hires.",
20
+ );
21
+ });
22
+ });
23
+
24
+ describe("splitSentences", () => {
25
+ test("splits on sentence ends but not on abbreviations or decimals", () => {
26
+ expect(
27
+ splitSentences(
28
+ "The U.S. business grew. Margins were 27.8% vs. 25% last year. Acme Inc. expects $5 million and under; management sees this as transferable.",
29
+ ),
30
+ ).toEqual([
31
+ "The U.S. business grew.",
32
+ "Margins were 27.8% vs. 25% last year.",
33
+ "Acme Inc. expects $5 million and under; management sees this as transferable.",
34
+ ]);
35
+ });
36
+ });
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Text shaping for the call reader. Model summaries arrive as dense
3
+ * paragraphs; on a wide pane they are a wall. Splitting them into one point
4
+ * per sentence and setting the figures in bold is what makes them scannable.
5
+ */
6
+
7
+ export interface ProseRun {
8
+ text: string;
9
+ /** A number the eye should land on: money, percentages, quantities. */
10
+ figure: boolean;
11
+ }
12
+
13
+ /**
14
+ * Money, percentages and quantities with a unit, plus bare numbers except
15
+ * years. Ordinals and fiscal labels (Q2, FY26, 2Q) are left alone.
16
+ */
17
+ const FIGURE_PATTERN =
18
+ /[$€£¥]\s?\d[\d,]*(?:\.\d+)?(?:\s?(?:trillion|billion|million|thousand|bn|mn|k|m|b)\b)?|\b\d[\d,]*(?:\.\d+)?\s?(?:%|percent(?:age points?)?|bps|basis points|x\b|trillion|billion|million|thousand)|\b\d[\d,]*(?:\.\d+)?\b/gi;
19
+
20
+ const YEAR_PATTERN = /^(?:19|20)\d{2}$/;
21
+
22
+ export function splitFigures(text: string): ProseRun[] {
23
+ const runs: ProseRun[] = [];
24
+ let last = 0;
25
+ for (const match of text.matchAll(FIGURE_PATTERN)) {
26
+ const start = match.index ?? 0;
27
+ const value = match[0];
28
+ if (YEAR_PATTERN.test(value)) continue;
29
+ if (start > last) runs.push({ text: text.slice(last, start), figure: false });
30
+ runs.push({ text: value, figure: true });
31
+ last = start + value.length;
32
+ }
33
+ if (last < text.length) runs.push({ text: text.slice(last), figure: false });
34
+ return runs;
35
+ }
36
+
37
+ /**
38
+ * Sentence boundaries, minus the periods inside abbreviations ("U.S.",
39
+ * "Inc.", "vs.") and figures ("$27.8 million").
40
+ */
41
+ const SENTENCE_BOUNDARY =
42
+ /(?<!\b(?:[A-Z]|Inc|Ltd|Co|Corp|vs|Mr|Ms|Mrs|Dr|No|St|Jr|Sr|U\.S|e\.g|i\.e|approx))[.!?]["”')]?\s+(?=["“(]?[A-Z0-9$])/;
43
+
44
+ export function splitSentences(text: string): string[] {
45
+ const out: string[] = [];
46
+ let rest = text.trim();
47
+ while (rest.length > 0) {
48
+ const match = SENTENCE_BOUNDARY.exec(rest);
49
+ if (!match) {
50
+ out.push(rest);
51
+ break;
52
+ }
53
+ // Keep the closing punctuation, drop the whitespace that followed it.
54
+ const cut = match.index + match[0].trimEnd().length;
55
+ out.push(rest.slice(0, cut).trim());
56
+ rest = rest.slice(match.index + match[0].length);
57
+ }
58
+ return out.filter(Boolean);
59
+ }
@@ -0,0 +1,334 @@
1
+ import { useMemo, type RefObject } from "react";
2
+ import { Spinner } from "../../../components";
3
+ import { Tabs } from "../../../components/ui/tabs";
4
+ import { colors } from "../../../theme/colors";
5
+ import {
6
+ Box,
7
+ ScrollBox,
8
+ StyledText,
9
+ Text,
10
+ TextAttributes,
11
+ useUiCapabilities,
12
+ type ScrollBoxRenderable,
13
+ } from "../../../ui";
14
+ import { wrapTextLines } from "../../../utils/text-wrap";
15
+ import type {
16
+ CloudEarningsTranscriptPayload,
17
+ CloudTranscriptTurnPayload,
18
+ } from "../../../api-client";
19
+ import { formatCallDate, formatDuration, formatSentiment, formatTimestamp } from "./format";
20
+ import { splitFigures, splitSentences } from "./prose";
21
+
22
+ export type ReaderTab = "summary" | "transcript" | "qa";
23
+
24
+ export const READER_TABS: Array<{ label: string; value: ReaderTab }> = [
25
+ { label: "Summary", value: "summary" },
26
+ { label: "Transcript", value: "transcript" },
27
+ { label: "Q&A", value: "qa" },
28
+ ];
29
+
30
+ /**
31
+ * Prose is capped at a comfortable measure. On a wide pane a full-width line
32
+ * is over two hundred characters, which the eye loses on the way back.
33
+ */
34
+ const MAX_PROSE_WIDTH = 100;
35
+
36
+ function speakerColor(turn: CloudTranscriptTurnPayload): string {
37
+ if (turn.speaker === "Operator") return colors.textDim;
38
+ if (turn.role === "Analyst") return colors.warning;
39
+ return colors.textBright;
40
+ }
41
+
42
+ /** Role and firm after the name; "Operator" is not repeated as its own role. */
43
+ function turnDetail(turn: CloudTranscriptTurnPayload): string {
44
+ const role = turn.role && turn.role !== turn.speaker ? turn.role : null;
45
+ return [role, turn.company].filter(Boolean).join(", ");
46
+ }
47
+
48
+ const NATIVE_STRETCH_STYLE = { minWidth: 0 };
49
+ const NATIVE_TEXT_STYLE = { display: "block" };
50
+
51
+ /** Figures set in bold, the rest in the given colour. */
52
+ function styledRuns(text: string, color: string, attributes = 0): StyledText {
53
+ return new StyledText(
54
+ splitFigures(text).map((run) => ({
55
+ text: run.text,
56
+ fg: run.figure ? colors.textBright : color,
57
+ attributes: run.figure ? attributes | TextAttributes.BOLD : attributes,
58
+ })),
59
+ );
60
+ }
61
+
62
+ /**
63
+ * A paragraph with figures in bold. The terminal renderer does not wrap on
64
+ * its own, so lines are pre-wrapped and styled one at a time; desktop
65
+ * chrome wraps the styled paragraph itself. `indent` is applied to every
66
+ * line after the first, for bullets.
67
+ */
68
+ function Prose({
69
+ text,
70
+ width,
71
+ color,
72
+ nativePaneChrome,
73
+ prefix = "",
74
+ }: {
75
+ text: string;
76
+ width: number;
77
+ color: string;
78
+ nativePaneChrome: boolean;
79
+ /** Put before the first line; later lines are indented by its width. */
80
+ prefix?: string;
81
+ }) {
82
+ if (!text.trim()) return null;
83
+ if (nativePaneChrome) {
84
+ return (
85
+ <Box flexDirection="row" width="100%" style={NATIVE_STRETCH_STYLE}>
86
+ {prefix ? <Text fg={colors.textDim}>{prefix}</Text> : null}
87
+ <Text
88
+ wrapText
89
+ width="100%"
90
+ style={NATIVE_TEXT_STYLE}
91
+ content={styledRuns(text, color)}
92
+ />
93
+ </Box>
94
+ );
95
+ }
96
+ const indent = " ".repeat(prefix.length);
97
+ return wrapTextLines(text, Math.max(8, width - prefix.length)).map((line, index) => (
98
+ <Box key={index} height={1} flexDirection="row">
99
+ {prefix ? (
100
+ <Text fg={colors.textDim} flexShrink={0}>
101
+ {index === 0 ? prefix : indent}
102
+ </Text>
103
+ ) : null}
104
+ <Text content={styledRuns(line, color)} />
105
+ </Box>
106
+ ));
107
+ }
108
+
109
+ function SectionHeading({ title }: { title: string }) {
110
+ return (
111
+ <Box height={1} marginTop={1}>
112
+ <Text fg={colors.textDim} attributes={TextAttributes.BOLD}>
113
+ {title}
114
+ </Text>
115
+ </Box>
116
+ );
117
+ }
118
+
119
+ /** A summary section as one point per sentence. */
120
+ function Section({
121
+ title,
122
+ body,
123
+ width,
124
+ nativePaneChrome,
125
+ }: {
126
+ title: string;
127
+ body: string;
128
+ width: number;
129
+ nativePaneChrome: boolean;
130
+ }) {
131
+ if (!body.trim()) return null;
132
+ return (
133
+ <Box flexDirection="column">
134
+ <SectionHeading title={title} />
135
+ {splitSentences(body).map((sentence, index) => (
136
+ <Prose
137
+ key={index}
138
+ text={sentence}
139
+ width={width}
140
+ color={colors.text}
141
+ nativePaneChrome={nativePaneChrome}
142
+ prefix="• "
143
+ />
144
+ ))}
145
+ </Box>
146
+ );
147
+ }
148
+
149
+ function TurnView({
150
+ turn,
151
+ width,
152
+ nativePaneChrome,
153
+ }: {
154
+ turn: CloudTranscriptTurnPayload;
155
+ width: number;
156
+ nativePaneChrome: boolean;
157
+ }) {
158
+ const detail = turnDetail(turn);
159
+ return (
160
+ <Box flexDirection="column" marginTop={1}>
161
+ <Box height={1} flexDirection="row" gap={1} overflow="hidden">
162
+ <Text fg={colors.textDim}>{formatTimestamp(turn.startSeconds)}</Text>
163
+ <Text fg={speakerColor(turn)} attributes={TextAttributes.BOLD}>
164
+ {turn.speaker}
165
+ </Text>
166
+ {detail ? <Text fg={colors.textDim}>{detail}</Text> : null}
167
+ </Box>
168
+ <Prose text={turn.text} width={width} color={colors.text} nativePaneChrome={nativePaneChrome} />
169
+ </Box>
170
+ );
171
+ }
172
+
173
+ export function TranscriptView({
174
+ transcript,
175
+ loading,
176
+ error,
177
+ tab,
178
+ onTabChange,
179
+ tabsFocused,
180
+ query,
181
+ width,
182
+ scrollRef,
183
+ }: {
184
+ transcript: CloudEarningsTranscriptPayload | null;
185
+ loading: boolean;
186
+ error: string | null;
187
+ tab: ReaderTab;
188
+ onTabChange: (tab: ReaderTab) => void;
189
+ /** Whether left/right should move between tabs. */
190
+ tabsFocused: boolean;
191
+ /** Free-text filter applied to the turns, for finding a topic in a long call. */
192
+ query?: string;
193
+ width: number;
194
+ /** Lets the owning pane drive keyboard scrolling. */
195
+ scrollRef?: RefObject<ScrollBoxRenderable | null>;
196
+ }) {
197
+ const { nativePaneChrome } = useUiCapabilities();
198
+ const isNative = nativePaneChrome === true;
199
+
200
+ const turns = useMemo(() => {
201
+ const all = transcript?.turns ?? [];
202
+ const scoped = tab === "qa" ? all.filter((turn) => turn.isQa) : all;
203
+ const needle = (query ?? "").trim().toLowerCase();
204
+ if (!needle) return scoped;
205
+ return scoped.filter((turn) =>
206
+ `${turn.speaker} ${turn.company ?? ""} ${turn.text}`.toLowerCase().includes(needle),
207
+ );
208
+ }, [transcript, tab, query]);
209
+
210
+ if (loading && !transcript) {
211
+ return (
212
+ <Box flexGrow={1} alignItems="center" justifyContent="center">
213
+ <Spinner label="Loading transcript..." />
214
+ </Box>
215
+ );
216
+ }
217
+
218
+ if (error && !transcript) {
219
+ return (
220
+ <Box flexGrow={1} paddingX={1}>
221
+ <Text fg={colors.negative}>{error}</Text>
222
+ </Box>
223
+ );
224
+ }
225
+
226
+ if (!transcript) return null;
227
+
228
+ // The stack title already names ticker and period, so lead with metadata.
229
+ const meta = [
230
+ formatCallDate(transcript.callAt),
231
+ formatDuration(transcript.durationSeconds),
232
+ transcript.participants.length > 0 ? `${transcript.participants.length} speakers` : null,
233
+ transcript.sentiment !== null ? `sentiment ${formatSentiment(transcript.sentiment)}` : null,
234
+ ]
235
+ .filter(Boolean)
236
+ .join(" · ");
237
+
238
+ // One column of padding each side inside the scroll box.
239
+ const bodyWidth = Math.max(12, width - 2);
240
+ const proseWidth = Math.min(bodyWidth, MAX_PROSE_WIDTH);
241
+ const contentWidth = isNative ? "100%" : bodyWidth;
242
+ const contentStyle = isNative ? NATIVE_STRETCH_STYLE : undefined;
243
+ const hasQa = (transcript.turns ?? []).some((turn) => turn.isQa);
244
+
245
+ return (
246
+ <Box
247
+ flexDirection="column"
248
+ flexGrow={1}
249
+ flexShrink={1}
250
+ flexBasis={0}
251
+ minHeight={0}
252
+ overflow="hidden"
253
+ >
254
+ <Box height={1} flexShrink={0} paddingX={1} overflow="hidden">
255
+ <Tabs
256
+ tabs={READER_TABS.map((entry) => ({
257
+ label: entry.label,
258
+ value: entry.value,
259
+ disabled: entry.value === "qa" && !hasQa,
260
+ }))}
261
+ activeValue={tab}
262
+ onSelect={(value) => onTabChange(value as ReaderTab)}
263
+ compact
264
+ variant="bare"
265
+ focused={tabsFocused}
266
+ />
267
+ </Box>
268
+ <ScrollBox
269
+ ref={scrollRef}
270
+ flexGrow={1}
271
+ flexShrink={1}
272
+ flexBasis={0}
273
+ minHeight={0}
274
+ scrollY
275
+ focusable={false}
276
+ paddingX={1}
277
+ >
278
+ <Box flexDirection="column" width={contentWidth} style={contentStyle}>
279
+ {tab === "summary" ? (
280
+ <>
281
+ <Prose text={meta} width={proseWidth} color={colors.textDim} nativePaneChrome={isNative} />
282
+ <Section title="SUMMARY" body={transcript.summary ?? ""} width={proseWidth} nativePaneChrome={isNative} />
283
+ <Section title="WHAT STOOD OUT" body={transcript.notable ?? ""} width={proseWidth} nativePaneChrome={isNative} />
284
+ <Section title="ANALYSTS PRESSED ON" body={transcript.analystFocus ?? ""} width={proseWidth} nativePaneChrome={isNative} />
285
+ <Section title="GUIDANCE" body={transcript.guidance ?? ""} width={proseWidth} nativePaneChrome={isNative} />
286
+ <Section title="RISKS" body={transcript.riskFactors ?? ""} width={proseWidth} nativePaneChrome={isNative} />
287
+ {transcript.participants.length > 0 && (
288
+ <Box flexDirection="column">
289
+ <SectionHeading title="PARTICIPANTS" />
290
+ {transcript.participants.map((participant) => (
291
+ <Prose
292
+ key={participant.name}
293
+ text={[participant.name, participant.role, participant.company]
294
+ .filter(Boolean)
295
+ .join(" · ")}
296
+ width={proseWidth}
297
+ color={colors.text}
298
+ nativePaneChrome={isNative}
299
+ />
300
+ ))}
301
+ </Box>
302
+ )}
303
+ </>
304
+ ) : (
305
+ <>
306
+ {turns.map((turn, index) => (
307
+ <TurnView
308
+ key={`${turn.startSeconds}-${index}`}
309
+ turn={turn}
310
+ width={proseWidth}
311
+ nativePaneChrome={isNative}
312
+ />
313
+ ))}
314
+ {turns.length === 0 && (
315
+ <Box marginTop={1}>
316
+ <Prose
317
+ text={
318
+ query?.trim()
319
+ ? `Nothing matching "${query.trim()}" in this call.`
320
+ : "No question and answer section in this call."
321
+ }
322
+ width={proseWidth}
323
+ color={colors.textDim}
324
+ nativePaneChrome={isNative}
325
+ />
326
+ </Box>
327
+ )}
328
+ </>
329
+ )}
330
+ </Box>
331
+ </ScrollBox>
332
+ </Box>
333
+ );
334
+ }
@@ -1,7 +1,7 @@
1
1
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
2
  import { Box, ScrollBox, Text, TextAttributes, type ScrollBoxRenderable } from "../../../ui";
3
3
  import { StaticChartSurface } from "../../../components";
4
- import { resolveChartPalette } from "../../../components/chart/core/renderer";
4
+ import { resolveChartPalette } from "../../../components/chart/core/palette";
5
5
  import type { ProjectedChartPoint } from "../../../components/chart/core/data";
6
6
  import { colors } from "../../../theme/colors";
7
7
  import { apiClient, type CloudFredObservationPayload } from "../../../api-client";
@@ -263,7 +263,6 @@ export function EconDetailView({ event, width, height, focused }: EconDetailView
263
263
  height={chartHeight}
264
264
  mode="area"
265
265
  colors={palette}
266
- timeAxisDates={chartPoints.map((p) => p.date)}
267
266
  showTimeAxis
268
267
  timeAxisColor={colors.textDim}
269
268
  yAxisLabel={units ? `Value (${units})` : "Value"}
@@ -98,6 +98,21 @@ const FRED_CATALOG_SERIES: ReadonlyArray<{ seriesId: string; label: string }> =
98
98
  }));
99
99
  })();
100
100
 
101
+ /**
102
+ * Macro series the catalog should offer that are not economic-calendar releases, so
103
+ * they have no natural home in SERIES_MAP above.
104
+ */
105
+ const EXTRA_CATALOG_SERIES: ReadonlyArray<{ seriesId: string; label: string }> = [
106
+ { seriesId: "M2SL", label: "M2 Money Stock" },
107
+ { seriesId: "DFII10", label: "10Y TIPS Real Yield" },
108
+ { seriesId: "NCBEILQ027S", label: "Corporate Equities, Z.1" },
109
+ { seriesId: "TNWMVBSNNCB", label: "Corporate Net Worth, Z.1" },
110
+ ];
111
+
101
112
  export function listFredCatalogSeries(): ReadonlyArray<{ seriesId: string; label: string }> {
102
- return FRED_CATALOG_SERIES;
113
+ const known = new Set(FRED_CATALOG_SERIES.map((entry) => entry.seriesId));
114
+ return [
115
+ ...FRED_CATALOG_SERIES,
116
+ ...EXTRA_CATALOG_SERIES.filter((entry) => !known.has(entry.seriesId)),
117
+ ];
103
118
  }
@@ -399,14 +399,16 @@ export const economicCalendarModule: PluginModule = {
399
399
  defaultPosition: "right",
400
400
  defaultMode: "floating",
401
401
  defaultFloatingSize: { width: 100, height: 30 },
402
+ tableExport: true,
402
403
  }],
403
404
  paneTemplates: [{
404
405
  id: "econ-calendar-pane",
405
406
  paneId: "econ-calendar",
406
407
  label: "Economic Calendar",
407
408
  description: "Upcoming economic events, releases, and indicators.",
409
+ // "econ" stays a keyword so the old ECON prefix still finds this pane.
408
410
  keywords: ["econ", "economic", "calendar", "events", "macro", "releases", "fed", "cpi", "gdp"],
409
- shortcut: { prefix: "ECON" },
411
+ shortcut: { prefix: "ECO" },
410
412
  }],
411
413
  setup(ctx) {
412
414
  attachEconCalendarPersistence(ctx.persistence);
@@ -0,0 +1,4 @@
1
+ import { createSeriesCache } from "../shared/series-cache";
2
+
3
+ /** Most of these print monthly, so a six-hour window is plenty. */
4
+ export const statsCache = createSeriesCache("econ-statistics-series", 6 * 60 * 60 * 1000);
@@ -0,0 +1,92 @@
1
+ import { apiClient } from "../../../api-client";
2
+ import type { DatedObservation } from "../shared/series-cache";
3
+ import { statsCache } from "./cache";
4
+ import type { StatDef } from "./defs";
5
+ import { STATS } from "./stats";
6
+ import { applyTransform, type StatPoint } from "./transform";
7
+ import { fitTrend, type TrendFit } from "./trend";
8
+
9
+ export type StatSeriesLoader = (def: StatDef) => Promise<DatedObservation[]>;
10
+
11
+ export interface StatBuild {
12
+ stat: StatDef;
13
+ points: StatPoint[];
14
+ trend: TrendFit;
15
+ }
16
+
17
+ export interface StatsBundle {
18
+ builds: StatBuild[];
19
+ errors: string[];
20
+ fetchedAt: number;
21
+ }
22
+
23
+ export type StatsCloudClient = Pick<typeof apiClient, "getCloudFredSeries">;
24
+
25
+ export function createStatSeriesLoader(client: StatsCloudClient): StatSeriesLoader {
26
+ const cloudLoader: StatSeriesLoader = async (def) => {
27
+ const data = await client.getCloudFredSeries(def.seriesId, {
28
+ limit: def.limit,
29
+ sortOrder: "desc",
30
+ });
31
+ return data.observations;
32
+ };
33
+ return (def) => statsCache.load(def.seriesId, () => cloudLoader(def));
34
+ }
35
+
36
+ export const defaultStatLoader = createStatSeriesLoader(apiClient);
37
+
38
+ function build(def: StatDef, observations: DatedObservation[]): StatBuild | null {
39
+ const scale = def.scale ?? 1;
40
+ const scaled = scale === 1
41
+ ? observations
42
+ : observations.map((entry) => ({
43
+ date: entry.date,
44
+ value: entry.value == null ? null : entry.value * scale,
45
+ }));
46
+ const points = applyTransform(scaled, def.transform);
47
+ if (points.length === 0) return null;
48
+ return { stat: def, points, trend: fitTrend(points) };
49
+ }
50
+
51
+ /** Builds whatever the on-disk cache can already answer, for an instant first paint. */
52
+ export function getCachedStatsBundle(stats: readonly StatDef[] = STATS): StatsBundle | null {
53
+ const builds: StatBuild[] = [];
54
+ for (const def of stats) {
55
+ const cached = statsCache.get(def.seriesId, { allowExpired: true });
56
+ if (!cached) continue;
57
+ const entry = build(def, cached.observations);
58
+ if (entry) builds.push(entry);
59
+ }
60
+ return builds.length > 0 ? { builds, errors: [], fetchedAt: Date.now() } : null;
61
+ }
62
+
63
+ export async function loadStatsBundle(options?: {
64
+ loader?: StatSeriesLoader;
65
+ stats?: readonly StatDef[];
66
+ }): Promise<StatsBundle> {
67
+ const loader = options?.loader ?? defaultStatLoader;
68
+ const stats = options?.stats ?? STATS;
69
+ const errors: string[] = [];
70
+
71
+ const settled = await Promise.all(stats.map(async (def) => {
72
+ try {
73
+ return { def, observations: await loader(def) };
74
+ } catch (error) {
75
+ errors.push(`${def.seriesId}: ${error instanceof Error ? error.message : String(error)}`);
76
+ return null;
77
+ }
78
+ }));
79
+
80
+ const builds: StatBuild[] = [];
81
+ for (const entry of settled) {
82
+ if (!entry) continue;
83
+ const made = build(entry.def, entry.observations);
84
+ if (made) builds.push(made);
85
+ else errors.push(`${entry.def.seriesId}: no observations after transform`);
86
+ }
87
+
88
+ if (builds.length === 0) {
89
+ throw new Error(errors[0] ?? "Economic statistics unavailable");
90
+ }
91
+ return { builds, errors, fetchedAt: Date.now() };
92
+ }
@@ -0,0 +1,66 @@
1
+ import { blendHex, colors } from "../../../theme/colors";
2
+ import type { StatTransform } from "./transform";
3
+
4
+ export type StatCategoryId =
5
+ | "inflation"
6
+ | "labor"
7
+ | "growth"
8
+ | "consumer"
9
+ | "housing"
10
+ | "rates"
11
+ | "trade";
12
+
13
+ export const STAT_CATEGORIES: ReadonlyArray<{ id: StatCategoryId; label: string }> = [
14
+ { id: "inflation", label: "Inflation" },
15
+ { id: "labor", label: "Labor" },
16
+ { id: "growth", label: "Growth" },
17
+ { id: "consumer", label: "Consumer" },
18
+ { id: "housing", label: "Housing" },
19
+ { id: "rates", label: "Rates & Money" },
20
+ { id: "trade", label: "Trade & Costs" },
21
+ ];
22
+
23
+ /**
24
+ * Whether a rising number is welcome. Unemployment climbing is bad, payrolls
25
+ * climbing is good, and an index level like CPI is neither on its own, so the
26
+ * table colours the change accordingly instead of pretending every rise is green.
27
+ */
28
+ export type StatDirection = "higher-is-good" | "higher-is-bad" | "neutral";
29
+
30
+ export interface StatDef {
31
+ id: string;
32
+ label: string;
33
+ /** Fits the table's INDICATOR column. */
34
+ shortLabel: string;
35
+ category: StatCategoryId;
36
+ seriesId: string;
37
+ transform: StatTransform;
38
+ /**
39
+ * Multiplier applied to raw observations before the transform, to bring a series
40
+ * into the unit it is quoted in. Percent changes are ratios, so scaling cannot
41
+ * affect them; it only matters for levels.
42
+ */
43
+ scale?: number;
44
+ direction: StatDirection;
45
+ /** Drives the chart axis and the cursor readout. */
46
+ axisUnit: "%" | "";
47
+ formatValue: (value: number) => string;
48
+ /** A level that means something: the Fed's target, zero for a spread. */
49
+ reference: { value: number; label: string } | null;
50
+ /** How old the newest print may get before the pane flags it stale. */
51
+ staleAfterMs: number;
52
+ note: string;
53
+ limit: number;
54
+ }
55
+
56
+ export function categoryLabel(id: StatCategoryId): string {
57
+ return STAT_CATEGORIES.find((entry) => entry.id === id)?.label ?? id;
58
+ }
59
+
60
+ /** Green when the move is welcome, red when it is not, plain when it is neither. */
61
+ export function changeColor(direction: StatDirection, change: number | null): string {
62
+ if (change == null || !Number.isFinite(change) || change === 0) return colors.textMuted;
63
+ if (direction === "neutral") return colors.text;
64
+ const good = direction === "higher-is-good" ? change > 0 : change < 0;
65
+ return good ? colors.positive : blendHex(colors.negative, "#000000", 0.1);
66
+ }