gloomberb 0.16.3 → 0.16.4

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 (451) hide show
  1. package/package.json +4 -1
  2. package/src/api-client/askg.ts +43 -0
  3. package/src/api-client/auth.ts +15 -0
  4. package/src/api-client/beneficial-owners.ts +4 -0
  5. package/src/api-client/chat.ts +19 -0
  6. package/src/api-client/company-kpis.ts +14 -2
  7. package/src/api-client/data.ts +34 -16
  8. package/src/api-client/index.ts +12 -0
  9. package/src/api-client/normalizers.ts +27 -0
  10. package/src/api-client/paths.ts +88 -23
  11. package/src/api-client/request.ts +2 -1
  12. package/src/api-client/research-activity.ts +31 -0
  13. package/src/api-client/socket.ts +39 -8
  14. package/src/api-client/types.ts +57 -1
  15. package/src/api-client/upgrade-experiment.ts +90 -0
  16. package/src/app/global-shortcuts.ts +11 -1
  17. package/src/app/keybindings/actions.ts +10 -0
  18. package/src/app/pane-runtime/index.tsx +8 -0
  19. package/src/app/pane-runtime/pane-template-runtime.ts +5 -0
  20. package/src/app/pane-runtime/plugin-bindings.ts +65 -25
  21. package/src/app/pane-runtime/ticker-inspector-runtime.ts +12 -0
  22. package/src/app/pane-runtime/ticker-open-runtime.ts +18 -1
  23. package/src/app/runtime/desktop-deeplink.ts +5 -0
  24. package/src/app/star-prompt/model.ts +98 -0
  25. package/src/app/star-prompt/runtime.ts +41 -0
  26. package/src/app/star-prompt/status-line.tsx +190 -0
  27. package/src/app.tsx +13 -7
  28. package/src/architecture/core-builtin-allowlist.json +70 -0
  29. package/src/brokers/signed-in/adapter.ts +33 -2
  30. package/src/brokers/signed-in/client.ts +10 -0
  31. package/src/brokers/signed-in/sign-in-dialog.tsx +23 -9
  32. package/src/brokers/signed-in/sign-in.ts +62 -5
  33. package/src/capabilities/registry.ts +8 -1
  34. package/src/capabilities/types.ts +2 -0
  35. package/src/cli/commands/crypto-hints.ts +12 -4
  36. package/src/cli/commands/market.ts +359 -99
  37. package/src/cli/commands/options-chain.ts +172 -0
  38. package/src/cli/commands/options-leaps.ts +422 -0
  39. package/src/cli/commands/overview.ts +164 -26
  40. package/src/cli/commands/search.ts +29 -8
  41. package/src/cli/commands/system.ts +99 -19
  42. package/src/cli/commands/themes.ts +64 -0
  43. package/src/cli/commands/ticker.ts +62 -30
  44. package/src/cli/desktop-pane-shot.ts +97 -16
  45. package/src/cli/errors.ts +25 -2
  46. package/src/cli/freshness.ts +7 -4
  47. package/src/cli/fx-pair.ts +57 -0
  48. package/src/cli/help.ts +14 -1
  49. package/src/cli/index-roots.ts +51 -0
  50. package/src/cli/index.ts +9 -6
  51. package/src/cli/listing-arg.ts +211 -3
  52. package/src/cli/not-a-ticker.ts +37 -0
  53. package/src/cli/options.ts +50 -6
  54. package/src/cli/pane-functions/capabilities.ts +8 -2
  55. package/src/cli/pane-functions/catalog.ts +13 -2
  56. package/src/cli/pane-functions/data.ts +36 -5
  57. package/src/cli/pane-functions/dom.ts +21 -13
  58. package/src/cli/pane-functions/freshness.ts +205 -26
  59. package/src/cli/pane-functions/function-help.ts +67 -22
  60. package/src/cli/pane-functions/headless.ts +5 -4
  61. package/src/cli/pane-functions/index.ts +3 -0
  62. package/src/cli/pane-functions/market-session.ts +133 -0
  63. package/src/cli/pane-functions/options.ts +15 -4
  64. package/src/cli/pane-functions/report-notices.ts +63 -0
  65. package/src/cli/pane-functions/report.ts +4 -0
  66. package/src/cli/pane-functions/resolver.ts +4 -6
  67. package/src/cli/pane-functions/screenshot-evidence.ts +2 -0
  68. package/src/cli/pane-functions/screenshot.ts +105 -23
  69. package/src/cli/registry.ts +2 -1
  70. package/src/cli/result.ts +50 -13
  71. package/src/cli/row-window.ts +76 -0
  72. package/src/cli/scroll-truncation.ts +34 -0
  73. package/src/components/chart/composite/composite-chart.tsx +43 -4
  74. package/src/components/chart/composite/legend.tsx +39 -6
  75. package/src/components/chart/composite/rasterizer.ts +64 -4
  76. package/src/components/chart/composite/series-cues.ts +71 -0
  77. package/src/components/chart/composite/text-renderer.ts +16 -8
  78. package/src/components/chart/composite/types.ts +3 -0
  79. package/src/components/command-bar/commands/direct/index.ts +9 -1
  80. package/src/components/command-bar/commands/plugin/items.ts +3 -1
  81. package/src/components/command-bar/commands/registry.ts +7 -0
  82. package/src/components/command-bar/layout-items/focused-pane.ts +38 -0
  83. package/src/components/command-bar/pane-templates/workflow.ts +5 -0
  84. package/src/components/command-bar/routes/root/command-items.ts +2 -0
  85. package/src/components/command-bar/routes/root/plugin-turn-on.ts +92 -0
  86. package/src/components/command-bar/routes/root/results.ts +40 -10
  87. package/src/components/command-bar/routes/root/runtime.ts +8 -0
  88. package/src/components/command-bar/routes/root/search-providers.ts +97 -14
  89. package/src/components/command-bar/surface/index.tsx +18 -4
  90. package/src/components/command-bar/theme-picker.tsx +48 -13
  91. package/src/components/command-bar/workflow/ops.ts +5 -3
  92. package/src/components/data-table/view.tsx +28 -9
  93. package/src/components/layout/floating-pane.tsx +6 -2
  94. package/src/components/layout/market-summary.ts +23 -9
  95. package/src/components/layout/pane/content.tsx +46 -29
  96. package/src/components/layout/pane/cycle-order.ts +2 -11
  97. package/src/components/layout/pane/header.tsx +2 -1
  98. package/src/components/layout/pane/title.ts +1 -1
  99. package/src/components/layout/presentation.ts +22 -0
  100. package/src/components/layout/shell/active-drag.ts +1 -1
  101. package/src/components/layout/shell/drag/index.ts +4 -0
  102. package/src/components/layout/shell/drag/live.ts +76 -5
  103. package/src/components/layout/shell/drag/runtime.ts +8 -8
  104. package/src/components/layout/shell/index.tsx +47 -6
  105. package/src/components/layout/shell/layout-state.ts +5 -11
  106. package/src/components/layout/shell/menu.ts +28 -1
  107. package/src/components/layout/shell/pane/actions.ts +2 -1
  108. package/src/components/layout/shell/pane/layers.tsx +51 -10
  109. package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
  110. package/src/components/layout/shell/pane/new-layout.ts +116 -0
  111. package/src/components/layout/shell/shortcuts.ts +5 -2
  112. package/src/components/layout/shell/visible-layout.ts +44 -1
  113. package/src/components/layout/status-bar.tsx +4 -1
  114. package/src/components/markdown-text.tsx +9 -5
  115. package/src/components/metric-treemap/heat-surface.tsx +27 -8
  116. package/src/components/metric-treemap/heat-tiles.ts +48 -17
  117. package/src/components/onboarding/onboarding-frame.tsx +33 -10
  118. package/src/components/onboarding/onboarding-wizard.tsx +49 -40
  119. package/src/components/onboarding/portfolio-step/positions-panel.tsx +5 -2
  120. package/src/components/onboarding/pro-step-layout.ts +86 -0
  121. package/src/components/onboarding/use-onboarding-upgrade.ts +6 -2
  122. package/src/components/ui/button.tsx +10 -0
  123. package/src/components/ui/data-table/index.tsx +38 -0
  124. package/src/components/ui/data-table/types.ts +8 -1
  125. package/src/components/ui/data-table/visible-range.ts +2 -0
  126. package/src/components/ui/icon.tsx +11 -2
  127. package/src/components/ui/index.ts +1 -1
  128. package/src/core/connection-health.ts +1 -1
  129. package/src/core/state/app/layout-reducer.ts +89 -3
  130. package/src/core/state/app/state.ts +8 -5
  131. package/src/core/state/app/types.ts +13 -2
  132. package/src/data/config/layout.ts +55 -21
  133. package/src/data/config/store/fold-panes.ts +149 -0
  134. package/src/data/config/store/migrations.ts +7 -0
  135. package/src/data/config/store/node.ts +27 -6
  136. package/src/data/config/store/normalize.ts +37 -3
  137. package/src/i18n/es.ts +14 -1
  138. package/src/i18n/ja.ts +14 -1
  139. package/src/i18n/ko.ts +14 -1
  140. package/src/i18n/zh-cn.ts +14 -1
  141. package/src/i18n/zh-tw.ts +14 -1
  142. package/src/layout/desks.ts +1 -2
  143. package/src/layout/pane-layout-move.ts +264 -0
  144. package/src/layout/pane-manager/hidden-panes.ts +160 -0
  145. package/src/layout/pane-manager/index.ts +1 -0
  146. package/src/market-data/coordinator/entries.ts +7 -1
  147. package/src/market-data/coordinator/quotes.ts +2 -2
  148. package/src/market-data/market/freshness.ts +118 -18
  149. package/src/market-data/market/status.ts +40 -0
  150. package/src/market-data/options-alternatives.ts +69 -0
  151. package/src/market-data/quotes/contributions.ts +19 -2
  152. package/src/market-data/quotes/metadata.ts +16 -0
  153. package/src/market-data/quotes/screener-live-quotes.ts +56 -5
  154. package/src/plugins/builtin/account-management/keyboard.ts +3 -0
  155. package/src/plugins/builtin/account-management/model.ts +1 -0
  156. package/src/plugins/builtin/account-management/navigation.ts +1 -1
  157. package/src/plugins/builtin/account-management/pane.tsx +31 -4
  158. package/src/plugins/builtin/alerts/quotes.ts +3 -0
  159. package/src/plugins/builtin/analytics/risk-headless.ts +3 -1
  160. package/src/plugins/builtin/analytics/risk-model.ts +37 -8
  161. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -4
  162. package/src/plugins/builtin/backtest/pane.tsx +1 -1
  163. package/src/plugins/builtin/broker-manager/detail.tsx +4 -0
  164. package/src/plugins/builtin/broker-manager/index.tsx +7 -3
  165. package/src/plugins/builtin/broker-manager/model.ts +8 -0
  166. package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -2
  167. package/src/plugins/builtin/broker-manager/sign-in-end.ts +82 -0
  168. package/src/plugins/builtin/broker-manager/table.ts +5 -0
  169. package/src/plugins/builtin/builtin-plugin-meta.ts +121 -11
  170. package/src/plugins/builtin/chart-composer/pane.tsx +1 -1
  171. package/src/plugins/builtin/chat/channels.ts +34 -36
  172. package/src/plugins/builtin/chat/command-bar.ts +138 -0
  173. package/src/plugins/builtin/chat/composer-commands.ts +21 -1
  174. package/src/plugins/builtin/chat/content/composer-runtime.ts +11 -1
  175. package/src/plugins/builtin/chat/content/footer.tsx +5 -3
  176. package/src/plugins/builtin/chat/content/index.tsx +21 -1
  177. package/src/plugins/builtin/chat/content/mentions.ts +2 -1
  178. package/src/plugins/builtin/chat/content/user-map.ts +3 -1
  179. package/src/plugins/builtin/chat/controller/index.ts +48 -0
  180. package/src/plugins/builtin/chat/conversations.ts +256 -0
  181. package/src/plugins/builtin/chat/discord-command.ts +91 -0
  182. package/src/plugins/builtin/chat/edit-window.ts +12 -6
  183. package/src/plugins/builtin/chat/ghost-user.ts +14 -0
  184. package/src/plugins/builtin/chat/index.tsx +40 -2
  185. package/src/plugins/builtin/chat/layout.ts +2 -0
  186. package/src/plugins/builtin/chat/message/desktop.tsx +2 -1
  187. package/src/plugins/builtin/chat/message/header.tsx +24 -15
  188. package/src/plugins/builtin/chat/message/profile-popover.tsx +2 -1
  189. package/src/plugins/builtin/chat/message/render-state.ts +3 -0
  190. package/src/plugins/builtin/chat/pane-requests.ts +60 -0
  191. package/src/plugins/builtin/chat/pane-state.ts +32 -2
  192. package/src/plugins/builtin/chat/pane.tsx +8 -0
  193. package/src/plugins/builtin/cloud/askg/controller.ts +112 -4
  194. package/src/plugins/builtin/cloud/askg/conversation-store.ts +21 -0
  195. package/src/plugins/builtin/cloud/askg/manifest.ts +1 -0
  196. package/src/plugins/builtin/cloud/askg/model.ts +129 -70
  197. package/src/plugins/builtin/cloud/askg/pane.tsx +772 -377
  198. package/src/plugins/builtin/cloud/askg/protocol.ts +40 -2
  199. package/src/plugins/builtin/cloud/askg/tool-display.ts +437 -0
  200. package/src/plugins/builtin/cloud/askg/tool-timeline.tsx +403 -0
  201. package/src/plugins/builtin/cloud/company-picker.tsx +1 -9
  202. package/src/plugins/builtin/cloud/mcp-connect/command.ts +24 -0
  203. package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +360 -0
  204. package/src/plugins/builtin/cloud/mcp-connect/model.ts +135 -0
  205. package/src/plugins/builtin/cloud/mcp-connect/sections.ts +62 -0
  206. package/src/plugins/builtin/cloud/plugin.tsx +36 -1
  207. package/src/plugins/builtin/cloud/starter-symbols.ts +8 -0
  208. package/src/plugins/builtin/cloud/terminal-relay/acm-tab.tsx +89 -0
  209. package/src/plugins/builtin/cloud/terminal-relay/client.ts +97 -0
  210. package/src/plugins/builtin/cloud/terminal-relay/connect-section.tsx +57 -0
  211. package/src/plugins/builtin/cloud/terminal-relay/engine.ts +666 -0
  212. package/src/plugins/builtin/cloud/terminal-relay/grants.ts +137 -0
  213. package/src/plugins/builtin/cloud/terminal-relay/host.tsx +81 -0
  214. package/src/plugins/builtin/cloud/terminal-relay/indicator.tsx +82 -0
  215. package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +234 -0
  216. package/src/plugins/builtin/cloud/terminal-relay/policy.ts +153 -0
  217. package/src/plugins/builtin/cloud/terminal-relay/prompts.tsx +168 -0
  218. package/src/plugins/builtin/cloud/terminal-relay/redact.ts +73 -0
  219. package/src/plugins/builtin/cloud/terminal-relay/summary.ts +62 -0
  220. package/src/plugins/builtin/cloud/upgrade-dialog.tsx +26 -2
  221. package/src/plugins/builtin/cloud/upgrade-personalization.ts +57 -0
  222. package/src/plugins/builtin/cloud/upgrade-tickers.ts +124 -0
  223. package/src/plugins/builtin/comment-letters/client.ts +1 -3
  224. package/src/plugins/builtin/company-kpis/client.ts +23 -8
  225. package/src/plugins/builtin/company-kpis/headless.ts +3 -1
  226. package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
  227. package/src/plugins/builtin/company-kpis/screenshot-evidence.ts +10 -3
  228. package/src/plugins/builtin/composite-plugins.ts +107 -64
  229. package/src/plugins/builtin/credit-boards/cdx-pane.tsx +4 -4
  230. package/src/plugins/builtin/credit-boards/index.tsx +21 -21
  231. package/src/plugins/builtin/credit-boards/model.ts +0 -1
  232. package/src/plugins/builtin/credit-boards/pane.tsx +30 -0
  233. package/src/plugins/builtin/credit-boards/sovr-pane.tsx +4 -4
  234. package/src/plugins/builtin/debt-maturities/client.ts +37 -9
  235. package/src/plugins/builtin/debt-maturities/headless.ts +3 -1
  236. package/src/plugins/builtin/debt-maturities/pane.tsx +5 -4
  237. package/src/plugins/builtin/dividend-yield/index.tsx +1 -1
  238. package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
  239. package/src/plugins/builtin/earnings-calls/data.ts +28 -7
  240. package/src/plugins/builtin/earnings-calls/headless.ts +3 -2
  241. package/src/plugins/builtin/earnings-calls/pane.tsx +10 -5
  242. package/src/plugins/builtin/econ/calendar-model.ts +42 -17
  243. package/src/plugins/builtin/econ/detail-view.tsx +2 -2
  244. package/src/plugins/builtin/econ/index.tsx +68 -22
  245. package/src/plugins/builtin/econ-statistics/headless.ts +12 -2
  246. package/src/plugins/builtin/econ-statistics/pane.tsx +1 -1
  247. package/src/plugins/builtin/econ-statistics/view.ts +17 -0
  248. package/src/plugins/builtin/executives/data.ts +17 -6
  249. package/src/plugins/builtin/executives/pane.tsx +16 -7
  250. package/src/plugins/builtin/filing-events/pane.tsx +22 -5
  251. package/src/plugins/builtin/fx-matrix/freshness.ts +65 -0
  252. package/src/plugins/builtin/fx-matrix/index.tsx +22 -1
  253. package/src/plugins/builtin/fx-matrix/pairs.ts +1 -1
  254. package/src/plugins/builtin/fx-matrix/settings.ts +42 -2
  255. package/src/plugins/builtin/gpu/headless.ts +9 -3
  256. package/src/plugins/builtin/gpu/model.ts +14 -5
  257. package/src/plugins/builtin/help/index.tsx +1 -0
  258. package/src/plugins/builtin/holders/beneficial-client.ts +29 -7
  259. package/src/plugins/builtin/holders/beneficial-view.tsx +7 -3
  260. package/src/plugins/builtin/holders/headless.ts +6 -1
  261. package/src/plugins/builtin/holders/pane.tsx +3 -0
  262. package/src/plugins/builtin/insider/index.tsx +4 -4
  263. package/src/plugins/builtin/iv-history/charts.tsx +21 -7
  264. package/src/plugins/builtin/iv-history/client.ts +12 -2
  265. package/src/plugins/builtin/iv-history/format.ts +33 -2
  266. package/src/plugins/builtin/iv-history/headless.ts +22 -10
  267. package/src/plugins/builtin/iv-history/index.tsx +3 -3
  268. package/src/plugins/builtin/iv-history/model.ts +61 -13
  269. package/src/plugins/builtin/iv-history/pane.tsx +11 -3
  270. package/src/plugins/builtin/iv-history/screen-pane.tsx +42 -18
  271. package/src/plugins/builtin/jobs/pane.tsx +1 -1
  272. package/src/plugins/builtin/kelly-sizer/headless.ts +149 -0
  273. package/src/plugins/builtin/kelly-sizer/history.ts +80 -0
  274. package/src/plugins/builtin/kelly-sizer/index.tsx +2 -0
  275. package/src/plugins/builtin/kelly-sizer/pane.tsx +109 -39
  276. package/src/plugins/builtin/kelly-sizer/sections.tsx +14 -0
  277. package/src/plugins/builtin/kelly-sizer/types.ts +9 -1
  278. package/src/plugins/builtin/layout-manager/index.tsx +9 -1
  279. package/src/plugins/builtin/macro-day/client.ts +24 -0
  280. package/src/plugins/builtin/macro-day/headless.ts +11 -8
  281. package/src/plugins/builtin/macro-day/index.tsx +3 -0
  282. package/src/plugins/builtin/macro-day/model.ts +11 -6
  283. package/src/plugins/builtin/macro-day/pane.tsx +12 -8
  284. package/src/plugins/builtin/macro-day/release-table.ts +40 -0
  285. package/src/plugins/builtin/macro-day/releases.ts +62 -44
  286. package/src/plugins/builtin/market-heatmap/data.ts +25 -5
  287. package/src/plugins/builtin/market-heatmap/evidence.ts +90 -0
  288. package/src/plugins/builtin/market-heatmap/index.tsx +144 -63
  289. package/src/plugins/builtin/market-heatmap/model.ts +94 -3
  290. package/src/plugins/builtin/market-heatmap/portfolio.ts +12 -2
  291. package/src/plugins/builtin/market-movers/headless.ts +31 -4
  292. package/src/plugins/builtin/market-movers/index.tsx +8 -1
  293. package/src/plugins/builtin/market-movers/model.ts +71 -17
  294. package/src/plugins/builtin/market-movers/table.ts +50 -23
  295. package/src/plugins/builtin/news/headless.ts +9 -2
  296. package/src/plugins/builtin/news/wire/news/footer.ts +10 -0
  297. package/src/plugins/builtin/openfda/client.ts +1 -1
  298. package/src/plugins/builtin/options/carry.ts +95 -0
  299. package/src/plugins/builtin/options/index.tsx +58 -5
  300. package/src/plugins/builtin/options/strike-window.ts +183 -0
  301. package/src/plugins/builtin/options/table.ts +55 -3
  302. package/src/plugins/builtin/options/types.ts +8 -1
  303. package/src/plugins/builtin/options/view.tsx +161 -12
  304. package/src/plugins/builtin/options-positioning/headless.ts +4 -2
  305. package/src/plugins/builtin/options-scenario/charts.tsx +2 -2
  306. package/src/plugins/builtin/options-scenario/client.ts +73 -17
  307. package/src/plugins/builtin/options-scenario/headless.ts +71 -24
  308. package/src/plugins/builtin/options-scenario/hedge.ts +284 -0
  309. package/src/plugins/builtin/options-scenario/index.tsx +9 -2
  310. package/src/plugins/builtin/options-scenario/model.ts +33 -0
  311. package/src/plugins/builtin/options-scenario/pane.tsx +40 -16
  312. package/src/plugins/builtin/pe-band/model.ts +2 -2
  313. package/src/plugins/builtin/pe-band/report-dates.ts +11 -7
  314. package/src/plugins/builtin/perps/equity-row.tsx +8 -5
  315. package/src/plugins/builtin/perps/model.ts +16 -5
  316. package/src/plugins/builtin/plugin-marketplace/model.ts +64 -21
  317. package/src/plugins/builtin/plugin-marketplace/packs.ts +118 -0
  318. package/src/plugins/builtin/plugin-marketplace/pane.tsx +344 -32
  319. package/src/plugins/builtin/plugin-marketplace/store.ts +7 -1
  320. package/src/plugins/builtin/plugin-module.ts +87 -4
  321. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +71 -36
  322. package/src/plugins/builtin/portfolio-list/cli/render.ts +87 -18
  323. package/src/plugins/builtin/portfolio-list/cli/shared.ts +2 -16
  324. package/src/plugins/builtin/portfolio-list/headless.ts +38 -7
  325. package/src/plugins/builtin/realized-vol/headless.ts +3 -0
  326. package/src/plugins/builtin/realized-vol/pane.tsx +1 -1
  327. package/src/plugins/builtin/research/corporate-actions-pane.tsx +2 -2
  328. package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +1 -1
  329. package/src/plugins/builtin/revenue-breakdown/client.ts +13 -6
  330. package/src/plugins/builtin/revenue-breakdown/headless.ts +4 -1
  331. package/src/plugins/builtin/revenue-breakdown/pane.tsx +7 -4
  332. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -1
  333. package/src/plugins/builtin/reverse-dcf/pane.tsx +1 -1
  334. package/src/plugins/builtin/risk-factors/data.ts +23 -9
  335. package/src/plugins/builtin/risk-factors/headless.ts +5 -2
  336. package/src/plugins/builtin/risk-factors/pane.tsx +6 -5
  337. package/src/plugins/builtin/seasonality/pane.tsx +1 -1
  338. package/src/plugins/builtin/sec/index.tsx +5 -5
  339. package/src/plugins/builtin/sectors/client.ts +10 -2
  340. package/src/plugins/builtin/sectors/headless.ts +35 -6
  341. package/src/plugins/builtin/sectors/index.tsx +18 -1
  342. package/src/plugins/builtin/sectors/sector-model.ts +150 -41
  343. package/src/plugins/builtin/shared/command-tab.ts +51 -0
  344. package/src/plugins/builtin/shared/headless-market-data.ts +33 -1
  345. package/src/plugins/builtin/shared/members-menu.tsx +4 -4
  346. package/src/plugins/builtin/shared/report-freshness.ts +50 -2
  347. package/src/plugins/builtin/shared/ticker-request.ts +25 -1
  348. package/src/plugins/builtin/shared/use-quote-board.ts +25 -5
  349. package/src/plugins/builtin/shared/volatility/options.ts +1 -46
  350. package/src/plugins/builtin/short-interest/index.tsx +19 -4
  351. package/src/plugins/builtin/short-interest/surface.tsx +4 -4
  352. package/src/plugins/builtin/short-volume/index.tsx +1 -10
  353. package/src/plugins/builtin/supply-chain/model.ts +1 -1
  354. package/src/plugins/builtin/ticker-detail/headless.ts +43 -21
  355. package/src/plugins/builtin/ticker-detail/pane.tsx +4 -5
  356. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +94 -6
  357. package/src/plugins/builtin/treasury-auctions/cache.ts +5 -2
  358. package/src/plugins/builtin/treasury-auctions/client.ts +22 -8
  359. package/src/plugins/builtin/treasury-auctions/headless.ts +40 -4
  360. package/src/plugins/builtin/treasury-auctions/index.tsx +11 -9
  361. package/src/plugins/builtin/treasury-auctions/model.ts +197 -30
  362. package/src/plugins/builtin/treasury-auctions/pane.tsx +35 -11
  363. package/src/plugins/builtin/treasury-auctions/types.ts +11 -1
  364. package/src/plugins/builtin/vol-surface/charts.tsx +1 -3
  365. package/src/plugins/builtin/vol-surface/client.ts +6 -2
  366. package/src/plugins/builtin/vol-surface/evidence.ts +11 -6
  367. package/src/plugins/builtin/vol-surface/headless.ts +124 -20
  368. package/src/plugins/builtin/vol-surface/model.ts +23 -11
  369. package/src/plugins/builtin/vol-surface/pane.tsx +38 -27
  370. package/src/plugins/builtin/vol-surface/tables.ts +124 -0
  371. package/src/plugins/builtin/world-indices/client.ts +22 -4
  372. package/src/plugins/builtin/world-indices/headless.ts +54 -27
  373. package/src/plugins/builtin/world-indices/model.ts +4 -2
  374. package/src/plugins/builtin/world-indices/table.tsx +13 -2
  375. package/src/plugins/builtin/world-venue-map/geo-series.ts +1 -1
  376. package/src/plugins/builtin/world-venue-map/index.ts +1 -1
  377. package/src/plugins/builtin/yield-curve/index.tsx +1 -1
  378. package/src/plugins/builtin-editorial.ts +126 -0
  379. package/src/plugins/catalog-backend.ts +16 -9
  380. package/src/plugins/catalog-browser.ts +127 -63
  381. package/src/plugins/catalog-manifest.ts +73 -40
  382. package/src/plugins/catalog-ui.ts +26 -6
  383. package/src/plugins/extracted-plugins.ts +7 -1
  384. package/src/plugins/ownership.ts +150 -7
  385. package/src/plugins/plugin-functions.ts +54 -0
  386. package/src/plugins/plugin-off.ts +36 -0
  387. package/src/plugins/registry/host-actions.ts +16 -0
  388. package/src/plugins/registry/index.ts +84 -24
  389. package/src/plugins/registry/plugin-state.ts +25 -1
  390. package/src/plugins/runtime/context.tsx +29 -0
  391. package/src/react/input.ts +48 -1
  392. package/src/remote/app-host.tsx +5 -0
  393. package/src/remote/controller.ts +3 -53
  394. package/src/remote/matching.ts +69 -0
  395. package/src/remote/schema.ts +1 -1
  396. package/src/remote/semantic-tree.tsx +14 -0
  397. package/src/remote/types.ts +2 -0
  398. package/src/renderers/dom/desktop/icons.tsx +14 -0
  399. package/src/renderers/dom/desktop/query-bar.tsx +59 -32
  400. package/src/renderers/dom/host/box.tsx +1 -1
  401. package/src/renderers/dom/host/drag-shield.ts +17 -6
  402. package/src/renderers/dom/host/live-frame.ts +10 -2
  403. package/src/renderers/dom/input-host.tsx +6 -1
  404. package/src/renderers/dom/styles.css +13 -0
  405. package/src/renderers/electrobun/bun/desktop/backend.ts +5 -1
  406. package/src/renderers/electrobun/bun/desktop/detached-windows.ts +12 -1
  407. package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -1
  408. package/src/renderers/electrobun/shared/protocol.ts +2 -0
  409. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -1
  410. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +101 -5
  411. package/src/renderers/electrobun/view/main.tsx +4 -1
  412. package/src/renderers/opentui/input-host.tsx +6 -1
  413. package/src/renderers/opentui/start.tsx +9 -0
  414. package/src/shares/portable-layout.ts +16 -6
  415. package/src/sources/gloomberb-cloud/index.ts +19 -10
  416. package/src/sources/gloomberb-cloud/normalizers.ts +4 -1
  417. package/src/sources/listing-symbols.ts +11 -5
  418. package/src/sources/provider-errors.ts +88 -3
  419. package/src/sources/provider-router/cached-routes.ts +8 -4
  420. package/src/sources/provider-router/financial-routes.ts +11 -5
  421. package/src/sources/provider-router/financials.ts +42 -15
  422. package/src/sources/provider-router/history.ts +27 -11
  423. package/src/sources/provider-router/index.ts +1 -1
  424. package/src/sources/provider-router/primary.ts +23 -5
  425. package/src/sources/sec-edgar.ts +68 -8
  426. package/src/sync/core-contributors.ts +4 -3
  427. package/src/test-support/cli-context.ts +2 -1
  428. package/src/test-support/data-provider.ts +34 -0
  429. package/src/test-support/shell-pane-keys.tsx +1 -0
  430. package/src/theme/series-colors.ts +46 -0
  431. package/src/theme/themes.ts +149 -0
  432. package/src/tickers/search/index.ts +22 -2
  433. package/src/tickers/search/ranking.ts +45 -1
  434. package/src/tickers/search/types.ts +3 -1
  435. package/src/time-series/resolve.ts +1 -14
  436. package/src/types/config.ts +61 -1
  437. package/src/types/headless.ts +11 -0
  438. package/src/types/instrument.ts +8 -0
  439. package/src/types/plugin.ts +49 -3
  440. package/src/ui/host.tsx +8 -4
  441. package/src/ui/index.tsx +2 -0
  442. package/src/utils/cli-output.ts +14 -1
  443. package/src/utils/exchanges.ts +13 -0
  444. package/src/utils/format.ts +2 -1
  445. package/src/utils/fx-market-hours.ts +34 -0
  446. package/src/utils/option-expiry.ts +126 -0
  447. package/src/utils/price-history.ts +39 -6
  448. package/src/utils/rpc-timeout-error.ts +6 -2
  449. package/src/utils/sec-eps-basis.ts +159 -30
  450. package/src/utils/sec.ts +38 -34
  451. package/src/utils/throttled-fetch.ts +51 -22
