gloomberb 0.18.4 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (600) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/package.json +1 -1
  4. package/patches/electrobun@1.18.1.patch +169 -5
  5. package/src/account/auth-model.ts +7 -3
  6. package/src/api-client/auth.ts +9 -4
  7. package/src/api-client/catalysts.ts +2 -1
  8. package/src/api-client/cot.ts +6 -2
  9. package/src/api-client/credit-documents.ts +6 -0
  10. package/src/api-client/data.ts +31 -6
  11. package/src/api-client/equity-screener.ts +24 -0
  12. package/src/api-client/fund-facts.ts +45 -4
  13. package/src/api-client/index.ts +171 -11
  14. package/src/api-client/live-scan.ts +91 -0
  15. package/src/api-client/market-discovery.ts +10 -0
  16. package/src/api-client/market-movers.ts +7 -0
  17. package/src/api-client/members.ts +13 -1
  18. package/src/api-client/perps.ts +43 -0
  19. package/src/api-client/request.ts +222 -41
  20. package/src/api-client/research-activity.ts +6 -12
  21. package/src/api-client/types.ts +74 -0
  22. package/src/app/keybindings/index.ts +3 -0
  23. package/src/app/keybindings/labels.ts +18 -1
  24. package/src/app/keybindings/resolve.ts +14 -0
  25. package/src/app/pane-runtime/plugin-bindings.ts +9 -5
  26. package/src/app/runtime/broker-import.ts +42 -27
  27. package/src/app/runtime/plugin-bindings.ts +73 -56
  28. package/src/architecture/core-builtin-allowlist.json +1 -1
  29. package/src/architecture/pane-tabs-allowlist.json +3 -0
  30. package/src/architecture/ttl-caches-allowlist.json +19 -0
  31. package/src/brokers/sync-broker-instance.ts +2 -8
  32. package/src/capabilities/chart-series.ts +1 -1
  33. package/src/cli/commands/crypto-hints.ts +40 -24
  34. package/src/cli/commands/fred-series.ts +5 -0
  35. package/src/cli/commands/keybindings.ts +23 -0
  36. package/src/cli/commands/market.ts +138 -46
  37. package/src/cli/commands/options-chain.ts +107 -10
  38. package/src/cli/commands/options-leaps.ts +9 -3
  39. package/src/cli/commands/overview.ts +176 -39
  40. package/src/cli/commands/search.ts +39 -3
  41. package/src/cli/commands/themes.ts +11 -2
  42. package/src/cli/commands/ticker.ts +57 -35
  43. package/src/cli/company-name-hint.ts +41 -0
  44. package/src/cli/earnings-calendar.ts +1 -1
  45. package/src/cli/errors.ts +10 -2
  46. package/src/cli/export-source.ts +29 -1
  47. package/src/cli/glossary.ts +73 -1
  48. package/src/cli/history-rows.ts +87 -16
  49. package/src/cli/index.ts +2 -0
  50. package/src/cli/known-gaps.ts +5 -2
  51. package/src/cli/listing-arg.ts +195 -56
  52. package/src/cli/output-file.ts +68 -0
  53. package/src/cli/pane-functions/capabilities.ts +3 -0
  54. package/src/cli/pane-functions/catalog.ts +80 -7
  55. package/src/cli/pane-functions/dom.ts +13 -3
  56. package/src/cli/pane-functions/freshness.ts +111 -16
  57. package/src/cli/pane-functions/function-help.ts +23 -13
  58. package/src/cli/pane-functions/headless.ts +56 -12
  59. package/src/cli/pane-functions/index.ts +58 -16
  60. package/src/cli/pane-functions/market-session.ts +81 -9
  61. package/src/cli/pane-functions/options.ts +19 -1
  62. package/src/cli/pane-functions/report.ts +5 -1
  63. package/src/cli/pane-functions/resolver.ts +35 -8
  64. package/src/cli/pane-functions/screenshot-assessment.ts +6 -4
  65. package/src/cli/pane-functions/screenshot-types.ts +5 -0
  66. package/src/cli/quote-answer.ts +30 -13
  67. package/src/cli/report-tables.ts +18 -7
  68. package/src/cli/restore-plugins.ts +13 -1
  69. package/src/cli/result.ts +57 -5
  70. package/src/cli/ticker-resolution.ts +3 -2
  71. package/src/components/chart/composite/composite-chart.tsx +26 -4
  72. package/src/components/chart/composite/format.ts +27 -0
  73. package/src/components/chart/composite/interactions.ts +6 -0
  74. package/src/components/chart/composite/legend.tsx +45 -7
  75. package/src/components/chart/composite/levels.ts +2 -0
  76. package/src/components/chart/composite/panel-gestures.ts +33 -0
  77. package/src/components/chart/composite/panel-series.ts +1 -0
  78. package/src/components/chart/composite/panel-surface.tsx +61 -5
  79. package/src/components/chart/composite/range-statistics.ts +62 -0
  80. package/src/components/chart/composite/rasterizer.ts +24 -13
  81. package/src/components/chart/composite/scene.ts +46 -7
  82. package/src/components/chart/composite/series-axis-tags.tsx +16 -9
  83. package/src/components/chart/composite/series-cues.ts +14 -0
  84. package/src/components/chart/composite/text-renderer.ts +9 -2
  85. package/src/components/chart/composite/tool-catalog.ts +36 -0
  86. package/src/components/chart/composite/tools.ts +203 -44
  87. package/src/components/chart/composite/types.ts +4 -0
  88. package/src/components/chart/composite/use-composite-chart-keyboard.ts +11 -4
  89. package/src/components/command-bar/assist/inventory.ts +2 -1
  90. package/src/components/command-bar/commands/direct/index.ts +28 -13
  91. package/src/components/command-bar/pane-templates/items.ts +3 -2
  92. package/src/components/command-bar/panel/prompt-binding.ts +0 -8
  93. package/src/components/command-bar/workflow/alert.ts +1 -1
  94. package/src/components/command-bar/workflow/collection-actions.ts +3 -3
  95. package/src/components/layout/header.tsx +26 -40
  96. package/src/components/layout/pane/footer/index.tsx +1 -0
  97. package/src/components/layout/pane/footer/model.ts +27 -0
  98. package/src/components/onboarding/onboarding-frame.tsx +1 -1
  99. package/src/components/onboarding/onboarding-wizard.tsx +4 -1
  100. package/src/components/onboarding/portfolio-step/positions-panel.tsx +14 -9
  101. package/src/components/onboarding/wizard-broker-sync.ts +0 -1
  102. package/src/components/onboarding/wizard-positions.ts +3 -3
  103. package/src/components/ticker/list-table-view.tsx +4 -1
  104. package/src/components/ui/choice-dialog.tsx +23 -18
  105. package/src/components/ui/choice-groups.ts +18 -0
  106. package/src/components/ui/data-table/opentui/index.tsx +9 -7
  107. package/src/components/ui/data-table/opentui/model.ts +26 -1
  108. package/src/components/ui/data-table/types.ts +2 -0
  109. package/src/components/ui/display.tsx +5 -1
  110. package/src/components/ui/list-view.tsx +8 -4
  111. package/src/components/ui/query-bar-keys.tsx +2 -0
  112. package/src/components/ui/query-bar.tsx +1 -0
  113. package/src/components/ui/select-button.tsx +5 -1
  114. package/src/components/ui/select-field.tsx +1 -0
  115. package/src/components/ui/table-layout.ts +8 -5
  116. package/src/core/app-runtime.ts +2 -0
  117. package/src/core/app-services.ts +2 -2
  118. package/src/core/state/app/layout.ts +2 -0
  119. package/src/core/state/app/state.ts +9 -0
  120. package/src/core/state/app/types.ts +3 -0
  121. package/src/data/config/chart-settings.ts +1 -1
  122. package/src/data/json-ticker-repository.ts +17 -8
  123. package/src/data/saved-copies.ts +55 -16
  124. package/src/data/ticker-repository.ts +16 -9
  125. package/src/data/ticker-writes.ts +81 -0
  126. package/src/data/ttl-cache.ts +165 -0
  127. package/src/docs/snapshot.json +427 -216
  128. package/src/i18n/es.ts +19 -3
  129. package/src/i18n/ja.ts +19 -3
  130. package/src/i18n/ko.ts +19 -3
  131. package/src/i18n/zh-cn.ts +19 -3
  132. package/src/i18n/zh-tw.ts +19 -3
  133. package/src/market-data/coordinator/financials.ts +10 -2
  134. package/src/market-data/coordinator/index.ts +3 -1
  135. package/src/market-data/coordinator/quotes.ts +4 -2
  136. package/src/market-data/depositary-pair.ts +2 -2
  137. package/src/market-data/fund-facts.ts +50 -0
  138. package/src/market-data/market/change-basis.ts +48 -7
  139. package/src/market-data/market/dated-previous-close.ts +203 -0
  140. package/src/market-data/market/format.ts +25 -0
  141. package/src/market-data/market/freshness.ts +2 -1
  142. package/src/market-data/market/futures-week.ts +68 -0
  143. package/src/market-data/market/status.ts +16 -4
  144. package/src/market-data/options-alternatives.ts +141 -4
  145. package/src/market-data/quotes/contributions.ts +20 -2
  146. package/src/market-data/quotes/resolution.ts +3 -0
  147. package/src/market-data/selectors.ts +3 -0
  148. package/src/news/aggregator.ts +113 -9
  149. package/src/news/cloud-session.ts +21 -0
  150. package/src/news/hooks.ts +1 -0
  151. package/src/news/news-model.ts +1 -0
  152. package/src/news/types.ts +11 -0
  153. package/src/notifications/notification-log.ts +6 -4
  154. package/src/plugins/builtin/alerts/alert-engine.ts +25 -2
  155. package/src/plugins/builtin/alerts/alert-log.ts +33 -0
  156. package/src/plugins/builtin/alerts/calendar-events.ts +57 -6
  157. package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
  158. package/src/plugins/builtin/alerts/cli.ts +380 -82
  159. package/src/plugins/builtin/alerts/command.ts +31 -0
  160. package/src/plugins/builtin/alerts/constants.ts +1 -1
  161. package/src/plugins/builtin/alerts/events-pane.tsx +19 -4
  162. package/src/plugins/builtin/alerts/fired-log.ts +113 -8
  163. package/src/plugins/builtin/alerts/format.ts +80 -4
  164. package/src/plugins/builtin/alerts/headless.ts +30 -7
  165. package/src/plugins/builtin/alerts/history-rows.ts +7 -3
  166. package/src/plugins/builtin/alerts/history.ts +2 -0
  167. package/src/plugins/builtin/alerts/index.tsx +135 -22
  168. package/src/plugins/builtin/alerts/live.ts +3 -3
  169. package/src/plugins/builtin/alerts/metric-command.ts +134 -0
  170. package/src/plugins/builtin/alerts/metric-data.ts +111 -0
  171. package/src/plugins/builtin/alerts/metric-model.ts +324 -0
  172. package/src/plugins/builtin/alerts/metric-parity-fixture.json +725 -0
  173. package/src/plugins/builtin/alerts/pane.tsx +90 -39
  174. package/src/plugins/builtin/alerts/quotes.ts +6 -0
  175. package/src/plugins/builtin/alerts/storage.ts +37 -10
  176. package/src/plugins/builtin/alerts/types.ts +7 -0
  177. package/src/plugins/builtin/analytics/index.tsx +2 -1
  178. package/src/plugins/builtin/analytics/risk-client.ts +16 -5
  179. package/src/plugins/builtin/analytics/risk-fx.ts +70 -15
  180. package/src/plugins/builtin/analytics/risk-headless.ts +27 -0
  181. package/src/plugins/builtin/analytics/risk-model.ts +55 -9
  182. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
  183. package/src/plugins/builtin/attention/index.tsx +2 -1
  184. package/src/plugins/builtin/attention/listing.ts +14 -0
  185. package/src/plugins/builtin/backtest/headless.ts +10 -5
  186. package/src/plugins/builtin/backtest/pane.tsx +9 -4
  187. package/src/plugins/builtin/backtest/presets.ts +29 -3
  188. package/src/plugins/builtin/broker-manager/index.tsx +10 -5
  189. package/src/plugins/builtin/broker-manager/model.ts +6 -1
  190. package/src/plugins/builtin/broker-trading/controller.ts +22 -12
  191. package/src/plugins/builtin/broker-trading/index.tsx +13 -1
  192. package/src/plugins/builtin/broker-trading/order-feed.ts +9 -4
  193. package/src/plugins/builtin/broker-trading/pane.tsx +1 -1
  194. package/src/plugins/builtin/buildout/cells.tsx +12 -4
  195. package/src/plugins/builtin/buildout/format.ts +38 -0
  196. package/src/plugins/builtin/buildout/model/normalizers.ts +36 -1
  197. package/src/plugins/builtin/buildout/pane/index.tsx +4 -1
  198. package/src/plugins/builtin/buildout/table-model.ts +23 -2
  199. package/src/plugins/builtin/builtin-plugin-meta.ts +7 -3
  200. package/src/plugins/builtin/catalysts/model.ts +2 -1
  201. package/src/plugins/builtin/changelog/index.tsx +9 -6
  202. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +6 -1
  203. package/src/plugins/builtin/chart-composer/chart-spec-edit.ts +1 -1
  204. package/src/plugins/builtin/chart-composer/cli-options.ts +9 -5
  205. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +12 -2
  206. package/src/plugins/builtin/chart-composer/editor/controller.ts +4 -2
  207. package/src/plugins/builtin/chart-composer/editor/model.ts +2 -0
  208. package/src/plugins/builtin/chart-composer/editor.tsx +7 -5
  209. package/src/plugins/builtin/chart-composer/formula.ts +71 -18
  210. package/src/plugins/builtin/chart-composer/headless-schema.ts +21 -4
  211. package/src/plugins/builtin/chart-composer/headless.ts +19 -3
  212. package/src/plugins/builtin/chart-composer/index.tsx +1 -1
  213. package/src/plugins/builtin/chart-composer/pane.tsx +53 -9
  214. package/src/plugins/builtin/chart-composer/presets.ts +50 -15
  215. package/src/plugins/builtin/chart-composer/quick-add.tsx +40 -25
  216. package/src/plugins/builtin/chart-composer/session-history.ts +18 -9
  217. package/src/plugins/builtin/chart-composer/settings.ts +27 -0
  218. package/src/plugins/builtin/chart-composer/use-series-catalog.ts +40 -26
  219. package/src/plugins/builtin/chat/content/command-suggestions.ts +93 -0
  220. package/src/plugins/builtin/chat/content/composer.tsx +13 -6
  221. package/src/plugins/builtin/chat/content/footer.tsx +6 -3
  222. package/src/plugins/builtin/chat/content/index.tsx +50 -21
  223. package/src/plugins/builtin/chat/content/open-targets.ts +14 -3
  224. package/src/plugins/builtin/chat/controller/session-runtime.ts +4 -2
  225. package/src/plugins/builtin/chat/index.tsx +23 -0
  226. package/src/plugins/builtin/chat/message/desktop.tsx +1 -0
  227. package/src/plugins/builtin/chat/message/inline-tokens.tsx +23 -0
  228. package/src/plugins/builtin/chat/message/pane-chip.tsx +50 -20
  229. package/src/plugins/builtin/chat/pane-links/commands.ts +271 -0
  230. package/src/plugins/builtin/chat/pane-links/model.ts +12 -3
  231. package/src/plugins/builtin/chat/pane-links/open.ts +8 -1
  232. package/src/plugins/builtin/chat/unread-inbox-pane.tsx +10 -1
  233. package/src/plugins/builtin/clinical-trials/model.ts +0 -9
  234. package/src/plugins/builtin/clinical-trials/pane.tsx +15 -22
  235. package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +8 -6
  236. package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
  237. package/src/plugins/builtin/cloud/plugin.tsx +1 -1
  238. package/src/plugins/builtin/company-attention/building.tsx +5 -1
  239. package/src/plugins/builtin/company-attention/client.ts +8 -9
  240. package/src/plugins/builtin/company-attention/evidence.ts +4 -1
  241. package/src/plugins/builtin/company-attention/moves.tsx +5 -1
  242. package/src/plugins/builtin/company-attention/pane.tsx +9 -2
  243. package/src/plugins/builtin/company-attention/signals.tsx +5 -1
  244. package/src/plugins/builtin/correlation/headless.ts +13 -5
  245. package/src/plugins/builtin/correlation/history.ts +22 -0
  246. package/src/plugins/builtin/cot/client.ts +22 -18
  247. package/src/plugins/builtin/cot/headless.ts +15 -10
  248. package/src/plugins/builtin/cot/model.ts +14 -1
  249. package/src/plugins/builtin/cot/pane.tsx +22 -14
  250. package/src/plugins/builtin/credit-boards/client.ts +6 -10
  251. package/src/plugins/builtin/credit-conditions/headless.ts +8 -2
  252. package/src/plugins/builtin/credit-conditions/index.tsx +5 -1
  253. package/src/plugins/builtin/credit-conditions/model.ts +4 -0
  254. package/src/plugins/builtin/credit-conditions/series.ts +3 -1
  255. package/src/plugins/builtin/credit-documents/detail.tsx +2 -2
  256. package/src/plugins/builtin/credit-documents/export.ts +173 -0
  257. package/src/plugins/builtin/credit-documents/headless.ts +7 -7
  258. package/src/plugins/builtin/credit-documents/model.ts +104 -14
  259. package/src/plugins/builtin/credit-documents/pane.tsx +12 -5
  260. package/src/plugins/builtin/custom-view/sources.ts +2 -0
  261. package/src/plugins/builtin/dividend-yield/client.ts +131 -23
  262. package/src/plugins/builtin/dividend-yield/headless.ts +44 -16
  263. package/src/plugins/builtin/dividend-yield/model.ts +8 -5
  264. package/src/plugins/builtin/dividend-yield/pane.tsx +38 -13
  265. package/src/plugins/builtin/dividend-yield/ranking.ts +37 -14
  266. package/src/plugins/builtin/dividend-yield/types.ts +20 -2
  267. package/src/plugins/builtin/dividend-yield/view.ts +30 -2
  268. package/src/plugins/builtin/docs/targets.ts +5 -1
  269. package/src/plugins/builtin/earnings/board-model.ts +16 -15
  270. package/src/plugins/builtin/earnings/board.tsx +62 -71
  271. package/src/plugins/builtin/earnings/headless.ts +40 -4
  272. package/src/plugins/builtin/earnings/scope.ts +31 -43
  273. package/src/plugins/builtin/earnings-calls/calendar.tsx +2 -2
  274. package/src/plugins/builtin/earnings-calls/format.ts +31 -2
  275. package/src/plugins/builtin/earnings-calls/headless.ts +7 -2
  276. package/src/plugins/builtin/earnings-calls/model.ts +6 -1
  277. package/src/plugins/builtin/earnings-calls/pane.tsx +5 -4
  278. package/src/plugins/builtin/econ/calendar-model.ts +14 -9
  279. package/src/plugins/builtin/econ/figures.ts +72 -0
  280. package/src/plugins/builtin/econ/index.tsx +33 -3
  281. package/src/plugins/builtin/econ/types.ts +4 -0
  282. package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
  283. package/src/plugins/builtin/econ-statistics/headless.ts +85 -35
  284. package/src/plugins/builtin/econ-statistics/stats.ts +37 -9
  285. package/src/plugins/builtin/econ-statistics/view.ts +3 -0
  286. package/src/plugins/builtin/equity-screener/categories.ts +179 -0
  287. package/src/plugins/builtin/equity-screener/client.ts +4 -2
  288. package/src/plugins/builtin/equity-screener/headless.ts +144 -13
  289. package/src/plugins/builtin/equity-screener/ideas.ts +49 -0
  290. package/src/plugins/builtin/equity-screener/model.ts +99 -10
  291. package/src/plugins/builtin/equity-screener/pane.tsx +70 -15
  292. package/src/plugins/builtin/exposure/charts.tsx +2 -2
  293. package/src/plugins/builtin/exposure/model.ts +13 -1
  294. package/src/plugins/builtin/exposure/pane.tsx +4 -4
  295. package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
  296. package/src/plugins/builtin/futures/index.tsx +1 -0
  297. package/src/plugins/builtin/futures/model.ts +2 -1
  298. package/src/plugins/builtin/futures/table.tsx +2 -2
  299. package/src/plugins/builtin/futures/use-board-terms.ts +42 -28
  300. package/src/plugins/builtin/futures/use-front-returns.ts +45 -28
  301. package/src/plugins/builtin/fx-matrix/client.ts +11 -3
  302. package/src/plugins/builtin/fx-matrix/headless.ts +6 -3
  303. package/src/plugins/builtin/fx-matrix/index.tsx +16 -4
  304. package/src/plugins/builtin/fx-matrix/live-legs.ts +54 -3
  305. package/src/plugins/builtin/gpu/headless.ts +8 -2
  306. package/src/plugins/builtin/help/function-index.ts +4 -1
  307. package/src/plugins/builtin/help/index.tsx +1 -0
  308. package/src/plugins/builtin/help/keybindings-editor.tsx +18 -14
  309. package/src/plugins/builtin/help/shortcut-model.ts +1 -1
  310. package/src/plugins/builtin/holders/fund-holders.ts +51 -0
  311. package/src/plugins/builtin/holders/headless.ts +18 -1
  312. package/src/plugins/builtin/iv-history/client.ts +52 -11
  313. package/src/plugins/builtin/iv-history/format.ts +36 -23
  314. package/src/plugins/builtin/iv-history/headless.ts +36 -18
  315. package/src/plugins/builtin/iv-history/index.tsx +2 -2
  316. package/src/plugins/builtin/iv-history/model.ts +163 -27
  317. package/src/plugins/builtin/iv-history/pane.tsx +10 -6
  318. package/src/plugins/builtin/iv-history/screen-pane.tsx +22 -15
  319. package/src/plugins/builtin/iv-history/universe.ts +5 -1
  320. package/src/plugins/builtin/jobs/client.ts +25 -29
  321. package/src/plugins/builtin/kelly-sizer/model.ts +12 -8
  322. package/src/plugins/builtin/kelly-sizer/pane.tsx +9 -9
  323. package/src/plugins/builtin/kelly-sizer/sections.tsx +19 -10
  324. package/src/plugins/builtin/kelly-sizer/types.ts +10 -4
  325. package/src/plugins/builtin/macro-day/pane.tsx +7 -5
  326. package/src/plugins/builtin/macro-day/release-table.ts +2 -2
  327. package/src/plugins/builtin/market-heatmap/data.ts +8 -33
  328. package/src/plugins/builtin/market-movers/client.ts +25 -7
  329. package/src/plugins/builtin/market-movers/footer.ts +2 -2
  330. package/src/plugins/builtin/market-movers/headless.ts +32 -12
  331. package/src/plugins/builtin/market-movers/index.tsx +26 -14
  332. package/src/plugins/builtin/market-movers/min-float.ts +74 -0
  333. package/src/plugins/builtin/market-movers/model.ts +4 -1
  334. package/src/plugins/builtin/market-movers/screener.ts +14 -0
  335. package/src/plugins/builtin/market-movers/session-body.tsx +13 -8
  336. package/src/plugins/builtin/market-movers/session-table.ts +4 -3
  337. package/src/plugins/builtin/market-movers/table.ts +17 -5
  338. package/src/plugins/builtin/members/breadth-pane.tsx +82 -0
  339. package/src/plugins/builtin/members/client.ts +19 -4
  340. package/src/plugins/builtin/members/headless.ts +20 -7
  341. package/src/plugins/builtin/members/index.tsx +9 -9
  342. package/src/plugins/builtin/members/model.ts +23 -4
  343. package/src/plugins/builtin/members/pane.tsx +25 -15
  344. package/src/plugins/builtin/news/index.tsx +1 -0
  345. package/src/plugins/builtin/news/wire/categories.ts +19 -3
  346. package/src/plugins/builtin/news/wire/industry-pane.tsx +1 -0
  347. package/src/plugins/builtin/news/wire/news/footer.ts +31 -5
  348. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +6 -1
  349. package/src/plugins/builtin/news/wire/news/table.tsx +2 -2
  350. package/src/plugins/builtin/openfda/client.ts +14 -2
  351. package/src/plugins/builtin/openfda/index.tsx +3 -2
  352. package/src/plugins/builtin/openfda/model.ts +9 -0
  353. package/src/plugins/builtin/openfda/pane.tsx +42 -6
  354. package/src/plugins/builtin/options/analytics.ts +54 -6
  355. package/src/plugins/builtin/options/enrichment.ts +17 -5
  356. package/src/plugins/builtin/options/model-spot.ts +5 -2
  357. package/src/plugins/builtin/options/strike-window.ts +2 -1
  358. package/src/plugins/builtin/options/table.ts +6 -9
  359. package/src/plugins/builtin/options/types.ts +2 -0
  360. package/src/plugins/builtin/options/view.tsx +42 -12
  361. package/src/plugins/builtin/options-positioning/client.ts +30 -0
  362. package/src/plugins/builtin/options-positioning/headless.ts +74 -21
  363. package/src/plugins/builtin/options-positioning/index.tsx +4 -4
  364. package/src/plugins/builtin/options-positioning/model.ts +122 -14
  365. package/src/plugins/builtin/options-positioning/pane.tsx +87 -42
  366. package/src/plugins/builtin/options-scenario/client.ts +116 -47
  367. package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +142 -10
  368. package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +206 -55
  369. package/src/plugins/builtin/options-scenario/csp-screen.ts +63 -23
  370. package/src/plugins/builtin/options-scenario/csp.ts +8 -2
  371. package/src/plugins/builtin/options-scenario/index.tsx +9 -7
  372. package/src/plugins/builtin/options-scenario/strategy-screen.ts +532 -0
  373. package/src/plugins/builtin/pe-band/model.ts +12 -49
  374. package/src/plugins/builtin/perps/cascade-tabs.tsx +161 -0
  375. package/src/plugins/builtin/perps/cascade.ts +236 -0
  376. package/src/plugins/builtin/perps/client.ts +56 -2
  377. package/src/plugins/builtin/perps/freshness.ts +12 -10
  378. package/src/plugins/builtin/perps/headless.ts +65 -9
  379. package/src/plugins/builtin/perps/index.tsx +7 -5
  380. package/src/plugins/builtin/perps/live-rows.ts +10 -3
  381. package/src/plugins/builtin/perps/model.ts +18 -4
  382. package/src/plugins/builtin/perps/pane.tsx +32 -31
  383. package/src/plugins/builtin/perps/status-line.ts +25 -0
  384. package/src/plugins/builtin/plugin-module.ts +5 -1
  385. package/src/plugins/builtin/portfolio-list/cached-account.ts +3 -1
  386. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +2 -8
  387. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +4 -4
  388. package/src/plugins/builtin/portfolio-list/cli/render.ts +12 -7
  389. package/src/plugins/builtin/portfolio-list/headless.ts +1 -1
  390. package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
  391. package/src/plugins/builtin/portfolio-list/pane/index.tsx +4 -1
  392. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +5 -2
  393. package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +3 -2
  394. package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +3 -1
  395. package/src/plugins/builtin/portfolio-list/summary/index.tsx +11 -8
  396. package/src/plugins/builtin/portfolio-list/table.tsx +11 -3
  397. package/src/plugins/builtin/primary-dealers/client.ts +13 -38
  398. package/src/plugins/builtin/rate-path/pane.tsx +2 -2
  399. package/src/plugins/builtin/realized-vol/index.tsx +2 -2
  400. package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
  401. package/src/plugins/builtin/relative-rotation/client.ts +18 -4
  402. package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
  403. package/src/plugins/builtin/relative-rotation/index.tsx +2 -1
  404. package/src/plugins/builtin/relative-rotation/model.ts +16 -40
  405. package/src/plugins/builtin/relative-rotation/pane.tsx +3 -2
  406. package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -2
  407. package/src/plugins/builtin/research/event-model.ts +10 -8
  408. package/src/plugins/builtin/research/headless.ts +1 -1
  409. package/src/plugins/builtin/research/relative-valuation-headless.ts +5 -1
  410. package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
  411. package/src/plugins/builtin/research/relative-valuation-pane.tsx +11 -9
  412. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -3
  413. package/src/plugins/builtin/reverse-dcf/model.ts +61 -6
  414. package/src/plugins/builtin/reverse-dcf/pane.tsx +7 -4
  415. package/src/plugins/builtin/scanner/feed.ts +2 -2
  416. package/src/plugins/builtin/scanner/flow-model.ts +50 -4
  417. package/src/plugins/builtin/scanner/flow-pane.tsx +367 -65
  418. package/src/plugins/builtin/scanner/flow-session.ts +146 -0
  419. package/src/plugins/builtin/scanner/headless.ts +204 -35
  420. package/src/plugins/builtin/scanner/index.ts +40 -6
  421. package/src/plugins/builtin/scanner/live-condition-editor.tsx +222 -0
  422. package/src/plugins/builtin/scanner/live-feed.ts +168 -0
  423. package/src/plugins/builtin/scanner/live-headless.ts +138 -0
  424. package/src/plugins/builtin/scanner/live-model.ts +362 -0
  425. package/src/plugins/builtin/scanner/live-pane.tsx +677 -0
  426. package/src/plugins/builtin/scanner/live-track.ts +186 -0
  427. package/src/plugins/builtin/scanner/lock-note.ts +3 -2
  428. package/src/plugins/builtin/seasonality/headless.ts +10 -1
  429. package/src/plugins/builtin/sec/fund-facts-view.tsx +5 -2
  430. package/src/plugins/builtin/sec/fund-facts.ts +116 -34
  431. package/src/plugins/builtin/sec/headless.ts +2 -1
  432. package/src/plugins/builtin/series-inventory.ts +3 -0
  433. package/src/plugins/builtin/shared/cloud-resource.ts +3 -2
  434. package/src/plugins/builtin/shared/cloud-upgrade.ts +4 -3
  435. package/src/plugins/builtin/shared/earnings-calendar.ts +6 -2
  436. package/src/plugins/builtin/shared/earnings-report.ts +2 -2
  437. package/src/plugins/builtin/shared/job-posting.ts +1 -0
  438. package/src/plugins/builtin/shared/macro-releases.ts +65 -0
  439. package/src/plugins/builtin/shared/members-menu.tsx +3 -2
  440. package/src/plugins/builtin/shared/pro-lock-note.ts +12 -12
  441. package/src/plugins/builtin/shared/report-freshness.ts +3 -2
  442. package/src/plugins/builtin/shared/scanner-capture.ts +3 -0
  443. package/src/plugins/builtin/shared/ticker-choice.tsx +2 -1
  444. package/src/plugins/builtin/shared/ticker-company.ts +77 -0
  445. package/src/plugins/builtin/shared/use-quote-board.ts +10 -3
  446. package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
  447. package/src/plugins/builtin/shared/volatility/pricing.ts +26 -1
  448. package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
  449. package/src/plugins/builtin/themes/headless.ts +6 -4
  450. package/src/plugins/builtin/themes/model.ts +17 -8
  451. package/src/plugins/builtin/themes/pane.tsx +7 -7
  452. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +43 -18
  453. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +3 -2
  454. package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +62 -0
  455. package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +83 -46
  456. package/src/plugins/builtin/ticker-detail/headless.ts +31 -16
  457. package/src/plugins/builtin/ticker-detail/overview/model.ts +1 -1
  458. package/src/plugins/builtin/ticker-detail/pane.tsx +3 -2
  459. package/src/plugins/builtin/ticker-detail/quote-footer.ts +6 -1
  460. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -1
  461. package/src/plugins/builtin/ticker-detail/settings.ts +1 -0
  462. package/src/plugins/builtin/time-sales/pane.tsx +31 -19
  463. package/src/plugins/builtin/treasury-daily/client.ts +41 -5
  464. package/src/plugins/builtin/treasury-daily/headless.ts +2 -2
  465. package/src/plugins/builtin/treasury-daily/index.tsx +1 -1
  466. package/src/plugins/builtin/treasury-daily/model.ts +50 -3
  467. package/src/plugins/builtin/treasury-daily/pane.tsx +81 -2
  468. package/src/plugins/builtin/vol-surface/charts.tsx +9 -4
  469. package/src/plugins/builtin/vol-surface/client.ts +9 -17
  470. package/src/plugins/builtin/vol-surface/headless.ts +63 -14
  471. package/src/plugins/builtin/vol-surface/index.tsx +2 -2
  472. package/src/plugins/builtin/vol-surface/model.ts +22 -7
  473. package/src/plugins/builtin/vol-surface/pane.tsx +48 -9
  474. package/src/plugins/builtin/vol-surface/tables.ts +31 -3
  475. package/src/plugins/builtin/volatility/dvol.ts +46 -0
  476. package/src/plugins/builtin/volatility/index.tsx +2 -2
  477. package/src/plugins/builtin/volatility/pane.tsx +4 -4
  478. package/src/plugins/builtin/volatility/series.ts +26 -0
  479. package/src/plugins/builtin/world-indices/footer.ts +1 -1
  480. package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
  481. package/src/plugins/catalog-browser.ts +13 -8
  482. package/src/plugins/catalog-manifest.ts +2 -1
  483. package/src/plugins/marketplace/model.ts +18 -5
  484. package/src/portfolio/column-values.ts +5 -5
  485. package/src/portfolio/futures-positions.ts +6 -3
  486. package/src/portfolio/position-quantity.ts +15 -9
  487. package/src/public/__snapshots__/public-api.test.ts.snap +1 -0
  488. package/src/remote/resources.ts +2 -1
  489. package/src/renderers/browser/app-services.ts +1 -7
  490. package/src/renderers/browser/config-host.ts +2 -55
  491. package/src/renderers/browser/main.tsx +7 -57
  492. package/src/renderers/dom/data-table/row.tsx +26 -0
  493. package/src/renderers/dom/desktop/list-view.tsx +13 -10
  494. package/src/renderers/dom/desktop/query-bar.tsx +4 -2
  495. package/src/renderers/dom/desktop/select-field.tsx +4 -2
  496. package/src/renderers/dom/host/chart-surface.tsx +21 -0
  497. package/src/renderers/electrobun/bun/desktop/app-name.ts +222 -0
  498. package/src/renderers/electrobun/bun/desktop/backend.ts +29 -7
  499. package/src/renderers/electrobun/bun/desktop/host-requests.ts +7 -0
  500. package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
  501. package/src/renderers/electrobun/bun/desktop/relaunch.ts +74 -25
  502. package/src/renderers/electrobun/bun/desktop/state-broadcaster.ts +1 -0
  503. package/src/renderers/electrobun/bun/desktop/update.ts +173 -5
  504. package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
  505. package/src/renderers/electrobun/bun/desktop/workspace/index.ts +2 -0
  506. package/src/renderers/electrobun/bun/index.ts +25 -1
  507. package/src/renderers/electrobun/shared/app-name.ts +30 -0
  508. package/src/renderers/electrobun/shared/protocol.ts +10 -0
  509. package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
  510. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +3 -3
  511. package/src/renderers/electrobun/view/desktop/window/snapshot.ts +1 -0
  512. package/src/renderers/electrobun/view/main.tsx +24 -1
  513. package/src/renderers/electrobun/view/news-source.ts +3 -1
  514. package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
  515. package/src/renderers/electrobun/view/view-ids.ts +85 -0
  516. package/src/renderers/opentui/start.tsx +5 -0
  517. package/src/renderers/opentui/toast-host.tsx +71 -43
  518. package/src/series/registry.ts +1 -0
  519. package/src/series/types.ts +6 -0
  520. package/src/series/validate.ts +1 -0
  521. package/src/sources/gloomberb-cloud/index.ts +1 -0
  522. package/src/sources/gloomberb-cloud/news.ts +47 -7
  523. package/src/sources/gloomberb-cloud/normalizers.ts +2 -0
  524. package/src/sources/provider-router/batches.ts +10 -2
  525. package/src/sources/provider-router/financial-routes.ts +3 -3
  526. package/src/sources/provider-router/financials.ts +15 -3
  527. package/src/sources/provider-router/history.ts +28 -7
  528. package/src/sources/provider-router/index.ts +149 -10
  529. package/src/sources/provider-router/search.ts +13 -2
  530. package/src/state/app/context/index.tsx +3 -0
  531. package/src/state/ticker-cleanup.ts +50 -0
  532. package/src/sync/auth-transition.ts +19 -9
  533. package/src/sync/baseline.ts +10 -4
  534. package/src/sync/controller.ts +70 -19
  535. package/src/sync/core-contributors.ts +90 -31
  536. package/src/sync/react.ts +4 -1
  537. package/src/sync/types.ts +3 -0
  538. package/src/telemetry/stall-reports-dom.ts +38 -0
  539. package/src/telemetry/stall-reports-node.ts +75 -0
  540. package/src/telemetry/stall-reports.ts +214 -0
  541. package/src/telemetry/usage-counts.ts +0 -18
  542. package/src/test-support/preload.ts +14 -0
  543. package/src/theme/colors.ts +8 -0
  544. package/src/theme/themes.ts +10 -0
  545. package/src/tickers/company-name.ts +103 -3
  546. package/src/tickers/listing-venues.ts +38 -3
  547. package/src/tickers/search/bare-symbol.ts +36 -0
  548. package/src/tickers/search/index.ts +36 -7
  549. package/src/tickers/search/ranking.ts +10 -1
  550. package/src/tickers/search/types.ts +5 -0
  551. package/src/tickers/terminal-codes.ts +19 -0
  552. package/src/time-series/alignment.ts +1 -1
  553. package/src/time-series/base-series.ts +13 -5
  554. package/src/time-series/daily-valuation.ts +62 -0
  555. package/src/time-series/economic.ts +18 -4
  556. package/src/time-series/field-catalog.ts +2 -4
  557. package/src/time-series/filed-trailing-eps.ts +197 -0
  558. package/src/time-series/forward-valuation.ts +32 -0
  559. package/src/time-series/fundamentals.ts +198 -7
  560. package/src/time-series/hooks.ts +1 -0
  561. package/src/time-series/ice-credit.ts +3 -0
  562. package/src/time-series/price-comparison.ts +30 -2
  563. package/src/time-series/price-units.ts +72 -2
  564. package/src/time-series/resolve-context.ts +7 -1
  565. package/src/time-series/resolve.ts +116 -15
  566. package/src/time-series/series-presentation.ts +40 -6
  567. package/src/time-series/session-bar.ts +11 -0
  568. package/src/time-series/spec.ts +5 -3
  569. package/src/time-series/studies.ts +278 -74
  570. package/src/time-series/total-return.ts +160 -0
  571. package/src/time-series/transforms.ts +3 -3
  572. package/src/time-series/types.ts +27 -1
  573. package/src/time-series/valuation-releases.ts +39 -0
  574. package/src/types/data-provider.ts +9 -1
  575. package/src/types/desktop-window.ts +2 -0
  576. package/src/types/financials.ts +73 -2
  577. package/src/types/headless.ts +61 -5
  578. package/src/types/instrument.ts +3 -0
  579. package/src/types/news-source.ts +1 -0
  580. package/src/types/plugin.ts +10 -1
  581. package/src/ui/host.tsx +3 -0
  582. package/src/updater/command-names.ts +12 -7
  583. package/src/updater/failure.ts +4 -1
  584. package/src/utils/cli-output.ts +42 -1
  585. package/src/utils/crypto-pair.ts +44 -0
  586. package/src/utils/financial-statements.ts +6 -1
  587. package/src/utils/forward-dividend.ts +2 -0
  588. package/src/utils/fundamentals.ts +24 -3
  589. package/src/utils/fx-market-hours.ts +26 -3
  590. package/src/utils/main-thread-stall.ts +35 -2
  591. package/src/utils/market-capitalization.ts +24 -1
  592. package/src/utils/network-failure.ts +9 -6
  593. package/src/utils/option-expiry.ts +48 -1
  594. package/src/utils/options.ts +10 -0
  595. package/src/utils/plugin-proxy-hosts.json +1 -0
  596. package/src/utils/price-history.ts +12 -1
  597. package/src/utils/shell-word.ts +8 -0
  598. package/src/utils/trailing-dividend.ts +53 -4
  599. package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
  600. package/src/renderers/browser/first-screen.ts +0 -193
