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
@@ -3,6 +3,7 @@ import { getCurrentPluginTarget } from "../plugins/current-target";
3
3
  import type { DesktopDeepLinkBridge } from "../types/desktop-deeplink";
4
4
  import { exposeExperiment, storedExperimentAssignments } from "./web-experiments";
5
5
  import { createWallExperimentSession, type WallExperiment, type WallExperimentContext, type WallTeaserVariant } from "./wall-experiments";
6
+ import { createUpgradeExperimentSession, UPGRADE_PERSONALIZED, type UpgradeExperimentContext, type UpgradePersonalizedVariant } from "./upgrade-experiment";
6
7
  import { hasProAccess } from "./plan-rules";
7
8
  import { withDeadline } from "../utils/async-deadline";
8
9
 
@@ -38,6 +39,7 @@ let anonymousId: string | undefined;
38
39
  let attribution: Record<string, string> = {};
39
40
  const wallExperiment = createWallExperimentSession();
40
41
  const signinWallExperiment = createWallExperimentSession("wall_teaser_signin");
42
+ const upgradeExperiment = createUpgradeExperimentSession();
41
43
  const WALL_VIEWS_SESSION_KEY = "gloomberb.wall-teaser.views";
42
44
 
43
45
  const ATTRIBUTION_STORAGE_KEY = "gloomberb.web.attribution";
@@ -524,6 +526,35 @@ export function exposeWallTeaser(experiment: WallExperiment = "wall_teaser"): Pr
524
526
  });
525
527
  }
526
528
 
529
+ function upgradeExperimentContext(): UpgradeExperimentContext {
530
+ const { accountId, pro, optedOut, automated, session } = wallExperimentContext();
531
+ return { accountId, pro, optedOut, automated, session };
532
+ }
533
+
534
+ /** The `upgrade_personalized` arm this session already has, without asking; see upgrade-experiment.ts. */
535
+ export function knownUpgradePersonalized(): UpgradePersonalizedVariant | null | undefined {
536
+ return upgradeExperiment.known(upgradeExperimentContext());
537
+ }
538
+
539
+ /**
540
+ * Asks for the `upgrade_personalized` arm when an upgrade surface is shown to
541
+ * a free account that has tickers to name, once per account and session. The
542
+ * request says where it showed and nothing about the holdings.
543
+ */
544
+ export function exposeUpgradePersonalized(placement: string): Promise<UpgradePersonalizedVariant | null> {
545
+ const context = upgradeExperimentContext();
546
+ const surface = getCurrentPluginTarget();
547
+ return upgradeExperiment.expose(context, () => {
548
+ const controller = new AbortController();
549
+ return withDeadline(apiClient.recordExperimentExposure({
550
+ eventId: crypto.randomUUID(),
551
+ surface,
552
+ experiment: UPGRADE_PERSONALIZED,
553
+ ...(ACTIVITY_ID.test(placement) ? { placement } : {}),
554
+ }, controller.signal), 3_000, "Upgrade experiment request timed out", (error) => controller.abort(error));
555
+ });
556
+ }
557
+
527
558
  /** Records the visible wall once per placement, including visitors outside the experiment. */