@@ -10,6 +10,53 @@ import { WebIcon } from "./icons";
10
10
 
11
11
  /** Input width of a text filter that names none, in cells (the terminal's default). */
12
12
  const DEFAULT_TEXT_FIELD_CELLS = 20;
13
+ /** Beyond this many choices an inline filter is a strip that scrolls by itself, as the terminal's does. */
14
+ const STRIP_OPTION_LIMIT = 6;
15
+
16
+ function isStrip(item: HostQueryBarItem): boolean {
17
+ return item.kind === "select" && item.inline === true && item.options.length > STRIP_OPTION_LIMIT;
18
+ }
19
+
20
+ /**
21
+ * The choices of an inline filter. A long one (expiries, chart ranges) is a
22
+ * strip that scrolls on its own, so the label and the controls beside it stay
23
+ * in view while it brings the chosen one into view.
24
+ */
25
+ function InlineChoices({ item }: { item: HostQueryBarItem }) {
26
+ const stripRef = useRef<HTMLDivElement | null>(null);
27
+ const strip = isStrip(item);
28
+ const overflow = useHorizontalOverflow(stripRef, [item.options.length, strip]);
29
+ return (
30
+ <div className="gloom-qb-inline" data-item-id={item.id} data-narrowing={item.narrowing ? "true" : undefined}
31
+ data-strip={strip ? "true" : undefined}>
32
+ <span className="gloom-qb-label">{item.label}</span>
33
+ <div
34
+ ref={stripRef}
35
+ className="gloom-qb-view gloom-qb-segments"
36
+ role="radiogroup"
37
+ aria-label={item.label}
38
+ style={strip ? overflow.maskStyle : undefined}
39
+ onWheel={strip ? overflow.onWheel : undefined}
40
+ >
41
+ {item.options.map((option) => (
42
+ <button
43
+ key={option.value}
44
+ type="button"
45
+ role="radio"
46
+ aria-checked={option.selected}
47
+ disabled={option.disabled}
48
+ title={option.hint ? `${option.label} (${option.hint})` : undefined}
49
+ data-active={option.selected ? "true" : undefined}
50
+ onMouseDown={(event) => event.stopPropagation()}
51
+ onClick={() => item.onSelect(option.value)}
52
+ >
53
+ <span className="gloom-qb-text">{option.label}</span>
54
+ </button>
55
+ ))}
56
+ </div>
57
+ </div>
58
+ );
59
+ }
13
60
 
