@gloom-sh/terminal 0.18.5 → 0.19.1

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 (570) 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 +163 -8
  5. package/src/api-client/app-attention.ts +3 -0
  6. package/src/api-client/beneficial-owners.ts +3 -0
  7. package/src/api-client/company-kpis.ts +3 -0
  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 +19 -7
  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/hiring.ts +3 -0
  14. package/src/api-client/index.ts +15 -0
  15. package/src/api-client/ipo.ts +24 -1
  16. package/src/api-client/market-discovery.ts +10 -0
  17. package/src/api-client/market-movers.ts +7 -0
  18. package/src/api-client/members.ts +1 -1
  19. package/src/api-client/mna.ts +3 -0
  20. package/src/api-client/paths.ts +9 -1
  21. package/src/api-client/perps.ts +43 -0
  22. package/src/api-client/research-activity.ts +3 -2
  23. package/src/api-client/roadmap.ts +85 -0
  24. package/src/api-client/types.ts +82 -2
  25. package/src/app/pane-runtime/plugin-bindings.ts +9 -5
  26. package/src/app/runtime/broker-import.ts +0 -2
  27. package/src/app/runtime/plugin-bindings.ts +73 -56
  28. package/src/architecture/core-builtin-allowlist.json +2 -2
  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/market.ts +107 -38
  35. package/src/cli/commands/news-search.ts +22 -0
  36. package/src/cli/commands/options-chain.ts +48 -7
  37. package/src/cli/commands/options-leaps.ts +9 -3
  38. package/src/cli/commands/overview.ts +147 -32
  39. package/src/cli/commands/search.ts +21 -1
  40. package/src/cli/commands/themes.ts +11 -2
  41. package/src/cli/commands/ticker.ts +87 -36
  42. package/src/cli/company-name-hint.ts +41 -0
  43. package/src/cli/earnings-calendar.ts +1 -1
  44. package/src/cli/errors.ts +25 -2
  45. package/src/cli/glossary.ts +31 -2
  46. package/src/cli/history-rows.ts +57 -9
  47. package/src/cli/index-roots.ts +0 -7
  48. package/src/cli/index.ts +2 -0
  49. package/src/cli/listing-arg.ts +235 -60
  50. package/src/cli/output-file.ts +68 -0
  51. package/src/cli/pane-functions/capabilities.ts +4 -0
  52. package/src/cli/pane-functions/catalog.ts +80 -7
  53. package/src/cli/pane-functions/discovery.ts +21 -1
  54. package/src/cli/pane-functions/dom.ts +13 -3
  55. package/src/cli/pane-functions/freshness.ts +93 -4
  56. package/src/cli/pane-functions/function-help.ts +20 -10
  57. package/src/cli/pane-functions/headless.ts +17 -7
  58. package/src/cli/pane-functions/index.ts +54 -16
  59. package/src/cli/pane-functions/market-session.ts +66 -4
  60. package/src/cli/pane-functions/options.ts +19 -1
  61. package/src/cli/pane-functions/resolver.ts +19 -8
  62. package/src/cli/pane-functions/screenshot-assessment.ts +6 -4
  63. package/src/cli/pane-functions/screenshot-types.ts +5 -0
  64. package/src/cli/quote-answer.ts +22 -11
  65. package/src/cli/saved-copy-persistence.ts +88 -0
  66. package/src/cli/ticker-resolution.ts +3 -2
  67. package/src/components/chart/composite/composite-chart.tsx +4 -0
  68. package/src/components/chart/composite/interactions.ts +6 -0
  69. package/src/components/chart/composite/legend.tsx +45 -7
  70. package/src/components/chart/composite/panel-gestures.ts +33 -0
  71. package/src/components/chart/composite/panel-surface.tsx +52 -3
  72. package/src/components/chart/composite/rasterizer.ts +24 -13
  73. package/src/components/chart/composite/scene.ts +39 -4
  74. package/src/components/chart/composite/series-cues.ts +14 -0
  75. package/src/components/chart/composite/text-renderer.ts +9 -2
  76. package/src/components/chart/composite/tool-catalog.ts +36 -0
  77. package/src/components/chart/composite/tools.ts +203 -44
  78. package/src/components/chart/composite/use-composite-chart-keyboard.ts +11 -4
  79. package/src/components/command-bar/assist/inventory.ts +2 -1
  80. package/src/components/command-bar/commands/direct/index.ts +28 -13
  81. package/src/components/command-bar/list/model.ts +5 -0
  82. package/src/components/command-bar/pane-templates/items.ts +3 -2
  83. package/src/components/command-bar/routes/root/runtime.ts +6 -7
  84. package/src/components/command-bar/selection-runtime.ts +52 -3
  85. package/src/components/command-bar/surface/index.tsx +2 -0
  86. package/src/components/command-bar/workflow/collection-actions.ts +3 -3
  87. package/src/components/layout/pane/capture-stamp.ts +2 -2
  88. package/src/components/layout/pane/footer/index.tsx +22 -6
  89. package/src/components/layout/pane/footer/model.ts +83 -15
  90. package/src/components/layout/pane/status-footer.ts +35 -2
  91. package/src/components/layout/status-widget-space.ts +9 -0
  92. package/src/components/onboarding/onboarding-wizard.tsx +4 -1
  93. package/src/components/onboarding/portfolio-step/positions-panel.tsx +12 -7
  94. package/src/components/onboarding/wizard-broker-sync.ts +0 -1
  95. package/src/components/onboarding/wizard-positions.ts +3 -3
  96. package/src/components/ticker/list-table-view.tsx +8 -21
  97. package/src/components/ui/choice-dialog.tsx +23 -18
  98. package/src/components/ui/choice-groups.ts +18 -0
  99. package/src/components/ui/data-table/opentui/index.tsx +9 -7
  100. package/src/components/ui/data-table/opentui/model.ts +26 -1
  101. package/src/components/ui/data-table/types.ts +2 -0
  102. package/src/components/ui/display.tsx +18 -3
  103. package/src/components/ui/icon.tsx +4 -1
  104. package/src/components/ui/list-view.tsx +8 -4
  105. package/src/components/ui/query-bar-keys.tsx +2 -0
  106. package/src/components/ui/query-bar.tsx +1 -0
  107. package/src/components/ui/select-button.tsx +5 -1
  108. package/src/components/ui/select-field.tsx +1 -0
  109. package/src/components/ui/table-layout.ts +8 -5
  110. package/src/core/app-runtime.ts +2 -0
  111. package/src/core/app-services.ts +2 -2
  112. package/src/data/config/chart-settings.ts +1 -1
  113. package/src/data/json-ticker-repository.ts +17 -8
  114. package/src/data/plugin-cache.ts +5 -3
  115. package/src/data/saved-copies.ts +8 -0
  116. package/src/data/ticker-repository.ts +16 -9
  117. package/src/data/ticker-writes.ts +81 -0
  118. package/src/data/ttl-cache.ts +165 -0
  119. package/src/docs/snapshot.json +103 -142
  120. package/src/i18n/es.ts +20 -6
  121. package/src/i18n/ja.ts +20 -6
  122. package/src/i18n/ko.ts +20 -6
  123. package/src/i18n/zh-cn.ts +20 -6
  124. package/src/i18n/zh-tw.ts +20 -6
  125. package/src/market-data/depositary-pair.ts +27 -3
  126. package/src/market-data/fund-facts.ts +50 -0
  127. package/src/market-data/market/change-basis.ts +16 -3
  128. package/src/market-data/market/format.ts +25 -0
  129. package/src/market-data/market/freshness.ts +2 -1
  130. package/src/market-data/options-alternatives.ts +81 -4
  131. package/src/market-data/quotes/metadata.ts +7 -0
  132. package/src/news/aggregator.ts +119 -9
  133. package/src/news/cloud-session.ts +21 -0
  134. package/src/news/hooks.ts +1 -0
  135. package/src/news/news-model.ts +1 -0
  136. package/src/news/types.ts +17 -0
  137. package/src/plugins/builtin/alerts/alert-engine.ts +20 -1
  138. package/src/plugins/builtin/alerts/alert-log.ts +33 -0
  139. package/src/plugins/builtin/alerts/calendar-events.ts +10 -4
  140. package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
  141. package/src/plugins/builtin/alerts/cli.ts +362 -80
  142. package/src/plugins/builtin/alerts/command.ts +25 -1
  143. package/src/plugins/builtin/alerts/event-rows.ts +24 -3
  144. package/src/plugins/builtin/alerts/events-pane.tsx +34 -10
  145. package/src/plugins/builtin/alerts/fired-log.ts +73 -9
  146. package/src/plugins/builtin/alerts/format.ts +75 -5
  147. package/src/plugins/builtin/alerts/headless.ts +34 -12
  148. package/src/plugins/builtin/alerts/history-rows.ts +3 -2
  149. package/src/plugins/builtin/alerts/history.ts +1 -1
  150. package/src/plugins/builtin/alerts/index.tsx +79 -16
  151. package/src/plugins/builtin/alerts/metric-command.ts +77 -4
  152. package/src/plugins/builtin/alerts/metric-data.ts +54 -13
  153. package/src/plugins/builtin/alerts/metric-model.ts +163 -14
  154. package/src/plugins/builtin/alerts/metric-parity-fixture.json +1 -1
  155. package/src/plugins/builtin/alerts/pane.tsx +61 -23
  156. package/src/plugins/builtin/alerts/quotes.ts +6 -0
  157. package/src/plugins/builtin/alerts/storage.ts +12 -8
  158. package/src/plugins/builtin/alerts/types.ts +4 -0
  159. package/src/plugins/builtin/analytics/index.tsx +2 -1
  160. package/src/plugins/builtin/analytics/risk-client.ts +16 -5
  161. package/src/plugins/builtin/analytics/risk-fx.ts +70 -15
  162. package/src/plugins/builtin/analytics/risk-headless.ts +27 -0
  163. package/src/plugins/builtin/analytics/risk-model.ts +55 -9
  164. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
  165. package/src/plugins/builtin/attention/index.tsx +2 -1
  166. package/src/plugins/builtin/attention/listing.ts +14 -0
  167. package/src/plugins/builtin/broker-manager/index.tsx +7 -3
  168. package/src/plugins/builtin/buildout/cells.tsx +12 -4
  169. package/src/plugins/builtin/buildout/format.ts +38 -0
  170. package/src/plugins/builtin/buildout/model/normalizers.ts +36 -1
  171. package/src/plugins/builtin/buildout/pane/index.tsx +4 -1
  172. package/src/plugins/builtin/buildout/table-model.ts +23 -2
  173. package/src/plugins/builtin/builtin-plugin-meta.ts +6 -2
  174. package/src/plugins/builtin/cds/client.ts +142 -30
  175. package/src/plugins/builtin/cds/headless.ts +35 -8
  176. package/src/plugins/builtin/cds/model.ts +145 -18
  177. package/src/plugins/builtin/cds/pane.tsx +16 -5
  178. package/src/plugins/builtin/changelog/index.tsx +9 -6
  179. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +3 -0
  180. package/src/plugins/builtin/chart-composer/cli-options.ts +4 -0
  181. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +7 -0
  182. package/src/plugins/builtin/chart-composer/editor/controller.ts +2 -2
  183. package/src/plugins/builtin/chart-composer/follow-binding.ts +14 -2
  184. package/src/plugins/builtin/chart-composer/headless-schema.ts +7 -1
  185. package/src/plugins/builtin/chart-composer/headless.ts +6 -1
  186. package/src/plugins/builtin/chart-composer/index.tsx +5 -6
  187. package/src/plugins/builtin/chart-composer/pane.tsx +37 -8
  188. package/src/plugins/builtin/chart-composer/settings.ts +27 -0
  189. package/src/plugins/builtin/chat/content/command-suggestions.ts +1 -0
  190. package/src/plugins/builtin/chat/controller/index.ts +5 -1
  191. package/src/plugins/builtin/chat/controller/notifications.ts +33 -3
  192. package/src/plugins/builtin/chat/controller/state.ts +25 -2
  193. package/src/plugins/builtin/chat/controller/unread-people.ts +89 -23
  194. package/src/plugins/builtin/chat/controller/utils.ts +7 -2
  195. package/src/plugins/builtin/chat/pane-links/commands.ts +7 -1
  196. package/src/plugins/builtin/chat/status-preview.ts +79 -0
  197. package/src/plugins/builtin/chat/status-widget.tsx +147 -10
  198. package/src/plugins/builtin/chat/unread-badge.ts +51 -10
  199. package/src/plugins/builtin/clinical-trials/client.ts +49 -4
  200. package/src/plugins/builtin/clinical-trials/model.ts +86 -31
  201. package/src/plugins/builtin/clinical-trials/pane.tsx +61 -49
  202. package/src/plugins/builtin/clinical-trials/sponsor-listing.ts +90 -0
  203. package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +95 -15
  204. package/src/plugins/builtin/clinical-trials/trial-pages.ts +50 -0
  205. package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
  206. package/src/plugins/builtin/company-attention/building.tsx +5 -1
  207. package/src/plugins/builtin/company-attention/client.ts +8 -9
  208. package/src/plugins/builtin/company-attention/evidence.ts +4 -1
  209. package/src/plugins/builtin/company-attention/headless.ts +2 -1
  210. package/src/plugins/builtin/company-attention/moves.tsx +5 -1
  211. package/src/plugins/builtin/company-attention/pane.tsx +12 -2
  212. package/src/plugins/builtin/company-attention/signals.tsx +5 -1
  213. package/src/plugins/builtin/company-kpis/headless.ts +8 -3
  214. package/src/plugins/builtin/company-kpis/model.ts +73 -5
  215. package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
  216. package/src/plugins/builtin/composite-plugins.ts +2 -1
  217. package/src/plugins/builtin/correlation/headless.ts +13 -5
  218. package/src/plugins/builtin/correlation/history.ts +22 -0
  219. package/src/plugins/builtin/cot/client.ts +22 -18
  220. package/src/plugins/builtin/cot/headless.ts +15 -10
  221. package/src/plugins/builtin/cot/model.ts +14 -1
  222. package/src/plugins/builtin/cot/pane.tsx +22 -14
  223. package/src/plugins/builtin/credit-boards/client.ts +6 -10
  224. package/src/plugins/builtin/credit-conditions/headless.ts +8 -2
  225. package/src/plugins/builtin/credit-conditions/index.tsx +5 -1
  226. package/src/plugins/builtin/credit-conditions/model.ts +4 -0
  227. package/src/plugins/builtin/credit-conditions/series.ts +3 -1
  228. package/src/plugins/builtin/credit-documents/detail.tsx +2 -2
  229. package/src/plugins/builtin/credit-documents/export.ts +173 -0
  230. package/src/plugins/builtin/credit-documents/headless.ts +10 -10
  231. package/src/plugins/builtin/credit-documents/model.ts +104 -14
  232. package/src/plugins/builtin/credit-documents/pane.tsx +12 -5
  233. package/src/plugins/builtin/custom-view/sources.ts +2 -0
  234. package/src/plugins/builtin/dividend-yield/client.ts +131 -23
  235. package/src/plugins/builtin/dividend-yield/headless.ts +44 -16
  236. package/src/plugins/builtin/dividend-yield/model.ts +8 -5
  237. package/src/plugins/builtin/dividend-yield/pane.tsx +38 -13
  238. package/src/plugins/builtin/dividend-yield/ranking.ts +37 -14
  239. package/src/plugins/builtin/dividend-yield/types.ts +20 -2
  240. package/src/plugins/builtin/dividend-yield/view.ts +30 -2
  241. package/src/plugins/builtin/docs/targets.ts +5 -1
  242. package/src/plugins/builtin/doe/headless.ts +3 -2
  243. package/src/plugins/builtin/doe/pane.tsx +13 -3
  244. package/src/plugins/builtin/earnings/board-model.ts +16 -15
  245. package/src/plugins/builtin/earnings/board.tsx +62 -71
  246. package/src/plugins/builtin/earnings/headless.ts +40 -4
  247. package/src/plugins/builtin/earnings/scope.ts +31 -43
  248. package/src/plugins/builtin/earnings-calls/calendar.tsx +2 -2
  249. package/src/plugins/builtin/earnings-calls/data.ts +18 -6
  250. package/src/plugins/builtin/earnings-calls/format.ts +31 -2
  251. package/src/plugins/builtin/earnings-calls/headless.ts +20 -5
  252. package/src/plugins/builtin/earnings-calls/model.ts +6 -1
  253. package/src/plugins/builtin/earnings-calls/pane.tsx +12 -4
  254. package/src/plugins/builtin/econ/calendar-model.ts +48 -1
  255. package/src/plugins/builtin/econ/calendar-source.ts +4 -2
  256. package/src/plugins/builtin/econ/detail-view.tsx +2 -2
  257. package/src/plugins/builtin/econ/figures.ts +48 -0
  258. package/src/plugins/builtin/econ/index.tsx +14 -5
  259. package/src/plugins/builtin/econ/print-watch.ts +3 -1
  260. package/src/plugins/builtin/econ/types.ts +2 -0
  261. package/src/plugins/builtin/econ-statistics/client.ts +11 -0
  262. package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
  263. package/src/plugins/builtin/econ-statistics/headless.ts +16 -6
  264. package/src/plugins/builtin/econ-statistics/pane.tsx +8 -4
  265. package/src/plugins/builtin/econ-statistics/stats.ts +39 -9
  266. package/src/plugins/builtin/equity-screener/categories.ts +179 -0
  267. package/src/plugins/builtin/equity-screener/client.ts +4 -2
  268. package/src/plugins/builtin/equity-screener/headless.ts +144 -13
  269. package/src/plugins/builtin/equity-screener/ideas.ts +49 -0
  270. package/src/plugins/builtin/equity-screener/model.ts +99 -10
  271. package/src/plugins/builtin/equity-screener/pane.tsx +68 -13
  272. package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
  273. package/src/plugins/builtin/futures/use-board-terms.ts +42 -28
  274. package/src/plugins/builtin/futures/use-front-returns.ts +45 -28
  275. package/src/plugins/builtin/fx-matrix/client.ts +6 -3
  276. package/src/plugins/builtin/fx-matrix/headless.ts +1 -1
  277. package/src/plugins/builtin/fx-matrix/index.tsx +4 -2
  278. package/src/plugins/builtin/fx-matrix/live-legs.ts +42 -3
  279. package/src/plugins/builtin/gpu/headless.ts +8 -2
  280. package/src/plugins/builtin/help/function-index.ts +4 -1
  281. package/src/plugins/builtin/help/index.tsx +62 -59
  282. package/src/plugins/builtin/help/shortcut-model.ts +1 -1
  283. package/src/plugins/builtin/help/shortcut-table.tsx +5 -6
  284. package/src/plugins/builtin/help/table-cells.tsx +13 -2
  285. package/src/plugins/builtin/holders/beneficial-client.ts +5 -1
  286. package/src/plugins/builtin/holders/beneficial-view.tsx +3 -0
  287. package/src/plugins/builtin/holders/fund-holders.ts +51 -0
  288. package/src/plugins/builtin/holders/headless.ts +22 -3
  289. package/src/plugins/builtin/holders/pane.tsx +3 -0
  290. package/src/plugins/builtin/holders/report-rows.ts +4 -1
  291. package/src/plugins/builtin/insider/headless.ts +2 -2
  292. package/src/plugins/builtin/ipo-calendar/client.ts +18 -2
  293. package/src/plugins/builtin/ipo-calendar/headless.ts +75 -11
  294. package/src/plugins/builtin/ipo-calendar/listing-ipo.ts +160 -0
  295. package/src/plugins/builtin/ipo-calendar/model.ts +98 -2
  296. package/src/plugins/builtin/iv-history/client.ts +52 -11
  297. package/src/plugins/builtin/iv-history/format.ts +36 -23
  298. package/src/plugins/builtin/iv-history/headless.ts +33 -18
  299. package/src/plugins/builtin/iv-history/index.tsx +2 -2
  300. package/src/plugins/builtin/iv-history/model.ts +163 -27
  301. package/src/plugins/builtin/iv-history/pane.tsx +10 -6
  302. package/src/plugins/builtin/iv-history/screen-pane.tsx +22 -15
  303. package/src/plugins/builtin/iv-history/universe.ts +5 -1
  304. package/src/plugins/builtin/jobs/client.ts +25 -29
  305. package/src/plugins/builtin/kelly-sizer/model.ts +12 -8
  306. package/src/plugins/builtin/kelly-sizer/pane.tsx +9 -9
  307. package/src/plugins/builtin/kelly-sizer/sections.tsx +19 -10
  308. package/src/plugins/builtin/kelly-sizer/types.ts +10 -4
  309. package/src/plugins/builtin/macro-day/headless.ts +12 -5
  310. package/src/plugins/builtin/macro-day/index.tsx +1 -1
  311. package/src/plugins/builtin/macro-day/model.ts +39 -5
  312. package/src/plugins/builtin/macro-day/pane.tsx +27 -19
  313. package/src/plugins/builtin/macro-day/release-table.ts +2 -2
  314. package/src/plugins/builtin/market-heatmap/data.ts +8 -33
  315. package/src/plugins/builtin/market-movers/client.ts +27 -7
  316. package/src/plugins/builtin/market-movers/headless.ts +34 -12
  317. package/src/plugins/builtin/market-movers/index.tsx +26 -14
  318. package/src/plugins/builtin/market-movers/min-float.ts +74 -0
  319. package/src/plugins/builtin/market-movers/model.ts +4 -1
  320. package/src/plugins/builtin/market-movers/screener.ts +22 -4
  321. package/src/plugins/builtin/market-movers/session-body.tsx +13 -8
  322. package/src/plugins/builtin/market-movers/session-table.ts +4 -3
  323. package/src/plugins/builtin/market-movers/table.ts +17 -5
  324. package/src/plugins/builtin/members/breadth-pane.tsx +2 -1
  325. package/src/plugins/builtin/mna/headless.ts +2 -0
  326. package/src/plugins/builtin/mna/pane.tsx +3 -1
  327. package/src/plugins/builtin/news/headless.ts +3 -1
  328. package/src/plugins/builtin/news/index.tsx +4 -0
  329. package/src/plugins/builtin/news/wire/categories.ts +19 -3
  330. package/src/plugins/builtin/news/wire/industry-pane.tsx +1 -0
  331. package/src/plugins/builtin/news/wire/news/footer.ts +31 -5
  332. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +6 -1
  333. package/src/plugins/builtin/news/wire/news/table.tsx +2 -2
  334. package/src/plugins/builtin/openfda/client.ts +14 -2
  335. package/src/plugins/builtin/openfda/index.tsx +3 -2
  336. package/src/plugins/builtin/openfda/model.ts +9 -0
  337. package/src/plugins/builtin/openfda/pane.tsx +42 -6
  338. package/src/plugins/builtin/options/analytics.ts +29 -5
  339. package/src/plugins/builtin/options/enrichment.ts +17 -5
  340. package/src/plugins/builtin/options/table.ts +5 -2
  341. package/src/plugins/builtin/options/types.ts +2 -0
  342. package/src/plugins/builtin/options/view.tsx +17 -4
  343. package/src/plugins/builtin/options-positioning/client.ts +11 -0
  344. package/src/plugins/builtin/options-positioning/headless.ts +21 -8
  345. package/src/plugins/builtin/options-positioning/index.tsx +1 -1
  346. package/src/plugins/builtin/options-positioning/model.ts +78 -9
  347. package/src/plugins/builtin/options-positioning/pane.tsx +21 -6
  348. package/src/plugins/builtin/options-scenario/client.ts +116 -47
  349. package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +142 -10
  350. package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +206 -55
  351. package/src/plugins/builtin/options-scenario/csp-screen.ts +63 -23
  352. package/src/plugins/builtin/options-scenario/csp.ts +8 -2
  353. package/src/plugins/builtin/options-scenario/index.tsx +9 -7
  354. package/src/plugins/builtin/options-scenario/strategy-screen.ts +532 -0
  355. package/src/plugins/builtin/pe-band/model.ts +12 -49
  356. package/src/plugins/builtin/perps/cascade-tabs.tsx +161 -0
  357. package/src/plugins/builtin/perps/cascade.ts +236 -0
  358. package/src/plugins/builtin/perps/client.ts +56 -2
  359. package/src/plugins/builtin/perps/headless.ts +65 -9
  360. package/src/plugins/builtin/perps/index.tsx +7 -5
  361. package/src/plugins/builtin/perps/model.ts +18 -4
  362. package/src/plugins/builtin/perps/pane.tsx +19 -22
  363. package/src/plugins/builtin/perps/status-line.ts +25 -0
  364. package/src/plugins/builtin/plugin-module.ts +5 -1
  365. package/src/plugins/builtin/portfolio-list/cached-account.ts +3 -1
  366. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +2 -8
  367. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +4 -4
  368. package/src/plugins/builtin/portfolio-list/cli/render.ts +7 -5
  369. package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
  370. package/src/plugins/builtin/portfolio-list/pane/index.tsx +6 -7
  371. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +5 -2
  372. package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +3 -2
  373. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +0 -14
  374. package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +5 -2
  375. package/src/plugins/builtin/portfolio-list/table.tsx +15 -9
  376. package/src/plugins/builtin/primary-dealers/client.ts +13 -38
  377. package/src/plugins/builtin/realized-vol/client.ts +13 -5
  378. package/src/plugins/builtin/realized-vol/evidence.ts +29 -4
  379. package/src/plugins/builtin/realized-vol/headless.ts +8 -4
  380. package/src/plugins/builtin/realized-vol/index.tsx +2 -2
  381. package/src/plugins/builtin/realized-vol/model.ts +37 -6
  382. package/src/plugins/builtin/realized-vol/pane.tsx +11 -9
  383. package/src/plugins/builtin/relative-rotation/client.ts +18 -4
  384. package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
  385. package/src/plugins/builtin/relative-rotation/index.tsx +2 -1
  386. package/src/plugins/builtin/relative-rotation/model.ts +16 -40
  387. package/src/plugins/builtin/relative-rotation/pane.tsx +3 -2
  388. package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -2
  389. package/src/plugins/builtin/research/event-model.ts +40 -12
  390. package/src/plugins/builtin/research/headless.ts +1 -1
  391. package/src/plugins/builtin/research/index.tsx +7 -3
  392. package/src/plugins/builtin/research/relative-valuation-headless.ts +13 -26
  393. package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
  394. package/src/plugins/builtin/research/relative-valuation-pane.tsx +59 -20
  395. package/src/plugins/builtin/research/relative-valuation-peers.ts +217 -20
  396. package/src/plugins/builtin/research/relative-valuation-settings.ts +44 -0
  397. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -3
  398. package/src/plugins/builtin/reverse-dcf/model.ts +61 -6
  399. package/src/plugins/builtin/reverse-dcf/pane.tsx +7 -4
  400. package/src/plugins/builtin/risk-factors/data.ts +4 -2
  401. package/src/plugins/builtin/risk-factors/headless.ts +5 -2
  402. package/src/plugins/builtin/roadmap/index.tsx +28 -0
  403. package/src/plugins/builtin/roadmap/model.ts +67 -0
  404. package/src/plugins/builtin/roadmap/pane.tsx +244 -0
  405. package/src/plugins/builtin/scanner/flow-model.ts +50 -4
  406. package/src/plugins/builtin/scanner/flow-pane.tsx +367 -65
  407. package/src/plugins/builtin/scanner/flow-session.ts +146 -0
  408. package/src/plugins/builtin/scanner/headless.ts +204 -35
  409. package/src/plugins/builtin/scanner/index.ts +16 -5
  410. package/src/plugins/builtin/scanner/live-headless.ts +48 -5
  411. package/src/plugins/builtin/scanner/live-model.ts +11 -0
  412. package/src/plugins/builtin/scanner/live-pane.tsx +3 -3
  413. package/src/plugins/builtin/scanner/lock-note.ts +3 -2
  414. package/src/plugins/builtin/seasonality/headless.ts +10 -1
  415. package/src/plugins/builtin/sec/fund-facts-view.tsx +5 -2
  416. package/src/plugins/builtin/sec/fund-facts.ts +116 -34
  417. package/src/plugins/builtin/sec/headless.ts +4 -2
  418. package/src/plugins/builtin/sec/index.tsx +3 -0
  419. package/src/plugins/builtin/series-inventory.ts +2 -0
  420. package/src/plugins/builtin/shared/cloud-resource.ts +16 -7
  421. package/src/plugins/builtin/shared/cloud-upgrade.ts +4 -3
  422. package/src/plugins/builtin/shared/earnings-calendar.ts +6 -2
  423. package/src/plugins/builtin/shared/earnings-report.ts +2 -2
  424. package/src/plugins/builtin/shared/headless-market-data.ts +2 -2
  425. package/src/plugins/builtin/shared/job-posting.ts +1 -0
  426. package/src/plugins/builtin/shared/members-menu.tsx +3 -2
  427. package/src/plugins/builtin/shared/pro-lock-note.ts +12 -12
  428. package/src/plugins/builtin/shared/report-freshness.ts +11 -2
  429. package/src/plugins/builtin/shared/research-cells.tsx +1 -1
  430. package/src/plugins/builtin/shared/series-cache.ts +4 -4
  431. package/src/plugins/builtin/shared/ticker-choice.tsx +2 -1
  432. package/src/plugins/builtin/shared/ticker-company.ts +77 -0
  433. package/src/plugins/builtin/shared/use-quote-board.ts +2 -1
  434. package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
  435. package/src/plugins/builtin/shared/volatility/pricing.ts +19 -0
  436. package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
  437. package/src/plugins/builtin/themes/model.ts +16 -7
  438. package/src/plugins/builtin/themes/pane.tsx +5 -5
  439. package/src/plugins/builtin/thirteenf/api.ts +12 -1
  440. package/src/plugins/builtin/thirteenf/data.ts +7 -7
  441. package/src/plugins/builtin/thirteenf/headless.ts +31 -4
  442. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +43 -18
  443. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +3 -2
  444. package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +62 -0
  445. package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +83 -46
  446. package/src/plugins/builtin/ticker-detail/headless.ts +31 -16
  447. package/src/plugins/builtin/ticker-detail/overview/model.ts +11 -3
  448. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +2 -1
  449. package/src/plugins/builtin/ticker-detail/pane.tsx +14 -8
  450. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -1
  451. package/src/plugins/builtin/ticker-detail/settings.ts +1 -0
  452. package/src/plugins/builtin/time-sales/pane.tsx +31 -19
  453. package/src/plugins/builtin/treasury-auctions/headless.ts +21 -12
  454. package/src/plugins/builtin/treasury-auctions/pane.tsx +9 -3
  455. package/src/plugins/builtin/treasury-daily/client.ts +41 -5
  456. package/src/plugins/builtin/treasury-daily/headless.ts +2 -2
  457. package/src/plugins/builtin/treasury-daily/index.tsx +1 -1
  458. package/src/plugins/builtin/treasury-daily/model.ts +50 -3
  459. package/src/plugins/builtin/treasury-daily/pane.tsx +81 -2
  460. package/src/plugins/builtin/vol-surface/client.ts +3 -13
  461. package/src/plugins/builtin/vol-surface/headless.ts +3 -3
  462. package/src/plugins/builtin/vol-surface/index.tsx +1 -1
  463. package/src/plugins/builtin/vol-surface/pane.tsx +7 -5
  464. package/src/plugins/builtin/volatility/index.tsx +2 -2
  465. package/src/plugins/builtin/volatility/pane.tsx +7 -4
  466. package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
  467. package/src/plugins/builtin/yield-curve/client.ts +61 -1
  468. package/src/plugins/builtin/yield-curve/headless.ts +9 -6
  469. package/src/plugins/builtin/yield-curve/index.tsx +34 -10
  470. package/src/plugins/builtin/yield-curve/swaps.tsx +9 -5
  471. package/src/plugins/catalog-browser.ts +2 -1
  472. package/src/plugins/catalog-manifest.ts +2 -1
  473. package/src/plugins/host-link.ts +1 -1
  474. package/src/plugins/marketplace/model.ts +18 -5
  475. package/src/portfolio/column-values.ts +11 -7
  476. package/src/portfolio/futures-positions.ts +6 -3
  477. package/src/portfolio/position-quantity.ts +15 -9
  478. package/src/remote/resources.ts +2 -1
  479. package/src/renderers/dom/data-table/row.tsx +26 -0
  480. package/src/renderers/dom/desktop/icons.tsx +7 -0
  481. package/src/renderers/dom/desktop/list-view.tsx +13 -10
  482. package/src/renderers/dom/desktop/query-bar.tsx +4 -2
  483. package/src/renderers/dom/desktop/select-field.tsx +4 -2
  484. package/src/renderers/dom/host/chart-surface.tsx +21 -0
  485. package/src/renderers/electrobun/bun/desktop/app-name.ts +3 -3
  486. package/src/renderers/electrobun/bun/desktop/backend.ts +21 -10
  487. package/src/renderers/electrobun/bun/desktop/host-requests.ts +15 -0
  488. package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
  489. package/src/renderers/electrobun/bun/desktop/installer-folder.ts +731 -0
  490. package/src/renderers/electrobun/bun/desktop/relaunch.ts +102 -10
  491. package/src/renderers/electrobun/bun/desktop/update.ts +115 -14
  492. package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
  493. package/src/renderers/electrobun/bun/desktop/windows-script.ts +92 -0
  494. package/src/renderers/electrobun/bun/index.ts +26 -2
  495. package/src/renderers/electrobun/bun/window/create.ts +43 -4
  496. package/src/renderers/electrobun/bun/window/events.ts +20 -0
  497. package/src/renderers/electrobun/bun/window/windows-custom-chrome.ts +9 -2
  498. package/src/renderers/electrobun/bun/window/windows-native.ts +78 -0
  499. package/src/renderers/electrobun/bun/window/windows-quit.ts +57 -0
  500. package/src/renderers/electrobun/shared/app-name.ts +13 -0
  501. package/src/renderers/electrobun/shared/protocol.ts +12 -0
  502. package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
  503. package/src/renderers/electrobun/view/main.tsx +24 -1
  504. package/src/renderers/electrobun/view/news-source.ts +5 -1
  505. package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
  506. package/src/renderers/electrobun/view/ui-host.tsx +8 -0
  507. package/src/renderers/electrobun/view/view-ids.ts +85 -0
  508. package/src/renderers/opentui/start.tsx +5 -0
  509. package/src/series/registry.ts +1 -0
  510. package/src/series/types.ts +6 -0
  511. package/src/series/validate.ts +1 -0
  512. package/src/sources/gloomberb-cloud/fred-series.ts +3 -1
  513. package/src/sources/gloomberb-cloud/index.ts +43 -8
  514. package/src/sources/gloomberb-cloud/news.ts +47 -7
  515. package/src/sources/provider-router/financials.ts +15 -3
  516. package/src/sources/provider-router/index.ts +8 -5
  517. package/src/state/ticker-cleanup.ts +50 -0
  518. package/src/sync/controller.ts +4 -3
  519. package/src/sync/core-contributors.ts +90 -31
  520. package/src/telemetry/stall-reports-dom.ts +38 -0
  521. package/src/telemetry/stall-reports-node.ts +75 -0
  522. package/src/telemetry/stall-reports.ts +214 -0
  523. package/src/test-support/preload.ts +14 -0
  524. package/src/theme/themes.ts +10 -0
  525. package/src/tickers/company-name-listing.ts +53 -13
  526. package/src/tickers/company-name.ts +50 -4
  527. package/src/tickers/index-roots.ts +7 -0
  528. package/src/tickers/listing-fate.ts +2 -2
  529. package/src/tickers/listing-venues.ts +15 -3
  530. package/src/tickers/search/index.ts +6 -2
  531. package/src/tickers/search/ranking.ts +5 -1
  532. package/src/tickers/search/upsert.ts +13 -0
  533. package/src/tickers/terminal-codes.ts +19 -0
  534. package/src/tickers/ticker-changes.ts +8 -4
  535. package/src/time-series/alignment.ts +1 -1
  536. package/src/time-series/economic.ts +18 -4
  537. package/src/time-series/field-catalog.ts +1 -1
  538. package/src/time-series/filed-trailing-eps.ts +197 -0
  539. package/src/time-series/fundamentals.ts +44 -1
  540. package/src/time-series/ice-credit.ts +3 -0
  541. package/src/time-series/price-comparison.ts +30 -2
  542. package/src/time-series/resolve-context.ts +3 -1
  543. package/src/time-series/resolve.ts +56 -4
  544. package/src/time-series/session-bar.ts +11 -0
  545. package/src/time-series/spec.ts +3 -2
  546. package/src/time-series/studies.ts +8 -2
  547. package/src/time-series/total-return.ts +160 -0
  548. package/src/time-series/transforms.ts +2 -3
  549. package/src/time-series/types.ts +22 -1
  550. package/src/types/company-from.ts +40 -0
  551. package/src/types/data-provider.ts +7 -0
  552. package/src/types/financials.ts +59 -1
  553. package/src/types/headless.ts +27 -3
  554. package/src/types/news-source.ts +1 -0
  555. package/src/types/plugin.ts +9 -0
  556. package/src/ui/host.tsx +3 -0
  557. package/src/updater/failure.ts +4 -1
  558. package/src/utils/cli-output.ts +42 -1
  559. package/src/utils/crypto-pair.ts +165 -7
  560. package/src/utils/declared-dividends.ts +1 -1
  561. package/src/utils/forward-dividend.ts +2 -0
  562. package/src/utils/fundamentals.ts +60 -8
  563. package/src/utils/fx-market-hours.ts +26 -3
  564. package/src/utils/main-thread-stall.ts +35 -2
  565. package/src/utils/market-capitalization.ts +24 -1
  566. package/src/utils/price-history.ts +7 -1
  567. package/src/utils/shell-word.ts +8 -0
  568. package/src/utils/trailing-dividend.ts +53 -4
  569. package/src/version.ts +2 -2
  570. package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
