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
@@ -1,7 +1,6 @@
1
1
  import { mkdir, mkdtemp, rm, writeFile } from "fs/promises";
2
2
  import { tmpdir } from "os";
3
- import { join } from "path";
4
- import { pathToFileURL } from "url";
3
+ import { join, resolve, sep } from "path";
5
4
  import type { AppConfig } from "../types/config";
6
5
  import type { ResolvedSeries } from "../time-series/types";
7
6
  import type { OptionsChain, TickerFinancials } from "../types/financials";
@@ -9,6 +8,7 @@ import type { TickerRecord } from "../types/ticker";
9
8
  import type { PaneRuntimeState } from "../core/state/app/state";
10
9
  import type { RemoteUiNodeSnapshot } from "../remote/types";
11
10
  import type { FredSeriesCacheEntry } from "../data/fred-series";
11
+ import type { DatedObservation } from "../plugins/builtin/market-valuation/series";
12
12
  import {
13
13
  electrobunViewPath,
14
14
  writeElectrobunViewPage,
@@ -21,16 +21,46 @@ export interface DesktopPaneShotPayload {
21
21
  heightCells: number;
22
22
  widthPx: number;
23
23
  heightPx: number;
24
+ /** Chrome device scale factor; 2 keeps text crisp, higher values zoom the layout. */
25
+ deviceScaleFactor?: number;
26
+ /** Label drawn at the right edge of the pane title bar; null draws nothing. */
27
+ watermark?: string | null;
24
28
  tickers: TickerRecord[];
25
29
  financials: Array<[string, TickerFinancials]>;
26
30
  optionsChains: Array<[string, OptionsChain]>;
27
31
  fredSeries: Array<[string, FredSeriesCacheEntry]>;
32
+ valuationSeries: Array<[string, DatedObservation[]]>;
33
+ statSeries: Array<[string, DatedObservation[]]>;
28
34
  capabilitySeries: Array<[string, ResolvedSeries]>;
29
35
  paneState: Record<string, PaneRuntimeState>;
30
36
  }
31
37
 
38
+ /** Kept in Bun memory and never serialized into the browser page or CLI result. */
39
+ export interface DesktopPaneShotApiProxy {
40
+ baseUrl: string;
41
+ sessionToken: string | null;
42
+ }
43
+
44
+ export interface DesktopPaneShotRenderedCell {
45
+ columnId?: string;
46
+ columnLabel: string;
47
+ text: string;
48
+ }
49
+
50
+ export interface DesktopPaneShotRenderedRow {
51
+ tableIndex: number;
52
+ rowIndex: number;
53
+ key?: string;
54
+ selected: boolean;
55
+ cells: DesktopPaneShotRenderedCell[];
56
+ }
57
+
32
58
  export interface DesktopPaneShotRenderResult {
33
59
  visibleText: string;
60
+ rows: DesktopPaneShotRenderedRow[];
61
+ loadingStateDetected: boolean;
62
+ errorStateDetected: boolean;
63
+ errorStateMarkers: string[];
34
64
  emptyStateDetected: boolean;
35
65
  emptyStateMarkers: string[];
36
66
  semanticUi: RemoteUiNodeSnapshot[];
@@ -47,30 +77,47 @@ type PendingCdpCall = {
47
77
  reject: (error: Error) => void;
48
78
  };
49
79
 
80
+ const SHOT_MODE_CSS = [
81
+ "[data-gloom-role='composite-chart-toolbar']",
82
+ "[data-gloom-role='chart-series-quick-add']",
83
+ "[data-gloom-role='pane-close']",
84
+ ].join(", ") + " { display: none !important; }\n"
85
+ // Sits where the hidden close button was: one cell high, right-aligned in the title bar.
86
+ + "[data-gloom-role='shot-watermark'] { position: fixed; top: 1px; right: 10px; height: var(--cell-h);"
87
+ + " line-height: var(--cell-h); font-size: 12px; letter-spacing: 0.02em; color: var(--gloom-text-dim, #888);"
88
+ + " pointer-events: none; z-index: 1000; }";
89
+
50
90
  const CHROME_POLL_ATTEMPTS = 80;
51
- const SHOT_READY_TIMEOUT_MS = 10_000;
91
+ const SHOT_READY_TIMEOUT_MS = 45_000;
52
92
  const CDP_CALL_TIMEOUT_MS = 10_000;
53
- const SHOT_DEVICE_SCALE_FACTOR = 2;
93
+ const DEFAULT_DEVICE_SCALE_FACTOR = 2;
94
+ const SHOT_API_PROXY_PREFIX = "/__gloom_cli_api__";
95
+ const SESSION_COOKIE_NAMES = ["__Secure-gloomberb.session_token", "gloomberb.session_token"] as const;
54
96
 
55
97
  export async function renderDesktopPaneScreenshot(
56
98
  payload: DesktopPaneShotPayload,
57
99
  outputPath: string,
100
+ apiProxy: DesktopPaneShotApiProxy,
58
101
  ): Promise<DesktopPaneShotRenderResult> {
59
102
  const tempDir = await mkdtemp(join(tmpdir(), "gloom-pane-shot-"));
103
+ let server: ReturnType<typeof Bun.serve> | null = null;
60
104
  try {
61
105
  const outdir = join(tempDir, "assets");
62
106
  await mkdir(outdir, { recursive: true });
63
- const htmlPath = await buildShotPage(outdir, payload);
107
+ await buildShotPage(outdir, payload);
108
+ server = serveShotPage(outdir, apiProxy);
64
109
  const chrome = await findChromeExecutable();
65
110
  return await capturePageScreenshot({
66
111
  chrome,
67
- url: pathToFileURL(htmlPath).href,
112
+ url: new URL("/", server.url).href,
68
113
  outputPath,
69
114
  widthPx: payload.widthPx,
70
115
  heightPx: payload.heightPx,
116
+ deviceScaleFactor: payload.deviceScaleFactor ?? DEFAULT_DEVICE_SCALE_FACTOR,
71
117
  userDataDir: join(tempDir, "chrome-profile"),
72
118
  });
73
119
  } finally {
120
+ server?.stop(true);
74
121
  await rm(tempDir, { recursive: true, force: true });
75
122
  }
76
123
  }
@@ -90,6 +137,20 @@ async function buildShotPage(outdir: string, payload: DesktopPaneShotPayload): P
90
137
  loadingText: "Rendering pane...",
91
138
  bootstrapScript: `
92
139
  window.__GLOOM_CLI_SHOT_PAYLOAD__ = ${payloadJson};
140
+ (() => {
141
+ // A screenshot cannot be interacted with, so drawing tools, the
142
+ // quick-add input and the close button only add noise to the image.
143
+ const style = document.createElement("style");
144
+ style.textContent = ${JSON.stringify(SHOT_MODE_CSS)};
145
+ document.head.appendChild(style);
146
+ const watermark = ${JSON.stringify(payload.watermark ?? null)};
147
+ if (watermark) {
148
+ const mark = document.createElement("div");
149
+ mark.setAttribute("data-gloom-role", "shot-watermark");
150
+ mark.textContent = watermark;
151
+ document.addEventListener("DOMContentLoaded", () => document.body.appendChild(mark));
152
+ }
153
+ })();
93
154
  window.addEventListener("error", (event) => {
94
155
  window.__GLOOM_CLI_SHOT_ERROR__ = event.error && event.error.stack ? event.error.stack : String(event.error || event.message);
95
156
  });
@@ -100,6 +161,116 @@ async function buildShotPage(outdir: string, payload: DesktopPaneShotPayload): P
100
161
  });
101
162
  }
102
163
 
164
+ function serveShotPage(
165
+ outdir: string,
166
+ apiProxy: DesktopPaneShotApiProxy,
167
+ ): ReturnType<typeof Bun.serve> {
168
+ const staticRoot = resolve(outdir);
169
+ return Bun.serve({
170
+ hostname: "127.0.0.1",
171
+ port: 0,
172
+ idleTimeout: 60,
173
+ fetch(request) {
174
+ const url = new URL(request.url);
175
+ if (url.pathname.startsWith(`${SHOT_API_PROXY_PREFIX}/`)) {
176
+ return proxyShotApiRequest(request, url, apiProxy);
177
+ }
178
+ return serveShotAsset(url, staticRoot);
179
+ },
180
+ });
181
+ }
182
+
183
+ async function serveShotAsset(url: URL, staticRoot: string): Promise<Response> {
184
+ let pathname: string;
185
+ try {
186
+ pathname = decodeURIComponent(url.pathname);
187
+ } catch {
188
+ return new Response("Bad request", { status: 400 });
189
+ }
190
+ const relativePath = pathname === "/" ? "index.html" : pathname.replace(/^\/+/, "");
191
+ const filePath = resolve(staticRoot, relativePath);
192
+ if (filePath !== staticRoot && !filePath.startsWith(`${staticRoot}${sep}`)) {
193
+ return new Response("Not found", { status: 404 });
194
+ }
195
+ const file = Bun.file(filePath);
196
+ if (!(await file.exists())) return new Response("Not found", { status: 404 });
197
+ return new Response(file);
198
+ }
199
+
200
+ async function proxyShotApiRequest(
201
+ request: Request,
202
+ requestUrl: URL,
203
+ apiProxy: DesktopPaneShotApiProxy,
204
+ ): Promise<Response> {
205
+ const baseUrl = new URL(apiProxy.baseUrl);
206
+ const proxiedPath = requestUrl.pathname.slice(SHOT_API_PROXY_PREFIX.length) || "/";
207
+ const target = new URL(baseUrl);
208
+ const basePath = baseUrl.pathname.replace(/\/+$/, "");
209
+ target.pathname = `${basePath}${proxiedPath.startsWith("/") ? proxiedPath : `/${proxiedPath}`}`;
210
+ target.search = requestUrl.search;
211
+ target.hash = "";
212
+ const headers = new Headers(request.headers);
213
+ for (const name of [
214
+ "authorization",
215
+ "connection",
216
+ "content-length",
217
+ "cookie",
218
+ "host",
219
+ "origin",
220
+ "proxy-authorization",
221
+ "referer",
222
+ "te",
223
+ "trailer",
224
+ "transfer-encoding",
225
+ "upgrade",
226
+ "x-forwarded-for",
227
+ "x-forwarded-host",
228
+ "x-forwarded-proto",
229
+ ]) {
230
+ headers.delete(name);
231
+ }
232
+ const browserOwnedHeaders: string[] = [];
233
+ headers.forEach((_value, name) => {
234
+ if (name.startsWith("sec-") || name === "accept-encoding") browserOwnedHeaders.push(name);
235
+ });
236
+ for (const name of browserOwnedHeaders) headers.delete(name);
237
+ headers.set("Origin", baseUrl.origin);
238
+ if (apiProxy.sessionToken) {
239
+ headers.set(
240
+ "Cookie",
241
+ SESSION_COOKIE_NAMES.map((name) => `${name}=${apiProxy.sessionToken}`).join("; "),
242
+ );
243
+ }
244
+
245
+ try {
246
+ const response = await fetch(target, {
247
+ method: request.method,
248
+ headers,
249
+ body: request.method === "GET" || request.method === "HEAD" ? undefined : request.body,
250
+ redirect: "follow",
251
+ });
252
+ const responseHeaders = new Headers(response.headers);
253
+ for (const name of [
254
+ "access-control-allow-credentials",
255
+ "access-control-allow-origin",
256
+ "content-encoding",
257
+ "content-length",
258
+ "set-cookie",
259
+ "transfer-encoding",
260
+ ]) {
261
+ responseHeaders.delete(name);
262
+ }
263
+ responseHeaders.set("Cache-Control", "no-store");
264
+ return new Response(response.body, {
265
+ status: response.status,
266
+ statusText: response.statusText,
267
+ headers: responseHeaders,
268
+ });
269
+ } catch {
270
+ return Response.json({ error: "Cloud API request failed." }, { status: 502 });
271
+ }
272
+ }
273
+
103
274
  async function findChromeExecutable(): Promise<string> {
104
275
  const candidates = [
105
276
  process.env.CHROME_PATH,
@@ -123,6 +294,7 @@ async function capturePageScreenshot({
123
294
  outputPath,
124
295
  widthPx,
125
296
  heightPx,
297
+ deviceScaleFactor,
126
298
  userDataDir,
127
299
  }: {
128
300
  chrome: string;
@@ -130,6 +302,7 @@ async function capturePageScreenshot({
130
302
  outputPath: string;
131
303
  widthPx: number;
132
304
  heightPx: number;
305
+ deviceScaleFactor: number;
133
306
  userDataDir: string;
134
307
  }): Promise<DesktopPaneShotRenderResult> {
135
308
  await mkdir(userDataDir, { recursive: true });
@@ -166,7 +339,7 @@ async function capturePageScreenshot({
166
339
  await session.send("Emulation.setDeviceMetricsOverride", {
167
340
  width: widthPx,
168
341
  height: heightPx,
169
- deviceScaleFactor: SHOT_DEVICE_SCALE_FACTOR,
342
+ deviceScaleFactor,
170
343
  mobile: false,
171
344
  });
172
345
  await waitForShotReady(session);
@@ -189,6 +362,21 @@ async function capturePageScreenshot({
189
362
  }
190
363
  }
191
364
 
365
+ const LOADING_STATE_PATTERNS = [
366
+ /\bLoading(?: [^.]{0,80})?\.{3}/gi,
367
+ /\bRendering pane\.{3}/gi,
368
+ ];
369
+
370
+ const ERROR_STATE_PATTERNS = [
371
+ /\b[^.]{1,100} unavailable\./gi,
372
+ /\bFailed to fetch\b/gi,
373
+ /\bCould not load\b/gi,
374
+ /\bSign in to\b/gi,
375
+ /\bVerify your email\b/gi,
376
+ /\bpart of Gloom Cloud Pro\b/gi,
377
+ /\bCloud API request failed\b/gi,
378
+ ];
379
+
192
380
  const EMPTY_STATE_PATTERNS = [
193
381
  /\bNo chart data\b/gi,
194
382
  /\bNo graph data\b/gi,
@@ -197,21 +385,94 @@ const EMPTY_STATE_PATTERNS = [
197
385
  /\bNo comparison tickers configured\b/gi,
198
386
  /\bNo relationship tickers configured\b/gi,
199
387
  /\bNo overlapping price history\b/gi,
200
- /\bLoading chart\.{0,3}\b/gi,
201
- /\bRendering pane\.{0,3}\b/gi,
202
- /\bNo tickers selected\b/gi,
203
- /\bMarket data unavailable\b/gi,
388
+ /\bNo tickers? selected\b/gi,
204
389
  /\bNo data(?: available)?\b/gi,
390
+ /\bNothing to show yet\b/gi,
391
+ /\bNo transcribed calls(?: for [^.]+)? yet\b/gi,
392
+ /\bNo short interest (?:data|found)\b/gi,
393
+ /\bNo (?:House PTR |insider )?(?:trades|members|transactions|filings)\b/gi,
205
394
  ];
206
395
 
396
+ function stateMarkers(text: string, patterns: RegExp[]): string[] {
397
+ return [...new Set(patterns.flatMap((pattern) => (
398
+ [...text.matchAll(pattern)].map((match) => match[0])
399
+ )))];
400
+ }
401
+
207
402
  async function readRenderedPaneState(session: CdpSession): Promise<DesktopPaneShotRenderResult> {
208
403
  const result = await session.send("Runtime.evaluate", {
209
404
  expression: `(() => {
210
405
  const root = document.getElementById("root") || document.body;
406
+ const semanticUi = window.__GLOOM_CLI_SHOT_SEMANTIC_UI__ || [];
407
+ const normalize = (value) => String(value || "").replace(/\\s+/g, " ").trim();
408
+ const isVisible = (element) => {
409
+ if (!(element instanceof HTMLElement)) return false;
410
+ const style = getComputedStyle(element);
411
+ if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") return false;
412
+ const rect = element.getBoundingClientRect();
413
+ return rect.width > 0 && rect.height > 0
414
+ && rect.right > 0 && rect.bottom > 0
415
+ && rect.left < window.innerWidth && rect.top < window.innerHeight;
416
+ };
417
+ const semanticTables = semanticUi.filter((node) => node && node.role === "table");
418
+ const rows = [];
419
+ [...root.querySelectorAll('[data-gloom-role="data-table"]')]
420
+ .filter(isVisible)
421
+ .forEach((table, tableIndex) => {
422
+ const tableMetadata = semanticTables[tableIndex] && semanticTables[tableIndex].metadata || {};
423
+ const semanticColumns = Array.isArray(tableMetadata.columns) ? tableMetadata.columns : [];
424
+ const semanticRows = Array.isArray(tableMetadata.rows) ? tableMetadata.rows : [];
425
+ const headers = [...table.querySelectorAll('[data-gloom-role="data-table-header-cell"]')]
426
+ .map((cell) => normalize(cell.innerText || cell.textContent));
427
+ const rowElements = [...table.querySelectorAll(
428
+ '[data-gloom-role="data-table-row"], [data-gloom-role="data-table-section-header"]',
429
+ )].filter(isVisible);
430
+ rowElements.forEach((row, rowIndex) => {
431
+ const cellElements = [...row.querySelectorAll('[data-gloom-role="data-table-cell"]')];
432
+ const values = cellElements.length > 0 ? cellElements : [row];
433
+ const semanticRow = semanticRows[rowIndex] || {};
434
+ const cells = values.map((cell, cellIndex) => {
435
+ const semanticColumn = semanticColumns[cellIndex] || {};
436
+ return {
437
+ ...(typeof semanticColumn.id === "string" ? { columnId: semanticColumn.id } : {}),
438
+ columnLabel: typeof semanticColumn.label === "string"
439
+ ? semanticColumn.label
440
+ : headers[cellIndex] || (values.length === 1 ? "Row" : String(cellIndex + 1)),
441
+ text: normalize(cell.innerText || cell.textContent),
442
+ };
443
+ }).filter((cell) => cell.text.length > 0);
444
+ if (cells.length === 0) return;
445
+ rows.push({
446
+ tableIndex,
447
+ rowIndex,
448
+ ...(typeof semanticRow.key === "string" ? { key: semanticRow.key } : {}),
449
+ selected: row.getAttribute("data-selected") === "true" || semanticRow.selected === true,
450
+ cells,
451
+ });
452
+ });
453
+ });
454
+ if (rows.length === 0) {
455
+ [...root.querySelectorAll('[data-gloom-role="desktop-list-row"]')]
456
+ .filter(isVisible)
457
+ .forEach((row, rowIndex) => {
458
+ const text = normalize(row.innerText || row.textContent);
459
+ if (!text) return;
460
+ rows.push({
461
+ tableIndex: 0,
462
+ rowIndex,
463
+ selected: row.getAttribute("data-selected") === "true",
464
+ cells: [{ columnLabel: "Row", text }],
465
+ });
466
+ });
467
+ }
211
468
  return {
212
469
  visibleText: root.innerText || root.textContent || "",
213
470
  error: window.__GLOOM_CLI_SHOT_ERROR__ || "",
214
- semanticUi: window.__GLOOM_CLI_SHOT_SEMANTIC_UI__ || [],
471
+ loadingStateDetected: root.querySelector('[data-gloom-status="loading"]') !== null,
472
+ errorStateDetected: root.querySelector('[data-gloom-status="error"]') !== null,
473
+ emptyStateDetected: root.querySelector('[data-gloom-status="empty"]') !== null,
474
+ rows,
475
+ semanticUi,
215
476
  };
216
477
  })()`,
217
478
  returnByValue: true,
@@ -220,6 +481,10 @@ async function readRenderedPaneState(session: CdpSession): Promise<DesktopPaneSh
220
481
  value?: {
221
482
  visibleText?: string;
222
483
  error?: string;
484
+ loadingStateDetected?: boolean;
485
+ errorStateDetected?: boolean;
486
+ emptyStateDetected?: boolean;
487
+ rows?: DesktopPaneShotRenderedRow[];
223
488
  semanticUi?: RemoteUiNodeSnapshot[];
224
489
  };
225
490
  };
@@ -227,12 +492,16 @@ async function readRenderedPaneState(session: CdpSession): Promise<DesktopPaneSh
227
492
  const value = result.result?.value;
228
493
  if (value?.error) throw new Error(value.error);
229
494
  const visibleText = (value?.visibleText ?? "").replace(/\s+/g, " ").trim();
230
- const emptyStateMarkers = [...new Set(EMPTY_STATE_PATTERNS.flatMap((pattern) => (
231
- [...visibleText.matchAll(pattern)].map((match) => match[0])
232
- )))];
495
+ const loadingStateMarkers = stateMarkers(visibleText, LOADING_STATE_PATTERNS);
496
+ const errorStateMarkers = stateMarkers(visibleText, ERROR_STATE_PATTERNS);
497
+ const emptyStateMarkers = stateMarkers(visibleText, EMPTY_STATE_PATTERNS);
233
498
  return {
234
499
  visibleText,
235
- emptyStateDetected: emptyStateMarkers.length > 0,
500
+ rows: Array.isArray(value?.rows) ? value.rows : [],
501
+ loadingStateDetected: value?.loadingStateDetected === true || loadingStateMarkers.length > 0,
502
+ errorStateDetected: value?.errorStateDetected === true || errorStateMarkers.length > 0,
503
+ errorStateMarkers,
504
+ emptyStateDetected: value?.emptyStateDetected === true || emptyStateMarkers.length > 0,
236
505
  emptyStateMarkers,
237
506
  semanticUi: Array.isArray(value?.semanticUi) ? value.semanticUi : [],
238
507
  };
package/src/cli/index.ts CHANGED
@@ -121,7 +121,7 @@ function createCoreCliCommands(
121
121
  aliases: ["screenshot"],
122
122
  description: "Render a desktop-style screenshot for a pane-backed market function",
123
123
  help: {
124
- usage: ["shot <function-or-pane> [argument] [--output path] [--width px] [--height px] [--key value]"],
124
+ usage: ["shot <function-or-pane> [argument] [--output path] [--width px] [--height px] [--theme id] [--scale n] [--watermark text] [--key value]"],
125
125
  },
126
126
  execute: async (args, ctx) => {
127
127
  await runPaneScreenshot(args, ctx);
@@ -1,32 +1,22 @@
1
1
  import { CHART_RESOLUTIONS } from "../../time-series/range";
2
- import type { PaneDef, PaneTemplateDef } from "../../types/plugin";
2
+ import type {
3
+ HeadlessPaneArgumentDef,
4
+ HeadlessPaneDefinition,
5
+ HeadlessPaneOptionDef,
6
+ HeadlessPaneOptionType,
7
+ HeadlessPaneOptionValue,
8
+ PaneDef,
9
+ PaneTemplateDef,
10
+ } from "../../types/plugin";
3
11
 
4
12
  export type PaneFunctionReadiness = "ready" | "partial" | "unsupported";
13
+ export type PaneFunctionScreenshotReadiness = PaneFunctionReadiness | "live-dom";
5
14
  export type PaneFunctionTickerCardinality = "none" | "one" | "one-or-more" | "two-or-more" | "one-or-two";
6
- export type PaneFunctionOptionType = "enum" | "integer" | "string" | "boolean";
15
+ export type PaneFunctionOptionType = HeadlessPaneOptionType;
16
+ export type PaneFunctionOptionValue = HeadlessPaneOptionValue;
17
+ export type PaneFunctionOptionDef = HeadlessPaneOptionDef;
7
18
  export type NormalizedPaneFunctionOptions = Record<string, string | number | boolean>;
8
19
 
9
- export interface PaneFunctionOptionValue {
10
- value: string;
11
- aliases?: string[];
12
- }
13
-
14
- export interface PaneFunctionOptionDef {
15
- key: string;
16
- description: string;
17
- type: PaneFunctionOptionType;
18
- aliases?: string[];
19
- values?: PaneFunctionOptionValue[];
20
- defaultValue?: string | number | boolean;
21
- minimum?: number;
22
- maximum?: number;
23
- settingKey?: string;
24
- pluginState?: {
25
- pluginId: string;
26
- key?: string;
27
- };
28
- }
29
-
30
20
  export interface PaneFunctionCapability {
31
21
  id: string;
32
22
  botSafe: boolean;
@@ -35,7 +25,7 @@ export interface PaneFunctionCapability {
35
25
  intents: string[];
36
26
  outputKind: string;
37
27
  reportReadiness: PaneFunctionReadiness;
38
- screenshotReadiness: PaneFunctionReadiness;
28
+ screenshotReadiness: PaneFunctionScreenshotReadiness;
39
29
  dataRequirements: string[];
40
30
  limitations: string[];
41
31
  options: PaneFunctionOptionDef[];
@@ -401,9 +391,11 @@ const UNSUPPORTED_CAPABILITY: PaneFunctionCapability = {
401
391
  intents: [],
402
392
  outputKind: "pane",
403
393
  reportReadiness: "unsupported",
404
- screenshotReadiness: "unsupported",
394
+ screenshotReadiness: "live-dom",
405
395
  dataRequirements: [],
406
- limitations: ["This UI pane has not been verified as a deterministic CLI or bot capability."],
396
+ limitations: [
397
+ "Reports are not verified for automation. Screenshots render live and derive evidence from visible DOM rows.",
398
+ ],
407
399
  options: [],
408
400
  };
409
401
 
@@ -411,11 +403,49 @@ function optionToken(value: string): string {
411
403
  return value.trim().toLowerCase().replace(/[\s_.-]+/g, "");
412
404
  }
413
405
 
406
+ function headlessTickerCardinality(argument: HeadlessPaneArgumentDef): PaneFunctionTickerCardinality {
407
+ switch (argument.kind) {
408
+ case "ticker":
409
+ return "one";
410
+ case "tickers":
411
+ case "symbol-list":
412
+ return (argument.minimum ?? 1) >= 2 ? "two-or-more" : "one-or-more";
413
+ case "none":
414
+ case "free-text":
415
+ return "none";
416
+ default: {
417
+ const _exhaustive: never = argument.kind;
418
+ return _exhaustive;
419
+ }
420
+ }
421
+ }
422
+
423
+ export function getHeadlessPaneDefinition(
424
+ template: PaneTemplateDef | undefined,
425
+ pane: PaneDef,
426
+ ): HeadlessPaneDefinition | undefined {
427
+ return template?.headless ?? pane.headless;
428
+ }
429
+
414
430
  export function getPaneFunctionCapability(
415
431
  template: PaneTemplateDef | undefined,
416
- _pane: PaneDef,
432
+ pane: PaneDef,
417
433
  ): PaneFunctionCapability {
418
- return template ? CAPABILITIES[template.id] ?? UNSUPPORTED_CAPABILITY : UNSUPPORTED_CAPABILITY;
434
+ const existing = template ? CAPABILITIES[template.id] ?? UNSUPPORTED_CAPABILITY : UNSUPPORTED_CAPABILITY;
435
+ const headless = getHeadlessPaneDefinition(template, pane);
436
+ if (!headless) return existing;
437
+
438
+ const hasExistingCapability = existing !== UNSUPPORTED_CAPABILITY;
439
+ return {
440
+ ...(hasExistingCapability ? existing : UNSUPPORTED_CAPABILITY),
441
+ id: hasExistingCapability ? existing.id : template?.id ?? pane.id,
442
+ botSafe: true,
443
+ tickerCardinality: headlessTickerCardinality(headless.argument),
444
+ outputKind: headless.shape,
445
+ reportReadiness: "ready",
446
+ options: [...headless.options],
447
+ ...(hasExistingCapability ? {} : { limitations: [] }),
448
+ };
419
449
  }
420
450
 
421
451
  function resolveOptionDef(
@@ -7,6 +7,7 @@ import {
7
7
  } from "./options";
8
8
  import {
9
9
  capabilityOptionSummary,
10
+ getHeadlessPaneDefinition,
10
11
  getPaneFunctionCapability,
11
12
  type PaneFunctionCapability,
12
13
  } from "./capabilities";
@@ -120,6 +121,7 @@ async function buildTemplateCatalogEntry(
120
121
  }
121
122
  }
122
123
 
124
+ const headless = getHeadlessPaneDefinition(template, pane);
123
125
  return {
124
126
  token: template.shortcut?.prefix ?? template.id,
125
127
  label: template.label,
@@ -128,8 +130,8 @@ async function buildTemplateCatalogEntry(
128
130
  paneName: pane.name,
129
131
  templateId: template.id,
130
132
  shortcut: template.shortcut?.prefix,
131
- argKind: template.shortcut?.argKind,
132
- argPlaceholder: template.shortcut?.argPlaceholder,
133
+ argKind: headless?.argument.kind ?? template.shortcut?.argKind,
134
+ argPlaceholder: headless?.argument.placeholder ?? template.shortcut?.argPlaceholder,
133
135
  keywords: template.keywords ?? [],
134
136
  defaultSettings,
135
137
  capability: getPaneFunctionCapability(template, pane),
@@ -158,6 +160,8 @@ export async function buildPaneCatalogEntries(
158
160
  description: `Open the ${pane.name} pane.`,
159
161
  paneId: pane.id,
160
162
  paneName: pane.name,
163
+ argKind: pane.headless?.argument.kind,
164
+ argPlaceholder: pane.headless?.argument.placeholder,
161
165
  keywords: [],
162
166
  defaultSettings: {},
163
167
  capability: getPaneFunctionCapability(undefined, pane),
@@ -251,7 +255,13 @@ export function renderPaneCatalogReport(entries: PaneCatalogEntry[], args: Parse
251
255
  }
252
256
 
253
257
  for (const entry of shown) {
254
- const arg = entry.argPlaceholder ? `<${entry.argPlaceholder}>` : entry.argKind ? `<${entry.argKind}>` : "[argument]";
258
+ const arg = entry.argKind === "none"
259
+ ? ""
260
+ : entry.argPlaceholder
261
+ ? `<${entry.argPlaceholder}>`
262
+ : entry.argKind
263
+ ? `<${entry.argKind}>`
264
+ : "[argument]";
255
265
  lines.push(`${entry.token} | ${entry.label}`);
256
266
  lines.push(` Description: ${entry.description}`);
257
267
  lines.push(` Pane: ${entry.paneId} (${entry.paneName})`);
@@ -273,7 +283,9 @@ export function renderPaneCatalogReport(entries: PaneCatalogEntry[], args: Parse
273
283
  lines.push(` Limitations: ${entry.capability.limitations.join(" ")}`);
274
284
  }
275
285
  lines.push(` Defaults: ${formatCatalogSettings(entry.defaultSettings)}`);
276
- lines.push(` Examples: gloomberb fn ${entry.token} ${arg} | gloomberb shot ${entry.token} ${arg} --output /tmp/${entry.token.toLowerCase()}.png`);
286
+ const fnExample = `gloomberb fn ${entry.token}${arg ? ` ${arg}` : ""}`;
287
+ const shotExample = `gloomberb shot ${entry.token}${arg ? ` ${arg}` : ""} --output /tmp/${entry.token.toLowerCase()}.png`;
288
+ lines.push(` Examples: ${fnExample} | ${shotExample}`);
277
289
  lines.push("");
278
290
  }
279
291
 
@@ -76,6 +76,7 @@ function buildDiscoveryContext({
76
76
  registerPane: (pane: PaneDef) => panes.set(pane.id, pane),
77
77
  registerPaneTemplate: (template: PaneTemplateDef) => paneTemplates.set(template.id, template),
78
78
  registerCommand: () => {},
79
+ registerCommandBarSearchProvider: () => () => {},
79
80
  registerColumn: () => {},
80
81
  registerBroker: () => {},
81
82
  registerCapability: () => {},