14
61
  function Chevron() {
15
62
  return <span className="gloom-qb-chevron"><WebIcon name="chevron-down" size={9} /></span>;
@@ -87,30 +134,7 @@ function FilterChip({ item, open, onOpenChange }: { item: HostQueryBarItem; open
87
134
  useEffect(() => {
88
135
  if (open) triggerRef.current?.scrollIntoView({ block: "nearest", inline: "nearest" });
89
136
  }, [open]);
90
- if (item.kind === "select" && item.inline) {
91
- return (
92
- <div className="gloom-qb-inline" data-item-id={item.id} data-narrowing={item.narrowing ? "true" : undefined}>
93
- <span className="gloom-qb-label">{item.label}</span>
94
- <div className="gloom-qb-view gloom-qb-segments" role="radiogroup" aria-label={item.label}>
95
- {item.options.map((option) => (
96
- <button
97
- key={option.value}
98
- type="button"
99
- role="radio"
100
- aria-checked={option.selected}
101
- disabled={option.disabled}
102
- title={option.hint ? `${option.label} (${option.hint})` : undefined}
103
- data-active={option.selected ? "true" : undefined}
104
- onMouseDown={(event) => event.stopPropagation()}
105
- onClick={() => item.onSelect(option.value)}
106
- >
107
- <span className="gloom-qb-text">{option.label}</span>
108
- </button>
109
- ))}
110
- </div>
111
- </div>
112
- );
113
- }
137
+ if (item.kind === "select" && item.inline) return <InlineChoices item={item} />;
114
138
  if (item.kind === "toggle") {
115
139
  return (
116
140
  <button
@@ -190,27 +214,30 @@ export function WebQueryBar({ search, items, view, onClearAll, meta, openRequest
190
214
  const overflow = useHorizontalOverflow(scrollRef, [items.length, !!search, !!view, !!onClearAll, meta]);
191
215
  const searchRef = useRef<HTMLDivElement | null>(null);
192
216
  // When an inline choice or the view changes (a shortcut, the next expiry),
193
- // bring the new selection into a scrolled bar. Not on mount: a view at the
194
- // right edge must not scroll the search away when the pane opens.
217
+ // bring the new selection into a scrolled bar. On mount only a strip that
218
+ // scrolls by itself shows its choice: an expiry chosen far down the strip
219
+ // is what the pane shows, but scrolling the whole bar to a view at the right
220
+ // edge would hide the search when the pane opens.
195
221
  const selectionKey = [
196
222
  ...items.filter((item) => item.inline).map((item) => `${item.id}=${item.options.find((option) => option.selected)?.value ?? ""}`),
197
223
  `view=${view?.value ?? ""}`,
198
224
  ].join("|");
199
- const previousSelectionKey = useRef(selectionKey);
225
+ const previousSelectionKey = useRef<string | null>(null);
200
226
  useEffect(() => {
201
227
  const previous = previousSelectionKey.current;
202
228
  previousSelectionKey.current = selectionKey;
203
229
  if (previous === selectionKey) return;
204
- const before = new Map(previous.split("|").map((entry) => entry.split("=") as [string, string]));
230
+ const before = previous == null ? null : new Map(previous.split("|").map((entry) => entry.split("=") as [string, string]));
205
231
  const bar = scrollRef.current;
206
232
  if (!bar) return;
207
233
  for (const entry of selectionKey.split("|")) {
208
234
  const [id, value] = entry.split("=") as [string, string];
209
- if (before.get(id) === value) continue;
235
+ if (before?.get(id) === value) continue;
210
236
  const group = id === "view"
211
237
  ? bar.querySelector(":scope > .gloom-qb-view")
212
- : bar.querySelector(`[data-item-id="${CSS.escape(id)}"]`);
213
- group?.querySelector<HTMLElement>("button[data-active=true]")?.scrollIntoView({ block: "nearest", inline: "nearest" });
238
+ : [...bar.querySelectorAll("[data-item-id]")].find((item) => item.getAttribute("data-item-id") === id);
239
+ if (!before && group?.getAttribute("data-strip") !== "true") continue;
240
+ group?.querySelector<HTMLElement>("button[data-active=true]")?.scrollIntoView?.({ block: "nearest", inline: "nearest" });
214
241
  }
215
242
  }, [selectionKey]);
216
243
  useEffect(() => {
@@ -280,7 +307,7 @@ export function WebQueryBar({ search, items, view, onClearAll, meta, openRequest
280
307
  )}
281
308
  {searchNode}
282
309
  {items.length > 0 && (
283
- <div className="gloom-qb-filters">
310
+ <div className="gloom-qb-filters" data-strip={items.some(isStrip) ? "true" : undefined}>
284
311
  {items.map((item) => (
285
312
  <FilterChip key={item.id} item={item} open={openId === item.id} onOpenChange={(open) => setOpen(item.id, open)} />
286
313
  ))}
@@ -248,7 +248,7 @@ export const WebBox = forwardRef<HTMLDivElement, Record<string, unknown> & { chi
248
248
  if (current && !current.shielded) {
249
249
  if (dx * dx + dy * dy < 9) return;
250
250
  current.shielded = true;
251
- showDragShield();
251
+ showDragShield(elementRef.current);
252
252
  // Captured once it is a drag (a click keeps its usual target), the
253
253
  // moves skip hit testing and leave every other element's hover state
254
254
  // alone: nothing under the pointer restyles or repaints while a pane,
@@ -7,14 +7,25 @@ let shield: HTMLElement | null = null;
7
7
  * It is a single element on purpose: a class on body matched by descendant
8
8
  * rules restyled every element with a role (thousands on a busy layout) in
9
9
  * the frame the drag began, and again when it ended.
10
+ *
11
+ * A press on a resize edge (a pane's corner, a split) keeps its resize cursor
12
+ * for the drag instead, as a window edge does.
10
13
  */
11
- export function showDragShield(): void {
14
+ export function showDragShield(pressed?: Element | null): void {
12
15
  document.body.classList.add("gloom-dragging");
13
- if (shield?.isConnected) return;
14
- shield = document.createElement("div");
15
- shield.setAttribute("data-gloom-role", "drag-shield");
16
- shield.setAttribute("aria-hidden", "true");
17
- document.body.appendChild(shield);
16
+ if (!shield?.isConnected) {
17
+ shield = document.createElement("div");
18
+ shield.setAttribute("data-gloom-role", "drag-shield");
19
+ shield.setAttribute("aria-hidden", "true");
20
+ document.body.appendChild(shield);
21
+ }
22
+ shield.style.cursor = resizeCursor(pressed);
23
+ }
24
+
25
+ function resizeCursor(element: Element | null | undefined): string {
26
+ if (!element || typeof getComputedStyle !== "function") return "";
27
+ const cursor = getComputedStyle(element).cursor;
28
+ return cursor.endsWith("-resize") ? cursor : "";
18
29
  }
19
30
 
20
31
  export function hideDragShield(): void {
@@ -18,7 +18,9 @@ const ANIMATION_CARRIES_SLIDE = typeof navigator !== "undefined"
18
18
 
19
19
  const slides = new WeakMap<HTMLElement, Animation>();
20
20
 
21
+ /** A style value as React writes it: a bare number is pixels. */
21
22
  function renderedValue(value: unknown): string {
23
+ if (typeof value === "number") return `${value}px`;
22
24
  return typeof value === "string" ? value : "";
23
25
  }
24
26
 
@@ -36,7 +38,8 @@ function slide(element: HTMLElement, transform: string): void {
36
38
  /**
37
39
  * Draws a box where a drag has it this frame by restyling its element,
38
40
  * outside React: an offset on its own compositor layer, with no layout or
39
- * repaint. `null` hands the properties back to what React last rendered.
41
+ * repaint, and a size, which lays out only the box's subtree. `null` hands
42
+ * the properties back to what React last rendered.
40
43
  */
41
44
  export function applyLiveFrame(element: HTMLElement, frame: LiveBoxFrame | null, props: Record<string, unknown>): void {
42
45
  const style = element.style;
@@ -46,9 +49,14 @@ export function applyLiveFrame(element: HTMLElement, frame: LiveBoxFrame | null,
46
49
  slides.delete(element);
47
50
  style.transform = renderedValue(rendered.transform);
48
51
  style.willChange = renderedValue(rendered.willChange);
52
+ style.width = renderedValue(rendered.width);
53
+ style.height = renderedValue(rendered.height);
49
54
  return;
50
55
  }
56
+ if (frame.width !== undefined) style.width = `${frame.width * WEB_CELL_WIDTH}px`;
57
+ if (frame.height !== undefined) style.height = `${frame.height * WEB_CELL_HEIGHT}px`;
58
+ if (frame.dx === undefined && frame.dy === undefined) return;
51
59
  // Promoted once, before the first frame of the drag.
52
60
  if (style.willChange !== "transform") style.willChange = "transform";
53
- slide(element, `translate3d(${frame.dx * WEB_CELL_WIDTH}px, ${frame.dy * WEB_CELL_HEIGHT}px, 0)`);
61
+ slide(element, `translate3d(${(frame.dx ?? 0) * WEB_CELL_WIDTH}px, ${(frame.dy ?? 0) * WEB_CELL_HEIGHT}px, 0)`);
54
62
  }
@@ -1,8 +1,10 @@
1
1
  /** @jsxImportSource react */
2
2
  import { useEffect, useMemo, useSyncExternalStore, type ReactNode } from "react";
3
+ import { flushSync } from "react-dom";
3
4
  import {
4
5
  createShortcutRegistry,
5
6
  InputHostProvider,
7
+ useRegisteredBeforeShortcut,
6
8
  useRegisteredShortcut,
7
9
  type InputHost,
8
10
  type KeyEventLike,
@@ -76,7 +78,7 @@ function getViewport() {
76
78
  }
77
79
 
78
80
  export function WebInputHostProvider({ children }: { children: ReactNode }) {
79
- const shortcutRegistry = useMemo(() => createShortcutRegistry(), []);
81
+ const shortcutRegistry = useMemo(() => createShortcutRegistry({ flushSync }), []);
80
82
 
81
83
  useEffect(() => {
82
84
  // Windows fires the Menu key's contextmenu on keyup. When the app used the
@@ -144,6 +146,9 @@ export function WebInputHostProvider({ children }: { children: ReactNode }) {
144
146
  useShortcut(handler, options) {
145
147
  useRegisteredShortcut(shortcutRegistry, handler, options);
146
148
  },
149
+ useBeforeShortcut(listener) {
150
+ useRegisteredBeforeShortcut(shortcutRegistry, listener);
151
+ },
147
152
  useViewport() {
148
153
  return useSyncExternalStore(subscribeViewport, getViewport, getViewport);
149
154
  },
@@ -753,6 +753,19 @@ html[data-gloom-screenshot="true"] [data-gloom-role="chart-watermark"] {
753
753
  box-shadow: inset 1px 0 0 var(--qb-hairline);
754
754
  }
755
755
  .gloom-qb-inline .gloom-qb-view { margin-left: 0; box-shadow: none; align-self: stretch; }
756
+ /* A long inline choice (an expiry strip) scrolls by itself, as the terminal's
757
+ does, so its label and the controls beside it stay in view. */
758
+ .gloom-qb-filters[data-strip="true"],
759
+ .gloom-qb-inline[data-strip="true"] { flex: 0 1 auto; min-width: 0; }
760
+ .gloom-qb-inline[data-strip="true"] .gloom-qb-segments {
761
+ flex: 0 1 auto;
762
+ min-width: 12ch;
763
+ overflow-x: auto;
764
+ scrollbar-width: none;
765
+ }
766
+ .gloom-qb-inline[data-strip="true"] .gloom-qb-segments::-webkit-scrollbar { display: none; }
767
+ .gloom-qb-inline[data-strip="true"] .gloom-qb-segments > button { flex: none; white-space: nowrap; }
768
+ .gloom-qb-filters[data-strip="true"] > :not([data-strip="true"]) { flex: none; }
756
769
  .gloom-qb-view button {
757
770
  height: 100%;
758
771
  padding: 0 8px;
@@ -105,6 +105,8 @@ export class DesktopBackend {
105
105
  getWindowKey: this.rpcs.getRpcWindowKey,
106
106
  });
107
107
  readonly detachedWindows = new DesktopDetachedWindowManager(this);
108
+ /** Each pane type's plugin, for hiding popped-out windows with their plugin. */
109
+ paneOwners: ReadonlyMap<string, string> = new Map();
108
110
 
109
111
  private crashReporterInstalled = false;
110
112
  private restartInProgress = false;
@@ -162,9 +164,11 @@ export class DesktopBackend {
162
164
  async startServices(nextConfig: AppConfig): Promise<{ config: AppConfig; services: AppServices }> {
163
165
  this.setConfig(nextConfig);
164
166
  const config = this.requireConfig();
167
+ const { paneOwners, ...backendPlugins } = await loadDesktopBackendPlugins();
168
+ this.paneOwners = paneOwners;
165
169
  const services = createAppServices({
166
170
  config,
167
- ...await loadDesktopBackendPlugins(),
171
+ ...backendPlugins,
168
172
  });
169
173
  this.services = services;
170
174
  await services.ready;
@@ -73,7 +73,18 @@ export class DesktopDetachedWindowManager {
73
73
  const config = this.backend.config;
74
74
  if (!config) return;
75
75
 
76
- const desiredEntries = new Map(config.layout.detached.map((entry) => [entry.instanceId, entry] as const));
76
+ // A pane whose plugin is switched off stays in the layout, out of sight,
77
+ // as it does in the main window: its window closes without undocking or
78
+ // removing it, and opens again where it was when the plugin is back on.
79
+ const disabledPlugins = new Set(config.disabledPlugins);
80
+ const hidden = (instanceId: string) => {
81
+ const paneType = findPaneInstance(config.layout, instanceId)?.paneId;
82
+ const owner = paneType ? this.backend.paneOwners.get(paneType) : undefined;
83
+ return !!owner && disabledPlugins.has(owner);
84
+ };
85
+ const desiredEntries = new Map(config.layout.detached
86
+ .filter((entry) => !hidden(entry.instanceId))
87
+ .map((entry) => [entry.instanceId, entry] as const));
77
88
  for (const [instanceId, entry] of desiredEntries) {
78
89
  const existingWindow = this.windows.get(instanceId);
79
90
  if (!existingWindow) {
@@ -1,5 +1,5 @@
1
1
  import { existsSync, mkdirSync } from "fs";
2
- import { homedir } from "os";
2
+ import { homedir, hostname } from "os";
3
3
  import { getGloomberbDirs } from "../../../../data/config/home";
4
4
  import type { AppServices } from "../../../../core/app-services";
5
5
  import { restoreExtractedPlugins } from "../../../../cli/restore-plugins";
@@ -81,6 +81,7 @@ function buildInitializationPayload(
81
81
  savedResources: loadSavedResources(services, config),
82
82
  capabilityManifests: desktopRendererCapabilityManifests(services.pluginRegistry.capabilities),
83
83
  desktopPlatform: process.platform,
84
+ hostName: hostname(),
84
85
  windowKind: windowTarget.kind,
85
86
  paneId: windowTarget.paneId,
86
87
  telemetry: {
@@ -23,6 +23,8 @@ export interface ElectrobunBackendInit {
23
23
  savedResources?: CachedResourceRecord[];
24
24
  capabilityManifests: CapabilityManifest[];
25
25
  desktopPlatform: string;
26
+ /** The machine name, which a remote assistant sees this desktop app under. */
27
+ hostName?: string;
26
28
  windowKind: "main" | "detached";
27
29
  paneId?: string;
28
30
  /** What the view needs to send crash reports and usage counts the same way the Bun process would. */
@@ -441,7 +441,7 @@ function ShotPane({ payload, registry }: { payload: DesktopPaneShotPayload; regi
441
441
  const height = payload.heightCells;
442
442
  // PORT keeps what its basket covers, so a capture never reads as the whole account.
443
443
  return (
444
- <PaneShotFrame paneId={instance.instanceId} title={title} width={width} height={height} preserveStatus={["time-sales", "gpu", "hiring", "apps", "catalysts", "litigation", "perps", "members", "analytics"].includes(pane.id)}>
444
+ <PaneShotFrame paneId={instance.instanceId} title={title} width={width} height={height} statusLine={payload.statusLine === true} preserveStatus={["time-sales", "gpu", "hiring", "apps", "catalysts", "litigation", "perps", "members", "analytics"].includes(pane.id)}>
445
445
  {(bodyFrame) => <PaneContent
446
446
  component={pane.component}
447
447
  paneId={instance.instanceId}
@@ -1,20 +1,116 @@
1
1
  /** @jsxImportSource react */
2
- import type { ReactNode } from "react";
2
+ import { useEffect, useState, type ReactNode } from "react";
3
3
  import { FloatingPaneWrapper } from "../../../components/layout/floating-pane";
4
- import { PaneFooterProvider, hasPaneFooterContent } from "../../../components/layout/pane/footer";
4
+ import { PaneFooterProvider, hasPaneFooterContent, type PaneFooterSegment } from "../../../components/layout/pane/footer";
5
5
  import { resolvePaneBodyFrame } from "../../../components/layout/pane/sizing";
6
6
 
7
- /** Static captures retain warnings and optional source status without action shortcuts. */
8
- export function PaneShotFrame({ paneId, title, width, height, preserveStatus = false, children }: {
7
+ declare global {
8
+ interface Window {
9
+ /**
10
+ * Set by a capture that carries a status line: shows the first of the
11
+ * variants (longest first) that fits the footer, cut in the middle when
12
+ * none does, and resolves to the text shown.
13
+ */
14
+ __GLOOM_CLI_SHOT_FIT_STATUS__?: (variants: string[]) => Promise<string>;
15
+ }
16
+ }
17
+
18
+ /**
19
+ * Holds the footer row from the first frame, while the status line is still
20
+ * being worked out from what the pane loaded, so the body never resizes.
21
+ */
22
+ const PENDING_STATUS = "\u00a0";
23
+
24
+ function nextFrames(count: number): Promise<void> {
25
+ return new Promise((resolve) => {
26
+ const step = (left: number) => (left <= 0 ? resolve() : requestAnimationFrame(() => step(left - 1)));
27
+ step(count);
28
+ });
29
+ }
30
+
31
+ /** Keeps the start and the end of a line that does not fit, `keep` characters in all. */
32
+ function middleEllipsis(text: string, keep: number): string {
33
+ if (keep >= text.length) return text;
34
+ const head = Math.ceil(keep / 2);
35
+ const tail = Math.max(0, keep - head);
36
+ return `${text.slice(0, head).trimEnd()}…${tail > 0 ? text.slice(text.length - tail).trimStart() : ""}`;
37
+ }
38
+
39
+ /**
40
+ * The first variant that fits, else the last one cut in the middle as little
41
+ * as it must be. `show` puts a text on screen and resolves once it is drawn.
42
+ */
43
+ async function fitStatusLine(
44
+ variants: readonly string[],
45
+ show: (text: string) => Promise<void>,
46
+ overflows: () => boolean,
47
+ ): Promise<string> {
48
+ for (const variant of variants) {
49
+ await show(variant);
50
+ if (!overflows()) return variant;
51
+ }
52
+ const last = variants.at(-1) ?? "";
53
+ let low = 1;
54
+ let high = last.length - 1;
55
+ let best = middleEllipsis(last, 1);
56
+ while (low <= high) {
57
+ const keep = Math.floor((low + high) / 2);
58
+ const text = middleEllipsis(last, keep);
59
+ await show(text);
60
+ if (overflows()) {
61
+ high = keep - 1;
62
+ } else {
63
+ best = text;
64
+ low = keep + 1;
65
+ }
66
+ }
67
+ await show(best);
68
+ return best;
69
+ }
70
+
71
+ function footerOverflows(): boolean {
72
+ const footer = document.querySelector('[data-gloom-role="pane-footer"]');
73
+ if (!footer) return false;
74
+ return [footer, ...footer.querySelectorAll("*")].some((element) => (
75
+ element instanceof HTMLElement && element.scrollWidth > element.clientWidth + 1
76
+ ));
77
+ }
78
+
79
+ function useShotStatusLine(enabled: boolean): string {
80
+ const [text, setText] = useState(PENDING_STATUS);
81
+ useEffect(() => {
82
+ if (!enabled) return;
83
+ window.__GLOOM_CLI_SHOT_FIT_STATUS__ = (variants) => fitStatusLine(variants, async (next) => {
84
+ setText(next);
85
+ await nextFrames(2);
86
+ }, footerOverflows);
87
+ return () => {
88
+ delete window.__GLOOM_CLI_SHOT_FIT_STATUS__;
89
+ };
90
+ }, [enabled]);
91
+ return text;
92
+ }
93
+
94
+ /**
95
+ * Static captures retain warnings and optional source status without action
96
+ * shortcuts. With `statusLine` the footer also carries the capture's dated
97
+ * status line (`Fri 9 Oct close · 15 min delayed · markets closed until Mon`),
98
+ * inside the requested size: the body gives up the footer row for it.
99
+ */
100
+ export function PaneShotFrame({ paneId, title, width, height, preserveStatus = false, statusLine = false, children }: {
9
101
  paneId: string;
10
102
  title: string;
11
103
  width: number;
12
104
  height: number;
13
105
  preserveStatus?: boolean;
106
+ statusLine?: boolean;
14
107
  children: (frame: ReturnType<typeof resolvePaneBodyFrame>) => ReactNode;
15
108
  }) {
109
+ const statusText = useShotStatusLine(statusLine);
16
110
  return <PaneFooterProvider>{(registeredFooter) => {
17
- const footer = { info: registeredFooter.info.filter((segment) => preserveStatus || segment.icon === "warning"), hints: [], menu: [], keys: [] };
111
+ const status: PaneFooterSegment[] = statusLine ? [{ id: "shot-status", parts: [{ text: statusText, tone: "label" }] }] : [];
112
+ const info = registeredFooter.info.filter((segment) => preserveStatus || segment.icon === "warning");
113
+ const footer = { info: [...info, ...status], hints: [], menu: [], keys: [] };
18
114
  const bodyFrame = resolvePaneBodyFrame({
19
115
  width,
20
116
  height,
@@ -210,7 +210,10 @@ async function boot() {
210
210
  });
211
211
 
212
212
  const remoteControlAdapter = init.windowKind === "main"
213
- ? { registerHandler: setElectrobunRemoteRequestHandler }
213
+ ? {
214
+ registerHandler: setElectrobunRemoteRequestHandler,
215
+ terminalDevice: { kind: "desktop" as const, name: init.hostName?.trim() || "Gloom desktop" },
216
+ }
214
217
  : undefined;
215
218
  measurePerfAsync("startup.electrobun.root-render", async () => {
216
219
  root.render(
@@ -1,8 +1,10 @@
1
+ import { flushSync } from "@opentui/react";
1
2
  import { useMemo, type ReactNode } from "react";
2
3
  import { useNativeRenderer } from "../../ui";
3
4
  import {
4
5
  createShortcutRegistry,
5
6
  InputHostProvider,
7
+ useRegisteredBeforeShortcut,
6
8
  useRegisteredShortcut,
7
9
  type InputHost,
8
10
  } from "../../react/input";
@@ -10,7 +12,7 @@ import { toKeyEventLike, useKeyboard, useTerminalDimensions } from "./host";
10
12
 
11
13
  export function OpenTuiInputHostProvider({ children }: { children: ReactNode }) {
12
14
  const renderer = useNativeRenderer();
13
- const shortcutRegistry = useMemo(() => createShortcutRegistry(), []);
15
+ const shortcutRegistry = useMemo(() => createShortcutRegistry({ flushSync }), []);
14
16
 
15
17
  useKeyboard((event) => {
16
18
  const shortcutEvent = toKeyEventLike(event);
@@ -22,6 +24,9 @@ export function OpenTuiInputHostProvider({ children }: { children: ReactNode })
22
24
  useShortcut(handler, options) {
23
25
  useRegisteredShortcut(shortcutRegistry, handler, options);
24
26
  },
27
+ useBeforeShortcut(listener) {
28
+ useRegisteredBeforeShortcut(shortcutRegistry, listener);
29
+ },
25
30
  useViewport() {
26
31
  const dimensions = useTerminalDimensions();
27
32
  return { width: dimensions.width, height: dimensions.height };
@@ -1,3 +1,4 @@
1
+ import { hostname } from "os";
1
2
  import { getGloomberbDirs } from "../../data/config/home";
2
3
  import { existsSync, mkdirSync } from "fs";
3
4
  import { App } from "../../app";
@@ -38,6 +39,8 @@ import {
38
39
  import { currentTelemetryConfig } from "../../telemetry/live-config";
39
40
  import { flushUsageCounts, installUsageCounter, usageCountsEnabled } from "../../telemetry/usage-counts";
40
41
  import { attentionCountsEnabled, installAttentionCounter } from "../../telemetry/attention-counts";
42
+ import { starPromptEnvironmentAllows } from "../../app/star-prompt/model";
43
+ import { allowStarPrompt } from "../../app/star-prompt/runtime";
41
44
 
42
45
  // Declared here rather than sniffed: the desktop view and the hosted browser
43
46
  // app are both browser contexts but differ in what plugins may do.
@@ -56,6 +59,7 @@ export async function startOpenTuiApp({ externalPlugins, cliLaunchRequest }: Sta
56
59
  const appLog = debugLog.createLogger("app");
57
60
  appLog.info("Gloomberb starting");
58
61
  const remoteControlAdapter: RemoteControlAdapter = {
62
+ terminalDevice: { kind: "tui", name: hostname() },
59
63
  startServer: ({ dataDir, handle }) => {
60
64
  let closed = false;
61
65
  const serverPromise: Promise<RemoteControlServer> = startRemoteControlServer({
@@ -121,6 +125,11 @@ export async function startOpenTuiApp({ externalPlugins, cliLaunchRequest }: Sta
121
125
  officialPluginIds: loadOfficialPluginIds,
122
126
  });
123
127
  const stopAttention = installAttentionCounter(() => attentionCountsEnabled(currentTelemetryConfig(config), process.env));
128
+ allowStarPrompt(starPromptEnvironmentAllows({
129
+ env: process.env,
130
+ stdinIsTTY: process.stdin.isTTY,
131
+ stdoutIsTTY: process.stdout.isTTY,
132
+ }));
124
133
  host = await measurePerfAsync("startup.opentui.create-host", () => createOpenTuiHost());
125
134
  const renderer = host.renderer;
126
135
  renderer.once("destroy", stopAttention);
@@ -1,4 +1,5 @@
1
1
  import type { PaneRuntimeState } from "../core/state/app/types";
2
+ import { foldPaneLayout } from "../data/config/store/fold-panes";
2
3
  import { publicTickerBindingSymbol } from "../tickers/selection";
3
4
  import { pinFollowingPane } from "../layout/pane-follow";
4
5
  import {
@@ -641,28 +642,37 @@ export function materializeMarketplaceLayout(
641
642
  `${paneId.slice(0, 80)}:shared-${crypto.randomUUID().replaceAll("-", "").slice(0, 20)}`
642
643
  ),
643
644
  ): { layout: LayoutConfig; paneState: Record<string, PaneRuntimeState> } {
644
- const ids = new Map(payload.layout.instances.map((instance, index) => [
645
+ // A layout or pane shared before SIV and SOVR folded into SI and CDX still
646
+ // names the old panes. It opens the way a saved layout migrates.
647
+ const folded = foldPaneLayout(payload.layout);
648
+ const source = {
649
+ layout: folded.layout,
650
+ paneState: Object.fromEntries(Object.entries(payload.paneState).flatMap(([id, state]) => (
651
+ folded.removed.has(id) ? [] : [[folded.idMap.get(id) ?? id, state]]
652
+ ))),
653
+ };
654
+ const ids = new Map(source.layout.instances.map((instance, index) => [
645
655
  instance.instanceId,
646
656
  createId(instance.paneId, index),
647
657
  ]));
648
658
  if (new Set(ids.values()).size !== ids.size) throw new Error("Could not create unique pane ids for this layout.");
649
659
  const layout: LayoutConfig = {
650
- dockRoot: mapDockNode(payload.layout.dockRoot, ids),
651
- instances: payload.layout.instances.map((instance) => ({
660
+ dockRoot: mapDockNode(source.layout.dockRoot, ids),
661
+ instances: source.layout.instances.map((instance) => ({
652
662
  ...structuredClone(instance),
653
663
  instanceId: ids.get(instance.instanceId)!,
654
664
  ...(instance.binding ? { binding: mapBinding(instance.binding, ids) } : {}),
655
665
  })),
656
- floating: payload.layout.floating.map((entry) => ({
666
+ floating: source.layout.floating.map((entry) => ({
657
667
  ...entry,
658
668
  instanceId: ids.get(entry.instanceId) ?? entry.instanceId,
659
669
  })),
660
- detached: payload.layout.detached.map((entry) => ({
670
+ detached: source.layout.detached.map((entry) => ({
661
671
  ...entry,
662
672
  instanceId: ids.get(entry.instanceId) ?? entry.instanceId,
663
673
  })),
664
674
  };
665
- const paneState = Object.fromEntries(Object.entries(payload.paneState).flatMap(([id, state]) => {
675
+ const paneState = Object.fromEntries(Object.entries(source.paneState).flatMap(([id, state]) => {
666
676
  const materializedId = ids.get(id);
667
677
  return materializedId ? [[materializedId, structuredClone(state)]] : [];
668
678
  }));