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
@@ -54,7 +54,13 @@ import {
54
54
  type ResolvedRegistryPaneQuickSetting,
55
55
  type ResolvedRegistryPaneSettings,
56
56
  } from "./pane-settings";
57
- import { RegistryResumeStateListeners, createPluginPaneSettingsState, createPluginResumeState } from "./plugin-state";
57
+ import {
58
+ RegistryResumeStateListeners,
59
+ bindPluginContextNamespaces,
60
+ createPluginPaneSettingsState,
61
+ createPluginResumeState,
62
+ type NamespacedPluginContext,
63
+ } from "./plugin-state";
58
64
  import { createPluginSetupCommand, isPluginConfigured } from "./setup-command";
59
65
  import {
60
66
  bindSharedRegistry,
@@ -76,6 +82,12 @@ interface PluginRegistryOptions {
76
82
  }
77
83
 
78
84
  export type { WindowEditMode } from "./host-actions";
85
+
86
+ /** A switched-off plugin, named for a "Turn on" offer. */
87
+ export interface DisabledPluginOwner {
88
+ id: string;
89
+ name: string;
90
+ }
79
91
  export {
80
92
  getSharedMarketData,
81
93
  getSharedRegistry,
@@ -167,6 +179,7 @@ export class PluginRegistry implements PluginRuntimeAccess {
167
179
  bindSharedRegistry(this, marketData);
168
180
  this.capabilities = new CapabilityRegistry({
169
181
  isPluginEnabled: (pluginId) => !this.getConfig().disabledPlugins.includes(pluginId),
182
+ pluginName: (pluginId) => this.plugins.get(pluginId)?.name,
170
183
  isCapabilityEnabled: (capability, pluginId) => {
171
184
  const disabledSources = this.getConfig().disabledSources ?? [];
172
185
  return !disabledSources.includes(capability.sourceId ?? capability.id) && !this.getConfig().disabledPlugins.includes(pluginId);
@@ -407,6 +420,41 @@ export class PluginRegistry implements PluginRuntimeAccess {
407
420
  return this.contributions.paneTemplatesMap.owners.get(templateId);
408
421
  }
409
422
 
423
+ /**
424
+ * The plugin that owns this pane type when it is switched off. A pane of a
425
+ * switched-off plugin stays hidden, so callers offer to turn it on instead
426
+ * of adding one.
427
+ */
428
+ getDisabledPaneOwner(
429
+ paneType: string,
430
+ disabledPlugins: readonly string[] = this.getConfig().disabledPlugins,
431
+ ): DisabledPluginOwner | null {
432
+ return this.disabledOwner(this.getPanePluginId(paneType), disabledPlugins);
433
+ }
434
+
435
+ /** The same for a template: its own plugin, or the one owning the pane it opens. */
436
+ getDisabledPaneTemplateOwner(
437
+ templateId: string,
438
+ disabledPlugins: readonly string[] = this.getConfig().disabledPlugins,
439
+ ): DisabledPluginOwner | null {
440
+ const paneType = this.paneTemplates.get(templateId)?.paneId;
441
+ return this.disabledOwner(this.getPaneTemplatePluginId(templateId), disabledPlugins)
442
+ ?? (paneType ? this.getDisabledPaneOwner(paneType, disabledPlugins) : null);
443
+ }
444
+
445
+ /** The plugin behind a research tab when it is switched off, which hides the tab. */
446
+ getDisabledTickerResearchTabOwner(
447
+ tabId: string,
448
+ disabledPlugins: readonly string[] = this.getConfig().disabledPlugins,
449
+ ): DisabledPluginOwner | null {
450
+ return this.disabledOwner(this.getTickerResearchTabPluginId(tabId), disabledPlugins);
451
+ }
452
+
453
+ private disabledOwner(pluginId: string | undefined, disabledPlugins: readonly string[]): DisabledPluginOwner | null {
454
+ if (!pluginId || !disabledPlugins.includes(pluginId)) return null;
455
+ return { id: pluginId, name: this.plugins.get(pluginId)?.name ?? pluginId };
456
+ }
457
+
410
458
  getBrokerPluginId(brokerType: string): string | undefined {
411
459
  return this.contributions.brokersMap.owners.get(brokerType);
412
460
  }
@@ -429,10 +477,42 @@ export class PluginRegistry implements PluginRuntimeAccess {
429
477
  }
430
478
  }
431
479
 
480
+ /**
481
+ * The state parts of a plugin's context. `namespaceKey` is the plugin's id,
482
+ * or the namespace a composed module keeps; both resolve the way a render
483
+ * context's id does.
484
+ */
485
+ private createNamespacedContext(namespaceKey: string): NamespacedPluginContext {
486
+ const stateId = this.stateNamespace(namespaceKey);
487
+ return {
488
+ persistence: createPluginPersistence(
489
+ this.persistence.pluginState,
490
+ this.persistence.resources,
491
+ `plugin:${stateId}`,
492
+ stateId,
493
+ ),
494
+ resume: createPluginResumeState({
495
+ pluginId: stateId,
496
+ getResumeState: (key, version) => this.getResumeState(namespaceKey, key, version),
497
+ setResumeState: (key, value, version) => this.setResumeState(namespaceKey, key, value, version),
498
+ deleteResumeState: (key) => this.deleteResumeState(namespaceKey, key),
499
+ getPaneRuntimeState: (paneId) => this.getPaneRuntimeState(paneId),
500
+ updatePaneRuntimeState: (paneId, patch) => this.updatePaneRuntimeState(paneId, patch),
501
+ }),
502
+ teamState: createPluginTeamState(stateId),
503
+ configState: {
504
+ get: (key) => this.getConfigState(namespaceKey, key),
505
+ set: (key, value) => this.setConfigState(namespaceKey, key, value),
506
+ delete: (key) => this.deleteConfigState(namespaceKey, key),
507
+ keys: () => this.getConfigStateKeys(namespaceKey),
508
+ },
509
+ };
510
+ }
511
+
432
512
  private createContext(pluginId: string): GloomPluginContext {
433
513
  const contributions = this.contributions;
434
514
  const items = contributions.getOrCreatePluginItems(pluginId);
435
- return {
515
+ return bindPluginContextNamespaces({
436
516
  registerPane: (pane) => contributions.registerPane(pluginId, pane),
437
517
  registerPaneTemplate: (template) => contributions.registerPaneTemplate(pluginId, template, true),
438
518
  registerCommand: (command) => contributions.registerCommand(pluginId, command),
@@ -468,33 +548,13 @@ export class PluginRegistry implements PluginRuntimeAccess {
468
548
  marketData: this.marketData,
469
549
  connectionHealth: this.connectionHealth,
470
550
  tickerRepository: this.tickerRepository,
471
- persistence: createPluginPersistence(
472
- this.persistence.pluginState,
473
- this.persistence.resources,
474
- `plugin:${this.stateNamespace(pluginId)}`,
475
- this.stateNamespace(pluginId),
476
- ),
551
+ ...this.createNamespacedContext(pluginId),
477
552
  log: debugLog.createLogger(pluginId),
478
- resume: createPluginResumeState({
479
- pluginId: this.stateNamespace(pluginId),
480
- getResumeState: (key, version) => this.getResumeState(pluginId, key, version),
481
- setResumeState: (key, value, version) => this.setResumeState(pluginId, key, value, version),
482
- deleteResumeState: (key) => this.deleteResumeState(pluginId, key),
483
- getPaneRuntimeState: (paneId) => this.getPaneRuntimeState(paneId),
484
- updatePaneRuntimeState: (paneId, patch) => this.updatePaneRuntimeState(paneId, patch),
485
- }),
486
- teamState: createPluginTeamState(this.stateNamespace(pluginId)),
487
553
  paneSettings: createPluginPaneSettingsState({
488
554
  getLayout: () => this.getLayout(),
489
555
  updateLayout: (layout) => this.updateLayout(layout),
490
556
  resolvePaneTarget: (paneId) => this.resolvePaneTarget(paneId),
491
557
  }),
492
- configState: {
493
- get: (key) => this.getConfigState(pluginId, key),
494
- set: (key, value) => this.setConfigState(pluginId, key, value),
495
- delete: (key) => this.deleteConfigState(pluginId, key),
496
- keys: () => this.getConfigStateKeys(pluginId),
497
- },
498
558
  createBrokerInstance: (brokerType, label, values) => this.createBrokerInstance(brokerType, label, values),
499
559
  updateBrokerInstance: this.updateBrokerInstance,
500
560
  syncBrokerInstance: this.syncBrokerInstance,
@@ -519,7 +579,7 @@ export class PluginRegistry implements PluginRuntimeAccess {
519
579
  // Every plugin event is also a host event; TypeScript cannot see that through the generic key.
520
580
  emit: (event, payload) => this.events.emit(event, payload as HostEvents[typeof event]),
521
581
  notify: (notification) => this.notify(notification),
522
- };
582
+ }, (stateId) => this.createNamespacedContext(stateId));
523
583
  }
524
584
 
525
585
  private registryLog = debugLog.createLogger("registry");
@@ -1,7 +1,7 @@
1
1
  import type { PaneRuntimeState } from "../../core/state/app/state";
2
2
  import { deletePaneSetting, setPaneSetting } from "../../pane-settings";
3
3
  import type { LayoutConfig } from "../../types/config";
4
- import type { PluginPaneSettingsState, PluginResumeState } from "../../types/plugin";
4
+ import type { GloomPluginContext, PluginPaneSettingsState, PluginResumeState } from "../../types/plugin";
5
5
  import { deletePluginPaneStateValue, setPluginPaneStateValue } from "../../layout/pane-state";
6
6
 
7
7
  export class RegistryResumeStateListeners {
@@ -35,6 +35,30 @@ export class RegistryResumeStateListeners {
35
35
  }
36
36
  }
37
37
 
38
+ /** The parts of a plugin context that read and write under its state namespace. */
39
+ export type NamespacedPluginContext = Pick<GloomPluginContext, "persistence" | "resume" | "configState" | "teamState">;
40
+
41
+ const namespacedContexts = new WeakMap<GloomPluginContext, (stateId: string) => NamespacedPluginContext>();
42
+
43
+ /** Lets `pluginContextInNamespace` rebuild this context's state parts for another namespace. */
44
+ export function bindPluginContextNamespaces(
45
+ context: GloomPluginContext,
46
+ createNamespaced: (stateId: string) => NamespacedPluginContext,
47
+ ): GloomPluginContext {
48
+ namespacedContexts.set(context, createNamespaced);
49
+ return context;
50
+ }
51
+
52
+ /**
53
+ * The same plugin context with its persistence, resume, config and team state
54
+ * under another namespace. Contributions still register to the plugin that
55
+ * owns the context. A context the registry did not create comes back as is.
56
+ */
57
+ export function pluginContextInNamespace(context: GloomPluginContext, stateId: string): GloomPluginContext {
58
+ const createNamespaced = namespacedContexts.get(context);
59
+ return createNamespaced ? { ...context, ...createNamespaced(stateId) } : context;
60
+ }
61
+
38
62
  interface PluginResumeStateOptions {
39
63
  pluginId: string;
40
64
  getResumeState: <T = unknown>(key: string, schemaVersion?: number) => T | null;
@@ -2,6 +2,7 @@ import {
2
2
  createContext,
3
3
  createElement,
4
4
  useContext,
5
+ useMemo,
5
6
  type ReactNode,
6
7
  } from "react";
7
8
  import type { BrokerAdapter } from "../../types/broker";
@@ -67,6 +68,34 @@ export function withPluginRender<P>(
67
68
  );
68
69
  }
69
70
 
71
+ function PluginStateNamespace({ stateId, children }: { stateId: string; children: ReactNode }) {
72
+ const outer = useContext(PluginRenderContext);
73
+ const runtime = outer?.runtime;
74
+ const value = useMemo(() => (runtime ? { pluginId: stateId, runtime } : null), [runtime, stateId]);
75
+ if (!value) return children;
76
+ return <PluginRenderContext value={value}>{children}</PluginRenderContext>;
77
+ }
78
+
79
+ /**
80
+ * Renders a component under another state namespace of the plugin render
81
+ * context it is already in. A built-in module that moved to another plugin
82
+ * keeps reading and writing the pane and plugin state it already has.
83
+ */
84
+ export function withPluginStateNamespace<P>(
85
+ stateId: string,
86
+ component: (props: P) => ReactNode,
87
+ ): (props: P) => ReactNode {
88
+ const scoped = (props: P) => (
89
+ <PluginStateNamespace stateId={stateId}>
90
+ {createElement(component as (props: any) => ReactNode, props)}
91
+ </PluginStateNamespace>
92
+ );
93
+ // Names the namespace in React's tools, and to tests that check a moved
94
+ // module still reads the state it had.
95
+ scoped.displayName = `PluginStateNamespace(${stateId})`;
96
+ return scoped;
97
+ }
98
+
70
99
  export function usePluginRenderContext(): PluginRenderContextValue {
71
100
  const context = useContext(PluginRenderContext);
72
101
  if (!context) {
@@ -57,9 +57,20 @@ interface ShortcutEntry extends ShortcutRegistration {
57
57
 
58
58
  export interface ShortcutRegistry {
59
59
  register(registration: ShortcutRegistration): () => void;
60
+ /**
61
+ * Runs `listener` for every key, before any handler is picked for it. State
62
+ * it sets has rendered by the time the handlers run (when the host can
63
+ * flush), so they read it instead of what the last frame held.
64
+ */
65
+ beforeDispatch(listener: (event: KeyEventLike) => void): () => void;
60
66
  dispatch(event: KeyEventLike): void;
61
67
  }
62
68
 
69
+ export interface ShortcutRegistryOptions {
70
+ /** The renderer's synchronous render flush, which `beforeDispatch` listeners run inside. */
71
+ flushSync?: (run: () => void) => void;
72
+ }
73
+
63
74
  const SHORTCUT_PHASES: ReadonlyArray<ShortcutEntry["phase"]> = [
64
75
  "capture",
65
76
  "before",
@@ -88,9 +99,10 @@ function orderShortcutEntries(entries: ShortcutEntry[]): ShortcutEntry[] {
88
99
  });
89
100
  }
90
101
 
91
- export function createShortcutRegistry(): ShortcutRegistry {
102
+ export function createShortcutRegistry({ flushSync }: ShortcutRegistryOptions = {}): ShortcutRegistry {
92
103
  let nextOrder = 1;
93
104
  const entries = new Map<number, ShortcutEntry>();
105
+ const beforeListeners = new Set<(event: KeyEventLike) => void>();
94
106
 
95
107
  return {
96
108
  register(registration) {
@@ -104,7 +116,20 @@ export function createShortcutRegistry(): ShortcutRegistry {
104
116
  entries.delete(order);
105
117
  };
106
118
  },
119
+ beforeDispatch(listener) {
120
+ beforeListeners.add(listener);
121
+ return () => {
122
+ beforeListeners.delete(listener);
123
+ };
124
+ },
107
125
  dispatch(event) {
126
+ if (beforeListeners.size > 0) {
127
+ const run = () => {
128
+ for (const listener of [...beforeListeners]) listener(event);
129
+ };
130
+ if (flushSync) flushSync(run);
131
+ else run();
132
+ }
108
133
  const eligible = orderShortcutEntries(
109
134
  [...entries.values()].filter((entry) => (
110
135
  entry.isEnabled() && shouldDeliverShortcut(event, entry.allowsEditable())
@@ -151,11 +176,24 @@ export function useRegisteredShortcut(
151
176
  }), [options?.phase, options?.scope, registry]);
152
177
  }
153
178
 
179
+ export function useRegisteredBeforeShortcut(
180
+ registry: ShortcutRegistry,
181
+ listener: (event: KeyEventLike) => void,
182
+ ): void {
183
+ const listenerRef = useRef(listener);
184
+ listenerRef.current = listener;
185
+ useLayoutEffect(
186
+ () => registry.beforeDispatch((event) => listenerRef.current(event)),
187
+ [registry],
188
+ );
189
+ }
190
+
154
191
  export interface InputHost {
155
192
  useShortcut(
156
193
  handler: (event: KeyEventLike) => void,
157
194
  options?: ShortcutOptions,
158
195
  ): void;
196
+ useBeforeShortcut(listener: (event: KeyEventLike) => void): void;
159
197
  useViewport(): { width: number; height: number };
160
198
  }
161
199
 
@@ -186,6 +224,15 @@ export function useShortcut(
186
224
  useInputHost().useShortcut(handler, options);
187
225
  }
188
226
 
227
+ /**
228
+ * Runs before every key reaches any shortcut handler, whatever its phase or
229
+ * scope, for state the handlers read: set it here and it has rendered by the
230
+ * time they run. Not for acting on the key.
231
+ */
232
+ export function useBeforeShortcut(listener: (event: KeyEventLike) => void): void {
233
+ useInputHost().useBeforeShortcut(listener);
234
+ }
235
+
189
236
  export function shouldDeliverShortcut(
190
237
  event: KeyEventLike,
191
238
  allowEditable: boolean,
@@ -23,6 +23,11 @@ export function useRemoteControlHandler(): RemoteControlHandler | null {
23
23
  export interface RemoteControlAdapter {
24
24
  startServer?(options: { dataDir: string; handle: RemoteControlHandler }): void | (() => void | Promise<void>);
25
25
  registerHandler?(handler: RemoteControlHandler | null): void | (() => void);
26
+ /**
27
+ * Set where a remote assistant may drive this app through Gloom Cloud: what
28
+ * kind of app it is and the machine name it shows under.
29
+ */
30
+ terminalDevice?: { kind: "desktop" | "tui"; name: string };
26
31
  }
27
32
 
28
33
  interface RemoteControlHostProps {
@@ -30,6 +30,7 @@ import type {
30
30
  } from "./types";
31
31
  import type { RemoteUiRegistry } from "./semantic-tree";
32
32
  import { commandBarResultsFromNodes, isCommandBarInputNode } from "./command-bar";
33
+ import { findCommandBarResult, findMatchingUiNode } from "./matching";
33
34
  import { REMOTE_AGENT_HELP, remoteControlSchema } from "./schema";
34
35
  import {
35
36
  fail,
@@ -48,7 +49,6 @@ import {
48
49
  } from "./layout-helpers";
49
50
  import { createRemoteResources } from "./resources";
50
51
  import { dismissTopmostDialog, isDialogOpen as isAnyDialogOpen } from "../ui/dialog-stack";
51
- import { findFormNode } from "./form";
52
52
  import { asRecord } from "../utils/guards";
53
53
  import { runAutomated } from "../telemetry/usage-counts";
54
54
 
@@ -247,20 +247,7 @@ export function createAppRemoteController({
247
247
  };
248
248
 
249
249
  const activateCommandBarResult = async (input: Record<string, unknown>): Promise<unknown> => {
250
- const nodeId = optionalString(input, "nodeId");
251
- const index = optionalNumber(input, "index");
252
- const itemId = optionalString(input, "itemId");
253
- const label = optionalString(input, "label");
254
- const results = commandBarResultsFromNodes(uiRegistry?.snapshot() ?? []);
255
- const result = nodeId
256
- ? results.find((entry) => entry.nodeId === nodeId)
257
- : itemId
258
- ? results.find((entry) => entry.itemId === itemId)
259
- : label
260
- ? results.find((entry) => entry.label === label)
261
- : typeof index === "number"
262
- ? results.find((entry) => entry.index === index)
263
- : results.find((entry) => entry.selected) ?? results[0];
250
+ const result = findCommandBarResult(commandBarResultsFromNodes(uiRegistry?.snapshot() ?? []), input);
264
251
  if (!result) throw new Error("No matching command-bar result is visible.");
265
252
  if (!result.actions.includes("activate")) {
266
253
  throw new Error(`Command-bar result "${result.nodeId}" does not expose activate.`);
@@ -270,45 +257,8 @@ export function createAppRemoteController({
270
257
  };
271
258
 
272
259
  const invokeMatchingUiNode = async (input: Record<string, unknown>): Promise<unknown> => {
273
- const role = optionalString(input, "role");
274
- const label = optionalString(input, "label");
275
- const contains = optionalString(input, "contains");
276
- const index = optionalNumber(input, "index");
277
260
  const action = optionalString(input, "action") ?? "press";
278
- const metadataFilter = asRecord(input.metadata);
279
- const nodes = uiRegistry?.snapshot() ?? [];
280
- // A dialog over the form (a listing picker, a sign-in) keeps its controls
281
- // out of reach, as it does from the mouse.
282
- const formCovered = findFormNode(nodes)?.metadata?.covered === true;
283
- const candidates = nodes.filter((node) => {
284
- if (formCovered && node.metadata?.scope === "form") return false;
285
- if (role && node.role !== role) return false;
286
- if (label && node.label !== label && node.metadata?.item && typeof node.metadata.item === "object") {
287
- const item = node.metadata.item as Record<string, unknown>;
288
- if (item.label !== label && item.id !== label) return false;
289
- } else if (label && node.label !== label) {
290
- return false;
291
- }
292
- if (contains) {
293
- const haystack = [
294
- node.label,
295
- node.role,
296
- JSON.stringify(node.metadata ?? {}),
297
- ].filter((entry): entry is string => typeof entry === "string").join(" ").toLowerCase();
298
- if (!haystack.includes(contains.toLowerCase())) return false;
299
- }
300
- for (const [key, value] of Object.entries(metadataFilter)) {
301
- if (node.metadata?.[key] !== value) return false;
302
- }
303
- if (!node.actions.includes(action)) return false;
304
- if (node.disabled) return false;
305
- return true;
306
- });
307
- // An open form or confirm covers every pane, so its own controls come
308
- // first: its Cancel, not a pane's Cancel behind it.
309
- const inForm = candidates.filter((node) => node.metadata?.scope === "form");
310
- const matches = inForm.length > 0 ? inForm : candidates;
311
- const node = typeof index === "number" ? matches[index] : matches[0];
261
+ const node = findMatchingUiNode(uiRegistry?.snapshot() ?? [], input);
312
262
  if (!node) throw new Error("No matching semantic UI node is visible.");
313
263
  const result = await uiRegistry?.invoke(node.id, action, input.input);
314
264
  return getAfterMutationSummary({ invokedNode: node, result });
@@ -0,0 +1,69 @@
1
+ import { optionalNumber, optionalString } from "./controller-utils";
2
+ import { findFormNode } from "./form";
3
+ import type { RemoteUiNodeSnapshot } from "./types";
4
+ import { asRecord } from "../utils/guards";
5
+
6
+ /** The visible command-bar row commandBar.activateResult picks for this input. */
7
+ export function findCommandBarResult<T extends { nodeId: string; itemId?: unknown; label?: unknown; index?: unknown; selected: boolean }>(
8
+ results: readonly T[],
9
+ input: Record<string, unknown>,
10
+ ): T | undefined {
11
+ const nodeId = optionalString(input, "nodeId");
12
+ const index = optionalNumber(input, "index");
13
+ const itemId = optionalString(input, "itemId");
14
+ const label = optionalString(input, "label");
15
+ return nodeId
16
+ ? results.find((entry) => entry.nodeId === nodeId)
17
+ : itemId
18
+ ? results.find((entry) => entry.itemId === itemId)
19
+ : label
20
+ ? results.find((entry) => entry.label === label)
21
+ : typeof index === "number"
22
+ ? results.find((entry) => entry.index === index)
23
+ : results.find((entry) => entry.selected) ?? results[0];
24
+ }
25
+
26
+ /** The semantic UI node ui.invokeMatching acts on for this input, or undefined. */
27
+ export function findMatchingUiNode(
28
+ nodes: RemoteUiNodeSnapshot[],
29
+ input: Record<string, unknown>,
30
+ ): RemoteUiNodeSnapshot | undefined {
31
+ const role = optionalString(input, "role");
32
+ const label = optionalString(input, "label");
33
+ const contains = optionalString(input, "contains");
34
+ const index = optionalNumber(input, "index");
35
+ const action = optionalString(input, "action") ?? "press";
36
+ const metadataFilter = asRecord(input.metadata);
37
+ // A dialog over the form (a listing picker, a sign-in) keeps its controls
38
+ // out of reach, as it does from the mouse.
39
+ const formCovered = findFormNode(nodes)?.metadata?.covered === true;
40
+ const candidates = nodes.filter((node) => {
41
+ if (formCovered && node.metadata?.scope === "form") return false;
42
+ if (role && node.role !== role) return false;
43
+ if (label && node.label !== label && node.metadata?.item && typeof node.metadata.item === "object") {
44
+ const item = node.metadata.item as Record<string, unknown>;
45
+ if (item.label !== label && item.id !== label) return false;
46
+ } else if (label && node.label !== label) {
47
+ return false;
48
+ }
49
+ if (contains) {
50
+ const haystack = [
51
+ node.label,
52
+ node.role,
53
+ JSON.stringify(node.metadata ?? {}),
54
+ ].filter((entry): entry is string => typeof entry === "string").join(" ").toLowerCase();
55
+ if (!haystack.includes(contains.toLowerCase())) return false;
56
+ }
57
+ for (const [key, value] of Object.entries(metadataFilter)) {
58
+ if (node.metadata?.[key] !== value) return false;
59
+ }
60
+ if (!node.actions.includes(action)) return false;
61
+ if (node.disabled) return false;
62
+ return true;
63
+ });
64
+ // An open form or confirm covers every pane, so its own controls come
65
+ // first: its Cancel, not a pane's Cancel behind it.
66
+ const inForm = candidates.filter((node) => node.metadata?.scope === "form");
67
+ const matches = inForm.length > 0 ? inForm : candidates;
68
+ return typeof index === "number" ? matches[index] : matches[0];
69
+ }
@@ -115,7 +115,7 @@ export const REMOTE_OPERATIONS: RemoteOperationSchema[] = [
115
115
  op("pane.close", "Close a pane instance or pane type.", "{ paneId: string }", "local-write", paneIdInput),
116
116
  op(
117
117
  "pane.createFromTemplate",
118
- "Create a pane from a registered template.",
118
+ "Open a pane from a registered template. It only opens the pane: it adds nothing to a portfolio or watchlist.",
119
119
  "{ templateId: string, options?: object }",
120
120
  "local-write",
121
121
  objectSchema({ templateId: requiredStringSchema, options: openObjectSchema }, ["templateId"]),
@@ -11,6 +11,8 @@ type RemoteUiAction = (input?: unknown) => unknown | Promise<unknown>;
11
11
 
12
12
  export interface RemoteUiNodeRegistration {
13
13
  role: string;
14
+ /** The pane instance the node is drawn in, set from RemoteUiPaneScope. */
15
+ paneId?: string;
14
16
  label?: string;
15
17
  disabled?: boolean;
16
18
  actions?: Record<string, RemoteUiAction | undefined>;
@@ -56,6 +58,7 @@ export function createRemoteUiRegistry(): RemoteUiRegistry {
56
58
  .map(([action]) => action)
57
59
  .sort(),
58
60
  metadata: registration.getMetadata?.() ?? registration.metadata,
61
+ ...(registration.paneId ? { paneId: registration.paneId } : {}),
59
62
  };
60
63
  });
61
64
  },
@@ -110,6 +113,13 @@ export function useRemoteUiScope(): string | null {
110
113
  return useContext(RemoteUiScopeContext);
111
114
  }
112
115
 
116
+ const RemoteUiPaneContext = createContext<string | null>(null);
117
+
118
+ /** Tags the nodes drawn below with the pane instance they belong to. */
119
+ export function RemoteUiPaneScope({ paneId, children }: { paneId: string; children: ReactNode }) {
120
+ return <RemoteUiPaneContext value={paneId}>{children}</RemoteUiPaneContext>;
121
+ }
122
+
113
123
  let nextRemoteNodeSequence = 0;
114
124
 
115
125
  /**
@@ -120,9 +130,12 @@ let nextRemoteNodeSequence = 0;
120
130
  */
121
131
  export function useRemoteUiNode(registration: RemoteUiNodeRegistration | null | undefined): string | null {
122
132
  const registry = useRemoteUiRegistry();
133
+ const paneId = useContext(RemoteUiPaneContext);
123
134
  const nodeIdRef = useRef<string | null>(null);
124
135
  const registrationRef = useRef(registration);
125
136
  registrationRef.current = registration;
137
+ const paneIdRef = useRef(paneId);
138
+ paneIdRef.current = paneId;
126
139
  const dynamicRegistrationRef = useRef<RemoteUiNodeRegistration | null>(null);
127
140
  const registered = registration != null;
128
141
  if (registered && !nodeIdRef.current) {
@@ -130,6 +143,7 @@ export function useRemoteUiNode(registration: RemoteUiNodeRegistration | null |
130
143
  nodeIdRef.current = `ui:${nextRemoteNodeSequence.toString(36)}`;
131
144
  dynamicRegistrationRef.current = {
132
145
  get role() { return registrationRef.current?.role ?? "unknown"; },
146
+ get paneId() { return registrationRef.current?.paneId ?? paneIdRef.current ?? undefined; },
133
147
  get label() { return registrationRef.current?.label; },
134
148
  get disabled() { return registrationRef.current?.disabled; },
135
149
  get actions() { return registrationRef.current?.actions; },
@@ -172,6 +172,8 @@ export interface RemoteControlSchema {
172
172
  export interface RemoteUiNodeSnapshot {
173
173
  id: string;
174
174
  role: string;
175
+ /** The pane instance the node is drawn in; absent outside panes. */
176
+ paneId?: string;
175
177
  label?: string;
176
178
  disabled?: boolean;
177
179
  actions: string[];
@@ -88,6 +88,20 @@ const ICONS: Record<IconName, ReactNode> = {
88
88
  <path d="M2 9.1 4.7 6.6l1.6 1.5 1.8-2 2.4 2.5" stroke="currentColor" strokeWidth="1.1" strokeLinecap="round" strokeLinejoin="round" />
89
89
  </>
90
90
  ),
91
+ // A hand with the thumb raised, on the 24-unit grid; the thumbs down is the
92
+ // same hand turned over.
93
+ "thumbs-up": (
94
+ <g transform="scale(0.5)">
95
+ <rect x="3" y="10" width="4" height="10" rx="1" stroke="currentColor" strokeWidth="1.8" strokeLinejoin="round" />
96
+ <path d="M7 10l3.5-6.5a1.8 1.8 0 0 1 3.3 1.2L13 9.5h5.6a2 2 0 0 1 2 2.4l-1.4 6.5a2 2 0 0 1-2 1.6H7" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" />
97
+ </g>
98
+ ),
99
+ "thumbs-down": (
100
+ <g transform="scale(0.5) rotate(180 12 12)">
101
+ <rect x="3" y="10" width="4" height="10" rx="1" stroke="currentColor" strokeWidth="1.8" strokeLinejoin="round" />
102
+ <path d="M7 10l3.5-6.5a1.8 1.8 0 0 1 3.3 1.2L13 9.5h5.6a2 2 0 0 1 2 2.4l-1.4 6.5a2 2 0 0 1-2 1.6H7" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" />
103
+ </g>
104
+ ),
91
105
  restore: (
92
106
  <>
93
107
  <rect x="2" y="4" width="6" height="6" rx="0.8" stroke="currentColor" strokeWidth="1.1" />