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
@@ -2,8 +2,9 @@
2
2
  // the browser catalog (catalog-browser.ts). Keep this file import-free so the
3
3
  // browser catalog can read it without pulling in native-only modules.
4
4
  //
5
- // Market Overview and Macro ship fewer panes on the web, so each catalog gives
6
- // them its own description rather than advertising panes it lacks.
5
+ // Screeners & Movers, Ownership & Insiders and Earnings ship fewer panes on
6
+ // the web, so each catalog gives them their own description rather than
7
+ // advertising panes they lack.
7
8
 
8
9
  export const applicationPluginMeta = {
9
10
  id: "application",
@@ -20,11 +21,44 @@ export const portfolioPluginMeta = {
20
21
  toggleable: true,
21
22
  };
22
23
 
23
- export const tickerResearchPluginMeta = {
24
- id: "ticker-research",
24
+ // Ticker Research's successors. Every module keeps the `ticker-research`
25
+ // state namespace: these four declare it, Alt Data and Quant default to it,
26
+ // and Credit & Bonds and Earnings compose theirs with an override.
27
+ // `ticker-research` itself now stands for all eight in disabledPlugins (see
28
+ // ownership.ts). The core keeps the name people know.
29
+
30
+ export const tickerCorePluginMeta = {
31
+ id: "ticker-core",
32
+ stateId: "ticker-research",
25
33
  name: "Ticker Research",
26
34
  version: "1.0.0",
27
- description: "Company research workspace: overview, charts, financials, filings, ownership, options, analyst research, and events.",
35
+ description: "Company overview, charts, financials, quotes and returns, analyst research and estimates, KPIs and segments, valuation, dividends, executives, and time and sales.",
36
+ toggleable: true,
37
+ };
38
+
39
+ export const optionsVolatilityPluginMeta = {
40
+ id: "options-volatility",
41
+ stateId: "ticker-research",
42
+ name: "Options & Volatility",
43
+ version: "1.0.0",
44
+ description: "Option chains and positioning, a pricing calculator, scenarios, the volatility surface, and realized and implied volatility history.",
45
+ toggleable: true,
46
+ };
47
+
48
+ export const ownershipPluginMeta = {
49
+ id: "ownership",
50
+ stateId: "ticker-research",
51
+ name: "Ownership & Insiders",
52
+ version: "1.0.0",
53
+ toggleable: true,
54
+ };
55
+
56
+ export const filingsPluginMeta = {
57
+ id: "filings",
58
+ stateId: "ticker-research",
59
+ name: "Filings & Events",
60
+ version: "1.0.0",
61
+ description: "SEC filings, risk factors, 8-K events, catalysts and litigation, and M&A and distress.",
28
62
  toggleable: true,
29
63
  };
30
64
 
@@ -36,16 +70,92 @@ export const newsPluginMeta = {
36
70
  toggleable: true,
37
71
  };
38
72
 
39
- export const marketOverviewPluginMeta = {
40
- id: "market-overview",
41
- name: "Market Overview",
73
+ // Market Overview's successors. Its modules keep the `market-overview` state
74
+ // namespace; Alt Data and Quant default to `ticker-research`, where their
75
+ // Ticker Research modules keep theirs, and compose their Market Overview
76
+ // modules with a `market-overview` override. `market-overview` itself
77
+ // now stands for all six in disabledPlugins (see ownership.ts).
78
+
79
+ export const globalMarketsPluginMeta = {
80
+ id: "global-markets",
81
+ stateId: "market-overview",
82
+ name: "Global Markets",
83
+ version: "1.0.0",
84
+ description: "World indices, the world map with chokepoint transits, sector and thematic performance, the FX matrix, and relative rotation.",
85
+ toggleable: true,
86
+ };
87
+
88
+ export const screenersPluginMeta = {
89
+ id: "screeners",
90
+ stateId: "market-overview",
91
+ name: "Screeners & Movers",
92
+ version: "1.0.0",
93
+ toggleable: true,
94
+ };
95
+
96
+ export const futuresCommoditiesPluginMeta = {
97
+ id: "futures-commodities",
98
+ stateId: "market-overview",
99
+ name: "Futures & Commodities",
100
+ version: "1.0.0",
101
+ description: "Futures board and curves, CFTC positioning, and EIA petroleum and gas storage.",
102
+ toggleable: true,
103
+ };
104
+
105
+ export const cryptoPluginMeta = {
106
+ id: "crypto",
107
+ stateId: "market-overview",
108
+ name: "Crypto & Perps",
109
+ version: "1.0.0",
110
+ description: "Top crypto assets by market cap, and perpetual funding, open interest and premiums.",
111
+ toggleable: true,
112
+ };
113
+
114
+ export const altDataPluginMeta = {
115
+ id: "alt-data",
116
+ stateId: "ticker-research",
117
+ name: "Supply Chain & Alt Data",
118
+ version: "1.0.0",
119
+ description: "Supply chains and exposure, government awards, hiring, apps and job postings, social mentions, research attention, GPU prices, and power queues.",
120
+ toggleable: true,
121
+ };
122
+
123
+ export const quantPluginMeta = {
124
+ id: "quant",
125
+ stateId: "ticker-research",
126
+ name: "Quant",
127
+ version: "1.0.0",
128
+ description: "Correlation matrix and relationship graphs, backtests, seasonality, and moves on macro release days.",
129
+ toggleable: true,
130
+ };
131
+
132
+ // Macro's successors keep its state namespace, so the pane state, settings and
133
+ // caches saved while they were one plugin stay theirs; the Ticker Research
134
+ // modules Credit & Bonds and Earnings hold keep `ticker-research`. `macro` itself now
135
+ // stands for all three in disabledPlugins (see ownership.ts).
136
+
137
+ export const ratesMacroPluginMeta = {
138
+ id: "rates-macro",
139
+ stateId: "macro",
140
+ name: "Rates & Macro",
141
+ version: "1.0.0",
142
+ description: "Economic calendar and statistics, consumer prices, yield curve, rate path, money markets, central bank rates, volatility, and market valuation.",
143
+ toggleable: true,
144
+ };
145
+
146
+ export const creditPluginMeta = {
147
+ id: "credit",
148
+ stateId: "macro",
149
+ name: "Credit & Bonds",
42
150
  version: "1.0.0",
151
+ description: "Single-name, index and sovereign CDS, credit spreads, Treasury auctions, a bond calculator, credit documents and covenants, and debt maturities.",
43
152
  toggleable: true,
44
153
  };
45
154
 
46
- export const macroPluginMeta = {
47
- id: "macro",
48
- name: "Macro",
155
+ export const earningsPluginMeta = {
156
+ id: "earnings",
157
+ stateId: "macro",
158
+ name: "Earnings",
49
159
  version: "1.0.0",
50
160
  toggleable: true,
51
161
  };
@@ -1145,7 +1145,7 @@ export function ChartComposerPane({ paneId, focused, width, height }: PaneProps)
1145
1145
  }
1146
1146
  }, [follows, ownedIds, savedIds, setSpec, spec, stored, target, unlinkedFrom, updateSettings]);
1147
1147
  if (follows && !target && ownedIds.length > 0) {
1148
- return <EmptyState title={error ?? "No ticker selected."} />;
1148
+ return <EmptyState title={error ?? "Select a ticker."} />;
1149
1149
  }
1150
1150
  return (
1151
1151
  <ChartComposerSurface
@@ -3,9 +3,10 @@ import type { ChatChannel, ChatChannelState } from "../../../api-client";
3
3
  import type { AppConfig } from "../../../types/config";
4
4
  import { t } from "../../../i18n";
5
5
  import { chatController } from "./controller";
6
- import { formatChannelLabel } from "./channel-labels";
7
6
  import { describeConversationStartError, knownConversationRefusal } from "./direct-messages";
8
7
  import { buildChatUserByUsername } from "./content/user-map";
8
+ import { chatConversationRow, chatNewDmRow, openChatConversation, openChatNewDm } from "./command-bar";
9
+ import { buildChatConversations, selectChatConversations } from "./conversations";
9
10
 
10
11
  export const DEFAULT_CHAT_CHANNEL_ID = "everyone";
11
12
  export const LAST_VISITED_CHAT_CHANNEL_KEY = "lastChatChannelId";
@@ -42,20 +43,6 @@ export function getPreferredChatOpenChannelId(
42
43
  return normalizeChannelId(unreadConversation?.channelId ?? unreadStates[0]?.channelId ?? getLastVisitedChatChannelId(config));
43
44
  }
44
45
 
45
- function conversationDetail(channel: ChatChannel): string {
46
- if (channel.kind === "direct") {
47
- const displayName = channel.dmUser?.displayName?.trim();
48
- const username = channel.dmUser?.username?.trim();
49
- return [displayName && username ? displayName : null, t("Direct message")].filter(Boolean).join(" - ");
50
- }
51
- const members = (channel.members ?? [])
52
- .map((member) => member.username ? `@${member.username}` : member.displayName)
53
- .filter(Boolean)
54
- .slice(0, 4);
55
- const suffix = (channel.members?.length ?? 0) > members.length ? ` +${(channel.members?.length ?? 0) - members.length}` : "";
56
- return members.length > 0 ? `${t("Group chat")} - ${members.join(", ")}${suffix}` : t("Group chat");
57
- }
58
-
59
46
  export function parseDmUsernames(value: string): string[] {
60
47
  const usernames = new Set<string>();
61
48
  for (const rawPart of value.split(/[\s,]+/)) {
@@ -106,18 +93,40 @@ export async function openDmTargetFromCommand(ctx: GloomPluginContext, usernames
106
93
  } catch (error) {
107
94
  throw new Error(describeConversationStartError(error, usernames, userByUsername));
108
95
  }
109
- openChatChannelFromCommand(ctx, channel.id);
96
+ openChatConversation(ctx, channel.id);
110
97
  }
111
98
 
99
+ /**
100
+ * `DM` in the command bar. Bare, it lists your DMs and groups, unread first,
101
+ * then the latest, and ends with New DM, which opens the Chat pane's dialog.
102
+ * With names after it, the conversations those names find come first, then
103
+ * the row that starts or opens a DM with exactly what was typed.
104
+ */
112
105
  export function buildDmCommandResults(ctx: GloomPluginContext, arg: string): CommandResultDef[] {
106
+ const list = chatController.listConversations();
107
+ const conversations = list
108
+ ? buildChatConversations(list.states, list.userId)
109
+ .filter((conversation) => conversation.kind === "direct" || conversation.kind === "group")
110
+ : [];
111
+ const open = (channelId: string) => openChatConversation(ctx, channelId);
112
+ const category = "Chat";
113
113
  const trimmed = arg.trim();
114
114
  if (trimmed) {
115
115
  const usernames = parseDmUsernames(trimmed);
116
116
  const valid = hasOnlyDmUsernameArgs(trimmed) && usernames.length > 0;
117
+ const found = selectChatConversations(conversations, { kind: "search", text: trimmed });
118
+ const rows: CommandResultDef[] = found.map((conversation) => ({
119
+ ...chatConversationRow(conversation, open),
120
+ category,
121
+ }));
122
+ // The DM you already share is the row above; a second one would open it too.
123
+ const alreadyShared = usernames.length === 1
124
+ && found.some((conversation) => conversation.kind === "direct" && conversation.username === usernames[0]);
125
+ if (alreadyShared) return rows;
117
126
  const label = usernames.length <= 1
118
127
  ? `DM ${usernames[0] ? `@${usernames[0]}` : trimmed}`
119
128
  : `Group ${usernames.map((username) => `@${username}`).join(", ")}`;
120
- return [{
129
+ return [...rows, {
121
130
  id: `start:${valid ? usernames.join(",") : trimmed}`,
122
131
  label,
123
132
  detail: valid
@@ -125,40 +134,29 @@ export function buildDmCommandResults(ctx: GloomPluginContext, arg: string): Com
125
134
  ? t("Start or open direct message")
126
135
  : t("Start group chat")
127
136
  : t("Use @username, or multiple usernames for a group chat"),
128
- category: t("Chat"),
129
- right: "DM",
137
+ category,
138
+ // No tag: it starts a conversation, so it must not read as one.
139
+ right: "",
130
140
  disabled: !valid,
131
141
  execute: () => openDmTargetFromCommand(ctx, usernames),
132
142
  }];
133
143
  }
134
144
 
135
- const conversations = chatController.getSnapshot().channels
136
- .filter((channel) => channel.kind === "direct" || channel.kind === "group");
137
- if (conversations.length === 0) {
145
+ if (!list) {
138
146
  return [{
139
147
  id: "empty",
140
148
  label: t("No DMs yet"),
141
149
  detail: t("Type DM @username to start one"),
142
- category: t("Chat"),
150
+ category,
143
151
  right: "DM",
144
152
  disabled: true,
145
153
  execute: () => {},
146
154
  }];
147
155
  }
148
156
 
149
- return conversations.map((channel) => ({
150
- id: `channel:${channel.id}`,
151
- label: formatChannelLabel(channel, channel.id),
152
- detail: conversationDetail(channel),
153
- category: t("Conversations"),
154
- right: channel.kind === "group" ? t("Group") : t("DM"),
155
- keywords: [
156
- channel.name,
157
- channel.dmUser?.username ?? "",
158
- ...(channel.members ?? []).map((member) => member.username ?? member.displayName),
159
- ],
160
- execute: () => openChatChannelFromCommand(ctx, channel.id),
161
- }));
157
+ const listed = selectChatConversations(conversations, { kind: "listing" })
158
+ .map((conversation) => ({ ...chatConversationRow(conversation, open), category }));
159
+ return [...listed, { ...chatNewDmRow(() => openChatNewDm(ctx)), category }];
162
160
  }
163
161
 
164
162
  /**
@@ -0,0 +1,138 @@
1
+ import { t, tf } from "../../../i18n";
2
+ import type {
3
+ CommandBarResultDef,
4
+ CommandBarSearchProvider,
5
+ GloomPluginContext,
6
+ } from "../../../types/plugin";
7
+ import { chatController } from "./controller";
8
+ import {
9
+ buildChatConversations,
10
+ parseChatConversationQuery,
11
+ selectChatConversations,
12
+ type ChatConversation,
13
+ type ChatConversationKind,
14
+ type ChatConversationList,
15
+ } from "./conversations";
16
+ import { requestChatPaneAction } from "./pane-requests";
17
+
18
+ const CHAT_PANE_TEMPLATE_ID = "new-chat-pane";
19
+ /**
20
+ * Pane template value: show the conversation in a Chat pane that is already
21
+ * open (switching it over) rather than in a pane of its own.
22
+ */
23
+ export const CHAT_REUSE_PANE_VALUE = "reusePane";
24
+ const CHAT_SEARCH_PROVIDER_ID = "chat:conversations";
25
+
26
+ type OpenChatPane = Pick<GloomPluginContext, "createPaneFromTemplate">;
27
+
28
+ /**
29
+ * Shows a conversation with its composer ready: in the open Chat pane, which
30
+ * switches to it, or in a new one when none is open.
31
+ */
32
+ export function openChatConversation(ctx: OpenChatPane, channelId: string): void {
33
+ requestChatPaneAction({ action: "compose", channelId });
34
+ ctx.createPaneFromTemplate(CHAT_PANE_TEMPLATE_ID, {
35
+ arg: channelId,
36
+ values: { [CHAT_REUSE_PANE_VALUE]: "1" },
37
+ });
38
+ }
39
+
40
+ /** The Chat pane's own New DM dialog, in the open Chat pane or a new one. */
41
+ export function openChatNewDm(ctx: OpenChatPane): void {
42
+ requestChatPaneAction({ action: "new-dm" });
43
+ ctx.createPaneFromTemplate(CHAT_PANE_TEMPLATE_ID, { values: { [CHAT_REUSE_PANE_VALUE]: "1" } });
44
+ }
45
+
46
+ const BADGES: Record<ChatConversationKind, string> = {
47
+ channel: "CHAT",
48
+ team: "TEAM",
49
+ direct: "DM",
50
+ group: "GROUP",
51
+ };
52
+
53
+ const DETAILS: Record<ChatConversationKind, string> = {
54
+ channel: "Channel",
55
+ team: "Team channel",
56
+ direct: "Direct message",
57
+ group: "Group chat",
58
+ };
59
+
60
+ function unreadLabel(count: number): string {
61
+ return tf("{count} unread", { count: count > 99 ? "99+" : String(count) });
62
+ }
63
+
64
+ /**
65
+ * One conversation as a command-bar row: a tag that tells channels from DMs,
66
+ * the name the Chat pane shows, and the unread count on the right. `right` is
67
+ * empty rather than unset so a command's shortcut never stands in for it.
68
+ */
69
+ export function chatConversationRow(
70
+ conversation: ChatConversation,
71
+ open: (channelId: string) => void,
72
+ ): CommandBarResultDef {
73
+ return {
74
+ id: `conversation:${conversation.channelId}`,
75
+ label: conversation.title,
76
+ ...(conversation.name ? { name: conversation.name } : {}),
77
+ badge: BADGES[conversation.kind],
78
+ detail: t(DETAILS[conversation.kind]),
79
+ right: conversation.unreadCount > 0 ? unreadLabel(conversation.unreadCount) : "",
80
+ keywords: [...conversation.words, "chat", "messages"],
81
+ execute: () => open(conversation.channelId),
82
+ };
83
+ }
84
+
85
+ export function chatNewDmRow(open: () => void): CommandBarResultDef {
86
+ return {
87
+ id: "new-dm",
88
+ label: t("New DM"),
89
+ detail: t("Start a direct or group chat"),
90
+ right: "",
91
+ keywords: ["new", "dm", "direct", "message", "group", "chat"],
92
+ execute: open,
93
+ };
94
+ }
95
+
96
+ interface ChatSearchSource {
97
+ listConversations(): ChatConversationList | null;
98
+ }
99
+
100
+ /**
101
+ * The conversations that answer the command bar's text, from the chat state
102
+ * the Chat pane already keeps: nothing is asked of the server. Signed out or
103
+ * unverified there is nothing, and the Chat pane command is all there is.
104
+ */
105
+ export function buildChatSearchRows(
106
+ ctx: OpenChatPane,
107
+ query: string,
108
+ source: ChatSearchSource = chatController,
109
+ ): CommandBarResultDef[] {
110
+ const list = source.listConversations();
111
+ if (!list) return [];
112
+ const parsed = parseChatConversationQuery(query);
113
+ const conversations = selectChatConversations(buildChatConversations(list.states, list.userId), parsed);
114
+ const rows = conversations.map((conversation) => chatConversationRow(conversation, (channelId) => {
115
+ openChatConversation(ctx, channelId);
116
+ }));
117
+ return parsed.kind === "listing" ? [...rows, chatNewDmRow(() => openChatNewDm(ctx))] : rows;
118
+ }
119
+
120
+ /**
121
+ * Channels and DMs in the command bar: typing part of a channel or a person
122
+ * finds the conversation, "chat" and "messages" list them, and the empty bar
123
+ * shows the unread and latest ones. Answered from memory on every keystroke.
124
+ */
125
+ export function createChatSearchProvider(
126
+ ctx: OpenChatPane,
127
+ source: ChatSearchSource = chatController,
128
+ ): CommandBarSearchProvider {
129
+ const match = (query: string) => buildChatSearchRows(ctx, query, source);
130
+ return {
131
+ id: CHAT_SEARCH_PROVIDER_ID,
132
+ category: "Chat",
133
+ minQueryLength: 0,
134
+ shortcuts: ["CHAT"],
135
+ match,
136
+ provide: async (query) => match(query),
137
+ };
138
+ }
@@ -1,13 +1,33 @@
1
1
  export type ChatComposerCommand =
2
2
  | { kind: "direct"; username: string; draft: string }
3
- | { kind: "group"; usernames: string[]; name: string | undefined };
3
+ | { kind: "group"; usernames: string[]; name: string | undefined }
4
+ | DiscordComposerCommand;
5
+
6
+ /** `usage` is anything after /discord that is not a subcommand: it answers with the usage instead of going to the channel as text. */
7
+ export type DiscordComposerCommand =
8
+ | { kind: "discord"; action: "connect" | "unlink" | "usage" }
9
+ | { kind: "discord"; action: "mirror"; enabled: boolean };
4
10
 
5
11
  const USERNAME_PATTERN = "[A-Za-z][A-Za-z0-9_]{2,29}";
6
12
  const DIRECT_COMMAND_RE = new RegExp(`^/dm\\s+@?(${USERNAME_PATTERN})(?:\\s+([\\s\\S]+))?$`, "i");
7
13
  const GROUP_COMMAND_RE = /^\/group\s+(.+)$/i;
14
+ const DISCORD_COMMAND_RE = /^\/discord(?:\s+([\s\S]*))?$/i;
8
15
  const GROUP_USERNAME_RE = new RegExp(`@(${USERNAME_PATTERN})`, "g");
9
16
 
17
+ function parseDiscordArguments(rest: string): DiscordComposerCommand {
18
+ const [subcommand, value, ...extra] = rest.toLowerCase().split(/\s+/).filter(Boolean);
19
+ if (!subcommand) return { kind: "discord", action: "connect" };
20
+ if (subcommand === "unlink" && value === undefined) return { kind: "discord", action: "unlink" };
21
+ if (subcommand === "mirror" && extra.length === 0 && (value === "on" || value === "off")) {
22
+ return { kind: "discord", action: "mirror", enabled: value === "on" };
23
+ }
24
+ return { kind: "discord", action: "usage" };
25
+ }
26
+
10
27
  export function parseChatComposerCommand(content: string): ChatComposerCommand | null {
28
+ const discordCommand = content.match(DISCORD_COMMAND_RE);
29
+ if (discordCommand) return parseDiscordArguments(discordCommand[1] ?? "");
30
+
11
31
  const directCommand = content.match(DIRECT_COMMAND_RE);
12
32
  if (directCommand) {
13
33
  return {
@@ -5,7 +5,7 @@ import {
5
5
  COMPOSER_ACTION_WIDTH,
6
6
  formatInlinePreview,
7
7
  } from "../layout";
8
- import { parseChatComposerCommand } from "../composer-commands";
8
+ import { parseChatComposerCommand, type DiscordComposerCommand } from "../composer-commands";
9
9
  import { t, tf } from "../../../../i18n";
10
10
  import type { ChatContentController } from "./types";
11
11
  import { getComposerCursorOffset, moveComposerCursorToOffset } from "./composer-cursor";
@@ -45,6 +45,7 @@ export function useChatComposerRuntime({
45
45
  draftAttachmentCount = 0,
46
46
  onPastedImagePaths,
47
47
  onConversationStartError,
48
+ onDiscordCommand,
48
49
  }: {
49
50
  applyingExternalDraftRef: MutableRef<boolean>;
50
51
  blurInput: () => void;
@@ -77,6 +78,8 @@ export function useChatComposerRuntime({
77
78
  onPastedImagePaths?: (paths: string[], pasted: string) => void;
78
79
  /** A `/dm` or `/group` command the server refused, with the names it asked for. */
79
80
  onConversationStartError?: (error: unknown, usernames: string[]) => void;
81
+ /** Runs a /discord command. Without it the command is sent as text. */
82
+ onDiscordCommand?: (command: DiscordComposerCommand) => void;
80
83
  }) {
81
84
  const replyToRef = useRef(replyTo);
82
85
  const draftAttachmentCountRef = useRef(draftAttachmentCount);
@@ -262,6 +265,11 @@ export function useChatComposerRuntime({
262
265
  }).catch((error: unknown) => onConversationStartError?.(error, [composerCommand.username]));
263
266
  return;
264
267
  }
268
+ if (composerCommand?.kind === "discord" && onDiscordCommand) {
269
+ replaceComposerDraft("");
270
+ onDiscordCommand(composerCommand);
271
+ return;
272
+ }
265
273
  if (composerCommand?.kind === "group") {
266
274
  void controller.openGroupChannel({
267
275
  usernames: composerCommand.usernames,
@@ -293,8 +301,10 @@ export function useChatComposerRuntime({
293
301
  inputValueRef,
294
302
  onChannelChange,
295
303
  onConversationStartError,
304
+ onDiscordCommand,
296
305
  expandDirectSection,
297
306
  persistDraft,
307
+ replaceComposerDraft,
298
308
  setEditingMessage,
299
309
  setFollowMessages,
300
310
  setSelectedIdx,
@@ -6,6 +6,7 @@ import type { InlineTickerCatalogEntry } from "../../../../state/hooks/inline-ti
6
6
  import type { ContextMenuItem } from "../../../../types/context-menu";
7
7
  import { useRendererHost, useUiCapabilities } from "../../../../ui";
8
8
  import { useOptionalDialog, type PromptContext } from "../../../../ui/dialog";
9
+ import { chatAuthorName, isDiscordGhost } from "../ghost-user";
9
10
  import { chatMessageOpenTargets, type ChatOpenTarget } from "./open-targets";
10
11
  import { openChatImageViewer } from "../attachments/desktop";
11
12
 
@@ -105,7 +106,7 @@ export function useChatFooter({
105
106
  attachments: selectedMessage.attachments,
106
107
  index: target.index,
107
108
  caption: selectedMessage.content,
108
- author: selectedMessage.user.username ?? "anon",
109
+ author: chatAuthorName(selectedMessage.user),
109
110
  });
110
111
  return;
111
112
  }
@@ -135,7 +136,8 @@ export function useChatFooter({
135
136
  if (target) openTarget(target);
136
137
  }, [dialog, openTarget, openTargets]);
137
138
 
138
- const author = selectedMessage?.user ?? null;
139
+ // A Discord ghost has no card to show.
140
+ const author = selectedMessage && !isDiscordGhost(selectedMessage.user) ? selectedMessage.user : null;
139
141
  // A card opened from the keyboard follows the selection: moving on closes it.
140
142
  const keyboardProfileRef = useRef(false);
141
143
  const toggleAuthorProfile = useCallback(() => {
@@ -215,7 +217,7 @@ export function useChatFooter({
215
217
  else if (canSend) hints.push({ id: "reply", key: "Enter", label: "reply", title: "Reply", onPress: () => latest.current.beginReplyTo(latest.current.selectedIdx, { deferFocus: true }) });
216
218
  if (canEdit) hints.push({ id: "edit", key: "e", label: "dit", title: "Edit Message", onPress: () => { latest.current.beginEditMessage(latest.current.selectedIdx, { deferFocus: true }); } });
217
219
  if (openTargets.length > 0) hints.push({ id: "open", key: "o", label: "pen", title: openTargetTitle, onPress: () => { void latest.current.openSelectedTargets(); } });
218
- // Every author has a card, if only their name and a way to write to them.
220
+ // Every author but a Discord ghost has a card, if only their name and a way to write to them.
219
221
  if (author) hints.push({ id: "profile", key: "p", label: "rofile", title: "Show Profile", onPress: () => latest.current.toggleAuthorProfile() });
220
222
  } else if (!composing && canSend) {
221
223
  hints.push({ id: "compose", key: "i", label: " compose", title: "Compose", onPress: () => queueMicrotask(() => latest.current.focusComposer()) });
@@ -1,4 +1,4 @@
1
- import { Box, Text, useContextMenu, useUiCapabilities } from "../../../../ui";
1
+ import { Box, Text, useContextMenu, useRendererHost, useUiCapabilities } from "../../../../ui";
2
2
  import { useState, useEffect, useRef, useCallback, useMemo, useSyncExternalStore } from "react";
3
3
  import { PageStackView } from "../../../../components/ui";
4
4
  import { type ScrollBoxRenderable, type TextareaRenderable } from "../../../../ui";
@@ -33,12 +33,15 @@ import {
33
33
  } from "./layout-metrics";
34
34
  import { buildChatUserByUsername } from "./user-map";
35
35
  import { useChatComposerRuntime } from "./composer-runtime";
36
+ import { runDiscordCommand } from "../discord-command";
37
+ import type { DiscordComposerCommand } from "../composer-commands";
36
38
  import { useChatMessageSelection } from "./selection-runtime";
37
39
  import type { ChatAttachment, ChatMessage, ChatUserSummary } from "../../../../api-client";
38
40
  import { DesktopChatDropOverlay, DesktopChatDropTarget } from "../attachments/desktop";
39
41
  import { uploadFromTransferFile, type TransferFile } from "../attachments/transfer";
40
42
  import { readChatImageFiles } from "../attachments/files";
41
43
  import { NewDmDialog } from "./new-dm-dialog";
44
+ import { takeChatPaneRequest, usePendingChatPaneRequest } from "../pane-requests";
42
45
  import {
43
46
  describeConversationStartError,
44
47
  directMessageAction,
@@ -93,6 +96,7 @@ export function ChatContent({
93
96
  channelIdRef.current = channelId;
94
97
  const initialSnapshot = controller.getSnapshot(channelId);
95
98
  const { nativePaneChrome } = useUiCapabilities();
99
+ const rendererHost = useRendererHost();
96
100
  const [inputFocused, setInputFocused] = useState(false);
97
101
  const [selectedIdx, setSelectedIdx] = useState(-1);
98
102
  const [hoveredIdx, setHoveredIdx] = useState<number | null>(null);
@@ -461,6 +465,10 @@ export function ChatContent({
461
465
  if (controller.attachToChannel(channelIdRef.current, files.map(uploadFromTransferFile)) > 0) focusInput();
462
466
  }, [canAttach, controller, focusInput, imagesAvailable, notify]);
463
467
 
468
+ const handleDiscordCommand = useCallback((command: DiscordComposerCommand) => {
469
+ void runDiscordCommand(command, { openUrl: (url) => rendererHost.openExternal(url) }).then(notify);
470
+ }, [notify, rendererHost]);
471
+
464
472
  // The terminal: a pasted or dropped path to an image file attaches the file
465
473
  // and leaves the composer. A path that is no file stays as typed.
466
474
  const handlePastedImagePaths = useCallback((paths: string[], pasted: string) => {
@@ -543,6 +551,7 @@ export function ChatContent({
543
551
  draftAttachmentCount: draftAttachments.length,
544
552
  onPastedImagePaths: !nativePaneChrome && canAttach ? handlePastedImagePaths : undefined,
545
553
  onConversationStartError: reportConversationStartError,
554
+ onDiscordCommand: handleDiscordCommand,
546
555
  });
547
556
  replaceComposerDraftRef.current = replaceComposerDraft;
548
557
 
@@ -558,6 +567,17 @@ export function ChatContent({
558
567
  if (canSend) focusComposer();
559
568
  }, [canSend, closeNewDmDialog, closeProfilePopover, expandDirectSection, focusComposer, selectSidebarChannel, setSidebarFocused]);
560
569
 
570
+ // Asked for from outside the pane (the command bar): once this pane has the
571
+ // keyboard on that conversation, the composer takes it, or New DM opens.
572
+ const pendingPaneRequest = usePendingChatPaneRequest();
573
+ useEffect(() => {
574
+ if (!pendingPaneRequest || !focused || commandBarOpen) return;
575
+ const request = takeChatPaneRequest(channelId);
576
+ if (!request || !canSend) return;
577
+ if (request.action === "new-dm") openNewDmDialog();
578
+ else showConversation(channelId);
579
+ }, [canSend, channelId, commandBarOpen, focused, openNewDmDialog, pendingPaneRequest, showConversation]);
580
+
561
581
  const openConversationFromDialog = useCallback(async (usernames: string[]) => {
562
582
  const channel = usernames.length === 1
563
583
  ? await controller.openDirectChannel({ username: usernames[0] })
@@ -1,4 +1,5 @@
1
1
  import type { ChatChannel, ChatMessage } from "../../../../api-client";
2
+ import { isDiscordGhost } from "../ghost-user";
2
3
 
3
4
  export interface ChatMentionSuggestion {
4
5
  username: string;
@@ -49,7 +50,7 @@ export function buildRecentMentionSuggestions({
49
50
  for (let index = messages.length - 1; index >= 0; index -= 1) {
50
51
  const user = messages[index]?.user;
51
52
  const username = normalizeUsername(user?.username);
52
- if (!user || !username || user.id === currentUserId) continue;
53
+ if (!user || !username || user.id === currentUserId || isDiscordGhost(user)) continue;
53
54
  const key = username.toLowerCase();
54
55
  if (seen.has(key)) continue;
55
56
  seen.add(key);
@@ -1,5 +1,7 @@
1
1
  import type { ChatChannel, ChatMessage, ChatUserSummary } from "../../../../api-client";
2
+ import { isDiscordGhost } from "../ghost-user";
2
3
 
4
+ /** Who a name can open a profile for or start a conversation with: Discord ghosts are left out. */
3
5
  export function buildChatUserByUsername(
4
6
  channels: ChatChannel[],
5
7
  messages: ChatMessage[],
@@ -7,7 +9,7 @@ export function buildChatUserByUsername(
7
9
  const map = new Map<string, ChatUserSummary>();
8
10
  for (const message of messages) {
9
11
  const username = message.user.username?.toLowerCase();
10
- if (username) map.set(username, message.user);
12
+ if (username && !isDiscordGhost(message.user)) map.set(username, message.user);
11
13
  }
12
14
  for (const channel of channels) {
13
15
  for (const member of channel.members ?? []) {