@@ -23,6 +23,7 @@ import type { SeriesCatalogInstrument, SeriesCatalogSuggestion } from "./series-
23
23
  import { useSeriesCatalogSuggestions } from "./use-series-catalog";
24
24
  import { clampIndex } from "../../../utils/math";
25
25
 
26
+ const RETRY_SEARCH_ID = "\u0000retry-search";
26
27
  const MAX_VISIBLE_SUGGESTIONS = 4;
27
28
  const CHART_TOOLBAR_HEIGHT = 1;
28
29
  const MINIMUM_CHART_HEIGHT = 4;
@@ -116,7 +117,7 @@ export function ChartSeriesQuickAdd({
116
117
  const [selectedIndex, setSelectedIndex] = useState(0);
117
118
  const [error, setError] = useState<string | null>(null);
118
119
  const defaultInstrument = useMemo(() => defaultCatalogInstrument(spec), [spec]);
119
- const { suggestions, loading, error: searchError } = useSeriesCatalogSuggestions({
120
+ const { suggestions, loading, error: searchError, retry } = useSeriesCatalogSuggestions({
120
121
  query,
121
122
  defaultInstrument,
122
123
  enabled: active,
@@ -127,10 +128,7 @@ export function ChartSeriesQuickAdd({
127
128
  const drawerStatus = error
128
129
  ?? (loading && suggestions.length === 0
129
130
  ? "Searching instruments..."
130
- // A failed lookup is not zero matches, so it keeps its own message.
131
- : searchError && suggestions.length === 0
132
- ? searchError
133
- : active && query.trim().length > 0 && suggestions.length === 0
131
+ : active && !searchError && query.trim().length > 0 && suggestions.length === 0
134
132
  ? "No matching security or metric."
135
133
  : null);
136
134
  const maximumDrawerHeight = Math.max(
@@ -141,7 +139,7 @@ export function ChartSeriesQuickAdd({
141
139
  ),
142
140
  );
143
141
  const drawerHeight = active
144
- ? Math.min(maximumDrawerHeight, drawerStatus ? 1 : suggestions.length)
142
+ ? Math.min(maximumDrawerHeight, drawerStatus ? 1 : suggestions.length + (searchError ? 1 : 0))
145
143
  : 0;
146
144
  const nativeSurfaceManager = nativeRenderer.nativeSurfaceManager;
147
145
  useAppInputCapture(inputFocused && focused);
@@ -203,9 +201,14 @@ export function ChartSeriesQuickAdd({
203
201
 
204
202
  // Plugin catalogs land after the securities: only pull a stranded cursor
205
203
  // back into range, so a row the user already moved to stays selected.
204
+ // -1 is the retry row when a lookup failed. Suggestions retain their indices
205
+ // when that row arrives, so an in-flight failure cannot change what Enter adds.
206
+ const clampSelection = useCallback((index: number) => suggestions.length
207
+ ? Math.min(suggestions.length - 1, Math.max(searchError ? -1 : 0, index))
208
+ : -1, [searchError, suggestions.length]);
206
209
  useEffect(() => {
207
- setSelectedIndex((current) => clampIndex(current, suggestions.length, -1));
208
- }, [suggestions.length]);
210
+ setSelectedIndex(clampSelection);
211
+ }, [clampSelection]);
209
212
 
210
213
  useEffect(() => {
211
214
  if (focused || !active) return;
@@ -287,9 +290,16 @@ export function ChartSeriesQuickAdd({
287
290
  queueMicrotask(() => inputRef.current?.blur?.());
288
291
  }, [cancelPendingBlur, clearInput, onActiveChange, setSpec, spec]);
289
292
 
293
+ const retrySearch = useCallback(() => {
294
+ cancelPendingBlur();
295
+ setInputFocused(true);
296
+ queueMicrotask(() => inputRef.current?.focus?.());
297
+ void retry();
298
+ }, [cancelPendingBlur, retry]);
290
299
  const submit = useCallback(() => {
291
- commitSuggestion(suggestions[clampIndex(selectedIndex, suggestions.length, -1)]);
292
- }, [commitSuggestion, selectedIndex, suggestions]);
300
+ if (selectedIndex < 0 && searchError) retrySearch();
301
+ else commitSuggestion(suggestions[clampIndex(selectedIndex, suggestions.length, -1)]);
302
+ }, [commitSuggestion, retrySearch, searchError, selectedIndex, suggestions]);
293
303
  const cancel = useCallback(() => {
294
304
  cancelPendingBlur();
295
305
  inputRef.current?.blur?.();
@@ -317,11 +327,11 @@ export function ChartSeriesQuickAdd({
317
327
  } else if (event.name === "up") {
318
328
  event.preventDefault?.();
319
329
  event.stopPropagation?.();
320
- setSelectedIndex((current) => clampIndex(current - 1, suggestions.length, -1));
330
+ setSelectedIndex((current) => clampSelection(current - 1));
321
331
  } else if (event.name === "down") {
322
332
  event.preventDefault?.();
323
333
  event.stopPropagation?.();
324
- setSelectedIndex((current) => clampIndex(current + 1, suggestions.length, -1));
334
+ setSelectedIndex((current) => clampSelection(current + 1));
325
335
  }
326
336
  }, {
327
337
  phase: "before",
@@ -377,12 +387,17 @@ export function ChartSeriesQuickAdd({
377
387
  />
378
388
  );
379
389
 
380
- const items = useMemo<ListViewItem[]>(() => suggestions.map((suggestion) => ({
381
- id: suggestion.id,
382
- label: suggestion.label,
383
- description: suggestion.description,
384
- detail: suggestion.detail,
385
- })), [suggestions]);
390
+ const items = useMemo<ListViewItem[]>(() => [
391
+ ...(searchError ? [{ id: RETRY_SEARCH_ID, label: searchError, detail: "Retry", color: colors.negative }] : []),
392
+ ...suggestions.map((suggestion) => ({
393
+ id: suggestion.id, label: suggestion.label, description: suggestion.description, detail: suggestion.detail,
394
+ })),
395
+ ], [searchError, suggestions]);
396
+ const selectedItemIndex = selectedIndex + (searchError ? 1 : 0);
397
+ const activateItem = (id: string) => {
398
+ if (id === RETRY_SEARCH_ID) retrySearch();
399
+ else commitSuggestion(suggestions.find((suggestion) => suggestion.id === id));
400
+ };
386
401
 
387
402
  return (
388
403
  <Box
@@ -408,17 +423,17 @@ export function ChartSeriesQuickAdd({
408
423
  label="Chart series suggestions"
409
424
  minWidth={controlWidth * 8}
410
425
  closeOnSelect={false}
411
- highlightedId={suggestions[clampIndex(selectedIndex, suggestions.length, -1)]?.id ?? null}
412
- onHighlight={(id) => setSelectedIndex(Math.max(0, suggestions.findIndex((suggestion) => suggestion.id === id)))}
426
+ highlightedId={items[selectedItemIndex]?.id ?? null}
427
+ onHighlight={(id) => setSelectedIndex(id === RETRY_SEARCH_ID ? -1 : Math.max(0, suggestions.findIndex((suggestion) => suggestion.id === id)))}
413
428
  items={drawerStatus
414
429
  ? [{ id: "\u0000status", kind: "heading", label: drawerStatus }]
415
- : suggestions.map((suggestion) => ({
430
+ : items.map((suggestion) => ({
416
431
  id: suggestion.id,
417
432
  label: suggestion.label,
418
433
  description: suggestion.description,
419
434
  hint: suggestion.detail,
420
435
  }))}
421
- onSelect={(id) => commitSuggestion(suggestions.find((suggestion) => suggestion.id === id))}
436
+ onSelect={activateItem}
422
437
  />
423
438
  ) : quickAddRow}
424
439
  {!desktop && drawerHeight > 0 ? (
@@ -454,15 +469,15 @@ export function ChartSeriesQuickAdd({
454
469
  ) : (
455
470
  <ListView
456
471
  items={items}
457
- selectedIndex={clampIndex(selectedIndex, items.length, -1)}
472
+ selectedIndex={clampIndex(selectedItemIndex, items.length, -1)}
458
473
  height={drawerHeight}
459
474
  surface="plain"
460
475
  bgColor={colors.panel}
461
476
  scrollable={items.length > drawerHeight}
462
477
  rowGap={0}
463
478
  selectOnHover
464
- onSelect={setSelectedIndex}
465
- onActivate={(_, index) => commitSuggestion(suggestions[index])}
479
+ onSelect={(index) => setSelectedIndex(index - (searchError ? 1 : 0))}
480
+ onActivate={(item) => activateItem(item.id)}
466
481
  remoteLabel="Chart series suggestions"
467
482
  />
468
483
  )}
@@ -207,22 +207,27 @@ export function resolveIntradayRequest(options: {
207
207
  return { rangePreset, resolution, session };
208
208
  }
209
209
 
210
- function unavailableReason(symbol: string, request: IntradayRequest, kind: "empty" | "not-intraday"): string {
210
+ function unavailableReason(symbol: string, request: IntradayRequest, kind: "empty" | "not-intraday" | "failed"): string {
211
211
  const window = request.session
212
212
  ? `session ${request.session}`
213
213
  : request.rangePreset === "1W" ? "the latest five sessions" : "the latest session";
214
+ if (kind === "failed") return `Intraday history request failed for ${symbol}. Try again.`;
214
215
  return kind === "not-intraday"
215
216
  ? `The market-data provider did not return intraday bars for ${symbol} for ${window}.`
216
217
  : `No intraday price history is available for ${symbol} for ${window}.`;
217
218
  }
218
219
 
220
+ interface IntradayHistoryAttempt extends PriceHistoryResult {
221
+ requestFailed?: true;
222
+ }
223
+
219
224
  async function loadTrailingHistory(
220
225
  provider: DataProvider,
221
226
  symbol: string,
222
227
  exchange: string,
223
228
  request: IntradayRequest,
224
229
  context?: MarketDataRequestContext,
225
- ): Promise<PriceHistoryResult> {
230
+ ): Promise<IntradayHistoryAttempt> {
226
231
  const fetchRange: TimeRange = request.rangePreset === "1W" && request.resolution !== "1m"
227
232
  ? "1M"
228
233
  : "1W";
@@ -230,8 +235,9 @@ async function loadTrailingHistory(
230
235
  return await fetchHistoryResult(provider, symbol, exchange,
231
236
  { kind: "resolution", range: fetchRange, resolution: request.resolution }, context)
232
237
  ?? { points: [], resolution: request.resolution };
233
- } catch {
234
- return { points: [], resolution: request.resolution };
238
+ } catch (error) {
239
+ logFailure("chart-composer.session-history", "Load trailing intraday history failed")(error);
240
+ return { points: [], resolution: request.resolution, requestFailed: true };
235
241
  }
236
242
  }
237
243
 
@@ -243,18 +249,19 @@ async function loadHistoricalFallback(
243
249
  now: Date,
244
250
  timeZone: string,
245
251
  context?: MarketDataRequestContext,
246
- ): Promise<PriceHistoryResult> {
252
+ ): Promise<IntradayHistoryAttempt> {
247
253
  const empty = { points: [], resolution: request.resolution };
254
+ const failed: IntradayHistoryAttempt = { ...empty, requestFailed: true };
248
255
  if (request.session) {
249
256
  const bounds = sessionUtcBounds(request.session, timeZone);
250
257
  return await fetchHistoryResult(provider, symbol, exchange,
251
- { kind: "detail", start: bounds.start, end: bounds.end, interval: request.resolution }, context).catch(logFailure("chart-composer.session-history", "Load requested intraday session failed", null)) ?? empty;
258
+ { kind: "detail", start: bounds.start, end: bounds.end, interval: request.resolution }, context).catch(logFailure("chart-composer.session-history", "Load requested intraday session failed", failed)) ?? empty;
252
259
  }
253
260
  const end = new Date(now.getTime() - HISTORICAL_RETRY_DELAY_MS);
254
261
  const lookbackDays = request.rangePreset === "1W" ? 35 : 8;
255
262
  const start = new Date(end.getTime() - lookbackDays * DAY_MS);
256
263
  return await fetchHistoryResult(provider, symbol, exchange,
257
- { kind: "detail", start, end, interval: request.resolution }, context).catch(logFailure("chart-composer.session-history", "Load historical intraday fallback failed", null)) ?? empty;
264
+ { kind: "detail", start, end, interval: request.resolution }, context).catch(logFailure("chart-composer.session-history", "Load historical intraday fallback failed", failed)) ?? empty;
258
265
  }
259
266
 
260
267
  export async function loadIntradayWindow(options: {
@@ -280,6 +287,7 @@ export async function loadIntradayWindow(options: {
280
287
  options.exchange,
281
288
  options.request, options.context,
282
289
  );
290
+ let requestFailed = acquisition.requestFailed === true;
283
291
  let raw = acquisition.points;
284
292
  let window = resolveIntradaySessionWindow(raw, {
285
293
  rangePreset: options.request.rangePreset,
@@ -303,6 +311,7 @@ export async function loadIntradayWindow(options: {
303
311
  options.request,
304
312
  options.now ?? new Date(), timeZone, options.context,
305
313
  );
314
+ requestFailed ||= acquisition.requestFailed === true;
306
315
  raw = acquisition.points;
307
316
  window = resolveIntradaySessionWindow(raw, {
308
317
  rangePreset: options.request.rangePreset,
@@ -324,7 +333,7 @@ export async function loadIntradayWindow(options: {
324
333
  unavailableReason: unavailableReason(
325
334
  options.symbol,
326
335
  options.request,
327
- sawNonIntradayData ? "not-intraday" : "empty",
336
+ requestFailed ? "failed" : sawNonIntradayData ? "not-intraday" : "empty",
328
337
  ),
329
338
  };
330
339
  }
@@ -338,7 +347,7 @@ export async function loadIntradayWindow(options: {
338
347
  sessionDates: window.sessionDates,
339
348
  start: requestedWindow?.start ?? null,
340
349
  end: requestedWindow?.end ?? null,
341
- unavailableReason: unavailableReason(options.symbol, options.request, "not-intraday"),
350
+ unavailableReason: unavailableReason(options.symbol, options.request, requestFailed ? "failed" : "not-intraday"),
342
351
  };
343
352
  }
344
353
  const bufferedPoints = normalizedPoints(raw);
@@ -129,6 +129,20 @@ export const CHART_SETTING_KEYS = {
129
129
  realizedVolEstimator: "chartRealizedVolEstimator",
130
130
  } as const;
131
131
 
132
+ /**
133
+ * Whether rising candles take their series' colour or the theme's gain colour.
134
+ * Kept beside the spec in the pane's settings, so a build without it opens the
135
+ * chart unchanged; it is not one of CHART_SETTING_KEYS, which a spec edit
136
+ * clears as derived from the spec.
137
+ */
138
+ export const CHART_CANDLE_COLOURS_SETTING_KEY = "chartCandleColours";
139
+
140
+ export type ChartCandleColours = "series" | "change";
141
+
142
+ export function chartCandleColours(value: unknown): ChartCandleColours {
143
+ return value === "change" ? "change" : "series";
144
+ }
145
+
132
146
  function fallbackSpec(symbol: string | null | undefined): ChartSpec {
133
147
  return symbol ? buildPriceChartPreset(symbol) : buildEmptyChartPreset();
134
148
  }
@@ -417,6 +431,7 @@ export function buildChartComposerPaneSettingsDef(
417
431
  [CHART_SETTING_KEYS.range]: spec.viewport.range,
418
432
  [CHART_SETTING_KEYS.resolution]: spec.viewport.resolution,
419
433
  [CHART_SETTING_KEYS.mode]: inlineStyleTarget?.style ?? "",
434
+ [CHART_CANDLE_COLOURS_SETTING_KEY]: chartCandleColours(settings[CHART_CANDLE_COLOURS_SETTING_KEY]),
420
435
  ...(realizedVol ? {
421
436
  [CHART_SETTING_KEYS.realizedVolWindow]: String(realizedVol.parameters.window ?? 30),
422
437
  [CHART_SETTING_KEYS.realizedVolEstimator]: realizedVol.parameters.estimator ?? "close-to-close",
@@ -494,6 +509,18 @@ export function buildChartComposerPaneSettingsDef(
494
509
  options: modes.map((mode) => ({ value: mode, label: mode.toUpperCase() })),
495
510
  }]
496
511
  : []),
512
+ ...(spec.series.some((series) => isOhlcSeriesStyle(series.style))
513
+ ? [{
514
+ key: CHART_CANDLE_COLOURS_SETTING_KEY,
515
+ label: "Candle Colours",
516
+ description: "Draw rising candles in their series' colour, or in the theme's gain colour against its loss colour.",
517
+ type: "select" as const,
518
+ options: [
519
+ { value: "series", label: "Series colour" },
520
+ { value: "change", label: "Gains & losses" },
521
+ ],
522
+ }]
523
+ : []),
497
524
  ],
498
525
  applyValue: applyChartComposerPaneSetting,
499
526
  };
@@ -1,5 +1,6 @@
1
+ import { useAsyncResource } from "../../../react/async-resource";
1
2
  import { logFailure } from "../../../utils/log-failure";
2
- import { useEffect, useMemo, useState, useSyncExternalStore } from "react";
3
+ import { useCallback, useEffect, useMemo, useState, useSyncExternalStore } from "react";
3
4
  import {
4
5
  buildTickerSearchCandidates,
5
6
  searchTickerCandidates,
@@ -84,7 +85,7 @@ function candidateToInstrument(candidate: {
84
85
  * folder) are asked for once and listed after the rest; elsewhere, sources
85
86
  * that change while the app runs catch up when the catalog opens.
86
87
  */
87
- export function useRegisteredSeries(): readonly SeriesCatalogEntry[] {
88
+ export function useRegisteredSeries() {
88
89
  const registry = getSharedRegistry();
89
90
  const series = registry?.series;
90
91
  const version = useSyncExternalStore(
@@ -93,29 +94,32 @@ export function useRegisteredSeries(): readonly SeriesCatalogEntry[] {
93
94
  () => 0,
94
95
  );
95
96
  const disabledPlugins = useOptionalAppSelector((state) => state.config.disabledPlugins, EMPTY_PLUGINS);
96
- const [remote, setRemote] = useState<readonly SeriesCatalogEntry[] | null>(null);
97
+ const remoteLoader = useMemo(() => registry?.usesRemoteCapabilities && series ? async () => {
98
+ try {
99
+ const result = await registry.invokeCapability<{ entries: SeriesCatalogEntry[] }>(SERIES_CATALOG_CAPABILITY_ID, "list", {});
100
+ if (!Array.isArray(result?.entries)) throw new Error("Invalid series catalog response");
101
+ return result.entries;
102
+ } catch (error) {
103
+ logFailure("chart-composer.use-series-catalog", "Load remote chart series catalog failed")(error);
104
+ throw new Error("Series catalog unavailable.");
105
+ }
106
+ } : null, [registry, series]);
107
+ const remote = useAsyncResource(remoteLoader);
97
108
 
98
109
  useEffect(() => {
99
- if (!registry || !series) return;
100
- if (!registry.usesRemoteCapabilities) {
101
- void series.refresh();
102
- return;
103
- }
104
- let cancelled = false;
105
- void registry.invokeCapability<{ entries: SeriesCatalogEntry[] }>(SERIES_CATALOG_CAPABILITY_ID, "list", {})
106
- .then((result) => {
107
- if (!cancelled && Array.isArray(result?.entries)) setRemote(result.entries);
108
- })
109
- .catch(logFailure("chart-composer.use-series-catalog", "Load remote chart series catalog failed"));
110
- return () => { cancelled = true; };
110
+ if (registry && series && !registry.usesRemoteCapabilities) void series.refresh();
111
111
  }, [registry, series]);
112
+ const retry = useCallback(async () => {
113
+ if (remoteLoader) await remote.reload();
114
+ else await series?.refresh();
115
+ }, [remoteLoader, remote.reload, series]);
112
116
 
113
- return useMemo(() => {
117
+ const entries = useMemo(() => {
114
118
  const local = series?.catalog() ?? EMPTY_ENTRIES;
115
119
  let entries = local;
116
- if (remote?.length) {
120
+ if (remote.data?.length) {
117
121
  const known = new Set(local.map((entry) => entry.id));
118
- const extra = remote.filter((entry) => !known.has(entry.id));
122
+ const extra = remote.data.filter((entry) => !known.has(entry.id));
119
123
  if (extra.length) entries = [...local, ...extra];
120
124
  }
121
125
  if (disabledPlugins.length === 0) return entries;
@@ -123,7 +127,8 @@ export function useRegisteredSeries(): readonly SeriesCatalogEntry[] {
123
127
  return entries.some((entry) => disabled.has(entry.pluginId))
124
128
  ? entries.filter((entry) => !disabled.has(entry.pluginId))
125
129
  : entries;
126
- }, [disabledPlugins, remote, series, version]);
130
+ }, [disabledPlugins, remote.data, series, version]);
131
+ return { entries, loading: remote.loading, error: remote.error, retry };
127
132
  }
128
133
 
129
134
  export function useCatalogUniverse(query: string): {
@@ -230,6 +235,7 @@ export interface SeriesCatalogSearchResult {
230
235
  loading: boolean;
231
236
  /** Set when a lookup failed, so an outage is never reported as zero matches. */
232
237
  error: string | null;
238
+ retry: () => Promise<void>;
233
239
  }
234
240
 
235
241
  function searchFailureMessage(error: unknown): string {
@@ -248,6 +254,11 @@ export function useSeriesCatalogSuggestions({
248
254
  }): SeriesCatalogSearchResult {
249
255
  const tickers = useOptionalAppSelector((state) => state.tickers, EMPTY_TICKERS);
250
256
  const registered = useRegisteredSeries();
257
+ const [attempt, setAttempt] = useState(0);
258
+ const retry = useCallback(async () => {
259
+ setAttempt((value) => value + 1);
260
+ await registered.retry();
261
+ }, [registered.retry]);
251
262
  const analysis = useMemo(() => analyzeSeriesSearchQuery(query), [query]);
252
263
  const [providerSearch, setProviderSearch] = useState<{
253
264
  query: string;
@@ -295,7 +306,7 @@ export function useSeriesCatalogSuggestions({
295
306
  controller.abort();
296
307
  clearTimeout(timer);
297
308
  };
298
- }, [enabled, query]);
309
+ }, [attempt, enabled, query]);
299
310
 
300
311
  useEffect(() => {
301
312
  const instrumentQuery = analysis.instrumentQuery.trim();
@@ -344,7 +355,7 @@ export function useSeriesCatalogSuggestions({
344
355
  cancelled = true;
345
356
  clearTimeout(timer);
346
357
  };
347
- }, [analysis.directInstrument, analysis.instrumentQuery, enabled, tickers]);
358
+ }, [analysis.directInstrument, analysis.instrumentQuery, attempt, enabled, tickers]);
348
359
 
349
360
  const instruments = search.query === analysis.instrumentQuery
350
361
  ? search.instruments
@@ -353,18 +364,21 @@ export function useSeriesCatalogSuggestions({
353
364
  // and they arrive well before plugin catalogs (which can take seconds), so
354
365
  // late plugin results append below instead of pushing the list around.
355
366
  const suggestions = useMemo(() => {
356
- const builtIn = buildSeriesCatalogSuggestions(query, defaultInstrument, instruments, MAX_SERIES_SUGGESTIONS, registered);
367
+ const builtIn = buildSeriesCatalogSuggestions(query, defaultInstrument, instruments, MAX_SERIES_SUGGESTIONS, registered.entries);
357
368
  const provider = providerSearch.query === query.trim() ? providerSearch.suggestions : [];
358
369
  return [...builtIn, ...provider.filter((entry) => !builtIn.some((candidate) => candidate.id === entry.id))]
359
370
  .slice(0, MAX_SERIES_SUGGESTIONS);
360
- }, [defaultInstrument, instruments, providerSearch, query, registered]);
371
+ }, [defaultInstrument, instruments, providerSearch, query, registered.entries]);
361
372
 
362
373
  return {
363
374
  suggestions,
364
375
  instruments,
365
- loading: (search.loading && search.query === analysis.instrumentQuery)
376
+ retry,
377
+ loading: registered.loading || (search.loading && search.query === analysis.instrumentQuery)
366
378
  || (providerSearch.loading && providerSearch.query === query.trim()),
367
- error: (search.query === analysis.instrumentQuery ? search.error : null)
368
- ?? (providerSearch.query === query.trim() ? providerSearch.error : null),
379
+ error: [registered.error,
380
+ search.query === analysis.instrumentQuery ? search.error : null,
381
+ providerSearch.query === query.trim() ? providerSearch.error : null,
382
+ ].filter(Boolean).join(" ") || null,
369
383
  };
370
384
  }
@@ -0,0 +1,93 @@
1
+ import type { ChatFunction } from "../pane-links/commands";
2
+
3
+ /** A function offered after a backtick: its code and what it is called. */
4
+ export interface ChatCommandSuggestion {
5
+ code: string;
6
+ name: string;
7
+ takesArg: boolean;
8
+ }
9
+
10
+ interface ChatCommandTrigger {
11
+ /** Where the opening backtick is. */
12
+ start: number;
13
+ end: number;
14
+ query: string;
15
+ }
16
+
17
+ // An opening backtick after a space or punctuation (not inside a word, not a
18
+ // second tick of a fence), then the start of a code.
19
+ const COMMAND_TRIGGER_RE = /(^|[\s([{<,.;:!?"'])`([A-Za-z][A-Za-z0-9+-]{0,11})$/;
20
+ const FENCE_RE = /^\s{0,3}```/;
21
+ const MAX_COMMAND_SUGGESTIONS = 5;
22
+
23
+ /**
24
+ * The code being typed after an opening backtick, up to the cursor. A
25
+ * backtick that closes a span already open on the line, or one inside a
26
+ * fenced code block, starts no code.
27
+ */
28
+ export function detectChatCommandTrigger(draft: string, cursorOffset: number): ChatCommandTrigger | null {
29
+ const cursor = Math.max(0, Math.min(cursorOffset, draft.length));
30
+ const beforeCursor = draft.slice(0, cursor);
31
+ const match = beforeCursor.match(COMMAND_TRIGGER_RE);
32
+ if (!match) return null;
33
+ const query = match[2]!;
34
+ const start = cursor - query.length - 1;
35
+ const lines = draft.slice(0, start).split("\n");
36
+ const line = lines[lines.length - 1]!;
37
+ if ((line.match(/`/g)?.length ?? 0) % 2 === 1) return null;
38
+ if (lines.slice(0, -1).filter((previous) => FENCE_RE.test(previous)).length % 2 === 1) return null;
39
+ return { start, end: cursor, query };
40
+ }
41
+
42
+ /**
43
+ * The functions whose code starts with the query, the exact one and the
44
+ * shortest first, then those with a word of their name that does, as people
45
+ * are found by their handle or their name.
46
+ */
47
+ export function filterCommandSuggestions(
48
+ functions: ReadonlyMap<string, ChatFunction>,
49
+ query: string,
50
+ limit = MAX_COMMAND_SUGGESTIONS,
51
+ ): ChatCommandSuggestion[] {
52
+ const upper = query.trim().toUpperCase();
53
+ if (!upper) return [];
54
+ const lower = upper.toLowerCase();
55
+ const byCode: ChatFunction[] = [];
56
+ const byName: ChatFunction[] = [];
57
+ for (const fn of new Set(functions.values())) {
58
+ if (fn.unlisted) continue;
59
+ if (fn.code.startsWith(upper) || fn.aliases.includes(upper)) byCode.push(fn);
60
+ else if (fn.name.toLowerCase().split(/[\s/&()-]+/).some((word) => word.startsWith(lower))) byName.push(fn);
61
+ }
62
+ const rank = (fn: ChatFunction) => (fn.code === upper || fn.aliases.includes(upper) ? 0 : 1);
63
+ byCode.sort((a, b) => rank(a) - rank(b) || a.code.length - b.code.length || a.code.localeCompare(b.code));
64
+ byName.sort((a, b) => a.code.localeCompare(b.code));
65
+ return [...byCode, ...byName].slice(0, Math.min(limit, MAX_COMMAND_SUGGESTIONS))
66
+ .map(({ code, name, arg }) => ({ code, name, takesArg: arg !== "none" }));
67
+ }
68
+
69
+ /**
70
+ * The code in place of what was typed, with the closing backtick. A function
71
+ * that takes an argument leaves the cursor before it, after a space, for the
72
+ * ticker or topic; any other leaves it after the span and a space.
73
+ */
74
+ export function applyCommandSuggestion(
75
+ draft: string,
76
+ trigger: ChatCommandTrigger,
77
+ suggestion: ChatCommandSuggestion,
78
+ ): { draft: string; cursorOffset: number } {
79
+ const before = draft.slice(0, trigger.start);
80
+ let after = draft.slice(trigger.end);
81
+ // A closing backtick already there is the one this span ends with.
82
+ if (after.startsWith("`")) after = after.slice(1);
83
+ const code = `\`${suggestion.code}`;
84
+ if (suggestion.takesArg) {
85
+ return { draft: `${before}${code} \`${after}`, cursorOffset: before.length + code.length + 1 };
86
+ }
87
+ // A space to go on writing after, unless the text there already goes on.
88
+ const suffix = /^[\s.,;:!?)\]]/.test(after) ? "" : " ";
89
+ return {
90
+ draft: `${before}${code}\`${suffix}${after}`,
91
+ cursorOffset: before.length + code.length + 1 + suffix.length,
92
+ };
93
+ }
@@ -10,7 +10,6 @@ import { Button, ListView } from "../../../../components/ui";
10
10
  import { COMPOSER_ACTION_WIDTH } from "../layout";
11
11
  import { isManagedBot } from "../ghost-user";
12
12
  import { ChatBotIcon } from "../message/person-icons";
13
- import type { ChatMentionSuggestion } from "./mentions";
14
13
  import type { ChatDraftAttachment } from "../controller/attachments";
15
14
  import { DesktopChatAttachButton, DesktopChatAttachmentStrip } from "../attachments/desktop";
16
15
  import { TerminalChatAttachmentRows } from "../attachments/terminal";
@@ -48,7 +47,8 @@ interface ChatComposerAreaProps {
48
47
  replyPreview: string;
49
48
  replyTo: ChatMessage | null;
50
49
  sendMessage: () => void;
51
- mentionSuggestions: ChatMentionSuggestion[];
50
+ /** People after @, or functions after a backtick: one list at a time. */
51
+ suggestions: ChatComposerSuggestion[];
52
52
  mentionSelectedIndex: number;
53
53
  onMentionCursorChange: () => void;
54
54
  onMentionSelect: (index?: number) => boolean;
@@ -69,6 +69,13 @@ interface ChatComposerAreaProps {
69
69
  attachPickerRef: { current: (() => void) | null };
70
70
  }
71
71
 
72
+ /** A row of the list over the composer: a handle, or a function's code with its name at the end. */
73
+ interface ChatComposerSuggestion {
74
+ id: string;
75
+ label: string;
76
+ detail?: string;
77
+ }
78
+
72
79
  function ChatMentionSuggestions({
73
80
  contentWidth,
74
81
  onSelect,
@@ -78,14 +85,14 @@ function ChatMentionSuggestions({
78
85
  contentWidth: number;
79
86
  onSelect: (index?: number) => boolean;
80
87
  selectedIndex: number;
81
- suggestions: ChatMentionSuggestion[];
88
+ suggestions: ChatComposerSuggestion[];
82
89
  }) {
83
90
  if (suggestions.length === 0) return null;
84
91
  const width = Math.max(18, Math.min(contentWidth, 44));
85
92
  return (
86
93
  <Box width={width}>
87
94
  <ListView
88
- items={suggestions.map((suggestion) => ({ id: suggestion.username, label: `@${suggestion.username}` }))}
95
+ items={suggestions}
89
96
  selectedIndex={selectedIndex}
90
97
  height={suggestions.length}
91
98
  rowGap={0}
@@ -119,7 +126,7 @@ export function ChatComposerArea({
119
126
  replyPreview,
120
127
  replyTo,
121
128
  sendMessage,
122
- mentionSuggestions,
129
+ suggestions,
123
130
  mentionSelectedIndex,
124
131
  onMentionCursorChange,
125
132
  onMentionSelect,
@@ -211,7 +218,7 @@ export function ChatComposerArea({
211
218
  contentWidth={contentWidth}
212
219
  onSelect={onMentionSelect}
213
220
  selectedIndex={mentionSelectedIndex}
214
- suggestions={mentionSuggestions}
221
+ suggestions={suggestions}
215
222
  />
216
223
 
217
224
  {!editingMessage && draftAttachments.length > 0 && (nativePaneChrome ? (
@@ -9,6 +9,7 @@ import { useOptionalDialog, type PromptContext } from "../../../../ui/dialog";
9
9
  import { chatAuthorName, isDiscordGhost } from "../ghost-user";
10
10
  import { chatMessageOpenTargets, type ChatOpenTarget } from "./open-targets";
11
11
  import { chatMessagePaneLinks, type ChatMessagePaneLink } from "../pane-links/model";
12
+ import { useChatFunctions } from "../pane-links/commands";
12
13
  import { openChatImageViewer } from "../attachments/desktop";
13
14
  import { canReactToMessage } from "../reactions";
14
15
 
@@ -102,11 +103,13 @@ export function useChatFooter({
102
103
  const rendererHost = useRendererHost();
103
104
  const { links: paneLinks, content } = chatMessagePaneLinks(selectedMessage);
104
105
  const attachments = selectedMessage?.attachments;
106
+ const { nativePaneChrome } = useUiCapabilities();
107
+ const functions = useChatFunctions();
108
+ // The desktop and the web draw functions written in a message as chips, so Open offers them too.
105
109
  const openTargets = useMemo(
106
- () => chatMessageOpenTargets(content, catalog, attachments, paneLinks),
107
- [attachments, catalog, content, paneLinks],
110
+ () => chatMessageOpenTargets(content, catalog, attachments, paneLinks, nativePaneChrome ? functions : undefined),
111
+ [attachments, catalog, content, functions, nativePaneChrome, paneLinks],
108
112
  );
109
- const { nativePaneChrome } = useUiCapabilities();
110
113
 
111
114
  const openTarget = useCallback((target: ChatOpenTarget) => {
112
115
  if (target.kind === "pane") {