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
@@ -2,17 +2,23 @@ import { Fragment, createElement, type ReactNode } from "react";
2
2
  import type {
3
3
  GloomPlugin,
4
4
  GloomPluginContext,
5
- PluginCliDescriptor,
6
5
  } from "../../types/plugin";
7
6
 
8
- type PluginMetadataKey = "id" | "name" | "version" | "description" | "toggleable" | "order";
7
+ type PluginMetadataKey =
8
+ | "id"
9
+ | "name"
10
+ | "version"
11
+ | "description"
12
+ | "toggleable"
13
+ | "order"
14
+ | "targets"
15
+ | "homepage";
9
16
  type PluginMetadata = Pick<GloomPlugin, PluginMetadataKey>;
10
17
 
11
18
  export type PluginModule = Omit<GloomPlugin, PluginMetadataKey>;
12
19
 
13
20
  const HANDLED_MODULE_KEYS = [
14
21
  "cliCommands",
15
- "cli",
16
22
  "setup",
17
23
  "dispose",
18
24
  "panes",
@@ -23,11 +29,8 @@ const HANDLED_MODULE_KEYS = [
23
29
  ] as const satisfies readonly (keyof PluginModule)[];
24
30
 
25
31
  type MissingModuleKey = Exclude<keyof PluginModule, typeof HANDLED_MODULE_KEYS[number]>;
26
- type MissingCliDescriptorKey = Exclude<keyof PluginCliDescriptor, "commands">;
27
32
  const ALL_MODULE_KEYS_HANDLED: MissingModuleKey extends never ? true : never = true;
28
- const ALL_CLI_DESCRIPTOR_KEYS_HANDLED: MissingCliDescriptorKey extends never ? true : never = true;
29
33
  void ALL_MODULE_KEYS_HANDLED;
30
- void ALL_CLI_DESCRIPTOR_KEYS_HANDLED;
31
34
 
32
35
  interface CompositePluginOptions extends PluginMetadata {
33
36
  modules: readonly PluginModule[];
@@ -72,7 +75,6 @@ function composeSlots(modules: readonly PluginModule[]): GloomPlugin["slots"] {
72
75
  export function composeBuiltinPlugin(options: CompositePluginOptions): GloomPlugin {
73
76
  const { modules, ...metadata } = options;
74
77
  const cliCommands = modules.flatMap((module) => module.cliCommands ?? []);
75
- const cliDescriptors = modules.flatMap((module) => module.cli?.commands ?? []);
76
78
  const panes = modules.flatMap((module) => module.panes ?? []);
77
79
  const paneTemplates = modules.flatMap((module) => module.paneTemplates ?? []);
78
80
  const capabilities = modules.flatMap((module) => module.capabilities ?? []);
@@ -83,7 +85,6 @@ export function composeBuiltinPlugin(options: CompositePluginOptions): GloomPlug
83
85
  return {
84
86
  ...metadata,
85
87
  ...(cliCommands.length > 0 ? { cliCommands } : {}),
86
- ...(cliDescriptors.length > 0 ? { cli: { commands: cliDescriptors } } : {}),
87
88
  ...(panes.length > 0 ? { panes } : {}),
88
89
  ...(paneTemplates.length > 0 ? { paneTemplates } : {}),
89
90
  ...(capabilities.length > 0 ? { capabilities } : {}),
@@ -48,6 +48,7 @@ export const portfolioListModule: PluginModule = {
48
48
  defaultMode: "floating",
49
49
  defaultWidth: "40%",
50
50
  tickerSource: true,
51
+ tableExport: true,
51
52
  quickSettings: [LIVE_STREAMING_QUICK_SETTING],
52
53
  portableShare: {
53
54
  private: {
@@ -50,6 +50,7 @@ export const researchModule: PluginModule = {
50
50
  defaultPosition: "right",
51
51
  defaultMode: "floating",
52
52
  defaultFloatingSize: { width: 90, height: 28 },
53
+ tableExport: true,
53
54
  },
54
55
  {
55
56
  id: "equity-diagnostic",
@@ -68,6 +69,7 @@ export const researchModule: PluginModule = {
68
69
  defaultPosition: "right",
69
70
  defaultMode: "floating",
70
71
  defaultFloatingSize: { width: 104, height: 24 },
72
+ tableExport: true,
71
73
  },
72
74
  {
73
75
  id: "relative-valuation",
@@ -77,6 +79,7 @@ export const researchModule: PluginModule = {
77
79
  defaultPosition: "right",
78
80
  defaultMode: "floating",
79
81
  defaultFloatingSize: { width: 104, height: 24 },
82
+ tableExport: true,
80
83
  },
81
84
  {
82
85
  id: "earnings-estimates",
@@ -86,6 +89,7 @@ export const researchModule: PluginModule = {
86
89
  defaultPosition: "right",
87
90
  defaultMode: "floating",
88
91
  defaultFloatingSize: { width: 104, height: 22 },
92
+ tableExport: true,
89
93
  },
90
94
  ],
91
95
 
@@ -0,0 +1,81 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import type { CloudSearchHit } from "../../../api-client";
3
+ import { hitResultDef } from "./command-bar-search";
4
+ import { formatHitDateShort } from "./model";
5
+
6
+ const NOW = new Date("2026-09-01T12:00:00").getTime();
7
+
8
+ function hit(overrides: Partial<CloudSearchHit>): CloudSearchHit {
9
+ return {
10
+ id: "hit-1",
11
+ docType: "news",
12
+ sourceId: "doc-1",
13
+ chunkIndex: 0,
14
+ ticker: "NVDA",
15
+ publishedAt: "2026-08-30T09:00:00",
16
+ title: "Nvidia guides above estimates",
17
+ url: "https://example.com",
18
+ snippet: "record <mark>margin</mark> again",
19
+ score: 1,
20
+ metadata: { source: "Reuters" },
21
+ ...overrides,
22
+ };
23
+ }
24
+
25
+ describe("formatHitDateShort", () => {
26
+ /**
27
+ * Three formats hinge on two boundaries, a day and a year, and the column
28
+ * is too narrow to tell a mistake from a design choice.
29
+ */
30
+ test("switches from age to day to month at the day and year boundaries", () => {
31
+ const at = (iso: string) => formatHitDateShort(iso, NOW);
32
+ expect(at("2026-09-01T11:59:30")).toBe("1m");
33
+ expect(at("2026-09-01T08:45:00")).toBe("3h");
34
+ expect(at("2026-08-31T12:00:01")).toBe("23h");
35
+ expect(at("2026-08-31T12:00:00")).toBe("Aug 31");
36
+ expect(at("2026-01-01T00:00:00")).toBe("Jan 1");
37
+ expect(at("2025-12-31T23:59:59")).toBe("Dec 2025");
38
+ // A clock ahead of ours is not "in -2h".
39
+ expect(at("2026-09-01T14:00:00")).toBe("Sep 1");
40
+ expect(at("not a date")).toBe("");
41
+ expect(at(undefined)).toBe("");
42
+ });
43
+ });
44
+
45
+ describe("document hit rows", () => {
46
+ test("put the date on the right and the source and ticker ahead of the snippet", () => {
47
+ const row = hitResultDef(hit({}), () => {}, NOW);
48
+ expect(row.badge).toBe("NEWS");
49
+ expect(row.right).toBe("Aug 30");
50
+ expect(row.lines).toEqual([{
51
+ segments: [
52
+ { text: "Reuters · NVDA · ", emphasis: "muted" },
53
+ { text: "record ", emphasis: undefined },
54
+ { text: "margin", emphasis: "match" },
55
+ { text: " again", emphasis: undefined },
56
+ ],
57
+ }]);
58
+ });
59
+
60
+ test("lead with the speaker for a call and the section for a filing, whose form is the badge", () => {
61
+ const call = hitResultDef(hit({
62
+ docType: "transcript",
63
+ metadata: { speaker: "Colette Kress", role: "CFO" },
64
+ }), () => {}, NOW);
65
+ expect(call.badge).toBe("CALL");
66
+ expect(call.lines?.[0]?.segments[0]?.text).toBe("Colette Kress, CFO · NVDA · ");
67
+
68
+ const filing = hitResultDef(hit({
69
+ docType: "filing",
70
+ metadata: { form: "10-K", section: "Item 1A" },
71
+ }), () => {}, NOW);
72
+ expect(filing.badge).toBe("10-K");
73
+ expect(filing.lines?.[0]?.segments[0]?.text).toBe("Item 1A · NVDA · ");
74
+
75
+ const bare = hitResultDef(hit({ metadata: {} }), () => {}, NOW);
76
+ expect(bare.lines?.[0]?.segments[0]?.text).toBe("NVDA · ");
77
+
78
+ // The News section is all one kind, so a badge there would only repeat the heading.
79
+ expect(hitResultDef(hit({}), () => {}, NOW, { badge: false }).badge).toBeUndefined();
80
+ });
81
+ });
@@ -0,0 +1,175 @@
1
+ import type { CloudSearchDocType, CloudSearchHit } from "../../../api-client";
2
+ import type {
3
+ CommandBarResultDef,
4
+ CommandBarSearchProvider,
5
+ GloomPluginContext,
6
+ } from "../../../types/plugin";
7
+ import { apiClient } from "../../../api-client";
8
+ import { runDocumentSearch } from "./data";
9
+ import { requestDocumentFocus } from "./focus-handoff";
10
+ import {
11
+ formatHitDate,
12
+ formatHitDateShort,
13
+ hitLeadIn,
14
+ hitTypeLabel,
15
+ researchSearchInstanceId,
16
+ RESEARCH_SEARCH_TEMPLATE_ID,
17
+ } from "./model";
18
+ import { parseMarkedSnippet } from "./snippet";
19
+
20
+ /**
21
+ * Each hit costs two rows (title, then source and snippet), and the sheet now
22
+ * takes about half the terminal rather than a fixed sixteen rows. Four apiece
23
+ * fits both sections without pushing what follows off screen.
24
+ */
25
+ const COMMAND_BAR_HIT_LIMIT = 4;
26
+
27
+ const NEWS_DOC_TYPES: CloudSearchDocType[] = ["news"];
28
+ const FILING_DOC_TYPES: CloudSearchDocType[] = ["transcript", "filing"];
29
+
30
+ export function hitResultDef(
31
+ hit: CloudSearchHit,
32
+ openHit: (hit: CloudSearchHit) => void,
33
+ now = Date.now(),
34
+ options?: { badge?: boolean },
35
+ ): CommandBarResultDef {
36
+ const leadIn = [hitLeadIn(hit), hit.ticker].filter(Boolean).join(" · ");
37
+ const snippet = parseMarkedSnippet(hit.snippet).map((segment) => ({
38
+ text: segment.text,
39
+ emphasis: segment.marked ? "match" as const : undefined,
40
+ }));
41
+ // Source and ticker open the snippet line, so the right column is free for
42
+ // the date, which is what decides whether a hit is still worth reading.
43
+ const segments = leadIn
44
+ ? [{ text: `${leadIn} · `, emphasis: "muted" as const }, ...snippet]
45
+ : snippet;
46
+ return {
47
+ id: hit.id,
48
+ label: hit.title,
49
+ detail: [leadIn, formatHitDate(hit.publishedAt)].filter(Boolean).join(" · "),
50
+ // CALL or the filing form. Dropped in the News section, where every row is
51
+ // the same kind and a badge would only repeat the heading.
52
+ badge: options?.badge === false ? undefined : hitTypeLabel(hit),
53
+ right: formatHitDateShort(hit.publishedAt, now),
54
+ lines: segments.length > 0 ? [{ segments }] : undefined,
55
+ keywords: [hit.ticker, hitTypeLabel(hit)],
56
+ execute: () => openHit(hit),
57
+ };
58
+ }
59
+
60
+ interface CorpusSectionOptions {
61
+ id: string;
62
+ category: string;
63
+ priority: number;
64
+ docTypes: CloudSearchDocType[];
65
+ badge: boolean;
66
+ /** Appended below the hits by the section that offers the whole corpus. */
67
+ searchAllRow?: boolean;
68
+ }
69
+
70
+ /**
71
+ * Full-text corpus hits for free text the command bar could not resolve. Every
72
+ * failure — including the 402 a server-side refusal returns — degrades to no
73
+ * rows at all: the command bar is not the place to learn that a background
74
+ * search you never asked for went wrong.
75
+ *
76
+ * News is asked for in its own request rather than split out of a shared one: a
77
+ * single response has to spend its row budget on whichever kind scored better,
78
+ * so a query matching a hundred wire stories would leave no room for a filing.
79
+ */
80
+ function createCorpusSearchProvider(
81
+ ctx: GloomPluginContext,
82
+ section: CorpusSectionOptions,
83
+ ): CommandBarSearchProvider {
84
+ const openSearchPane = (query: string) => {
85
+ ctx.createPaneFromTemplate(RESEARCH_SEARCH_TEMPLATE_ID, { arg: query });
86
+ };
87
+
88
+ return {
89
+ id: section.id,
90
+ category: section.category,
91
+ /**
92
+ * Async sections sit below every synchronous one, ordered by how long they
93
+ * take to arrive, so a late arrival only ever pushes rows below itself and
94
+ * never the rows the eye is already on. Instruments answer in ~200ms, the
95
+ * corpus in ~400ms, Ask AI later still; the view model pins those bands at
96
+ * 100, 190/200 and 300.
97
+ */
98
+ priority: section.priority,
99
+ minQueryLength: 3,
100
+ debounceMs: 350,
101
+
102
+ async provide(query, _context, signal) {
103
+ // Only skip when there is no session at all, so a signed-out bar does not
104
+ // spend a request per query to be told so. Deliberately not gated on the
105
+ // cached user's plan: that cache is cleared whenever a session refresh
106
+ // comes back empty, which leaves the app authenticated for every other
107
+ // surface while reporting itself signed out here. Entitlement is the
108
+ // server's answer anyway, and it already returns 401 or 402 on its own.
109
+ if (!apiClient.isSignedIn()) return [];
110
+
111
+ let hits: CloudSearchHit[] = [];
112
+ try {
113
+ const response = await runDocumentSearch(
114
+ // One row per story: three matching paragraphs of one article is one
115
+ // result, and the teaser has three rows to spend.
116
+ {
117
+ query,
118
+ docTypes: section.docTypes,
119
+ limit: COMMAND_BAR_HIT_LIMIT,
120
+ count: false,
121
+ distinct: true,
122
+ },
123
+ signal,
124
+ );
125
+ hits = response.hits ?? [];
126
+ } catch {
127
+ return [];
128
+ }
129
+
130
+ const now = Date.now();
131
+ const rows = hits.map((hit) => hitResultDef(
132
+ hit,
133
+ (target) => {
134
+ openSearchPane(query);
135
+ requestDocumentFocus(researchSearchInstanceId(query), target);
136
+ },
137
+ now,
138
+ { badge: section.badge },
139
+ ));
140
+ if (!section.searchAllRow || rows.length === 0) return rows;
141
+ return [
142
+ ...rows,
143
+ {
144
+ id: "search-all",
145
+ label: "Search all documents →",
146
+ detail: "Earnings calls, news, and SEC filings",
147
+ badge: "DOCS",
148
+ right: "SRCH",
149
+ execute: () => openSearchPane(query),
150
+ },
151
+ ];
152
+ },
153
+ };
154
+ }
155
+
156
+ export function createNewsSearchProvider(ctx: GloomPluginContext): CommandBarSearchProvider {
157
+ return createCorpusSearchProvider(ctx, {
158
+ id: "research-search:news",
159
+ category: "News",
160
+ priority: 190,
161
+ docTypes: NEWS_DOC_TYPES,
162
+ badge: false,
163
+ });
164
+ }
165
+
166
+ export function createDocumentSearchProvider(ctx: GloomPluginContext): CommandBarSearchProvider {
167
+ return createCorpusSearchProvider(ctx, {
168
+ id: "research-search:documents",
169
+ category: "Documents",
170
+ priority: 200,
171
+ docTypes: FILING_DOC_TYPES,
172
+ badge: true,
173
+ searchAllRow: true,
174
+ });
175
+ }
@@ -0,0 +1,109 @@
1
+ import {
2
+ apiClient,
3
+ type CloudSavedSearch,
4
+ type CloudSavedSearchInput,
5
+ type CloudSearchDocType,
6
+ type CloudSearchDocument,
7
+ type CloudSearchResponse,
8
+ } from "../../../api-client";
9
+ import { ApiRequestError } from "../../../api-client/errors";
10
+ import type { CloudSearchParams } from "../../../api-client/paths";
11
+ import type { PluginPersistence } from "../../../types/plugin";
12
+
13
+ const DOCUMENT_KIND = "document";
14
+ const CACHE_SOURCE = "research-search";
15
+ const CACHE_SCHEMA_VERSION = 1;
16
+
17
+ /**
18
+ * A published transcript, wire story, or filing does not change once indexed,
19
+ * so re-opening a hit should not hit the network again.
20
+ */
21
+ const DOCUMENT_CACHE_POLICY = {
22
+ staleMs: 7 * 24 * 60 * 60 * 1000,
23
+ expireMs: 90 * 24 * 60 * 60 * 1000,
24
+ } as const;
25
+
26
+ let persistence: PluginPersistence | null = null;
27
+
28
+ export function attachResearchSearchPersistence(value: PluginPersistence): void {
29
+ persistence = value;
30
+ }
31
+
32
+ export function resetResearchSearchPersistence(): void {
33
+ persistence = null;
34
+ }
35
+
36
+ export function statusOf(error: unknown): number | undefined {
37
+ return error instanceof ApiRequestError ? error.status : undefined;
38
+ }
39
+
40
+ export function errorMessage(error: unknown): string {
41
+ return error instanceof Error ? error.message : String(error);
42
+ }
43
+
44
+ export function isAbortError(error: unknown): boolean {
45
+ return error instanceof Error && error.name === "AbortError";
46
+ }
47
+
48
+ export function runDocumentSearch(
49
+ params: CloudSearchParams,
50
+ signal?: AbortSignal,
51
+ ): Promise<CloudSearchResponse> {
52
+ return apiClient.searchCloudDocuments(params, { signal });
53
+ }
54
+
55
+ function documentKey(docType: CloudSearchDocType, sourceId: string): string {
56
+ return `${docType}:${sourceId}`;
57
+ }
58
+
59
+ export async function loadSearchDocument(
60
+ docType: CloudSearchDocType,
61
+ sourceId: string,
62
+ signal?: AbortSignal,
63
+ ): Promise<CloudSearchDocument> {
64
+ const key = documentKey(docType, sourceId);
65
+ const cached = persistence?.getResource<CloudSearchDocument>(DOCUMENT_KIND, key, {
66
+ sourceKey: CACHE_SOURCE,
67
+ schemaVersion: CACHE_SCHEMA_VERSION,
68
+ });
69
+ if (cached && !cached.stale) return cached.value;
70
+
71
+ try {
72
+ const document = await apiClient.getCloudSearchDocument(docType, sourceId, { signal });
73
+ persistence?.setResource(DOCUMENT_KIND, key, document, {
74
+ sourceKey: CACHE_SOURCE,
75
+ schemaVersion: CACHE_SCHEMA_VERSION,
76
+ cachePolicy: DOCUMENT_CACHE_POLICY,
77
+ });
78
+ return document;
79
+ } catch (error) {
80
+ const status = statusOf(error);
81
+ const expired = persistence?.getResource<CloudSearchDocument>(DOCUMENT_KIND, key, {
82
+ sourceKey: CACHE_SOURCE,
83
+ schemaVersion: CACHE_SCHEMA_VERSION,
84
+ allowExpired: true,
85
+ });
86
+ // Losing entitlement has to re-gate the document, not serve it from cache.
87
+ if (expired && status !== 401 && status !== 402 && status !== 403) return expired.value;
88
+ throw error;
89
+ }
90
+ }
91
+
92
+ export function loadSavedSearches(signal?: AbortSignal): Promise<CloudSavedSearch[]> {
93
+ return apiClient.getCloudSavedSearches({ signal });
94
+ }
95
+
96
+ export function createSavedSearch(input: CloudSavedSearchInput): Promise<CloudSavedSearch> {
97
+ return apiClient.createCloudSavedSearch(input);
98
+ }
99
+
100
+ export function updateSavedSearch(
101
+ id: string,
102
+ update: Partial<CloudSavedSearchInput>,
103
+ ): Promise<CloudSavedSearch> {
104
+ return apiClient.updateCloudSavedSearch(id, update);
105
+ }
106
+
107
+ export function deleteSavedSearch(id: string): Promise<void> {
108
+ return apiClient.deleteCloudSavedSearch(id);
109
+ }
@@ -0,0 +1,75 @@
1
+ import { afterEach, expect, test } from "bun:test";
2
+ import { act } from "react";
3
+ import { testRender } from "../../../renderers/opentui/test-utils";
4
+ import type { CloudSearchDocument, CloudSearchHit } from "../../../api-client";
5
+ import { SearchDocumentView } from "./document-view";
6
+
7
+ // One unbroken run, so every wrapped line is exactly as wide as the wrap width
8
+ // and the rightmost printed column is the wrap width rather than wherever the
9
+ // last word happened to end.
10
+ const PARAGRAPH = "forward-looking-statement.".repeat(40);
11
+
12
+ const HIT = {
13
+ id: "hit-1",
14
+ docType: "news",
15
+ sourceId: "doc-1",
16
+ chunkIndex: 0,
17
+ ticker: "NVDA",
18
+ publishedAt: "2026-08-31T12:00:00.000Z",
19
+ title: "Press Release",
20
+ url: "https://example.com/doc-1",
21
+ snippet: "lorem <mark>ipsum</mark> dolor",
22
+ score: 1,
23
+ metadata: { source: "Business Wire" },
24
+ } as unknown as CloudSearchHit;
25
+
26
+ const DOCUMENT = {
27
+ docType: "news",
28
+ sourceId: "doc-1",
29
+ ticker: "NVDA",
30
+ title: "Press Release",
31
+ url: "https://example.com/doc-1",
32
+ publishedAt: "2026-08-31T12:00:00.000Z",
33
+ chunks: Array.from({ length: 4 }, (_, index) => ({
34
+ id: `chunk-${index}`,
35
+ chunkIndex: index,
36
+ body: PARAGRAPH,
37
+ metadata: { source: "Business Wire" },
38
+ })),
39
+ } as unknown as CloudSearchDocument;
40
+
41
+ let testSetup: Awaited<ReturnType<typeof testRender>> | undefined;
42
+
43
+ afterEach(async () => {
44
+ if (testSetup) {
45
+ await act(async () => {
46
+ testSetup!.renderer.destroy();
47
+ });
48
+ testSetup = undefined;
49
+ }
50
+ });
51
+
52
+ /**
53
+ * The wrap width, the scroll box's inset and its scrollbar are three separate
54
+ * subtractions from the same pane, and counting one of them twice is invisible
55
+ * in review: the text still renders, just short of the edge.
56
+ */
57
+ for (const width of [60, 88, 120]) {
58
+ test(`paragraphs reach the column before the scrollbar at width ${width}`, async () => {
59
+ testSetup = await testRender(
60
+ <SearchDocumentView hit={HIT} document={DOCUMENT} loading={false} error={null} width={width} />,
61
+ { width, height: 20 },
62
+ );
63
+ for (let frame = 0; frame < 3; frame += 1) {
64
+ await act(async () => {
65
+ await testSetup!.renderOnce();
66
+ });
67
+ }
68
+
69
+ const rows = testSetup.captureCharFrame().split("\n");
70
+ // The scrollbar owns the last column on every row it covers, so measure the
71
+ // text against the column before it.
72
+ const textRight = Math.max(...rows.map((row) => row.slice(0, width - 1).trimEnd().length));
73
+ expect(textRight).toBe(width - 1);
74
+ });
75
+ }