@@ -1,8 +1,8 @@
1
1
  import { formatPercentRaw } from "../../../utils/format";
2
2
  import type { ChartVectorShape } from "../../../ui/host";
3
3
  import type { NativeChartBitmap } from "../native/chart-rasterizer";
4
- import { drawCircle, drawLine, fillRect, parseHex } from "../native/raster/primitives";
5
- import { formatCompositeAxisValue, formatCompositeCursorDate } from "./format";
4
+ import { drawCircle, drawLine, fillRect, parseHex, type RgbaColor } from "../native/raster/primitives";
5
+ import { formatCompositeAxisValue, formatCompositeCursorDate, formatCompositeCursorValue } from "./format";
6
6
  import { projectCompositeValue, unprojectCompositeValue } from "./scene";
7
7
  import { projectCompositeTimestamp, unprojectCompositeTimestamp } from "./time-scale";
8
8
  import type {
@@ -13,7 +13,14 @@ import type {
13
13
  import { clamp } from "../../../utils/math";
14
14
 
15
15
  /** Pointer tools that take the drag away from panning while one is picked. */
16
- export type ChartToolKind = "measure" | "zoom" | "line" | "pencil" | "level";
16
+ export type ChartToolKind = "measure" | "zoom" | "line" | "pencil" | "level" | "rect" | "fib" | "ray";
17
+
18
+ /** How a saved drawing is drawn from its points. */
19
+ export type ChartDrawingShape = "line" | "rect" | "fib" | "ray";
20
+ const SHAPED_TOOLS = ["rect", "fib", "ray"] as const;
21
+
22
+ /** Retracement ratios, measured back from the drag's end toward its start. */
23
+ const FIB_LEVELS = [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1] as const;
17
24
 
18
25
  export interface ChartDrawingPoint {
19
26
  time: number;
@@ -77,6 +84,18 @@ export function parseChartDrawings(value: unknown): ChartDrawing[] {
77
84
  return drawings;
78
85
  }
79
86
 
87
+ /**
88
+ * The shape rides in the id (`fib:3`) rather than a field of its own: an older
89
+ * build refuses a saved drawing with a key it does not know and would drop every
90
+ * drawing of the pane, while an id it does not know still draws as a line.
91
+ */
92
+ export function chartDrawingShape(drawing: Pick<ChartDrawing, "id" | "points">): ChartDrawingShape {
93
+ const prefix = drawing.id.slice(0, Math.max(0, drawing.id.indexOf(":")));
94
+ return drawing.points.length === 2 && (SHAPED_TOOLS as readonly string[]).includes(prefix)
95
+ ? prefix as ChartDrawingShape
96
+ : "line";
97
+ }
98
+
80
99
  export interface ChartDrawingHit {
81
100
  drawing: ChartDrawing;
82
101
  /** Grabbed endpoint, or null when the whole shape was grabbed. */
@@ -119,7 +138,7 @@ const HOUR_MS = 60 * MINUTE_MS;
119
138
  const DAY_MS = 24 * HOUR_MS;
120
139
 
121
140
  function drawingPointFrom(
122
- scene: CompositeChartScene,
141
+ scene: Pick<CompositeChartScene, "timeScale">,
123
142
  domain: CompositeAxisDomain,
124
143
  xRatio: number,
125
144
  yRatio: number,
@@ -132,14 +151,23 @@ function drawingPointFrom(
132
151
 
133
152
  /** Data anchors for a finished drag, so a drawing survives pan and zoom. */
134
153
  export function resolveDrawingFromDrag(
135
- scene: CompositeChartScene,
154
+ scene: Pick<CompositeChartScene, "timeScale">,
136
155
  panel: CompositePanelScene,
137
156
  drag: ChartToolDrag,
138
157
  color: string,
139
158
  id: string,
140
159
  ): ChartDrawing | null {
141
160
  const domain = resolveMeasureAxisDomain(panel);
142
- if (!domain || !isMeaningfulToolDrag(drag)) return null;
161
+ // A ray needs only its price, so a click places one.
162
+ if (!domain || (drag.kind !== "ray" && !isMeaningfulToolDrag(drag))) return null;
163
+ if (isShapedTool(drag.kind)) {
164
+ // A ray holds its first price, so its stored end is level with it and an
165
+ // older build draws the same horizontal run.
166
+ const endYRatio = drag.kind === "ray" ? drag.startYRatio : drag.endYRatio;
167
+ const start = drawingPointFrom(scene, domain, drag.startXRatio, drag.startYRatio);
168
+ const end = drawingPointFrom(scene, domain, drag.endXRatio, endYRatio);
169
+ return start && end ? { id: shapedDrawingId(drag.kind, id), panelId: panel.id, points: [start, end], color } : null;
170
+ }
143
171
  const source = drag.kind === "pencil" && drag.path.length > 1
144
172
  ? drag.path
145
173
  : [
@@ -152,11 +180,74 @@ export function resolveDrawingFromDrag(
152
180
  return points.length < 2 ? null : { id, panelId: panel.id, points, color };
153
181
  }
154
182
 
183
+ function isShapedTool(kind: ChartToolKind): kind is typeof SHAPED_TOOLS[number] {
184
+ return (SHAPED_TOOLS as readonly string[]).includes(kind);
185
+ }
186
+
187
+ /** The id a drawing of this tool is saved under, see `chartDrawingShape`. */
188
+ export function shapedDrawingId(kind: ChartToolKind, id: string): string {
189
+ return isShapedTool(kind) ? `${kind}:${id}` : id;
190
+ }
191
+
192
+ type RatioPoint = { xRatio: number; yRatio: number };
193
+
194
+ interface DrawingGeometry {
195
+ segments: Array<[RatioPoint, RatioPoint]>;
196
+ /** Corners of a filled box under the segments. */
197
+ box: [RatioPoint, RatioPoint] | null;
198
+ /** The points that can be grabbed and reshaped. */
199
+ anchors: RatioPoint[];
200
+ labels: Array<{ at: RatioPoint; text: string }>;
201
+ }
202
+
203
+ /** What a drawing paints, in plot ratios: shared by the bitmap, the vectors and the hit test. */
204
+ function drawingGeometry(
205
+ scene: Pick<CompositeChartScene, "timeScale">,
206
+ panel: CompositePanelScene,
207
+ drawing: ChartDrawing,
208
+ ): DrawingGeometry | null {
209
+ const projected = projectDrawing(scene, panel, drawing);
210
+ if (!projected) return null;
211
+ const shape = chartDrawingShape(drawing);
212
+ if (shape === "line") {
213
+ return {
214
+ segments: projected.slice(1).map((point, index) => [projected[index]!, point]),
215
+ box: null,
216
+ anchors: projected.length === 2 ? projected : [],
217
+ labels: [],
218
+ };
219
+ }
220
+ const [start, end] = projected as [RatioPoint, RatioPoint];
221
+ if (shape === "ray") {
222
+ return { segments: [[start, { xRatio: 1, yRatio: start.yRatio }]], box: null, anchors: [start], labels: [] };
223
+ }
224
+ if (shape === "rect") {
225
+ const corner = (xRatio: number, yRatio: number) => ({ xRatio, yRatio });
226
+ const a = corner(start.xRatio, end.yRatio);
227
+ const b = corner(end.xRatio, start.yRatio);
228
+ return { segments: [[start, a], [a, end], [end, b], [b, start]], box: [start, end], anchors: [start, end], labels: [] };
229
+ }
230
+ const domain = resolveMeasureAxisDomain(panel)!;
231
+ const left = Math.min(start.xRatio, end.xRatio);
232
+ const right = Math.max(start.xRatio, end.xRatio);
233
+ const [from, to] = drawing.points as [ChartDrawingPoint, ChartDrawingPoint];
234
+ const segments: DrawingGeometry["segments"] = [];
235
+ const labels: DrawingGeometry["labels"] = [];
236
+ for (const level of FIB_LEVELS) {
237
+ const value = to.value + (from.value - to.value) * level;
238
+ const yRatio = projectCompositeValue(value, domain);
239
+ if (yRatio === null) continue;
240
+ segments.push([{ xRatio: left, yRatio }, { xRatio: right, yRatio }]);
241
+ labels.push({ at: { xRatio: left, yRatio }, text: `${level} ${formatCompositeCursorValue(value, domain)}` });
242
+ }
243
+ return { segments, box: null, anchors: [start, end], labels };
244
+ }
245
+
155
246
  function projectDrawing(
156
247
  scene: Pick<CompositeChartScene, "timeScale">,
157
248
  panel: CompositePanelScene,
158
249
  drawing: ChartDrawing,
159
- ): Array<{ xRatio: number; yRatio: number }> | null {
250
+ ): RatioPoint[] | null {
160
251
  const domain = resolveMeasureAxisDomain(panel);
161
252
  if (!domain) return null;
162
253
  const projected: Array<{ xRatio: number; yRatio: number }> = [];
@@ -198,22 +289,21 @@ export function hitTestDrawings(
198
289
  let best: { hit: ChartDrawingHit; distance: number; endpoint: boolean } | null = null;
199
290
  const scale = Math.max(aspect, Number.EPSILON);
200
291
  const target = { x: pointer.xRatio * scale, y: pointer.yRatio };
292
+ const toPlot = (point: RatioPoint) => ({ x: point.xRatio * scale, y: point.yRatio });
201
293
  for (const drawing of drawings) {
202
294
  if (drawing.panelId !== panel.id) continue;
203
- const projected = projectDrawing(scene, panel, drawing);
204
- if (!projected) continue;
205
- const shape = projected.map((point) => ({ x: point.xRatio * scale, y: point.yRatio }));
206
- if (drawing.points.length === 2) {
207
- for (const [index, point] of shape.entries()) {
208
- const distance = Math.hypot(target.x - point.x, target.y - point.y);
209
- if (distance > DRAWING_GRAB_RATIO) continue;
210
- if (!best?.endpoint || best.distance > distance) {
211
- best = { hit: { drawing, pointIndex: index }, distance, endpoint: true };
212
- }
295
+ const geometry = drawingGeometry(scene, panel, drawing);
296
+ if (!geometry) continue;
297
+ for (const [index, anchor] of geometry.anchors.entries()) {
298
+ const point = toPlot(anchor);
299
+ const distance = Math.hypot(target.x - point.x, target.y - point.y);
300
+ if (distance > DRAWING_GRAB_RATIO) continue;
301
+ if (!best?.endpoint || best.distance > distance) {
302
+ best = { hit: { drawing, pointIndex: index }, distance, endpoint: true };
213
303
  }
214
304
  }
215
- for (let index = 1; index < shape.length; index += 1) {
216
- const distance = distanceToSegment(target, shape[index - 1]!, shape[index]!);
305
+ for (const [from, to] of geometry.segments) {
306
+ const distance = distanceToSegment(target, toPlot(from), toPlot(to));
217
307
  if (distance > DRAWING_GRAB_RATIO || best?.endpoint) continue;
218
308
  if (!best || best.distance > distance) {
219
309
  best = { hit: { drawing, pointIndex: null }, distance, endpoint: false };
@@ -235,19 +325,18 @@ export function shiftDrawing(
235
325
  const domain = resolveMeasureAxisDomain(panel);
236
326
  const projected = projectDrawing(scene, panel, drawing);
237
327
  if (!domain || !projected) return drawing;
238
- return {
239
- ...drawing,
240
- points: drawing.points.map((point, index) => {
241
- if (pointIndex !== null && index !== pointIndex) return point;
242
- const source = projected[index]!;
243
- return drawingPointFrom(
244
- scene,
245
- domain,
246
- clamp(source.xRatio + deltaXRatio, 0, 1),
247
- clamp(source.yRatio + deltaYRatio, 0, 1),
248
- ) ?? point;
249
- }),
250
- };
328
+ const points = drawing.points.map((point, index) => {
329
+ if (pointIndex !== null && index !== pointIndex) return point;
330
+ const source = projected[index]!;
331
+ return drawingPointFrom(
332
+ scene,
333
+ domain,
334
+ clamp(source.xRatio + deltaXRatio, 0, 1),
335
+ clamp(source.yRatio + deltaYRatio, 0, 1),
336
+ ) ?? point;
337
+ });
338
+ if (chartDrawingShape(drawing) === "ray") points[1] = { ...points[1]!, value: points[0]!.value };
339
+ return { ...drawing, points };
251
340
  }
252
341
 
253
342
  export function nextDrawingColor(color: string): string {
@@ -268,7 +357,7 @@ export function resolveChartToolKind(modifiers: {
268
357
  }
269
358
 
270
359
  export function isDrawingTool(tool: ChartToolKind | null): boolean {
271
- return tool === "line" || tool === "pencil";
360
+ return tool === "line" || tool === "pencil" || (tool !== null && isShapedTool(tool));
272
361
  }
273
362
 
274
363
  function isMeaningfulToolDrag(drag: ChartToolDrag): boolean {
@@ -458,20 +547,29 @@ export function buildChartToolVectors(input: {
458
547
  }): ChartVectorShape[] {
459
548
  const shapes: ChartVectorShape[] = [];
460
549
  for (const drawing of input.drawings) {
461
- const projected = projectDrawing(input.scene, input.panel, drawing);
462
- if (!projected) continue;
463
550
  const selected = input.selectedId === drawing.id;
464
- shapes.push({
465
- id: drawing.id,
466
- points: projected.map((point) => ({ x: point.xRatio, y: point.yRatio })),
467
- color: drawing.color,
468
- strokeWidth: selected ? 2.4 : 1.6,
469
- handles: selected && drawing.points.length === 2,
470
- });
551
+ if (chartDrawingShape(drawing) === "line") {
552
+ const projected = projectDrawing(input.scene, input.panel, drawing);
553
+ if (!projected) continue;
554
+ shapes.push({
555
+ id: drawing.id,
556
+ points: projected.map((point) => ({ x: point.xRatio, y: point.yRatio })),
557
+ color: drawing.color,
558
+ strokeWidth: selected ? 2.4 : 1.6,
559
+ handles: selected && drawing.points.length === 2,
560
+ });
561
+ continue;
562
+ }
563
+ shapes.push(...shapedDrawingVectors(input.scene, input.panel, drawing, selected));
471
564
  }
472
565
 
473
566
  const drag = input.drag;
474
567
  if (!drag) return shapes;
568
+ if (isShapedTool(drag.kind)) {
569
+ const preview = resolveDrawingFromDrag(input.scene, input.panel, drag, input.colors.draw, "tool:draw");
570
+ if (preview) shapes.push(...shapedDrawingVectors(input.scene, input.panel, preview, false));
571
+ return shapes;
572
+ }
475
573
  const start = { x: drag.startXRatio, y: drag.startYRatio };
476
574
  const end = { x: drag.endXRatio, y: drag.endYRatio };
477
575
  if (drag.kind === "zoom") {
@@ -505,6 +603,55 @@ export function buildChartToolVectors(input: {
505
603
  return shapes;
506
604
  }
507
605
 
606
+ function shapedDrawingVectors(
607
+ scene: Pick<CompositeChartScene, "timeScale">,
608
+ panel: CompositePanelScene,
609
+ drawing: ChartDrawing,
610
+ selected: boolean,
611
+ ): ChartVectorShape[] {
612
+ const geometry = drawingGeometry(scene, panel, drawing);
613
+ if (!geometry) return [];
614
+ const toVector = (point: RatioPoint) => ({ x: point.xRatio, y: point.yRatio });
615
+ const shapes: ChartVectorShape[] = [];
616
+ if (geometry.box) {
617
+ shapes.push({ id: `${drawing.id}:box`, points: geometry.box.map(toVector), color: drawing.color, box: true, fillOpacity: 0.12, strokeWidth: 0 });
618
+ }
619
+ geometry.segments.forEach((segment, index) => shapes.push({
620
+ id: `${drawing.id}:${index}`,
621
+ points: segment.map(toVector),
622
+ color: drawing.color,
623
+ strokeWidth: selected ? 2 : 1.3,
624
+ ...(geometry.labels[index] ? { label: geometry.labels[index]!.text } : {}),
625
+ }));
626
+ if (selected) {
627
+ shapes.push({ id: `${drawing.id}:anchors`, points: geometry.anchors.map(toVector), color: drawing.color, opacity: 0, handles: true });
628
+ }
629
+ return shapes;
630
+ }
631
+
632
+ /** Paints a shaped drawing's segments, box and handles onto a raster. */
633
+ function paintShapedDrawing(
634
+ data: Uint8Array,
635
+ width: number,
636
+ height: number,
637
+ geometry: DrawingGeometry,
638
+ color: RgbaColor,
639
+ selected: boolean,
640
+ ): void {
641
+ const maxX = Math.max(width - 1, 0);
642
+ const maxY = Math.max(height - 1, 0);
643
+ if (geometry.box) {
644
+ const [a, b] = geometry.box;
645
+ fillRect(data, width, height, Math.min(a.xRatio, b.xRatio) * maxX, Math.min(a.yRatio, b.yRatio) * maxY,
646
+ Math.max(a.xRatio, b.xRatio) * maxX, Math.max(a.yRatio, b.yRatio) * maxY, color, 0.12);
647
+ }
648
+ for (const [from, to] of geometry.segments) {
649
+ drawLine(data, width, height, from.xRatio * maxX, from.yRatio * maxY, to.xRatio * maxX, to.yRatio * maxY, color, selected ? 2.2 : 1.4);
650
+ }
651
+ if (!selected) return;
652
+ for (const anchor of geometry.anchors) drawCircle(data, width, height, anchor.xRatio * maxX, anchor.yRatio * maxY, 3.2, color);
653
+ }
654
+
508
655
  /**
509
656
  * Paints drawings and the active tool on a copy of the panel raster. Copying
510
657
  * keeps the cached plot bitmap intact and keeps a single bitmap layer, which is
@@ -529,10 +676,15 @@ export function drawChartToolOverlay(
529
676
  const maxY = Math.max(height - 1, 0);
530
677
 
531
678
  for (const drawing of drawings?.items ?? []) {
532
- const shape = projectDrawing(drawings!.scene, drawings!.panel, drawing);
533
- if (!shape) continue;
534
679
  const selected = drawings!.selectedId === drawing.id;
535
680
  const color = parseHex(drawing.color);
681
+ if (chartDrawingShape(drawing) !== "line") {
682
+ const geometry = drawingGeometry(drawings!.scene, drawings!.panel, drawing);
683
+ if (geometry) paintShapedDrawing(data, width, height, geometry, color, selected);
684
+ continue;
685
+ }
686
+ const shape = projectDrawing(drawings!.scene, drawings!.panel, drawing);
687
+ if (!shape) continue;
536
688
  for (let index = 1; index < shape.length; index += 1) {
537
689
  drawLine(
538
690
  data,
@@ -560,6 +712,13 @@ export function drawChartToolOverlay(
560
712
  const left = Math.min(x0, x1);
561
713
  const right = Math.max(x0, x1);
562
714
 
715
+ if (isShapedTool(drag.kind)) {
716
+ const preview = drawings && resolveDrawingFromDrag(drawings.scene, drawings.panel, drag, colors.draw, "tool:draw");
717
+ const geometry = preview && drawingGeometry(drawings.scene, drawings.panel, preview);
718
+ if (geometry) paintShapedDrawing(data, width, height, geometry, parseHex(colors.draw), false);
719
+ return { width, height, pixels: data };
720
+ }
721
+
563
722
  if (drag.kind === "line" || drag.kind === "pencil") {
564
723
  const color = parseHex(colors.draw);
565
724
  const trail = drag.kind === "pencil" && drag.path.length > 1
@@ -26,6 +26,7 @@ import {
26
26
  resolveZoomTimeRange,
27
27
  isDrawingTool,
28
28
  nextDrawingColor,
29
+ shapedDrawingId,
29
30
  type ChartDrawing,
30
31
  type ChartDrawingPoint,
31
32
  type ChartToolDrag,
@@ -301,7 +302,7 @@ export function useCompositeChartKeyboard({
301
302
  const placement = keyboardPlacement;
302
303
  setKeyboardPlacement(null);
303
304
  // A placement that never left its anchor spans nothing, like a click.
304
- if (!placement || placement.start.time === placement.end.time) return;
305
+ if (!placement || (placement.start.time === placement.end.time && placement.kind !== "ray")) return;
305
306
  if (placement.kind === "zoom") {
306
307
  if (!navigationFrame) return;
307
308
  const range = resolveZoomTimeRange(placement.start.time, placement.end.time, navigationFrame.minimumSpanMs);
@@ -313,9 +314,9 @@ export function useCompositeChartKeyboard({
313
314
  ? placement.path.filter((point, index, path) => (
314
315
  index === 0 || point.time !== path[index - 1]!.time || point.value !== path[index - 1]!.value
315
316
  ))
316
- : [placement.start, placement.end];
317
+ : [placement.start, placement.kind === "ray" ? { ...placement.end, value: placement.start.value } : placement.end];
317
318
  if (points.length < 2) return;
318
- addDrawing({ id: nextDrawingId(), panelId: placement.panelId, points, color: drawColor });
319
+ addDrawing({ id: shapedDrawingId(placement.kind, nextDrawingId()), panelId: placement.panelId, points, color: drawColor });
319
320
  };
320
321
  const stepLegend = (direction: -1 | 1) => {
321
322
  onActivate?.();
@@ -566,7 +567,10 @@ export function useCompositeChartKeyboard({
566
567
  || ((interaction === "arm-measure"
567
568
  || interaction === "arm-zoom"
568
569
  || interaction === "arm-line"
569
- || interaction === "arm-pencil") && !scene)
570
+ || interaction === "arm-pencil"
571
+ || interaction === "arm-ray"
572
+ || interaction === "arm-rect"
573
+ || interaction === "arm-fib") && !scene)
570
574
  || (interaction === "arm-level" && !levelsEditable)
571
575
  || (interaction === "delete-drawing" && !canDeleteDrawing)
572
576
  || (interaction === "cycle-colour" && !isDrawingTool(armedTool) && !selectedDrawingId)
@@ -586,6 +590,9 @@ export function useCompositeChartKeyboard({
586
590
  case "arm-zoom":
587
591
  case "arm-line":
588
592
  case "arm-pencil":
593
+ case "arm-ray":
594
+ case "arm-rect":
595
+ case "arm-fib":
589
596
  case "arm-level":
590
597
  onActivate?.();
591
598
  armTool(ARMED_TOOL_BY_INTERACTION[interaction]);
@@ -119,7 +119,8 @@ export function buildAssistCommandInventory({
119
119
  prefixes: normalizePrefixes(command.shortcut ? [command.shortcut] : []),
120
120
  })),
121
121
  ...paneTemplates.map((template) => ({
122
- descriptor: describe(
122
+ // An unlisted function keeps its codes, so nothing else takes them, but is never suggested.
123
+ descriptor: template.listed === false ? null : describe(
123
124
  template.shortcut?.prefix ?? "",
124
125
  getPaneTemplateDisplayLabel(template),
125
126
  template.shortcut?.aliases?.length ? `${template.description} Also ${template.shortcut.aliases.join(", ")}.` : template.description,
@@ -245,19 +245,34 @@ export function runDirectCommandAction(options: {
245
245
  }
246
246
  case "import-config": {
247
247
  const importPath = getDefaultConfigBackupPath();
248
- void importConfig(state.config.dataDir, importPath)
249
- .then((imported) => {
250
- cancelThemePreview();
251
- clearTransientThemePreview();
252
- dispatch({ type: "SET_CONFIG", config: imported });
253
- applyTheme(imported.theme);
254
- dispatch({ type: "SET_THEME", theme: imported.theme });
255
- notify(`Imported config from ${importPath}.`, { type: "success" });
256
- closeAll({ revertThemePreview: false });
257
- })
258
- .catch((error) => {
259
- notify(error instanceof Error ? error.message : "Import failed.", { type: "error" });
260
- });
248
+ // An import replaces every layout, portfolio and setting at once, so it
249
+ // is never one keystroke away.
250
+ openInlineConfirm({
251
+ confirmId: "import-config",
252
+ title: "Import Config",
253
+ body: [
254
+ `This replaces your layouts, portfolios, watchlists and settings with the ones saved in ${importPath}.`,
255
+ "What you have now is not kept unless you export it first.",
256
+ ],
257
+ confirmLabel: "Import",
258
+ cancelLabel: "Back",
259
+ tone: "danger",
260
+ // A stray Enter keeps what is there.
261
+ defaultAction: "cancel",
262
+ onConfirm: async () => {
263
+ try {
264
+ const imported = await importConfig(getState().config.dataDir, importPath);
265
+ cancelThemePreview();
266
+ clearTransientThemePreview();
267
+ dispatch({ type: "SET_CONFIG", config: imported });
268
+ applyTheme(imported.theme);
269
+ dispatch({ type: "SET_THEME", theme: imported.theme });
270
+ notify(`Imported config from ${importPath}.`, { type: "success" });
271
+ } catch (error) {
272
+ notify(error instanceof Error ? error.message : "Import failed.", { type: "error" });
273
+ }
274
+ },
275
+ });
261
276
  return;
262
277
  }
263
278
  case "cycle-chart-renderer": {
@@ -73,6 +73,11 @@ export interface ResultItem {
73
73
  action: () => void | Promise<void>;
74
74
  }
75
75
 
76
+ /** A row plain Enter can run, so the untouched selection may rest on it. */
77
+ export function isDefaultSelectable(item: ResultItem): boolean {
78
+ return item.disabled !== true && item.defaultSelectable !== false;
79
+ }
80
+
76
81
  type ListScreenKind = "root" | "mode" | "picker";
77
82
 
78
83
  export interface ListScreenState {
@@ -260,8 +260,9 @@ export function buildPaneShortcutItems(options: {
260
260
  showShortcut?: boolean;
261
261
  }) => ResultItem;
262
262
  }): ResultItem[] {
263
+ // An unlisted template (`listed: false`) still opens when its code is typed, which is resolved apart from these rows.
263
264
  const items = options.templates
264
- .filter((template) => template.shortcut && template.shortcut.showInBrowse !== false)
265
+ .filter((template) => template.shortcut && template.shortcut.showInBrowse !== false && template.listed !== false)
265
266
  .map((template) => options.createItem(template, {
266
267
  category: "Panes",
267
268
  createOptions: options.createOptions,
@@ -279,7 +280,7 @@ export function buildNonShortcutPaneTemplateItems(options: {
279
280
  createItem: (template: PaneTemplateDef, options?: { category?: string }) => ResultItem;
280
281
  }): ResultItem[] {
281
282
  const items = options.templates
282
- .filter((template) => !template.shortcut)
283
+ .filter((template) => !template.shortcut && template.listed !== false)
283
284
  .map((template) => options.createItem(template, { category: "Panes" }));
284
285
 
285
286
  return options.filterQuery
@@ -7,7 +7,7 @@ import type { TickerRecord } from "../../../../types/ticker";
7
7
  import { listingChoiceQuery, symbolSearchQuery, type TickerSearchCandidate } from "../../../../tickers/search";
8
8
  import type { AssistRowHandlers } from "../../assist/model";
9
9
  import { matchPrefix, type Command } from "../../commands/registry";
10
- import type { ResultItem } from "../../list/model";
10
+ import { isDefaultSelectable, type ResultItem } from "../../list/model";
11
11
  import type { CommandBarCategoryPriorities } from "../../view-model";
12
12
  import type { CommandBarRoute } from "../../workflow/types";
13
13
  import { normalizeCommandTickerSearchText, readRootTickerQuery } from "../ticker-search/results";
@@ -20,11 +20,6 @@ import { buildRootShortcutFeedback } from "./shortcut-feedback";
20
20
  import type { ShortcutIntent } from "./shortcuts";
21
21
  import { clampIndex } from "../../../../utils/math";
22
22
 
23
- /** A row plain Enter can run, so the untouched selection may rest on it. */
24
- function isDefaultSelectable(item: ResultItem): boolean {
25
- return item.disabled !== true && item.defaultSelectable !== false;
26
- }
27
-
28
23
  interface UseCommandBarRootRuntimeOptions {
29
24
  activeCollectionId: string | null;
30
25
  activePortfolio?: AppState["config"]["portfolios"][number];
@@ -343,8 +338,12 @@ export function useCommandBarRootRuntime({
343
338
  if (shiftedIdx >= 0) return shiftedIdx;
344
339
  return clampIndex(current, resultIds.length);
345
340
  }
346
- // Untouched, the selection follows the best row on offer.
341
+ // Untouched, the selection follows the best row on offer. When every row
342
+ // is an offer or a placeholder, none of them is the answer and none is
343
+ // selected: Enter must not take one of them for the ticker still being
344
+ // searched.
347
345
  const defaultIdx = orderedRootResults.findIndex(isDefaultSelectable);
346
+ if (defaultIdx < 0) return -1;
348
347
  return clampIndex(Math.max(rootResultModel.initialIdx, defaultIdx), resultIds.length);
349
348
  });
350
349
  }, [
@@ -1,4 +1,4 @@
1
- import { useCallback, type MutableRefObject } from "react";
1
+ import { useCallback, useEffect, useRef, type MutableRefObject } from "react";
2
2
  import type { AppState } from "../../state/app/context";
3
3
  import type { TickerRecord } from "../../types/ticker";
4
4
  import { t } from "../../i18n";
@@ -12,7 +12,7 @@ import type {
12
12
  PaneTemplateDef,
13
13
  } from "../../types/plugin";
14
14
  import { resolveCommandBarMode, type CommandBarMode } from "./view-model";
15
- import type { ListScreenState, ResultItem } from "./list/model";
15
+ import { isDefaultSelectable, type ListScreenState, type ResultItem } from "./list/model";
16
16
  import type { CommandBarRoute } from "./workflow/types";
17
17
  import type { CollectionCommandId } from "./helpers";
18
18
  import type { CommandBarCollectionWorkflowActions } from "./workflow/collection-actions";
@@ -67,6 +67,7 @@ interface UseCommandBarSelectionRuntimeOptions {
67
67
  rootModeKindRef: MutableRefObject<CommandBarMode>;
68
68
  rootQuery: string;
69
69
  rootQueryRef: MutableRefObject<string>;
70
+ rootSelectedIdx: number;
70
71
  rootSelectionNavigatedRef: MutableRefObject<boolean>;
71
72
  rootShortcutIntent: ShortcutIntent;
72
73
  rootThemeBaseIdRef: MutableRefObject<string | null>;
@@ -78,6 +79,11 @@ interface UseCommandBarSelectionRuntimeOptions {
78
79
  */
79
80
  runRootRow?: RunRootRow;
80
81
  runSecurityDescriptionShortcut: (query?: string, options?: { forceNewPane?: boolean }) => void | Promise<void>;
82
+ /**
83
+ * The search that decides the best row is still out, so the list on screen
84
+ * is a stand-in for the answer.
85
+ */
86
+ searchPending: boolean;
81
87
  setRootQuery: (query: string) => void;
82
88
  stateConfigLayout: AppState["config"]["layout"];
83
89
  stateRef: MutableRefObject<AppState>;
@@ -108,12 +114,14 @@ export function useCommandBarSelectionRuntime({
108
114
  rootModeKindRef,
109
115
  rootQuery,
110
116
  rootQueryRef,
117
+ rootSelectedIdx,
111
118
  rootSelectionNavigatedRef,
112
119
  rootShortcutIntent,
113
120
  rootThemeBaseIdRef,
114
121
  runDirectCommand,
115
122
  runRootRow,
116
123
  runSecurityDescriptionShortcut,
124
+ searchPending,
117
125
  setRootQuery,
118
126
  stateConfigLayout,
119
127
  stateRef,
@@ -276,6 +284,13 @@ export function useCommandBarSelectionRuntime({
276
284
  updateTopRoute,
277
285
  ]);
278
286
 
287
+ /**
288
+ * An Enter pressed with no row to run while the search is still out. It
289
+ * waits for the answer instead of running whatever else the list shows, and
290
+ * is dropped when the user types, moves the selection or leaves the root.
291
+ */
292
+ const pendingEnterRef = useRef<{ query: string; secondary: boolean } | null>(null);
293
+
279
294
  const activateListSelection = useCallback((options?: { secondary?: boolean; item?: ResultItem }) => {
280
295
  const listState = visibleListStateRef.current;
281
296
  if (!listState) return;
@@ -311,7 +326,13 @@ export function useCommandBarSelectionRuntime({
311
326
  secondaryAction: () => runSecurityDescriptionShortcut(symbol, { forceNewPane: true }) };
312
327
  }
313
328
  const selected = options?.item ?? typed ?? listState.results[listState.selectedIdx];
314
- if (!selected || selected.disabled) return;
329
+ if (!selected) {
330
+ if (searchPending && !currentRoute && !options?.item && !rootSelectionNavigatedRef.current) {
331
+ pendingEnterRef.current = { query: rootQueryRef.current, secondary: options?.secondary === true };
332
+ }
333
+ return;
334
+ }
335
+ if (selected.disabled) return;
315
336
 
316
337
  // Opened from an add row: a venue picked from the colon list joins that
317
338
  // collection. Any other text or screen runs as usual.
@@ -396,11 +417,39 @@ export function useCommandBarSelectionRuntime({
396
417
  rootShortcutIntent,
397
418
  runRootRow,
398
419
  runSecurityDescriptionShortcut,
420
+ searchPending,
399
421
  stateConfigLayout,
400
422
  stateRef,
401
423
  visibleListStateRef,
402
424
  ]);
403
425
 
426
+ useEffect(() => {
427
+ const pending = pendingEnterRef.current;
428
+ if (!pending) return;
429
+ if (currentRoute || rootQueryRef.current !== pending.query || rootSelectionNavigatedRef.current) {
430
+ pendingEnterRef.current = null;
431
+ return;
432
+ }
433
+ const listState = visibleListStateRef.current;
434
+ if (!listState?.results.some(isDefaultSelectable)) {
435
+ // Still searching: the answer may bring a row. Answered with none: there is nothing to run.
436
+ if (!searchPending) pendingEnterRef.current = null;
437
+ return;
438
+ }
439
+ // The selection follows the rows in the render after they land.
440
+ if (!listState.results[listState.selectedIdx]) return;
441
+ pendingEnterRef.current = null;
442
+ activateListSelection({ secondary: pending.secondary });
443
+ }, [
444
+ activateListSelection,
445
+ currentRoute,
446
+ rootQueryRef,
447
+ rootSelectedIdx,
448
+ rootSelectionNavigatedRef,
449
+ searchPending,
450
+ visibleListStateRef,
451
+ ]);
452
+
404
453
  return {
405
454
  acceptRootShortcutTab,
406
455
  acceptSelectedShortcutTab,
@@ -532,12 +532,14 @@ export function CommandBar({
532
532
  rootModeKindRef,
533
533
  rootQuery,
534
534
  rootQueryRef,
535
+ rootSelectedIdx,
535
536
  rootSelectionNavigatedRef,
536
537
  rootShortcutIntent,
537
538
  rootThemeBaseIdRef,
538
539
  runDirectCommand,
539
540
  runRootRow,
540
541
  runSecurityDescriptionShortcut,
542
+ searchPending: rootSearching || !rootResultsCurrent,
541
543
  setRootQuery,
542
544
  stateConfigLayout: state.config.layout,
543
545
  stateRef,