gloomberb 0.11.3 → 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 (489) hide show
  1. package/README.md +6 -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 +103 -1
  6. package/src/api-client/index.test.ts +127 -0
  7. package/src/api-client/index.ts +129 -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 +196 -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/cli/commands/plugins.ts +39 -11
  20. package/src/cli/desktop-pane-shot.ts +285 -16
  21. package/src/cli/index.ts +1 -1
  22. package/src/cli/pane-functions/capabilities.ts +58 -28
  23. package/src/cli/pane-functions/catalog.ts +16 -4
  24. package/src/cli/pane-functions/discovery.ts +1 -0
  25. package/src/cli/pane-functions/headless.test.ts +247 -0
  26. package/src/cli/pane-functions/headless.ts +423 -0
  27. package/src/cli/pane-functions/index.test.ts +9 -0
  28. package/src/cli/pane-functions/index.ts +6 -2
  29. package/src/cli/pane-functions/options.ts +27 -2
  30. package/src/cli/pane-functions/report.ts +4 -0
  31. package/src/cli/pane-functions/resolver.ts +26 -8
  32. package/src/cli/pane-functions/screenshot.test.ts +76 -0
  33. package/src/cli/pane-functions/screenshot.ts +236 -20
  34. package/src/cli/restore-plugins.ts +34 -0
  35. package/src/components/chart/{static/chart → composite}/axis-overlays.test.tsx +3 -3
  36. package/src/components/chart/{static/chart → composite}/axis-overlays.tsx +6 -11
  37. package/src/components/chart/{static/chart → composite}/bitmap.ts +17 -3
  38. package/src/components/chart/composite/composite-chart.test.tsx +68 -20
  39. package/src/components/chart/composite/composite-chart.tsx +301 -204
  40. package/src/components/chart/composite/format.ts +8 -2
  41. package/src/components/chart/composite/interactions.test.ts +68 -102
  42. package/src/components/chart/composite/interactions.ts +214 -264
  43. package/src/components/chart/composite/price-axis-labels.test.ts +21 -0
  44. package/src/components/chart/{price-axis-labels.tsx → composite/price-axis-labels.tsx} +9 -4
  45. package/src/components/chart/composite/scene.ts +57 -3
  46. package/src/components/chart/composite/text-renderer.ts +3 -2
  47. package/src/components/chart/composite/time-scale.ts +138 -57
  48. package/src/components/chart/composite/types.ts +25 -1
  49. package/src/components/chart/core/data.ts +9 -13
  50. package/src/components/chart/core/date-window.ts +7 -1
  51. package/src/components/chart/core/pointer.ts +3 -0
  52. package/src/components/chart/core/types.ts +0 -45
  53. package/src/components/chart/native/chart-rasterizer.ts +0 -1
  54. package/src/components/chart/native/kitty/index.test.ts +0 -188
  55. package/src/components/chart/native/raster/crosshair.ts +6 -5
  56. package/src/components/chart/native/raster/primitives.ts +0 -23
  57. package/src/components/chart/native/renderer-selection.ts +81 -31
  58. package/src/components/chart/static/{chart/surface.test.tsx → chart-surface.test.tsx} +28 -80
  59. package/src/components/chart/static/chart-surface.tsx +150 -0
  60. package/src/components/chart/static/index.ts +8 -0
  61. package/src/components/chart/static/multi-line-chart-surface.tsx +51 -166
  62. package/src/components/chart/static/scatter-chart-surface.test.ts +30 -0
  63. package/src/components/chart/static/scatter-chart-surface.tsx +135 -34
  64. package/src/components/chart/static/series.ts +49 -0
  65. package/src/components/command-bar/action-runtime.ts +1 -3
  66. package/src/components/command-bar/assist/model.test.ts +17 -2
  67. package/src/components/command-bar/assist/model.ts +40 -9
  68. package/src/components/command-bar/commands/direct/index.ts +1 -1
  69. package/src/components/command-bar/commands/direct/runtime.ts +1 -1
  70. package/src/components/command-bar/commands/registry.ts +0 -11
  71. package/src/components/command-bar/helpers.test.ts +2 -2
  72. package/src/components/command-bar/helpers.ts +2 -6
  73. package/src/components/command-bar/keyboard-shortcuts.ts +0 -11
  74. package/src/components/command-bar/list/badge.test.ts +54 -0
  75. package/src/components/command-bar/list/badge.ts +62 -0
  76. package/src/components/command-bar/list/model.test.ts +103 -0
  77. package/src/components/command-bar/list/model.ts +61 -4
  78. package/src/components/command-bar/list/view.test.tsx +113 -0
  79. package/src/components/command-bar/list/view.tsx +147 -131
  80. package/src/components/command-bar/pane-templates/workflow.ts +1 -1
  81. package/src/components/command-bar/panel/body.tsx +51 -65
  82. package/src/components/command-bar/panel/index.tsx +48 -13
  83. package/src/components/command-bar/panel/layout.test.ts +139 -0
  84. package/src/components/command-bar/panel/layout.ts +65 -28
  85. package/src/components/command-bar/panel/native-surface.ts +24 -0
  86. package/src/components/command-bar/panel/prompt-binding.ts +54 -0
  87. package/src/components/command-bar/panel/runtime.ts +6 -3
  88. package/src/components/command-bar/panel/state.ts +41 -7
  89. package/src/components/command-bar/panel/types.ts +7 -1
  90. package/src/components/command-bar/routes/root/provider-search.ts +34 -15
  91. package/src/components/command-bar/routes/root/results.test.ts +161 -0
  92. package/src/components/command-bar/routes/root/results.ts +30 -28
  93. package/src/components/command-bar/routes/root/runtime.ts +37 -20
  94. package/src/components/command-bar/routes/root/search-providers.test.tsx +224 -0
  95. package/src/components/command-bar/routes/root/search-providers.ts +205 -0
  96. package/src/components/command-bar/routes/root/selection.ts +1 -12
  97. package/src/components/command-bar/routes/root/shortcuts.test.ts +8 -8
  98. package/src/components/command-bar/routes/ticker-search/actions.ts +10 -9
  99. package/src/components/command-bar/routes/ticker-search/results.test.ts +54 -1
  100. package/src/components/command-bar/routes/ticker-search/results.ts +60 -19
  101. package/src/components/command-bar/routes/ticker-search/route.ts +1 -0
  102. package/src/components/command-bar/routing/actions.ts +1 -81
  103. package/src/components/command-bar/routing/launch-request.ts +1 -1
  104. package/src/components/command-bar/routing/list-state.ts +6 -21
  105. package/src/components/command-bar/selection-runtime.ts +1 -1
  106. package/src/components/command-bar/surface/assist.test.tsx +14 -10
  107. package/src/components/command-bar/surface/environment.ts +8 -1
  108. package/src/components/command-bar/surface/index.test.tsx +84 -68
  109. package/src/components/command-bar/surface/index.tsx +33 -36
  110. package/src/components/command-bar/surface/test-harness.tsx +15 -3
  111. package/src/components/command-bar/theme-picker.tsx +43 -15
  112. package/src/components/command-bar/view-model.test.ts +53 -8
  113. package/src/components/command-bar/view-model.ts +43 -14
  114. package/src/components/command-bar/workflow/tickers.ts +1 -0
  115. package/src/components/command-bar/workflow/types.ts +1 -1
  116. package/src/components/data-table/view.test.tsx +45 -6
  117. package/src/components/data-table/view.tsx +55 -9
  118. package/src/components/index.ts +10 -2
  119. package/src/components/input-search-bar.test.tsx +36 -2
  120. package/src/components/input-search-bar.tsx +18 -1
  121. package/src/components/layout/header.test.tsx +57 -10
  122. package/src/components/layout/header.tsx +292 -149
  123. package/src/components/layout/market-summary.test.ts +81 -0
  124. package/src/components/layout/market-summary.ts +109 -0
  125. package/src/components/layout/shell/chrome.ts +96 -1
  126. package/src/components/layout/shell/index.tsx +11 -3
  127. package/src/components/layout/shell/menu.ts +9 -0
  128. package/src/components/layout/shell/pane/actions.ts +29 -0
  129. package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
  130. package/src/components/layout/shell/shortcuts.ts +3 -0
  131. package/src/components/layout/status-bar.test.tsx +9 -8
  132. package/src/components/layout/status-bar.tsx +85 -54
  133. package/src/components/onboarding/onboarding-frame.tsx +2 -26
  134. package/src/components/price-sparkline/index.ts +61 -34
  135. package/src/components/price-sparkline/view.tsx +1 -1
  136. package/src/components/sign-in-gate.test.tsx +128 -0
  137. package/src/components/sign-in-gate.tsx +149 -0
  138. package/src/components/speedometer-gauge/desktop.tsx +2 -12
  139. package/src/components/ticker/badge/list.tsx +17 -3
  140. package/src/components/ticker/list-table-view.tsx +6 -3
  141. package/src/components/ui/data-table/index.tsx +3 -2
  142. package/src/components/ui/data-table/opentui/index.tsx +165 -120
  143. package/src/components/ui/frame.tsx +28 -0
  144. package/src/components/ui/index.ts +3 -0
  145. package/src/components/ui/native-select.tsx +20 -10
  146. package/src/components/ui/select-button.tsx +138 -0
  147. package/src/components/ui/status.tsx +2 -2
  148. package/src/components/ui/table-layout.ts +1 -1
  149. package/src/components/use-double-click-activation.ts +7 -3
  150. package/src/core/app-services.ts +3 -3
  151. package/src/data/config/layout.ts +12 -6
  152. package/src/data/config/store/index.test.ts +97 -0
  153. package/src/data/config/store/migrations.ts +41 -0
  154. package/src/data/config/store/normalize.ts +5 -3
  155. package/src/i18n/es.ts +6 -2
  156. package/src/i18n/ja.ts +6 -2
  157. package/src/i18n/ko.ts +6 -2
  158. package/src/i18n/zh-cn.ts +6 -2
  159. package/src/i18n/zh-tw.ts +6 -2
  160. package/src/layout-marketplace/pane-imagery.ts +1 -0
  161. package/src/layout-marketplace/payload.test.ts +17 -0
  162. package/src/layout-marketplace/payload.ts +40 -5
  163. package/src/market-data/coordinator/index.ts +3 -2
  164. package/src/market-data/coordinator/quotes.ts +173 -66
  165. package/src/market-data/coordinator/subscriptions.test.ts +215 -37
  166. package/src/pane-settings.ts +1 -1
  167. package/src/plugins/builtin/analytics/pane-model.ts +1 -1
  168. package/src/plugins/builtin/analytics/view.tsx +1 -1
  169. package/src/plugins/builtin/broker-manager/index.test.tsx +3 -2
  170. package/src/plugins/builtin/broker-manager/model.test.ts +2 -2
  171. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +16 -2
  172. package/src/plugins/builtin/chart-composer/pane.tsx +22 -8
  173. package/src/plugins/builtin/chat/content/composer.tsx +8 -4
  174. package/src/plugins/builtin/cloud/auth-dialog.tsx +14 -228
  175. package/src/plugins/builtin/cloud/auth-form.tsx +277 -0
  176. package/src/plugins/builtin/cloud/plugin.tsx +1 -0
  177. package/src/plugins/builtin/composite-plugins.ts +14 -1
  178. package/src/plugins/builtin/correlation/relationship/model.ts +1 -1
  179. package/src/plugins/builtin/correlation/relationship/pane.tsx +2 -6
  180. package/src/plugins/builtin/correlation/relationship/view-model.ts +2 -2
  181. package/src/plugins/builtin/credit-conditions/index.test.tsx +12 -6
  182. package/src/plugins/builtin/credit-conditions/index.tsx +2 -21
  183. package/src/plugins/builtin/dividend-yield/index.tsx +1 -0
  184. package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
  185. package/src/plugins/builtin/earnings-calls/data.ts +223 -0
  186. package/src/plugins/builtin/earnings-calls/format.ts +49 -0
  187. package/src/plugins/builtin/earnings-calls/index.tsx +69 -0
  188. package/src/plugins/builtin/earnings-calls/pane.tsx +756 -0
  189. package/src/plugins/builtin/earnings-calls/prose.test.ts +36 -0
  190. package/src/plugins/builtin/earnings-calls/prose.ts +59 -0
  191. package/src/plugins/builtin/earnings-calls/transcript-view.tsx +334 -0
  192. package/src/plugins/builtin/econ/detail-view.tsx +1 -2
  193. package/src/plugins/builtin/econ/fred-series-map.ts +16 -1
  194. package/src/plugins/builtin/econ/index.tsx +2 -1
  195. package/src/plugins/builtin/econ-statistics/cache.ts +4 -0
  196. package/src/plugins/builtin/econ-statistics/client.ts +92 -0
  197. package/src/plugins/builtin/econ-statistics/defs.ts +66 -0
  198. package/src/plugins/builtin/econ-statistics/detail.tsx +154 -0
  199. package/src/plugins/builtin/econ-statistics/headless.test.ts +54 -0
  200. package/src/plugins/builtin/econ-statistics/headless.ts +171 -0
  201. package/src/plugins/builtin/econ-statistics/index.tsx +54 -0
  202. package/src/plugins/builtin/econ-statistics/pane.tsx +356 -0
  203. package/src/plugins/builtin/econ-statistics/stats.ts +522 -0
  204. package/src/plugins/builtin/econ-statistics/transform.test.ts +86 -0
  205. package/src/plugins/builtin/econ-statistics/transform.ts +85 -0
  206. package/src/plugins/builtin/econ-statistics/trend.ts +57 -0
  207. package/src/plugins/builtin/econ-statistics/view.ts +90 -0
  208. package/src/plugins/builtin/fear-greed/charts.tsx +7 -18
  209. package/src/plugins/builtin/fear-greed/data.ts +6 -1
  210. package/src/plugins/builtin/help/index.tsx +4 -0
  211. package/src/plugins/builtin/holders/index.tsx +1 -0
  212. package/src/plugins/builtin/insider/index.tsx +1 -0
  213. package/src/plugins/builtin/kelly-sizer/pane.tsx +1 -1
  214. package/src/plugins/builtin/kelly-sizer/sections.tsx +2 -3
  215. package/src/plugins/builtin/market-valuation/align.ts +122 -0
  216. package/src/plugins/builtin/market-valuation/cache.ts +32 -0
  217. package/src/plugins/builtin/market-valuation/chart-projection.ts +217 -0
  218. package/src/plugins/builtin/market-valuation/chart-series.ts +89 -0
  219. package/src/plugins/builtin/market-valuation/client.test.ts +141 -0
  220. package/src/plugins/builtin/market-valuation/client.ts +116 -0
  221. package/src/plugins/builtin/market-valuation/defs.ts +251 -0
  222. package/src/plugins/builtin/market-valuation/detail.tsx +159 -0
  223. package/src/plugins/builtin/market-valuation/headless.test.ts +58 -0
  224. package/src/plugins/builtin/market-valuation/headless.ts +170 -0
  225. package/src/plugins/builtin/market-valuation/index.tsx +64 -0
  226. package/src/plugins/builtin/market-valuation/indicators.ts +422 -0
  227. package/src/plugins/builtin/market-valuation/model.test.ts +350 -0
  228. package/src/plugins/builtin/market-valuation/model.ts +8 -0
  229. package/src/plugins/builtin/market-valuation/pane.test.tsx +247 -0
  230. package/src/plugins/builtin/market-valuation/pane.tsx +344 -0
  231. package/src/plugins/builtin/market-valuation/series.ts +12 -0
  232. package/src/plugins/builtin/market-valuation/settings.ts +38 -0
  233. package/src/plugins/builtin/market-valuation/sources.ts +130 -0
  234. package/src/plugins/builtin/market-valuation/trend.ts +89 -0
  235. package/src/plugins/builtin/market-valuation/view.ts +137 -0
  236. package/src/plugins/builtin/market-valuation/zone-scale/desktop.tsx +123 -0
  237. package/src/plugins/builtin/market-valuation/zone-scale/index.tsx +12 -0
  238. package/src/plugins/builtin/market-valuation/zone-scale/model.ts +60 -0
  239. package/src/plugins/builtin/market-valuation/zone-scale/terminal.tsx +94 -0
  240. package/src/plugins/builtin/news/wire/breaking/filters.test.ts +99 -0
  241. package/src/plugins/builtin/news/wire/breaking/filters.ts +110 -0
  242. package/src/plugins/builtin/news/wire/breaking/notifications.test.ts +157 -53
  243. package/src/plugins/builtin/news/wire/breaking/notifications.ts +91 -25
  244. package/src/plugins/builtin/news/wire/index.tsx +31 -44
  245. package/src/plugins/builtin/options/index.tsx +1 -0
  246. package/src/plugins/builtin/options/view.test.tsx +29 -1
  247. package/src/plugins/builtin/options/view.tsx +1 -22
  248. package/src/plugins/builtin/plugin-marketplace/feed.ts +80 -0
  249. package/src/plugins/builtin/plugin-marketplace/index.tsx +31 -0
  250. package/src/plugins/builtin/plugin-marketplace/model.test.ts +200 -0
  251. package/src/plugins/builtin/plugin-marketplace/model.ts +204 -0
  252. package/src/plugins/builtin/plugin-marketplace/pane.tsx +385 -0
  253. package/src/plugins/builtin/plugin-marketplace/store.ts +46 -0
  254. package/src/plugins/builtin/plugin-module.ts +9 -1
  255. package/src/plugins/builtin/research/index.tsx +4 -0
  256. package/src/plugins/builtin/research-search/command-bar-search.test.ts +81 -0
  257. package/src/plugins/builtin/research-search/command-bar-search.ts +175 -0
  258. package/src/plugins/builtin/research-search/data.ts +109 -0
  259. package/src/plugins/builtin/research-search/document-view.test.tsx +75 -0
  260. package/src/plugins/builtin/research-search/document-view.tsx +254 -0
  261. package/src/plugins/builtin/research-search/filter-bar.tsx +62 -0
  262. package/src/plugins/builtin/research-search/focus-handoff.ts +47 -0
  263. package/src/plugins/builtin/research-search/index.tsx +85 -0
  264. package/src/plugins/builtin/research-search/model.test.ts +40 -0
  265. package/src/plugins/builtin/research-search/model.ts +291 -0
  266. package/src/plugins/builtin/research-search/pane.test.tsx +241 -0
  267. package/src/plugins/builtin/research-search/pane.tsx +678 -0
  268. package/src/plugins/builtin/research-search/saved-view.tsx +171 -0
  269. package/src/plugins/builtin/research-search/snippet-text.tsx +35 -0
  270. package/src/plugins/builtin/research-search/snippet.test.ts +78 -0
  271. package/src/plugins/builtin/research-search/snippet.ts +134 -0
  272. package/src/plugins/builtin/scanner/flow-model.test.ts +11 -1
  273. package/src/plugins/builtin/scanner/flow-model.ts +29 -1
  274. package/src/plugins/builtin/scanner/flow-pane.tsx +78 -22
  275. package/src/plugins/builtin/sec/index.tsx +1 -0
  276. package/src/plugins/builtin/shared/series-cache.ts +93 -0
  277. package/src/plugins/builtin/short-interest/index.tsx +1 -0
  278. package/src/plugins/builtin/short-interest/pane.tsx +1 -1
  279. package/src/plugins/builtin/thirteenf/index.tsx +1 -0
  280. package/src/plugins/builtin/ticker-detail/index.tsx +0 -14
  281. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +1 -1
  282. package/src/plugins/builtin/volatility/index.tsx +1 -1
  283. package/src/plugins/builtin/world-venue-map/map.desktop.test.tsx +106 -0
  284. package/src/plugins/builtin/world-venue-map/map.tsx +213 -69
  285. package/src/plugins/builtin/world-venue-map/model.test.ts +32 -0
  286. package/src/plugins/builtin/world-venue-map/model.ts +134 -13
  287. package/src/plugins/builtin/yield-curve/index.tsx +1 -1
  288. package/src/plugins/bundle.test.ts +109 -0
  289. package/src/plugins/bundle.ts +139 -0
  290. package/src/plugins/catalog-browser.test.ts +1 -0
  291. package/src/plugins/catalog-browser.ts +10 -0
  292. package/src/plugins/catalog-manifest.test.ts +43 -0
  293. package/src/plugins/catalog-manifest.ts +92 -0
  294. package/src/plugins/catalog-ui.ts +20 -4
  295. package/src/plugins/catalog.ts +9 -2
  296. package/src/plugins/current-target.ts +28 -0
  297. package/src/plugins/host-contract.ts +37 -0
  298. package/src/plugins/host-link.ts +158 -0
  299. package/src/plugins/host-modules.ts +61 -0
  300. package/src/plugins/loader.ts +41 -22
  301. package/src/plugins/ownership.ts +1 -0
  302. package/src/plugins/prediction-markets/index.tsx +1 -0
  303. package/src/plugins/registry/context.ts +3 -0
  304. package/src/plugins/registry/contributions.ts +27 -0
  305. package/src/plugins/registry/index.test.ts +25 -0
  306. package/src/plugins/registry/index.ts +8 -0
  307. package/src/plugins/registry/pane-settings.ts +6 -13
  308. package/src/plugins/seed.test.ts +83 -0
  309. package/src/plugins/seed.ts +80 -0
  310. package/src/public/broker.ts +31 -0
  311. package/src/public/market-data.ts +9 -0
  312. package/src/public/pane-hooks.ts +57 -0
  313. package/src/public/public-api.test.ts +285 -0
  314. package/src/public/react.ts +62 -0
  315. package/src/public/test-support.ts +23 -0
  316. package/src/public/time-series.ts +8 -0
  317. package/src/public/utils.ts +77 -0
  318. package/src/remote/layout-helpers.ts +6 -8
  319. package/src/remote/resources.ts +2 -0
  320. package/src/remote/schema.ts +1 -0
  321. package/src/remote/semantic-tree.test.tsx +105 -0
  322. package/src/remote/semantic-tree.tsx +53 -35
  323. package/src/renderers/browser/main.tsx +6 -0
  324. package/src/renderers/cloudflare/worker.test.ts +19 -0
  325. package/src/renderers/cloudflare/worker.ts +1 -1
  326. package/src/renderers/electrobun/bun/external-plugins.ts +140 -0
  327. package/src/renderers/electrobun/bun/index.ts +5 -2
  328. package/src/renderers/electrobun/shared/protocol.ts +21 -0
  329. package/src/renderers/electrobun/view/build-assets.ts +0 -1
  330. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +51 -1
  331. package/src/renderers/electrobun/view/external-plugins.test.ts +45 -0
  332. package/src/renderers/electrobun/view/external-plugins.ts +63 -0
  333. package/src/renderers/electrobun/view/host/ascii-text.test.ts +48 -0
  334. package/src/renderers/electrobun/view/host/ascii-text.ts +11 -1
  335. package/src/renderers/electrobun/view/host/chart-surface.tsx +38 -32
  336. package/src/renderers/electrobun/view/host/mouse.ts +16 -2
  337. package/src/renderers/electrobun/view/main.tsx +27 -2
  338. package/src/renderers/electrobun/view/styles.css +5 -0
  339. package/src/renderers/electrobun/view/toast-host.tsx +18 -0
  340. package/src/renderers/opentui/chart-surface.tsx +5 -1
  341. package/src/renderers/opentui/host.tsx +16 -2
  342. package/src/renderers/opentui/interaction-performance.test.ts +79 -0
  343. package/src/renderers/opentui/interaction-performance.ts +188 -0
  344. package/src/renderers/opentui/start.tsx +26 -1
  345. package/src/renderers/opentui/test-utils.tsx +13 -0
  346. package/src/renderers/opentui/toast-host.tsx +26 -2
  347. package/src/renderers/opentui/ui-host.tsx +1 -0
  348. package/src/sources/provider-router/search.test.ts +191 -0
  349. package/src/sources/provider-router/search.ts +92 -39
  350. package/src/state/app/context/index.tsx +2 -0
  351. package/src/state/app/context/selector.test.tsx +33 -1
  352. package/src/state/hooks/quote-streaming.test.tsx +93 -0
  353. package/src/state/hooks/quote-streaming.ts +106 -30
  354. package/src/state/pane-table-export-registry.ts +19 -0
  355. package/src/theme/color-utils.ts +1 -1
  356. package/src/theme/colors.ts +50 -0
  357. package/src/theme/themes.test.ts +15 -2
  358. package/src/theme/themes.ts +19 -1
  359. package/src/tickers/search/index.test.ts +26 -0
  360. package/src/tickers/search/index.ts +42 -17
  361. package/src/tickers/search/ranking.ts +14 -0
  362. package/src/time-series/chart-data.test.ts +216 -0
  363. package/src/time-series/resolution.test.ts +25 -26
  364. package/src/time-series/resolution.ts +41 -10
  365. package/src/time-series/resolve.ts +7 -0
  366. package/src/time-series/types.ts +3 -1
  367. package/src/time-series/use-chart-resolution.ts +5 -0
  368. package/src/types/config.ts +123 -3
  369. package/src/types/data-provider.ts +11 -0
  370. package/src/types/headless.ts +161 -0
  371. package/src/types/plugin.ts +102 -0
  372. package/src/ui/host.tsx +12 -1
  373. package/src/ui/index.tsx +1 -0
  374. package/src/ui/toast.tsx +8 -4
  375. package/src/utils/format.ts +35 -0
  376. package/src/utils/fuzzy-search.test.ts +94 -0
  377. package/src/utils/fuzzy-search.ts +81 -22
  378. package/src/utils/shortcut-labels.ts +14 -0
  379. package/src/components/chart/core/axis-format.ts +0 -157
  380. package/src/components/chart/core/index.test.ts +0 -1075
  381. package/src/components/chart/core/projection.ts +0 -357
  382. package/src/components/chart/core/render-utils.ts +0 -4
  383. package/src/components/chart/core/renderer.ts +0 -244
  384. package/src/components/chart/core/scene.ts +0 -180
  385. package/src/components/chart/core/time-axis.ts +0 -330
  386. package/src/components/chart/cursor-motion.ts +0 -47
  387. package/src/components/chart/indicators/types.ts +0 -26
  388. package/src/components/chart/market-session.ts +0 -176
  389. package/src/components/chart/multi-line/renderer.test.ts +0 -83
  390. package/src/components/chart/multi-line/renderer.ts +0 -161
  391. package/src/components/chart/multi-line/scene.ts +0 -188
  392. package/src/components/chart/native/raster/layout.ts +0 -129
  393. package/src/components/chart/native/raster/price-chart.ts +0 -244
  394. package/src/components/chart/rendering/chart-draw.ts +0 -270
  395. package/src/components/chart/rendering/geometry.ts +0 -95
  396. package/src/components/chart/rendering/pixel-buffer.ts +0 -267
  397. package/src/components/chart/scatter-chart-renderer.test.ts +0 -31
  398. package/src/components/chart/scatter-chart-renderer.ts +0 -223
  399. package/src/components/chart/static/chart/surface.tsx +0 -358
  400. package/src/components/command-bar/routes/root/article-results.test.ts +0 -83
  401. package/src/components/command-bar/routes/root/article-results.ts +0 -55
  402. package/src/plugins/builtin/news/wire/article-search.test.ts +0 -72
  403. package/src/plugins/builtin/news/wire/article-search.ts +0 -140
  404. package/src/plugins/builtin/substack/api/auth.ts +0 -185
  405. package/src/plugins/builtin/substack/api/cache.ts +0 -82
  406. package/src/plugins/builtin/substack/api/loaders.ts +0 -170
  407. package/src/plugins/builtin/substack/api/store.ts +0 -186
  408. package/src/plugins/builtin/substack/api/types.ts +0 -42
  409. package/src/plugins/builtin/substack/api.test.ts +0 -298
  410. package/src/plugins/builtin/substack/article-detail.tsx +0 -397
  411. package/src/plugins/builtin/substack/article-stack.tsx +0 -133
  412. package/src/plugins/builtin/substack/content.ts +0 -478
  413. package/src/plugins/builtin/substack/feed-tabs.tsx +0 -44
  414. package/src/plugins/builtin/substack/index.tsx +0 -49
  415. package/src/plugins/builtin/substack/login-view.tsx +0 -126
  416. package/src/plugins/builtin/substack/model.test.ts +0 -155
  417. package/src/plugins/builtin/substack/normalize.ts +0 -343
  418. package/src/plugins/builtin/substack/pane-footer.ts +0 -52
  419. package/src/plugins/builtin/substack/pane-state.ts +0 -170
  420. package/src/plugins/builtin/substack/pane.tsx +0 -547
  421. package/src/plugins/builtin/substack/read-state.ts +0 -30
  422. package/src/plugins/builtin/substack/table.ts +0 -111
  423. package/src/plugins/builtin/substack/types.ts +0 -71
  424. package/src/plugins/builtin/substack/utils.ts +0 -131
  425. package/src/plugins/builtin/ticker-detail/data-panes/provider-search.tsx +0 -229
  426. package/src/plugins/ibkr/account-cache.test.ts +0 -102
  427. package/src/plugins/ibkr/account-cache.ts +0 -24
  428. package/src/plugins/ibkr/broker-adapter.ts +0 -286
  429. package/src/plugins/ibkr/config.test.ts +0 -79
  430. package/src/plugins/ibkr/config.ts +0 -250
  431. package/src/plugins/ibkr/dialogs.tsx +0 -40
  432. package/src/plugins/ibkr/flex/index.test.ts +0 -264
  433. package/src/plugins/ibkr/flex/index.ts +0 -367
  434. package/src/plugins/ibkr/flex/statement-client.ts +0 -235
  435. package/src/plugins/ibkr/fundamental-parser.ts +0 -149
  436. package/src/plugins/ibkr/gateway/account-loaders.ts +0 -266
  437. package/src/plugins/ibkr/gateway/account-summary.ts +0 -164
  438. package/src/plugins/ibkr/gateway/client-lock.ts +0 -154
  439. package/src/plugins/ibkr/gateway/connection-events.ts +0 -114
  440. package/src/plugins/ibkr/gateway/connection.ts +0 -152
  441. package/src/plugins/ibkr/gateway/contracts.ts +0 -132
  442. package/src/plugins/ibkr/gateway/fundamentals.ts +0 -83
  443. package/src/plugins/ibkr/gateway/helpers.ts +0 -33
  444. package/src/plugins/ibkr/gateway/history-requests.ts +0 -158
  445. package/src/plugins/ibkr/gateway/history.ts +0 -120
  446. package/src/plugins/ibkr/gateway/instrument-lookup.ts +0 -117
  447. package/src/plugins/ibkr/gateway/market-data.ts +0 -345
  448. package/src/plugins/ibkr/gateway/order-execution.ts +0 -218
  449. package/src/plugins/ibkr/gateway/orders.ts +0 -63
  450. package/src/plugins/ibkr/gateway/price-normalization.test.ts +0 -44
  451. package/src/plugins/ibkr/gateway/price-normalization.ts +0 -50
  452. package/src/plugins/ibkr/gateway/quote-stream-controller.ts +0 -203
  453. package/src/plugins/ibkr/gateway/quote-stream.ts +0 -171
  454. package/src/plugins/ibkr/gateway/selection.ts +0 -51
  455. package/src/plugins/ibkr/gateway/service/index.test.ts +0 -675
  456. package/src/plugins/ibkr/gateway/service/index.ts +0 -429
  457. package/src/plugins/ibkr/gateway/service/lifecycle.ts +0 -191
  458. package/src/plugins/ibkr/gateway/service/manager.ts +0 -52
  459. package/src/plugins/ibkr/gateway/service/market-data.ts +0 -48
  460. package/src/plugins/ibkr/gateway/service/native-loader.ts +0 -12
  461. package/src/plugins/ibkr/gateway/service/native.ts +0 -543
  462. package/src/plugins/ibkr/gateway/timeouts.ts +0 -13
  463. package/src/plugins/ibkr/gateway/types.ts +0 -27
  464. package/src/plugins/ibkr/index.test.tsx +0 -162
  465. package/src/plugins/ibkr/index.tsx +0 -159
  466. package/src/plugins/ibkr/instance-selection.test.ts +0 -120
  467. package/src/plugins/ibkr/instance-selection.ts +0 -36
  468. package/src/plugins/ibkr/portfolio-performance.test.ts +0 -32
  469. package/src/plugins/ibkr/portfolio-performance.ts +0 -129
  470. package/src/plugins/ibkr/trade/badge.tsx +0 -28
  471. package/src/plugins/ibkr/trade/dialogs.tsx +0 -44
  472. package/src/plugins/ibkr/trade/preview-panel.tsx +0 -130
  473. package/src/plugins/ibkr/trade/tab/actions.tsx +0 -443
  474. package/src/plugins/ibkr/trade/tab/field-editors.tsx +0 -79
  475. package/src/plugins/ibkr/trade/tab/footer.ts +0 -53
  476. package/src/plugins/ibkr/trade/tab/header.tsx +0 -167
  477. package/src/plugins/ibkr/trade/tab/index.test.tsx +0 -178
  478. package/src/plugins/ibkr/trade/tab/index.tsx +0 -298
  479. package/src/plugins/ibkr/trade/tab/model.ts +0 -159
  480. package/src/plugins/ibkr/trade/tab/order-request.ts +0 -64
  481. package/src/plugins/ibkr/trade/tab/shortcuts.ts +0 -89
  482. package/src/plugins/ibkr/trade/tab/state-sync.ts +0 -86
  483. package/src/plugins/ibkr/trade/ticket-panel.tsx +0 -301
  484. package/src/plugins/ibkr/trade/utils.test.ts +0 -175
  485. package/src/plugins/ibkr/trade/utils.ts +0 -144
  486. package/src/plugins/ibkr/trading/pane-view.tsx +0 -237
  487. package/src/plugins/ibkr/trading/pane.tsx +0 -332
  488. package/src/plugins/ibkr/trading/state.test.ts +0 -109
  489. 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
  }
@@ -406,8 +406,9 @@ export const economicCalendarModule: PluginModule = {
406
406
  paneId: "econ-calendar",
407
407
  label: "Economic Calendar",
408
408
  description: "Upcoming economic events, releases, and indicators.",
409
+ // "econ" stays a keyword so the old ECON prefix still finds this pane.
409
410
  keywords: ["econ", "economic", "calendar", "events", "macro", "releases", "fed", "cpi", "gdp"],
410
- shortcut: { prefix: "ECON" },
411
+ shortcut: { prefix: "ECO" },
411
412
  }],
412
413
  setup(ctx) {
413
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
+ }