528
559
  export function recordWallViewed(placement: string, teaserKind?: "summary" | "sample" | "none"): void {
529
560
  recordResearchActivity("wall_viewed", undefined, undefined, { placement, teaser_kind: teaserKind });
@@ -33,7 +33,9 @@ const QUOTE_SUBSCRIPTION_FLUSH_MS = 25;
33
33
  const MARKET_BATCH_FEATURE = "market.batch";
34
34
  /** Chat frames carry `attachments`; without it the server writes images into the text. */
35
35
  export const CHAT_ATTACHMENTS_FEATURE = "chat.attachments";
36
- const CLIENT_SOCKET_FEATURES = [MARKET_BATCH_FEATURE, CHAT_ATTACHMENTS_FEATURE] as const;
36
+ /** The app can run terminal tools relayed from a remote assistant; it still announces itself first. */
37
+ export const TERMINAL_RELAY_FEATURE = "terminal.relay";
38
+ const CLIENT_SOCKET_FEATURES = [MARKET_BATCH_FEATURE, CHAT_ATTACHMENTS_FEATURE, TERMINAL_RELAY_FEATURE] as const;
37
39
  const cloudApiLog = debugLog.createLogger("cloud-api");
38
40
 
39
41
  type ChannelListener = (message: ChatMessage) => void;
@@ -131,6 +133,7 @@ export class CloudApiSocket {
131
133
  /** What the last "ready" frame offered; kept across reconnects so a blip does not hide features. */
132
134
  private offeredFeatures: ReadonlySet<string> = new Set();
133
135
  private readonly offeredFeatureListeners = new Set<() => void>();
136
+ private readonly connectionListeners = new Set<(event: "ready" | "closed") => void>();
134
137
  private readonly tapeListeners = new Map<string, { symbol: string; exchange: string; listeners: Set<(event: TapeFeedEvent) => void> }>();
135
138
  private readonly scannerListeners = new Map<
136
139
  ScannerKind,
@@ -164,6 +167,7 @@ export class CloudApiSocket {
164
167
  const ws = this.ws;
165
168
  this.ws = null;
166
169
  if (ws) {
170
+ this.emitConnection("closed");
167
171
  cloudApiLog.info("teardown websocket");
168
172
  this.health.reportSocketState(
169
173
  GLOOM_CLOUD_SOCKET_CONNECTION_ID,
@@ -260,6 +264,29 @@ export class CloudApiSocket {
260
264
  };
261
265
  }
262
266
 
267
+ /** "ready" after each server hello (features negotiated), "closed" when that connection ends. */
268
+ subscribeConnection(listener: (event: "ready" | "closed") => void): () => void {
269
+ this.connectionListeners.add(listener);
270
+ return () => {
271
+ this.connectionListeners.delete(listener);
272
+ };
273
+ }
274
+
275
+ /** Sends one frame on the open connection; false when there is none. */
276
+ sendFrame(payload: Record<string, unknown>): boolean {
277
+ if (this.ws?.readyState !== WebSocket.OPEN) return false;
278
+ try {
279
+ this.ws.send(JSON.stringify(payload));
280
+ return true;
281
+ } catch {
282
+ return false;
283
+ }
284
+ }
285
+
286
+ private emitConnection(event: "ready" | "closed"): void {
287
+ for (const listener of this.connectionListeners) listener(event);
288
+ }
289
+
263
290
  subscribeTeamNotifications(listener: TeamNotificationListener): () => void {
264
291
  this.teamNotificationListeners.add(listener);
265
292
  this.syncAuthState();
@@ -470,6 +497,7 @@ export class CloudApiSocket {
470
497
  });
471
498
  this.channelListeners.clear();
472
499
  this.offeredFeatureListeners.clear();
500
+ this.connectionListeners.clear();
473
501
  this.chatNotificationListeners.clear();
474
502
  this.chatPresenceListeners.clear();
475
503
  this.teamNotificationListeners.clear();
@@ -500,13 +528,15 @@ export class CloudApiSocket {
500
528
  if (parsed?.type === "ready") {
501
529
  recordServerClockSample(parsed.serverTime);
502
530
  this.negotiateSocketFeatures(parsed.features);
503
- if (!parsed.user) return;
504
- cloudApiLog.info("websocket ready", {
505
- emailVerified: parsed.user.emailVerified === true,
506
- });
507
- this.delegate.updateCurrentUserFromSocket(
508
- parsed.user as Partial<AuthUser>,
509
- );
531
+ if (parsed.user) {
532
+ cloudApiLog.info("websocket ready", {
533
+ emailVerified: parsed.user.emailVerified === true,
534
+ });
535
+ this.delegate.updateCurrentUserFromSocket(
536
+ parsed.user as Partial<AuthUser>,
537
+ );
538
+ }
539
+ this.emitConnection("ready");
510
540
  return;
511
541
  }
512
542
 
@@ -769,6 +799,7 @@ export class CloudApiSocket {
769
799
  tokenSource: usingWebSocketToken ? "websocket" : "session",
770
800
  });
771
801
  if (!activeSocket) return;
802
+ this.emitConnection("closed");
772
803
  this.health.reportSocketState(
773
804
  GLOOM_CLOUD_SOCKET_CONNECTION_ID,
774
805
  "closed",
@@ -10,10 +10,20 @@ import type {
10
10
  } from "../types/financials";
11
11
  import type { SyncSettings, SyncSnapshot } from "../sync/types";
12
12
 
13
+ /**
14
+ * What stands behind a chat account. `discord` is a person on the Gloom
15
+ * Discord who has not linked a Gloom account: a ghost with no profile, no DMs.
16
+ * A server that predates the field leaves it out, which reads as a human.
17
+ */
18
+ export type ChatAccountType = "human" | "managed_llm" | "discord";
19
+
20
+ /** Where a message was written. A message without one came from the app. */
21
+ export type ChatMessageOrigin = "app" | "discord";
22
+
13
23
  export interface ChatUserSummary {
14
24
  id: string;
15
25
  /** A person, a managed assistant (Gloombot), or a Discord user bridged into public channels. */
16
- accountType?: "human" | "managed_llm" | "discord";
26
+ accountType?: ChatAccountType;
17
27
  username: string | null;
18
28
  displayName: string;
19
29
  bio?: string | null;
@@ -47,6 +57,7 @@ export interface ChatMessage {
47
57
  createdAt: string;
48
58
  editedAt?: string | null;
49
59
  user: ChatUserSummary;
60
+ origin?: ChatMessageOrigin;
50
61
  replyTo?: {
51
62
  content: string;
52
63
  user: { id?: string; username: string };
@@ -67,6 +78,15 @@ export interface ChatMessage {
67
78
  clientMessageId?: string;
68
79
  }
69
80
 
81
+ /** Whether this account is tied to a person on the Gloom Discord, and whether its public messages go there. */
82
+ export interface ChatDiscordLink {
83
+ linked: boolean;
84
+ discordUsername?: string;
85
+ mirror: boolean;
86
+ /** False when the server has linking switched off. A server that predates the field is available. */
87
+ available: boolean;
88
+ }
89
+
70
90
  export interface ChatChannel {
71
91
  id: string;
72
92
  name: string;
@@ -225,6 +245,22 @@ export interface CloudPricing {
225
245
  yearly: CloudPricingTier;
226
246
  }
227
247
 
248
+ /**
249
+ * What an MCP key reaches beyond market data: `data` is market and filing
250
+ * tools only, `read` adds notes, teams and collections, `write` lets the agent
251
+ * save notes and change collection items.
252
+ */
253
+ export type McpKeyScope = "data" | "read" | "write";
254
+
255
+ /**
256
+ * A key just created with `POST /account/mcp/keys`. `token` is the secret,
257
+ * which the server never returns again: show it once, never store it.
258
+ */
259
+ export interface CreatedMcpKey {
260
+ token: string;
261
+ apiKey: { id: string; name: string; scope: McpKeyScope };
262
+ }
263
+
228
264
  /** The account's private calendar feed link (`/account/calendar-feed`). */
229
265
  export interface CalendarFeed {
230
266
  url: string;
@@ -382,6 +418,17 @@ export interface CloudEconEventPayload {
382
418
  impact: CloudEconImpact;
383
419
  }
384
420
 
421
+ /** CPI, payrolls and FOMC statement days as published, as GET /cloud/econ/release-days serves them. */
422
+ export interface CloudMacroReleaseDaysPayload {
423
+ /** When the publishers' pages were read, ISO. */
424
+ checkedAt: string;
425
+ /** The last New York day the lists are complete for. */
426
+ coveredThrough: string;
427
+ sources: string[];
428
+ /** New York dates, oldest first. */
429
+ releases: Record<"cpi" | "jobs" | "fomc", string[]>;
430
+ }
431
+
385
432
  export interface CloudFredObservationPayload {
386
433
  date: string;
387
434
  value: number | null;
@@ -408,6 +455,11 @@ export interface CloudFredSeriesPayload {
408
455
  coverage?: { observations: "available"; info: "available" | "unavailable" };
409
456
  }
410
457
 
458
+ /** The FRED series Gloom Cloud serves, from `GET /cloud/econ/series`; any other id is refused. */
459
+ export interface CloudFredSeriesCatalogPayload {
460
+ series: Array<{ id: string; title?: string | null; group?: string | null }>;
461
+ }
462
+
411
463
  /**
412
464
  * One month of Robert Shiller's dataset. FRED carries no long-run S&P earnings,
413
465
  * so this is what every earnings-based valuation ratio is built from.
@@ -1319,6 +1371,8 @@ export interface CloudMarketResponse<T> {
1319
1371
  status: CloudMarketStatus;
1320
1372
  data: T | null;
1321
1373
  reasonCode?: string;
1374
+ /** Untrusted, human-readable reason the answer is empty. Cleaned before it is shown. */
1375
+ message?: string;
1322
1376
  asOf?: string;
1323
1377
  staleAt?: string;
1324
1378
  stale?: boolean;
@@ -1362,6 +1416,8 @@ export interface CloudMarketBatchItem<T> {
1362
1416
  status: CloudMarketStatus;
1363
1417
  data: T | null;
1364
1418
  reasonCode?: string;
1419
+ /** Untrusted, human-readable reason this item is empty. Cleaned before it is shown. */
1420
+ message?: string;
1365
1421
  stale?: boolean;
1366
1422
  }
1367
1423
 
@@ -0,0 +1,90 @@
1
+ import type { ExperimentAnswer, StorageLike } from "./web-experiments";
2
+
3
+ /**
4
+ * `upgrade_personalized`: the upgrade sheet and onboarding's Pro step name
5
+ * the account's own holdings, or the tickers it watches, instead of the
6
+ * generic line. The registry, the arm and the stop
7
+ * switch live with the API (gloomberb-platform `shared/experiments.ts`); the
8
+ * unit is the signed-in free account, so the same person gets the same arm in
9
+ * the terminal, the desktop app, the web terminal and on gloom.sh/cloud.
10
+ *
11
+ * The tickers never leave the device: the exposure carries the experiment and
12
+ * where it was shown, nothing about the holdings.
13
+ */
14
+ export const UPGRADE_PERSONALIZED = "upgrade_personalized";
15
+
16
+ export type UpgradePersonalizedVariant = "control" | "personalized";
17
+
18
+ export interface UpgradeExperimentContext {
19
+ accountId?: string;
20
+ pro: boolean;
21
+ optedOut: boolean;
22
+ automated: boolean;
23
+ /** Keeps this session's answer across reloads where there is one (web, desktop). */
24
+ session?: StorageLike;
25
+ }
26
+
27
+ const SESSION_PREFIX = "gloomberb.upgrade-personalized.exposed:";
28
+
29
+ function unit(context: UpgradeExperimentContext): string | undefined {
30
+ if (!context.accountId || context.pro || context.optedOut || context.automated) return undefined;
31
+ return `account:${context.accountId}`;
32
+ }
33
+
34
+ function isVariant(value: unknown): value is UpgradePersonalizedVariant {
35
+ return value === "control" || value === "personalized";
36
+ }
37
+
38
+ function read(storage: StorageLike | undefined, key: string): string | null {
39
+ try { return storage?.getItem(key) ?? null; } catch { return null; }
40
+ }
41
+
42
+ function write(storage: StorageLike | undefined, key: string, value: string): void {
43
+ try { storage?.setItem(key, value); } catch { /* Storage is optional in private browsing. */ }
44
+ }
45
+
46
+ /**
47
+ * One exposure per account and session. An accepted arm, or the API's "not in
48
+ * the experiment", is kept for the session; a failed request (offline, an API
49
+ * that does not know the experiment yet, a timeout) is not an exposure and is
50
+ * asked again by the next surface.
51
+ */
52
+ export function createUpgradeExperimentSession() {
53
+ const answers = new Map<string, UpgradePersonalizedVariant | null>();
54
+ const pending = new Map<string, Promise<UpgradePersonalizedVariant | null>>();
55
+
56
+ function known(context: UpgradeExperimentContext): UpgradePersonalizedVariant | null | undefined {
57
+ const identity = unit(context);
58
+ if (!identity) return null;
59
+ if (answers.has(identity)) return answers.get(identity);
60
+ const saved = read(context.session, `${SESSION_PREFIX}${identity}`);
61
+ if (saved === "excluded") return null;
62
+ return isVariant(saved) ? saved : undefined;
63
+ }
64
+
65
+ return {
66
+ /** The arm already answered in this session, null outside it, undefined when not asked yet. */
67
+ known,
68
+ expose(
69
+ context: UpgradeExperimentContext,
70
+ ask: () => Promise<ExperimentAnswer>,
71
+ ): Promise<UpgradePersonalizedVariant | null> {
72
+ const identity = unit(context);
73
+ if (!identity) return Promise.resolve(null);
74
+ const previous = known(context);
75
+ if (previous !== undefined) return Promise.resolve(previous);
76
+ const inFlight = pending.get(identity);
77
+ if (inFlight) return inFlight;
78
+ const request = (async () => {
79
+ let answer: ExperimentAnswer;
80
+ try { answer = await ask(); } catch { return null; }
81
+ const variant = answer.accepted && isVariant(answer.variant) ? answer.variant : null;
82
+ answers.set(identity, variant);
83
+ write(context.session, `${SESSION_PREFIX}${identity}`, variant ?? "excluded");
84
+ return variant;
85
+ })().finally(() => pending.delete(identity));
86
+ pending.set(identity, request);
87
+ return request;
88
+ },
89
+ };
90
+ }
@@ -14,6 +14,7 @@ import { requestFunctionHelp, useFunctionHelpHost } from "../plugins/builtin/hel
14
14
  import { buildRegistryHelpIndex, helpFunctionForPane } from "../plugins/builtin/help/function-index";
15
15
  import { recordFunctionOpen } from "../telemetry/usage-counts";
16
16
  import { isMoveDownShortcut, isMoveUpShortcut } from "../components/command-bar/keyboard-handlers";
17
+ import { pressStarPromptKey } from "./star-prompt/runtime";
17
18
  import {
18
19
  copyActiveSelection,
19
20
  isCopyShortcut,
@@ -154,10 +155,19 @@ export function useAppGlobalShortcuts({
154
155
  }
155
156
 
156
157
  // Toasts float above everything, so their keys work over a dialog too.
158
+ // The status bar's star line answers the same keys once no toast does,
159
+ // but only over the workspace itself.
157
160
  if (action === "notification-action" || action === "notification-dismiss") {
158
- const handled = action === "notification-action"
161
+ const toastHandled = action === "notification-action"
159
162
  ? toastHost.activateNewest?.() === true
160
163
  : toastHost.dismissNewest?.() === true;
164
+ const handled = toastHandled || (
165
+ !dialogOpen
166
+ && !state.commandBarOpen
167
+ && !state.inputCaptured
168
+ && !isDetachedWindow
169
+ && pressStarPromptKey(action === "notification-action" ? "open" : "dismiss")
170
+ );
161
171
  if (handled) {
162
172
  event.preventDefault();
163
173
  event.stopPropagation();
@@ -22,6 +22,7 @@ export type CoreKeybindingActionId =
22
22
  | "close-floating-panes"
23
23
  | "pane-settings"
24
24
  | "pane-fullscreen"
25
+ | "pane-new-layout"
25
26
  | "pane-float"
26
27
  | "pane-pop-out"
27
28
  | "pane-screenshot"
@@ -52,6 +53,7 @@ export const PANE_ACTION_IDS = new Set<CoreKeybindingActionId>([
52
53
  "close-floating-panes",
53
54
  "pane-settings",
54
55
  "pane-fullscreen",
56
+ "pane-new-layout",
55
57
  "pane-float",
56
58
  "pane-pop-out",
57
59
  "pane-screenshot",
@@ -186,6 +188,14 @@ export const KEYBINDING_ACTIONS: readonly KeybindingActionDef[] = [
186
188
  description: "Fill the window with the focused pane, or restore it.",
187
189
  defaults: ["CmdOrCtrl+Shift+F"],
188
190
  },
191
+ {
192
+ id: "pane-new-layout",
193
+ category: "Pane Management",
194
+ description: "Move the focused pane to a new layout of its own, or back to the layout it came from.",
195
+ // N for new, and no app chord had it. Browsers keep Ctrl/Cmd+Shift+N for a
196
+ // private window, so on the web the pane menu and command bar carry it.
197
+ defaults: ["CmdOrCtrl+Shift+N"],
198
+ },
189
199
  {
190
200
  id: "pane-float",
191
201
  category: "Pane Management",
@@ -10,6 +10,7 @@ import {
10
10
  isPaneInLayout,
11
11
  } from "../../layout/pane-manager";
12
12
  import type { PluginRegistry } from "../../plugins/registry";
13
+ import { PluginOffError, notifyPluginOff } from "../../plugins/plugin-off";
13
14
  import type { LoadedExternalPlugin } from "../../plugins/loader";
14
15
  import {
15
16
  getFocusedCollectionId,
@@ -215,6 +216,12 @@ export function useAppPaneRuntime({
215
216
  const target = resolvePaneShowTarget(state.config.layout, paneId);
216
217
  const paneDef = pluginRegistry.panes.get(target.paneType);
217
218
  if (!paneDef) return;
219
+ // Its pane would be added, or focused, hidden.
220
+ const owner = pluginRegistry.getDisabledPaneOwner(target.paneType, stateRef.current.config.disabledPlugins);
221
+ if (owner) {
222
+ notifyPluginOff(pluginRegistry, new PluginOffError(owner, paneDef.name), () => pluginRegistry.showPane(paneId));
223
+ return;
224
+ }
218
225
 
219
226
  if (target.paneType === TICKER_RESEARCH_PANE_ID) {
220
227
  showTickerResearchPane();
@@ -241,6 +248,7 @@ export function useAppPaneRuntime({
241
248
  pluginRegistry,
242
249
  showTickerResearchPane,
243
250
  state.config.layout,
251
+ stateRef,
244
252
  ]);
245
253
 
246
254
  const { createPaneFromTemplate } = useAppPaneTemplateRuntime({
@@ -4,6 +4,7 @@ import { createPaneTemplateOrThrow } from "../../components/command-bar/workflow
4
4
  import { openFormModal } from "../../components/form-modal";
5
5
  import type { AppTickerRepositoryPort } from "../../core/app-service-ports";
6
6
  import type { PluginRegistry } from "../../plugins/registry";
7
+ import { PluginOffError, notifyPluginOff } from "../../plugins/plugin-off";
7
8
  import type { AppAction, AppState } from "../../state/app/context";
8
9
  import type { PaneBinding, PaneInstanceConfig } from "../../types/config";
9
10
  import type { DataProvider } from "../../types/data-provider";
@@ -70,6 +71,10 @@ export function useAppPaneTemplateRuntime({
70
71
  placePaneInstance,
71
72
  });
72
73
  } catch (error) {
74
+ if (error instanceof PluginOffError) {
75
+ notifyPluginOff(pluginRegistry, error, () => { void createPaneFromTemplate(templateId, options); });
76
+ return;
77
+ }
73
78
  notify(
74
79
  error instanceof Error ? error.message : `Could not create ${getPaneTemplateDisplayLabel(template).toLowerCase()}.`,
75
80
  { type: "info" },
@@ -8,6 +8,13 @@ import { openFormModal } from "../../components/form-modal";
8
8
  import { t } from "../../i18n";
9
9
  import { openBrokerAddFlow } from "../../plugins/builtin/broker-manager/add-request";
10
10
  import { getPanelFocusTarget } from "../../core/state/app/layout";
11
+ import { getVisiblePaneCycleOrder } from "../../components/layout/pane/cycle-order";
12
+ import {
13
+ collectDisabledPaneIds,
14
+ resolveShellVisibleLayout,
15
+ restoreShellHiddenPanes,
16
+ } from "../../components/layout/shell/visible-layout";
17
+ import { applyPluginToggles } from "../../plugins/ownership";
11
18
  import { setLayoutManagerDispatch } from "../../plugins/builtin/layout-manager";
12
19
  import { setMarketplaceHost } from "../../plugins/builtin/plugin-marketplace/store";
13
20
  import type { InstalledPlugin } from "../../plugins/builtin/plugin-marketplace/model";
@@ -25,6 +32,7 @@ import {
25
32
  removePane,
26
33
  } from "../../layout/pane-manager";
27
34
  import type { PluginRegistry } from "../../plugins/registry";
35
+ import { PluginOffError, notifyPluginOff } from "../../plugins/plugin-off";
28
36
  import { reportCrash } from "../../telemetry/crash-reports";
29
37
  import { recordFunctionOpen, usageFunctionForPane } from "../../telemetry/usage-counts";
30
38
  import { captureAttentionAction } from "../../telemetry/attention-counts";
@@ -33,15 +41,16 @@ import {
33
41
  resolveTickerNavigationReplacementPane,
34
42
  shouldFocusTickerNavigationTarget,
35
43
  } from "../../layout/ticker-navigation";
36
- import type {
37
- AppAction,
38
- AppState,
44
+ import {
45
+ resolveTickerForPane,
46
+ type AppAction,
47
+ type AppState,
39
48
  } from "../../state/app/context";
40
49
  import type {
41
50
  LayoutConfig,
42
51
  PaneInstanceConfig,
43
52
  } from "../../types/config";
44
- import { TICKER_RESEARCH_PANE_ID } from "../../types/config";
53
+ import { findPaneInstance, TICKER_RESEARCH_PANE_ID } from "../../types/config";
45
54
  import type { DataProvider } from "../../types/data-provider";
46
55
  import type {
47
56
  PaneDef,
@@ -129,7 +138,33 @@ export function bindAppPanePluginRegistry({
129
138
  switchTickerResearchTab,
130
139
  tickerRepository,
131
140
  }: BindAppPanePluginRegistryOptions): void {
141
+ // Switching plugins never edits the layout: the shell hides a switched-off
142
+ // plugin's panes where they are and shows them again when it comes back.
143
+ // Only focus moves, off a pane that just went hidden.
144
+ const setPluginsEnabled = (changes: Readonly<Record<string, boolean>>) => {
145
+ const current = stateRef.current;
146
+ // Setting a state, not flipping it, so a second "Turn on" from an older
147
+ // toast cannot switch the plugin back off.
148
+ const disabledPlugins = applyPluginToggles(current.config.disabledPlugins ?? [], changes);
149
+ if (!disabledPlugins) return;
150
+ const focused = current.focusedPaneId ? findPaneInstance(current.config.layout, current.focusedPaneId) : undefined;
151
+ const focusHidden = !!focused && collectDisabledPaneIds(pluginRegistry, disabledPlugins).has(focused.paneId);
152
+ dispatch(focusHidden
153
+ ? {
154
+ type: "SET_DISABLED_PLUGINS",
155
+ disabledPlugins,
156
+ focusedPaneId: getVisiblePaneCycleOrder(current.config.layout, pluginRegistry, disabledPlugins)[0] ?? null,
157
+ }
158
+ : { type: "SET_DISABLED_PLUGINS", disabledPlugins });
159
+ const nextConfig = { ...current.config, disabledPlugins };
160
+ persistConfig(nextConfig);
161
+ pluginRegistry.events.emit("config:changed", { config: nextConfig });
162
+ };
163
+ const setPluginEnabled = (pluginId: string, enabled: boolean) => setPluginsEnabled({ [pluginId]: enabled });
164
+
132
165
  pluginRegistry.bindHost({
166
+ setPluginEnabled,
167
+ setPluginsEnabled,
133
168
  selectTicker: (symbol, paneId) => selectTickerInPane(symbol, paneId),
134
169
  switchPanel: (panel) => {
135
170
  if (isDetachedWindow) return;
@@ -196,10 +231,9 @@ export function bindAppPanePluginRegistry({
196
231
  const sharedPane = materialized.layout.instances[0];
197
232
  if (!sharedPane) throw new Error("This shared pane is invalid.");
198
233
  const paneDef = pluginRegistry.panes.get(sharedPane.paneId);
199
- const ownerId = pluginRegistry.getPanePluginId(sharedPane.paneId);
200
- if (!paneDef || (ownerId && stateRef.current.config.disabledPlugins.includes(ownerId))) {
201
- throw new Error("This shared pane is unavailable in this version of Gloomberb.");
202
- }
234
+ if (!paneDef) throw new Error("This shared pane is unavailable in this version of Gloomberb.");
235
+ const owner = pluginRegistry.getDisabledPaneOwner(sharedPane.paneId, stateRef.current.config.disabledPlugins);
236
+ if (owner) throw new PluginOffError(owner, "this shared pane");
203
237
  const instance = buildPaneInstance(sharedPane.paneId, sharedPane);
204
238
  if (!instance) throw new Error("This shared pane could not be created.");
205
239
  dispatch({
@@ -242,6 +276,15 @@ export function bindAppPanePluginRegistry({
242
276
  showPane(paneId);
243
277
  return;
244
278
  }
279
+ // In the layout but hidden with its plugin, as the research pane that
280
+ // follows a list is while Ticker Research is off.
281
+ const paneType = findPaneInstance(stateRef.current.config.layout, instanceId)?.paneId;
282
+ const owner = paneType ? pluginRegistry.getDisabledPaneOwner(paneType, stateRef.current.config.disabledPlugins) : null;
283
+ if (owner) {
284
+ const what = resolveTickerForPane(stateRef.current, instanceId) ?? pluginRegistry.panes.get(paneType!)?.name ?? paneType!;
285
+ notifyPluginOff(pluginRegistry, new PluginOffError(owner, what), () => pluginRegistry.focusPane(paneId, layout));
286
+ return;
287
+ }
245
288
 
246
289
  focusVisiblePane(instanceId, layout);
247
290
  },
@@ -254,6 +297,12 @@ export function bindAppPanePluginRegistry({
254
297
  },
255
298
  navigateTicker: (rawSymbol, options) => {
256
299
  if (isDetachedWindow) return;
300
+ // Navigation lands in a research pane, which stays hidden while its plugin is off.
301
+ const researchOwner = pluginRegistry.getDisabledPaneOwner(TICKER_RESEARCH_PANE_ID, stateRef.current.config.disabledPlugins);
302
+ if (researchOwner) {
303
+ notifyPluginOff(pluginRegistry, new PluginOffError(researchOwner, rawSymbol), () => pluginRegistry.navigateTicker(rawSymbol, options));
304
+ return;
305
+ }
257
306
  const recordAttention = captureAttentionAction();
258
307
  recordFunctionOpen(usageFunctionForPane(pluginRegistry, TICKER_RESEARCH_PANE_ID));
259
308
  const sourcePaneId = options?.sourcePaneId ?? stateRef.current.focusedPaneId;
@@ -334,12 +383,15 @@ export function bindAppPanePluginRegistry({
334
383
  },
335
384
  });
336
385
 
386
+ // Layout commands (Tidy, Swap, Float, Close) act on what is on screen, as
387
+ // the shell's own menus do, and leave a switched-off plugin's panes alone.
388
+ const disabledPaneIds = () => collectDisabledPaneIds(pluginRegistry, stateRef.current.config.disabledPlugins);
337
389
  setLayoutManagerDispatch(dispatch, () => ({
338
- layout: state.config.layout,
390
+ layout: resolveShellVisibleLayout(state.config.layout, disabledPaneIds(), pluginRegistry.panes),
339
391
  termWidth: pluginRegistry.getTermSize().width,
340
392
  termHeight: pluginRegistry.getTermSize().height,
341
393
  focusedPaneId: state.focusedPaneId,
342
- }));
394
+ }), (edited) => restoreShellHiddenPanes(stateRef.current.config.layout, edited, disabledPaneIds(), pluginRegistry.panes));
343
395
 
344
396
  setExternalPlugins(externalPlugins);
345
397
 
@@ -457,7 +509,7 @@ export function bindAppPanePluginRegistry({
457
509
  });
458
510
  const commands = [...pluginRegistry.commands.entries()]
459
511
  .filter(([id]) => pluginRegistry.getCommandPluginId(id) === pluginId && !id.endsWith(":setup"))
460
- .map(([id, command]) => ({ id, label: command.label }));
512
+ .map(([id, command]) => ({ id, label: command.label, ...(command.shortcut ? { shortcut: command.shortcut } : {}) }));
461
513
  const capabilities = pluginRegistry.capabilities.manifests()
462
514
  .filter((manifest) => pluginRegistry.getCapabilityPluginId(manifest.id) === pluginId).length;
463
515
  const broker = [...pluginRegistry.brokers.keys()].some((type) => pluginRegistry.getBrokerPluginId(type) === pluginId);
@@ -466,19 +518,7 @@ export function bindAppPanePluginRegistry({
466
518
  notify: (notification) => {
467
519
  pluginRegistry.notify(notification);
468
520
  },
469
- setPluginEnabled: (pluginId, enabled) => {
470
- const current = stateRef.current.config;
471
- if (!enabled) {
472
- for (const paneId of pluginRegistry.getPluginPaneIds(pluginId)) pluginRegistry.hidePane(paneId);
473
- }
474
- dispatch({ type: "TOGGLE_PLUGIN", pluginId });
475
- const disabled = current.disabledPlugins ?? [];
476
- persistConfig({
477
- ...current,
478
- disabledPlugins: enabled
479
- ? disabled.filter((entry) => entry !== pluginId)
480
- : [...disabled, pluginId],
481
- });
482
- },
521
+ setPluginEnabled,
522
+ setPluginsEnabled,
483
523
  });
484
524
  }
@@ -5,6 +5,7 @@ import {
5
5
  findDockLeaf,
6
6
  } from "../../layout/pane-manager";
7
7
  import type { PluginRegistry } from "../../plugins/registry";
8
+ import { PluginOffError, notifyPluginOff } from "../../plugins/plugin-off";
8
9
  import {
9
10
  findTickerResearchFollower,
10
11
  listVisibleTickerSourcePanes,
@@ -110,6 +111,15 @@ export function useAppTickerInspectorRuntime({
110
111
  }, [pluginRegistry, resolveInspectorPane, state.config.layout]);
111
112
 
112
113
  const switchTickerResearchTab = useCallback((tabId: string, preferredPaneId?: string | null) => {
114
+ // The tab lives in a research pane, which stays hidden while its plugin is
115
+ // off, and the tab itself is hidden while its own plugin is.
116
+ const owner = pluginRegistry.getDisabledPaneOwner(TICKER_RESEARCH_PANE_ID, state.config.disabledPlugins)
117
+ ?? pluginRegistry.getDisabledTickerResearchTabOwner(tabId, state.config.disabledPlugins);
118
+ if (owner) {
119
+ const tabName = pluginRegistry.tickerResearchTabs.get(tabId)?.name ?? "this tab";
120
+ notifyPluginOff(pluginRegistry, new PluginOffError(owner, tabName), () => pluginRegistry.switchTab(tabId, preferredPaneId ?? undefined));
121
+ return;
122
+ }
113
123
  const targetPaneId = (() => {
114
124
  const target = preferredPaneId ? resolvePaneInstance(state.config.layout, preferredPaneId) : null;
115
125
  if (target?.paneId === TICKER_RESEARCH_PANE_ID) return target.instanceId;
@@ -131,7 +141,9 @@ export function useAppTickerInspectorRuntime({
131
141
  dispatch,
132
142
  ensureInspectorPane,
133
143
  persistLayout,
144
+ pluginRegistry,
134
145
  resolveTickerSourcePaneId,
146
+ state.config.disabledPlugins,
135
147
  state.config.layout,
136
148
  state.focusedPaneId,
137
149
  ]);