@gloom-sh/terminal 0.19.1 → 0.19.3

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 (554) hide show
  1. package/README.md +7 -1
  2. package/package.json +2 -1
  3. package/patches/electrobun@1.18.1.patch +108 -1
  4. package/src/account/upgrade-dialog.tsx +4 -23
  5. package/src/api-client/coalesce.ts +118 -0
  6. package/src/api-client/credit-boards.ts +13 -0
  7. package/src/api-client/crypto-etf-flows.ts +6 -0
  8. package/src/api-client/crypto-markets.ts +40 -0
  9. package/src/api-client/data.ts +201 -14
  10. package/src/api-client/equity-screener.ts +10 -0
  11. package/src/api-client/errors.ts +58 -0
  12. package/src/api-client/gpu.ts +29 -0
  13. package/src/api-client/index.ts +5 -2
  14. package/src/api-client/plan-access.ts +10 -1
  15. package/src/api-client/plan-rules.ts +35 -0
  16. package/src/api-client/research-activity.ts +1 -32
  17. package/src/api-client/types.ts +21 -1
  18. package/src/app/attention-notice.tsx +94 -0
  19. package/src/app/runtime/startup.ts +3 -1
  20. package/src/app.tsx +23 -1
  21. package/src/architecture/core-builtin-allowlist.json +2 -1
  22. package/src/capabilities/chart-series.ts +2 -1
  23. package/src/capabilities/factories.ts +1 -0
  24. package/src/cli/ascii-output.ts +42 -0
  25. package/src/cli/base-converter.ts +10 -2
  26. package/src/cli/command-options.ts +16 -1
  27. package/src/cli/commands/crypto-hints.ts +23 -4
  28. package/src/cli/commands/market.ts +101 -27
  29. package/src/cli/commands/news-search.ts +24 -5
  30. package/src/cli/commands/overview.ts +14 -10
  31. package/src/cli/commands/search.ts +84 -25
  32. package/src/cli/commands/system.ts +22 -30
  33. package/src/cli/commands/ticker.ts +49 -9
  34. package/src/cli/commands/version.ts +33 -0
  35. package/src/cli/context.ts +6 -9
  36. package/src/cli/data-dir.ts +7 -0
  37. package/src/cli/desktop-pane-shot-api-policy.ts +85 -0
  38. package/src/cli/desktop-pane-shot-browser.ts +320 -0
  39. package/src/cli/desktop-pane-shot-routes.ts +26 -1
  40. package/src/cli/desktop-pane-shot.ts +196 -256
  41. package/src/cli/early-command.ts +42 -0
  42. package/src/cli/earnings-calendar.ts +9 -0
  43. package/src/cli/entry.ts +19 -13
  44. package/src/cli/errors.ts +12 -2
  45. package/src/cli/glossary.ts +2 -1
  46. package/src/cli/help.ts +1 -1
  47. package/src/cli/history-export.ts +22 -2
  48. package/src/cli/history-rows.ts +189 -11
  49. package/src/cli/index.ts +18 -1
  50. package/src/cli/known-gaps.ts +51 -5
  51. package/src/cli/listing-arg.ts +63 -10
  52. package/src/cli/options.ts +1 -1
  53. package/src/cli/output-file.ts +60 -13
  54. package/src/cli/pane-functions/catalog.ts +14 -3
  55. package/src/cli/pane-functions/data.ts +1 -1
  56. package/src/cli/pane-functions/dom.ts +45 -0
  57. package/src/cli/pane-functions/freshness.ts +46 -9
  58. package/src/cli/pane-functions/function-help.ts +17 -9
  59. package/src/cli/pane-functions/headless.ts +22 -14
  60. package/src/cli/pane-functions/index.ts +6 -2
  61. package/src/cli/pane-functions/options.ts +6 -2
  62. package/src/cli/pane-functions/resolver.ts +27 -5
  63. package/src/cli/pane-functions/screenshot-assessment.ts +33 -0
  64. package/src/cli/pane-functions/screenshot-bridge.ts +94 -13
  65. package/src/cli/pane-functions/screenshot-http.ts +70 -0
  66. package/src/cli/pane-functions/screenshot-render.ts +19 -5
  67. package/src/cli/quote-answer.ts +49 -6
  68. package/src/cli/registry.ts +41 -13
  69. package/src/cli/result.ts +12 -6
  70. package/src/cli/ticker-resolution.ts +2 -1
  71. package/src/cli/types.ts +2 -0
  72. package/src/components/chart/composite/format.ts +3 -0
  73. package/src/components/chart-table/header.tsx +1 -1
  74. package/src/components/chart-table/layout.ts +28 -11
  75. package/src/components/command-bar/action-runtime.ts +4 -0
  76. package/src/components/command-bar/commands/direct/index.ts +18 -33
  77. package/src/components/command-bar/commands/direct/runtime.ts +5 -0
  78. package/src/components/command-bar/commands/registry.ts +10 -1
  79. package/src/components/command-bar/routes/root/command-items.ts +9 -4
  80. package/src/components/command-bar/routes/root/runtime.ts +51 -6
  81. package/src/components/command-bar/selection-runtime.ts +12 -29
  82. package/src/components/command-bar/surface/index.tsx +9 -3
  83. package/src/components/company-logo.tsx +2 -1
  84. package/src/components/layout/detached-pane-shell.tsx +2 -2
  85. package/src/components/layout/header.tsx +10 -5
  86. package/src/components/layout/market-summary.ts +25 -7
  87. package/src/components/layout/pane/footer/index.tsx +8 -2
  88. package/src/components/layout/pane/footer/model.ts +73 -6
  89. package/src/components/layout/pane/header.tsx +2 -2
  90. package/src/components/layout/pane/status-footer.ts +2 -1
  91. package/src/components/market-board/index.tsx +3 -2
  92. package/src/components/onboarding/desks-step.tsx +12 -4
  93. package/src/components/onboarding/first-run-workspace.ts +81 -4
  94. package/src/components/onboarding/onboarding-frame.tsx +4 -2
  95. package/src/components/onboarding/onboarding-keyboard.ts +8 -5
  96. package/src/components/onboarding/onboarding-wizard.tsx +60 -26
  97. package/src/components/onboarding/portfolio-step/positions-panel.tsx +7 -2
  98. package/src/components/onboarding/run-setup-again.ts +24 -0
  99. package/src/components/onboarding/use-onboarding-progress.ts +33 -15
  100. package/src/components/onboarding/use-onboarding-upgrade.ts +2 -6
  101. package/src/components/onboarding/wizard-model.ts +57 -3
  102. package/src/components/onboarding/wizard-positions.ts +1 -1
  103. package/src/components/ui/delayed-mark.tsx +56 -0
  104. package/src/components/ui/display.tsx +16 -5
  105. package/src/components/ui/external-link.tsx +3 -6
  106. package/src/components/ui/icon.tsx +16 -3
  107. package/src/components/ui/logo-mark.tsx +8 -5
  108. package/src/components/ui/stat-grid.tsx +263 -19
  109. package/src/core/app-services.ts +11 -2
  110. package/src/data/app-persistence.ts +9 -7
  111. package/src/data/config/store/node.ts +11 -5
  112. package/src/data/config/store/normalize.ts +16 -3
  113. package/src/data/plugin-cache.ts +15 -0
  114. package/src/data/plugin-state-store.ts +81 -24
  115. package/src/data/profile-permissions.ts +47 -0
  116. package/src/data/resource-store.ts +165 -90
  117. package/src/data/session-store.ts +31 -8
  118. package/src/data/sqlite/access.ts +324 -0
  119. package/src/data/sqlite/checkpointer.ts +285 -0
  120. package/src/data/sqlite/database.ts +80 -4
  121. package/src/data/sqlite/retry.ts +50 -9
  122. package/src/data/ticker-store.ts +57 -23
  123. package/src/docs/snapshot.json +191 -78
  124. package/src/i18n/es.ts +18 -2
  125. package/src/i18n/ja.ts +18 -2
  126. package/src/i18n/ko.ts +18 -2
  127. package/src/i18n/zh-cn.ts +18 -2
  128. package/src/i18n/zh-tw.ts +18 -2
  129. package/src/layout/desk-keys.ts +4 -1
  130. package/src/layout/desks.ts +38 -5
  131. package/src/layout-marketplace/gallery-desktop.tsx +45 -16
  132. package/src/market-data/batched-reads.ts +16 -0
  133. package/src/market-data/coordinator/auxiliary.ts +4 -2
  134. package/src/market-data/coordinator/index.ts +11 -2
  135. package/src/market-data/futures-board.ts +1 -1
  136. package/src/market-data/futures-contract.ts +3 -1
  137. package/src/market-data/market/index-level.ts +6 -4
  138. package/src/market-data/published-apac-sessions.ts +6 -0
  139. package/src/market-data/realized-volatility.ts +4 -2
  140. package/src/news/aggregator.ts +7 -4
  141. package/src/news/news-model.ts +11 -0
  142. package/src/news/story-id.ts +22 -0
  143. package/src/news/ticker-symbols.ts +17 -1
  144. package/src/notifications/app-notifier.ts +35 -14
  145. package/src/plugins/auto-update-node.ts +10 -6
  146. package/src/plugins/builtin/account-management/form-components.tsx +1 -1
  147. package/src/plugins/builtin/account-management/index.tsx +2 -3
  148. package/src/plugins/builtin/account-management/model.ts +1 -1
  149. package/src/plugins/builtin/alerts/cli-forms.ts +15 -1
  150. package/src/plugins/builtin/alerts/events-pane.tsx +7 -5
  151. package/src/plugins/builtin/alerts/events.ts +10 -2
  152. package/src/plugins/builtin/alerts/index.tsx +5 -5
  153. package/src/plugins/builtin/alerts/research-builder.ts +5 -1
  154. package/src/plugins/builtin/alerts/research-rules.ts +18 -0
  155. package/src/plugins/builtin/alerts/targets.ts +4 -1
  156. package/src/plugins/builtin/attention/headless.ts +1 -1
  157. package/src/plugins/builtin/attention/index.tsx +2 -4
  158. package/src/plugins/builtin/attention/listing.ts +1 -1
  159. package/src/plugins/builtin/attention/series.ts +1 -1
  160. package/src/plugins/builtin/awards/index.tsx +1 -2
  161. package/src/plugins/builtin/backtest/client.ts +1 -0
  162. package/src/plugins/builtin/backtest/index.tsx +1 -2
  163. package/src/plugins/builtin/backtest/rules.ts +3 -1
  164. package/src/plugins/builtin/backtest/window.ts +8 -7
  165. package/src/plugins/builtin/broker-manager/detail.tsx +16 -3
  166. package/src/plugins/builtin/broker-manager/index.tsx +1 -1
  167. package/src/plugins/builtin/buildout/cells.tsx +5 -0
  168. package/src/plugins/builtin/buildout/company/flow.tsx +367 -0
  169. package/src/plugins/builtin/buildout/company/id.ts +2 -0
  170. package/src/plugins/builtin/buildout/company/model.ts +247 -0
  171. package/src/plugins/builtin/buildout/company/pane.tsx +317 -0
  172. package/src/plugins/builtin/buildout/data-runtime.ts +31 -10
  173. package/src/plugins/builtin/buildout/facilities.ts +309 -0
  174. package/src/plugins/builtin/buildout/format.ts +9 -0
  175. package/src/plugins/builtin/buildout/index.tsx +51 -5
  176. package/src/plugins/builtin/buildout/model/index.ts +62 -10
  177. package/src/plugins/builtin/buildout/model/normalizers.ts +5 -0
  178. package/src/plugins/builtin/buildout/model/types.ts +11 -2
  179. package/src/plugins/builtin/buildout/pane/index.tsx +91 -20
  180. package/src/plugins/builtin/buildout/pane/status.tsx +1 -1
  181. package/src/plugins/builtin/buildout/pane/themes.tsx +110 -0
  182. package/src/plugins/builtin/buildout/table-model.ts +26 -2
  183. package/src/plugins/builtin/builtin-plugin-meta.ts +16 -0
  184. package/src/plugins/builtin/cds/headless.ts +10 -2
  185. package/src/plugins/builtin/cds/model.ts +7 -3
  186. package/src/plugins/builtin/central-bank-rates/index.tsx +1 -2
  187. package/src/plugins/builtin/central-bank-rates/pane.tsx +2 -1
  188. package/src/plugins/builtin/central-bank-rates/series.ts +2 -3
  189. package/src/plugins/builtin/changelog/index.tsx +3 -2
  190. package/src/plugins/builtin/chart-composer/cli-options.ts +16 -0
  191. package/src/plugins/builtin/chart-composer/headless-schema.ts +10 -0
  192. package/src/plugins/builtin/chart-composer/headless.ts +88 -21
  193. package/src/plugins/builtin/chart-composer/index.tsx +2 -3
  194. package/src/plugins/builtin/chart-composer/pane.tsx +30 -2
  195. package/src/plugins/builtin/chart-composer/series-expression.ts +3 -2
  196. package/src/plugins/builtin/chart-composer/session-range.ts +5 -2
  197. package/src/plugins/builtin/chart-composer/studies.ts +1 -1
  198. package/src/plugins/builtin/chat/controller/index.ts +1 -1
  199. package/src/plugins/builtin/chat/controller/messages.ts +15 -4
  200. package/src/plugins/builtin/chat/ghost-user.ts +8 -0
  201. package/src/plugins/builtin/chat/index.tsx +5 -5
  202. package/src/plugins/builtin/clinical-trials/index.tsx +1 -2
  203. package/src/plugins/builtin/cloud/company-picker.tsx +1 -1
  204. package/src/plugins/builtin/cloud/index.tsx +1 -2
  205. package/src/plugins/builtin/cloud/plugin.tsx +6 -4
  206. package/src/plugins/builtin/cloud/team/module.tsx +1 -1
  207. package/src/plugins/builtin/cloud/thesis/module.tsx +1 -1
  208. package/src/plugins/builtin/cloud/trial-offer-status-widget.tsx +24 -1
  209. package/src/plugins/builtin/cloud/upgrade-status-widget.tsx +39 -15
  210. package/src/plugins/builtin/comment-letters/index.tsx +1 -2
  211. package/src/plugins/builtin/commodity-balances/client.ts +4 -1
  212. package/src/plugins/builtin/commodity-balances/index.tsx +3 -3
  213. package/src/plugins/builtin/commodity-balances/pane.tsx +2 -2
  214. package/src/plugins/builtin/congress-trades/aggregates.ts +13 -2
  215. package/src/plugins/builtin/congress-trades/detail.tsx +20 -4
  216. package/src/plugins/builtin/congress-trades/headless.ts +2 -0
  217. package/src/plugins/builtin/congress-trades/model.ts +92 -64
  218. package/src/plugins/builtin/congress-trades/table.tsx +14 -2
  219. package/src/plugins/builtin/connections/index.ts +2 -3
  220. package/src/plugins/builtin/cot/client.ts +28 -2
  221. package/src/plugins/builtin/cot/headless.ts +4 -1
  222. package/src/plugins/builtin/cot/index.tsx +5 -4
  223. package/src/plugins/builtin/cot/model.ts +58 -7
  224. package/src/plugins/builtin/cot/pane.tsx +6 -2
  225. package/src/plugins/builtin/cpi/client.ts +6 -5
  226. package/src/plugins/builtin/cpi/index.tsx +1 -2
  227. package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
  228. package/src/plugins/builtin/credit-boards/client.ts +7 -2
  229. package/src/plugins/builtin/credit-boards/headless.ts +25 -12
  230. package/src/plugins/builtin/credit-boards/model.ts +29 -0
  231. package/src/plugins/builtin/credit-boards/series.ts +3 -4
  232. package/src/plugins/builtin/credit-boards/sovr-pane.tsx +9 -4
  233. package/src/plugins/builtin/credit-conditions/client.ts +22 -14
  234. package/src/plugins/builtin/crypto-board/bitcoin-network.ts +143 -0
  235. package/src/plugins/builtin/crypto-board/client.ts +5 -5
  236. package/src/plugins/builtin/crypto-board/headless.ts +4 -3
  237. package/src/plugins/builtin/crypto-board/index.tsx +4 -2
  238. package/src/plugins/builtin/crypto-board/series.ts +6 -4
  239. package/src/plugins/builtin/crypto-etf-flows/client.ts +9 -2
  240. package/src/plugins/builtin/crypto-etf-flows/headless.ts +61 -6
  241. package/src/plugins/builtin/crypto-etf-flows/index.tsx +1 -2
  242. package/src/plugins/builtin/crypto-etf-flows/model.ts +14 -1
  243. package/src/plugins/builtin/crypto-etf-flows/pane.tsx +8 -5
  244. package/src/plugins/builtin/debt-maturities/headless.ts +4 -3
  245. package/src/plugins/builtin/debt-maturities/model.ts +17 -1
  246. package/src/plugins/builtin/debt-maturities/pane.tsx +18 -4
  247. package/src/plugins/builtin/debt-maturities/series.ts +3 -3
  248. package/src/plugins/builtin/debug/index.tsx +1 -1
  249. package/src/plugins/builtin/dividend-yield/client.ts +32 -2
  250. package/src/plugins/builtin/dividend-yield/headless.ts +58 -33
  251. package/src/plugins/builtin/dividend-yield/index.tsx +37 -2
  252. package/src/plugins/builtin/dividend-yield/pane.tsx +32 -13
  253. package/src/plugins/builtin/dividend-yield/ranking-reads.ts +73 -0
  254. package/src/plugins/builtin/dividend-yield/ranking-view.tsx +163 -0
  255. package/src/plugins/builtin/dividend-yield/ranking.ts +1 -1
  256. package/src/plugins/builtin/dividend-yield/view.ts +21 -0
  257. package/src/plugins/builtin/docs/index.tsx +2 -3
  258. package/src/plugins/builtin/doe/client.ts +6 -5
  259. package/src/plugins/builtin/doe/headless.ts +16 -3
  260. package/src/plugins/builtin/doe/index.tsx +5 -4
  261. package/src/plugins/builtin/doe/prices.ts +2 -3
  262. package/src/plugins/builtin/doe/series.ts +4 -4
  263. package/src/plugins/builtin/earnings/board.tsx +2 -1
  264. package/src/plugins/builtin/earnings/history.tsx +2 -1
  265. package/src/plugins/builtin/earnings-calls/calendar.tsx +10 -2
  266. package/src/plugins/builtin/earnings-calls/data.ts +19 -11
  267. package/src/plugins/builtin/earnings-calls/pane.tsx +27 -14
  268. package/src/plugins/builtin/earnings-ripple/client.ts +32 -7
  269. package/src/plugins/builtin/earnings-ripple/index.tsx +1 -2
  270. package/src/plugins/builtin/econ/calendar-model.ts +29 -6
  271. package/src/plugins/builtin/econ/detail-view.tsx +16 -2
  272. package/src/plugins/builtin/econ-statistics/index.tsx +1 -2
  273. package/src/plugins/builtin/equity-screener/client.ts +1 -1
  274. package/src/plugins/builtin/equity-screener/headless.ts +1 -0
  275. package/src/plugins/builtin/equity-screener/ideas.ts +30 -1
  276. package/src/plugins/builtin/equity-screener/index.tsx +1 -2
  277. package/src/plugins/builtin/equity-screener/model.ts +28 -4
  278. package/src/plugins/builtin/equity-screener/pane.tsx +5 -0
  279. package/src/plugins/builtin/equity-screener/screen-check.ts +23 -1
  280. package/src/plugins/builtin/estimate-revisions/index.tsx +1 -2
  281. package/src/plugins/builtin/executives/data.ts +6 -1
  282. package/src/plugins/builtin/executives/pane.tsx +9 -21
  283. package/src/plugins/builtin/exposure/holdings.ts +35 -13
  284. package/src/plugins/builtin/exposure/index.tsx +1 -2
  285. package/src/plugins/builtin/fear-greed/cache.ts +4 -0
  286. package/src/plugins/builtin/fear-greed/cli.ts +44 -0
  287. package/src/plugins/builtin/fear-greed/index.tsx +5 -53
  288. package/src/plugins/builtin/fear-greed/pane.tsx +2 -1
  289. package/src/plugins/builtin/fear-greed/series.ts +6 -4
  290. package/src/plugins/builtin/fixed-income/index.tsx +1 -2
  291. package/src/plugins/builtin/futures/board-reads.ts +25 -0
  292. package/src/plugins/builtin/futures/coalesced-bump.ts +44 -0
  293. package/src/plugins/builtin/futures/index.tsx +15 -0
  294. package/src/plugins/builtin/futures/model.ts +14 -0
  295. package/src/plugins/builtin/futures/use-board-terms.ts +22 -15
  296. package/src/plugins/builtin/futures/use-front-returns.ts +20 -14
  297. package/src/plugins/builtin/futures-curve/headless.ts +6 -1
  298. package/src/plugins/builtin/futures-curve/index.tsx +1 -2
  299. package/src/plugins/builtin/fx-matrix/index.tsx +2 -2
  300. package/src/plugins/builtin/gpu/board.tsx +51 -12
  301. package/src/plugins/builtin/gpu/client.ts +19 -3
  302. package/src/plugins/builtin/gpu/equities.tsx +5 -2
  303. package/src/plugins/builtin/gpu/headless.ts +14 -2
  304. package/src/plugins/builtin/gpu/index.tsx +1 -2
  305. package/src/plugins/builtin/gpu/model.ts +20 -2
  306. package/src/plugins/builtin/gpu/pane.tsx +1 -1
  307. package/src/plugins/builtin/gpu/series.ts +2 -3
  308. package/src/plugins/builtin/help/index.tsx +1 -1
  309. package/src/plugins/builtin/holders/beneficial-view.tsx +1 -2
  310. package/src/plugins/builtin/holders/index-holders.ts +24 -12
  311. package/src/plugins/builtin/holders/index.tsx +2 -3
  312. package/src/plugins/builtin/ipo-calendar/index.tsx +1 -2
  313. package/src/plugins/builtin/iv-history/index.tsx +1 -2
  314. package/src/plugins/builtin/jodi/client.ts +4 -1
  315. package/src/plugins/builtin/jodi/index.tsx +1 -2
  316. package/src/plugins/builtin/kelly-sizer/index.tsx +1 -2
  317. package/src/plugins/builtin/layout-manager/index.tsx +9 -8
  318. package/src/plugins/builtin/macro-day/pane.tsx +4 -2
  319. package/src/plugins/builtin/market-halts/index.tsx +1 -2
  320. package/src/plugins/builtin/market-heatmap/index.tsx +11 -2
  321. package/src/plugins/builtin/market-movers/client.ts +1 -0
  322. package/src/plugins/builtin/market-movers/index.tsx +16 -5
  323. package/src/plugins/builtin/market-movers/screener.ts +23 -6
  324. package/src/plugins/builtin/market-movers/session-body.tsx +6 -2
  325. package/src/plugins/builtin/market-valuation/index.tsx +1 -2
  326. package/src/plugins/builtin/members/index.tsx +1 -2
  327. package/src/plugins/builtin/money-markets/index.tsx +1 -2
  328. package/src/plugins/builtin/money-markets/pane.tsx +2 -1
  329. package/src/plugins/builtin/money-markets/series.ts +3 -5
  330. package/src/plugins/builtin/news/headless.ts +7 -0
  331. package/src/plugins/builtin/news/wire/index.tsx +1 -1
  332. package/src/plugins/builtin/news/wire/news/pop-out.tsx +9 -3
  333. package/src/plugins/builtin/notes/index.tsx +3 -6
  334. package/src/plugins/builtin/openfda/index.tsx +1 -2
  335. package/src/plugins/builtin/options/view.tsx +6 -5
  336. package/src/plugins/builtin/options-calculator/index.tsx +1 -2
  337. package/src/plugins/builtin/options-positioning/index.tsx +1 -2
  338. package/src/plugins/builtin/options-scenario/index.tsx +2 -4
  339. package/src/plugins/builtin/perps/index.tsx +1 -2
  340. package/src/plugins/builtin/plugin-marketplace/index.tsx +2 -3
  341. package/src/plugins/builtin/plugin-marketplace/pane.tsx +2 -1
  342. package/src/plugins/builtin/plugin-module.ts +16 -2
  343. package/src/plugins/builtin/portfolio-list/cli/render.ts +53 -3
  344. package/src/plugins/builtin/portfolio-list/index.tsx +1 -2
  345. package/src/plugins/builtin/portfolio-list/pane/index.tsx +11 -0
  346. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -2
  347. package/src/plugins/builtin/power/gas-client.ts +2 -1
  348. package/src/plugins/builtin/power/index.tsx +1 -2
  349. package/src/plugins/builtin/power/prices-client.ts +3 -2
  350. package/src/plugins/builtin/rate-path/index.tsx +1 -2
  351. package/src/plugins/builtin/realized-vol/headless.ts +2 -0
  352. package/src/plugins/builtin/realized-vol/index.tsx +1 -2
  353. package/src/plugins/builtin/realized-vol/model.ts +16 -2
  354. package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
  355. package/src/plugins/builtin/relative-rotation/index.tsx +1 -2
  356. package/src/plugins/builtin/research/analyst-headless.ts +33 -6
  357. package/src/plugins/builtin/research/analyst-model.ts +37 -2
  358. package/src/plugins/builtin/research/analyst-pane.tsx +25 -5
  359. package/src/plugins/builtin/research/index.tsx +5 -8
  360. package/src/plugins/builtin/research-search/index.tsx +1 -2
  361. package/src/plugins/builtin/revenue-breakdown/headless.ts +22 -8
  362. package/src/plugins/builtin/revenue-breakdown/model.ts +23 -0
  363. package/src/plugins/builtin/reverse-dcf/pane.tsx +4 -2
  364. package/src/plugins/builtin/risk-factors/data.ts +8 -4
  365. package/src/plugins/builtin/risk-factors/index.tsx +3 -1
  366. package/src/plugins/builtin/risk-factors/pane.tsx +5 -8
  367. package/src/plugins/builtin/roadmap/index.tsx +2 -3
  368. package/src/plugins/builtin/roadmap/pane.tsx +49 -2
  369. package/src/plugins/builtin/scanner/hilo-model.ts +18 -1
  370. package/src/plugins/builtin/scanner/hilo-pane.tsx +12 -2
  371. package/src/plugins/builtin/scanner/live-pane.tsx +7 -4
  372. package/src/plugins/builtin/scanner/lock-note.ts +26 -3
  373. package/src/plugins/builtin/sec/filing-content.ts +38 -6
  374. package/src/plugins/builtin/sec/headless.ts +5 -1
  375. package/src/plugins/builtin/sec/index.tsx +4 -2
  376. package/src/plugins/builtin/sec/model.ts +2 -0
  377. package/src/plugins/builtin/sectors/index.tsx +11 -5
  378. package/src/plugins/builtin/series-inventory.ts +2 -0
  379. package/src/plugins/builtin/shared/cloud-resource.ts +73 -8
  380. package/src/plugins/builtin/shared/cloud-upgrade.ts +24 -1
  381. package/src/plugins/builtin/shared/forming-bars.ts +29 -0
  382. package/src/plugins/builtin/shared/fred-points.ts +6 -1
  383. package/src/plugins/builtin/shared/fx-rate-freshness.ts +17 -0
  384. package/src/plugins/builtin/shared/pro-lock-note.ts +23 -8
  385. package/src/plugins/builtin/shared/series-detail-chart.tsx +4 -2
  386. package/src/plugins/builtin/shared/series-warning.ts +13 -4
  387. package/src/plugins/builtin/shared/use-quote-board.ts +5 -0
  388. package/src/plugins/builtin/short-interest/publications-pane.tsx +2 -1
  389. package/src/plugins/builtin/short-volume/pane.tsx +2 -1
  390. package/src/plugins/builtin/short-volume/series.ts +2 -3
  391. package/src/plugins/builtin/social-mentions/client.ts +15 -10
  392. package/src/plugins/builtin/social-mentions/index.tsx +1 -2
  393. package/src/plugins/builtin/social-mentions/pane.tsx +2 -1
  394. package/src/plugins/builtin/social-mentions/series.ts +2 -3
  395. package/src/plugins/builtin/supply-chain/client.ts +22 -1
  396. package/src/plugins/builtin/supply-chain/graph-client.ts +22 -1
  397. package/src/plugins/builtin/supply-chain/index.tsx +1 -2
  398. package/src/plugins/builtin/thirteenf/api.ts +127 -29
  399. package/src/plugins/builtin/thirteenf/data.ts +165 -35
  400. package/src/plugins/builtin/thirteenf/index.tsx +2 -4
  401. package/src/plugins/builtin/thirteenf/model.ts +2 -0
  402. package/src/plugins/builtin/thirteenf/overlap-pane.tsx +5 -1
  403. package/src/plugins/builtin/thirteenf/pane.tsx +41 -16
  404. package/src/plugins/builtin/thirteenf/types.ts +14 -0
  405. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +22 -6
  406. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +81 -11
  407. package/src/plugins/builtin/ticker-detail/financials/model.ts +35 -2
  408. package/src/plugins/builtin/ticker-detail/financials/ratios.ts +53 -18
  409. package/src/plugins/builtin/ticker-detail/headless.ts +70 -11
  410. package/src/plugins/builtin/ticker-detail/index.tsx +5 -10
  411. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +16 -6
  412. package/src/plugins/builtin/time-sales/index.tsx +1 -2
  413. package/src/plugins/builtin/time-sales/pane.tsx +3 -1
  414. package/src/plugins/builtin/trace-bonds/headless.ts +11 -2
  415. package/src/plugins/builtin/trace-bonds/model.ts +44 -10
  416. package/src/plugins/builtin/trace-bonds/pane.tsx +12 -7
  417. package/src/plugins/builtin/treasury-auctions/cache.ts +20 -8
  418. package/src/plugins/builtin/treasury-auctions/client.ts +3 -3
  419. package/src/plugins/builtin/treasury-auctions/headless.ts +3 -1
  420. package/src/plugins/builtin/treasury-auctions/pane.tsx +10 -4
  421. package/src/plugins/builtin/vol-surface/client.ts +18 -3
  422. package/src/plugins/builtin/vol-surface/index.tsx +1 -2
  423. package/src/plugins/builtin/volatility/client.ts +43 -16
  424. package/src/plugins/builtin/volatility/headless.ts +5 -2
  425. package/src/plugins/builtin/volatility/index.tsx +1 -2
  426. package/src/plugins/builtin/volatility/model.ts +17 -6
  427. package/src/plugins/builtin/volatility/pane.tsx +6 -2
  428. package/src/plugins/builtin/volatility/series.ts +2 -1
  429. package/src/plugins/builtin/world-indices/footer.ts +6 -1
  430. package/src/plugins/builtin/world-venue-map/client.ts +10 -1
  431. package/src/plugins/builtin/world-venue-map/index.ts +6 -2
  432. package/src/plugins/builtin/world-venue-map/layered-pane.tsx +48 -7
  433. package/src/plugins/builtin/world-venue-map/layers.ts +17 -0
  434. package/src/plugins/builtin/world-venue-map/local-layers.ts +141 -0
  435. package/src/plugins/builtin/world-venue-map/map-symbols.ts +18 -0
  436. package/src/plugins/builtin/world-venue-map/map.tsx +4 -2
  437. package/src/plugins/builtin/world-venue-map/scope.ts +63 -0
  438. package/src/plugins/builtin/yield-curve/client.ts +4 -6
  439. package/src/plugins/builtin/yield-curve/curves.ts +16 -2
  440. package/src/plugins/builtin/yield-curve/headless.ts +3 -3
  441. package/src/plugins/bundle.ts +52 -2
  442. package/src/plugins/catalog-browser.ts +2 -2
  443. package/src/plugins/catalog-cli.ts +20 -0
  444. package/src/plugins/host-link.ts +13 -1
  445. package/src/plugins/installer.ts +64 -42
  446. package/src/plugins/loader.ts +43 -11
  447. package/src/plugins/peer-imports.ts +201 -0
  448. package/src/plugins/peer-retry.ts +26 -29
  449. package/src/plugins/plugin-process.ts +128 -0
  450. package/src/plugins/redirection-guard.ts +45 -0
  451. package/src/remote/endpoint.ts +16 -3
  452. package/src/renderers/browser/main.tsx +3 -2
  453. package/src/renderers/cloudflare/http-proxy.ts +7 -123
  454. package/src/renderers/dom/desktop/icons.tsx +5 -2
  455. package/src/renderers/dom/host/image-surface.tsx +46 -21
  456. package/src/renderers/dom/styles.css +7 -0
  457. package/src/renderers/dom/toast-host.tsx +2 -0
  458. package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
  459. package/src/renderers/electrobun/bun/desktop/detached-windows.ts +23 -1
  460. package/src/renderers/electrobun/bun/desktop/initialization.ts +12 -3
  461. package/src/renderers/electrobun/bun/desktop/rpc-timing.ts +88 -0
  462. package/src/renderers/electrobun/bun/desktop/update.ts +20 -4
  463. package/src/renderers/electrobun/bun/desktop/view-ids.ts +8 -4
  464. package/src/renderers/electrobun/bun/external-plugins.ts +4 -0
  465. package/src/renderers/electrobun/bun/window/attention.ts +54 -0
  466. package/src/renderers/electrobun/bun/window/create.ts +6 -2
  467. package/src/renderers/electrobun/bun/window/native-timing.ts +48 -0
  468. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +82 -4
  469. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +13 -5
  470. package/src/renderers/electrobun/view/desktop/window/snapshot.ts +3 -0
  471. package/src/renderers/electrobun/view/main.tsx +7 -2
  472. package/src/renderers/electrobun/view/remote/asset-data-client.ts +1 -0
  473. package/src/renderers/electrobun/view/ui-host.tsx +9 -2
  474. package/src/renderers/opentui/browser-open.ts +10 -4
  475. package/src/renderers/opentui/image/surface.tsx +1 -1
  476. package/src/renderers/opentui/start.tsx +12 -6
  477. package/src/renderers/opentui/toast-host.tsx +2 -1
  478. package/src/series/registry.ts +18 -0
  479. package/src/series/resolve.ts +2 -1
  480. package/src/series/standalone.ts +10 -1
  481. package/src/series/types.ts +12 -0
  482. package/src/series/validate.ts +11 -2
  483. package/src/sources/gloomberb-cloud/fred-series.ts +5 -3
  484. package/src/sources/gloomberb-cloud/index.ts +28 -5
  485. package/src/sources/gloomberb-cloud/news.ts +24 -3
  486. package/src/sources/gloomberb-cloud/normalizers.ts +17 -2
  487. package/src/sources/provider-errors.ts +26 -3
  488. package/src/sources/provider-router/history.ts +5 -0
  489. package/src/sources/provider-router/index.ts +13 -0
  490. package/src/sync/core-contributors.ts +32 -0
  491. package/src/telemetry/attention-counts.ts +64 -6
  492. package/src/telemetry/attention-setting.ts +33 -0
  493. package/src/telemetry/crash-reports-node.ts +3 -1
  494. package/src/telemetry/stall-clocks.ts +93 -0
  495. package/src/telemetry/stall-reports-node.ts +25 -9
  496. package/src/telemetry/stall-reports.ts +218 -13
  497. package/src/test-support/outage.ts +8 -0
  498. package/src/test-support/preload.ts +9 -0
  499. package/src/tickers/company-name-listing.ts +59 -29
  500. package/src/tickers/company-name.ts +84 -1
  501. package/src/tickers/listing-fate.ts +14 -3
  502. package/src/tickers/saved-instrument.ts +17 -0
  503. package/src/tickers/search/home-listing.ts +104 -8
  504. package/src/tickers/search/index.ts +5 -2
  505. package/src/tickers/search/types.ts +6 -1
  506. package/src/time-series/bar-aggregation.ts +7 -1
  507. package/src/time-series/base-series.ts +3 -3
  508. package/src/time-series/chart-bounds.ts +1 -1
  509. package/src/time-series/chart-data.ts +5 -2
  510. package/src/time-series/chart-seed.ts +1 -1
  511. package/src/time-series/coin-session-close.ts +211 -0
  512. package/src/time-series/live-bars.ts +13 -2
  513. package/src/time-series/live-quotes.ts +1 -1
  514. package/src/time-series/market.ts +5 -3
  515. package/src/time-series/official-close.ts +159 -0
  516. package/src/time-series/price-comparison.ts +75 -8
  517. package/src/time-series/resolve.ts +32 -4
  518. package/src/time-series/series-presentation.ts +1 -1
  519. package/src/time-series/studies/formula.ts +205 -0
  520. package/src/time-series/studies/indicators.ts +299 -0
  521. package/src/time-series/studies/leverage.ts +75 -0
  522. package/src/time-series/studies/pair.ts +138 -0
  523. package/src/time-series/studies/resolve.ts +333 -0
  524. package/src/time-series/studies/shared.ts +141 -0
  525. package/src/time-series/studies/units.ts +243 -0
  526. package/src/time-series/studies/vwap.ts +274 -0
  527. package/src/time-series/types.ts +7 -0
  528. package/src/types/config.ts +18 -2
  529. package/src/types/data-provider.ts +23 -0
  530. package/src/types/financials.ts +40 -3
  531. package/src/types/headless.ts +42 -3
  532. package/src/types/plugin.ts +32 -3
  533. package/src/ui/host.tsx +17 -1
  534. package/src/ui/toast.tsx +26 -1
  535. package/src/utils/ascii-text.ts +140 -0
  536. package/src/utils/cli-output.ts +91 -18
  537. package/src/utils/concurrency.ts +24 -0
  538. package/src/utils/exchanges.ts +6 -0
  539. package/src/utils/external-url.ts +20 -7
  540. package/src/utils/financial-statements.ts +73 -16
  541. package/src/utils/fundamentals.ts +17 -5
  542. package/src/utils/futures-generic.ts +4 -4
  543. package/src/utils/http-proxy-upstream.ts +124 -0
  544. package/src/utils/lease-liabilities.ts +54 -0
  545. package/src/utils/main-thread-stall.ts +122 -5
  546. package/src/utils/perf-marks.ts +58 -12
  547. package/src/utils/price-history-integrity.ts +47 -1
  548. package/src/utils/private-files.ts +141 -0
  549. package/src/utils/public-address.ts +99 -0
  550. package/src/account/upgrade-personalization.ts +0 -57
  551. package/src/account/upgrade-tickers.ts +0 -124
  552. package/src/api-client/upgrade-experiment.ts +0 -91
  553. package/src/plugins/builtin/cloud/browser.tsx +0 -3
  554. package/src/time-series/studies.ts +0 -1628
@@ -6,7 +6,7 @@ import type {
6
6
  GloomPlugin,
7
7
  } from "../types/plugin";
8
8
  import type { LoadedExternalPlugin } from "../plugins/loader";
9
- import { getPluginCatalog } from "../plugins/catalog";
9
+ import { getBuiltinCliCommands } from "../plugins/catalog-cli";
10
10
  import { initCliServices, initConfigData, initMarketData } from "./context";
11
11
  import { closeAndFail, fail } from "./errors";
12
12
  import { DEFAULT_CLI_OPTIONS, type CliGlobalOptions } from "./options";
@@ -38,11 +38,14 @@ export interface CliCommandRegistry {
38
38
  config: AppConfig | null;
39
39
  plugins: GloomPlugin[];
40
40
  externalPlugins: LoadedExternalPlugin[];
41
+ /** Loads installed contributions when dispatch or application services need them. */
42
+ ensureExternalPlugins?: () => Promise<void>;
41
43
  }
42
44
 
43
45
  interface BuildCliCommandRegistryOptions {
44
46
  coreCommands: CliCommandDef[];
45
47
  externalPlugins?: LoadedExternalPlugin[];
48
+ loadExternalPlugins?: () => Promise<LoadedExternalPlugin[]>;
46
49
  config?: AppConfig | null;
47
50
  }
48
51
 
@@ -72,11 +75,11 @@ export function normalizeCliDispatchResult(result: void | CliDispatchResult): Cl
72
75
  export function buildCliCommandRegistry({
73
76
  coreCommands,
74
77
  externalPlugins = [],
78
+ loadExternalPlugins,
75
79
  config = null,
76
80
  }: BuildCliCommandRegistryOptions): CliCommandRegistry {
77
81
  const allCommands: RegisteredCliCommand[] = [];
78
82
  const allTokens = new Map<string, RegisteredCliCommand>();
79
- const catalog = getPluginCatalog(externalPlugins);
80
83
  const disabledPlugins = new Set(config?.disabledPlugins ?? []);
81
84
 
82
85
  const registerCommand = (
@@ -110,8 +113,10 @@ export function buildCliCommandRegistry({
110
113
  registerCommand(command, "core", "core", true);
111
114
  }
112
115
 
113
- const loadablePlugins: GloomPlugin[] = [];
114
- for (const entry of catalog) {
116
+ for (const { pluginId, commands } of getBuiltinCliCommands()) {
117
+ for (const command of commands) registerCommand(command, pluginId, "plugin", true);
118
+ }
119
+ for (const entry of externalPlugins) {
115
120
  if (entry.error) {
116
121
  registryLog.warn(`Skipping external plugin "${entry.plugin.id}" for CLI registration.`, {
117
122
  path: entry.path,
@@ -119,9 +124,8 @@ export function buildCliCommandRegistry({
119
124
  });
120
125
  continue;
121
126
  }
122
- loadablePlugins.push(entry.plugin);
123
127
  for (const command of entry.plugin.cliCommands ?? []) {
124
- registerCommand(command, entry.plugin.id, "plugin", entry.source === "builtin");
128
+ registerCommand(command, entry.plugin.id, "plugin", false);
125
129
  }
126
130
  }
127
131
 
@@ -137,30 +141,54 @@ export function buildCliCommandRegistry({
137
141
  }
138
142
  }
139
143
 
140
- return {
144
+ let plugins: GloomPlugin[] | undefined;
145
+ let loading: Promise<void> | undefined;
146
+ const registry: CliCommandRegistry = {
141
147
  commands,
142
148
  lookup,
143
149
  config,
144
- plugins: loadablePlugins.filter((plugin) => !disabledPlugins.has(plugin.id)),
150
+ get plugins() {
151
+ const { getPluginCatalog } = require("../plugins/catalog") as typeof import("../plugins/catalog");
152
+ return plugins ??= getPluginCatalog(registry.externalPlugins)
153
+ .filter((entry) => !entry.error && !disabledPlugins.has(entry.plugin.id))
154
+ .map((entry) => entry.plugin);
155
+ },
145
156
  externalPlugins,
157
+ ...(loadExternalPlugins ? { ensureExternalPlugins: () => loading ??= (async () => {
158
+ const loaded = await loadExternalPlugins();
159
+ const expanded = buildCliCommandRegistry({ coreCommands, externalPlugins: loaded, config });
160
+ registry.commands = expanded.commands;
161
+ registry.lookup = expanded.lookup;
162
+ registry.externalPlugins = loaded;
163
+ // A command can retain ctx.plugins before opening market data (search does).
164
+ // Keep that reference current without materializing panes for command-only work.
165
+ if (plugins) plugins.splice(0, plugins.length, ...expanded.plugins);
166
+ })() } : {}),
146
167
  };
168
+ return registry;
147
169
  }
148
170
 
149
171
  export function createCliCommandContext(
150
172
  ownerId: string,
151
- registryOrPlugins: Pick<CliCommandRegistry, "plugins" | "externalPlugins"> | GloomPlugin[],
173
+ registryOrPlugins: Pick<CliCommandRegistry, "plugins" | "externalPlugins" | "ensureExternalPlugins"> | GloomPlugin[],
152
174
  cliOptions: CliGlobalOptions = DEFAULT_CLI_OPTIONS,
153
175
  args: readonly string[] = [],
154
176
  ): CliCommandContext {
155
- const registry = Array.isArray(registryOrPlugins)
177
+ const registry: Pick<CliCommandRegistry, "plugins" | "externalPlugins" | "ensureExternalPlugins"> = Array.isArray(registryOrPlugins)
156
178
  ? { plugins: registryOrPlugins, externalPlugins: [] }
157
179
  : registryOrPlugins;
158
180
  return {
159
181
  initConfigData,
160
- initMarketData: () => initMarketData({ plugins: registry.plugins }),
161
- initServices: () => initCliServices({ externalPlugins: registry.externalPlugins }),
182
+ initMarketData: async () => {
183
+ await registry.ensureExternalPlugins?.();
184
+ return initMarketData({ plugins: registry.plugins });
185
+ },
186
+ initServices: async () => {
187
+ await registry.ensureExternalPlugins?.();
188
+ return initCliServices({ externalPlugins: registry.externalPlugins });
189
+ },
162
190
  cliOptions,
163
- plugins: registry.plugins,
191
+ get plugins() { return registry.plugins; },
164
192
  fail,
165
193
  closeAndFail,
166
194
  output: {
package/src/cli/result.ts CHANGED
@@ -93,6 +93,8 @@ export interface CliResultRenderOptions<T = unknown, Row = Record<string, unknow
93
93
  heading?: string;
94
94
  /** Text-mode line printed under the rows, such as where a listing stops. Not printed with no rows: say it in `empty`. */
95
95
  footnote?: string;
96
+ /** Text-mode line under each table row, indented and muted, cut to the width: a story's summary under its headline. */
97
+ rowNote?: (row: Row) => string | null | undefined;
96
98
  /** Text-mode block printed under the rows and footnote, for figures about a row that are not columns. */
97
99
  details?: (data: T) => string;
98
100
  /**
@@ -446,6 +448,7 @@ function formatTextCell<Row extends Record<string, unknown>>(
446
448
  function renderTextTable<Row extends Record<string, unknown>>(
447
449
  rows: Row[],
448
450
  columns?: CliResultColumn<Row>[],
451
+ rowNote?: (row: Row) => string | null | undefined,
449
452
  ): string {
450
453
  const resolvedColumns = textColumns(rows, columns);
451
454
  const cells = rows.map((row) => resolvedColumns.map((column) => formatTextCell(column, row, "table")));
@@ -469,6 +472,7 @@ function renderTextTable<Row extends Record<string, unknown>>(
469
472
  stackRows: column.stackRows,
470
473
  })),
471
474
  cells.map((row) => shown.map(({ index }) => row[index]!)),
475
+ rowNote ? { below: rows.map((row) => { const note = rowNote(row); return note ? [note] : []; }), cutBelow: true } : {},
472
476
  );
473
477
  }
474
478
 
@@ -581,15 +585,16 @@ function serializeCliRows<T, Row extends Record<string, unknown>>(
581
585
  if (renderOptions.text) {
582
586
  return renderOptions.text(result.data);
583
587
  }
584
- const summary = renderOptions.summary?.(result.data) ?? "";
585
- const withHeading = (text: string) => renderOptions.heading ? `${renderOptions.heading}\n${text}` : text;
588
+ // The heading's lines, the summary and the notes are prose: each wraps to a narrow --width, as the source line does.
589
+ const summary = wrapToTerminal(renderOptions.summary?.(result.data) ?? "");
590
+ const withHeading = (text: string) => renderOptions.heading ? `${wrapToTerminal(renderOptions.heading)}\n${text}` : text;
586
591
  if (rows.length === 0) {
587
- return withHeading(summary || cliStyles.muted(renderOptions.empty ?? "No results."));
592
+ return withHeading(summary || cliStyles.muted(wrapToTerminal(renderOptions.empty ?? "No results.")));
588
593
  }
589
594
  const table = renderTextRows(rows as Row[], result.data, renderOptions);
590
595
  const notes = [window.note, renderOptions.footnote].filter((note): note is string => !!note);
591
596
  const details = renderOptions.details?.(result.data) ?? "";
592
- const body = [table, ...notes.map((note) => cliStyles.muted(note)), ...details ? ["", details] : []].join("\n");
597
+ const body = [table, ...notes.map((note) => cliStyles.muted(wrapToTerminal(note))), ...details ? ["", details] : []].join("\n");
593
598
  return withHeading(summary ? `${summary}\n\n${body}` : body);
594
599
  }
595
600
 
@@ -607,7 +612,7 @@ function renderTextRows<T, Row extends Record<string, unknown>>(
607
612
  }
608
613
  return layout === "record"
609
614
  ? renderTextRecords(rows as Row[], columns)
610
- : renderTextTable(rows as Row[], columns);
615
+ : renderTextTable(rows as Row[], columns, renderOptions.rowNote);
611
616
  }
612
617
 
613
618
  /** Settles once the result `printCliResult` last wrote has left the process: a pipe can hold it back. */
@@ -640,8 +645,9 @@ export function printCliResult<T, Row extends Record<string, unknown> = Record<s
640
645
  // JSON carries warnings in its envelope. Text, CSV and NDJSON are only the
641
646
  // rows, so a warning goes to stderr, out of a file the rows are piped into.
642
647
  if (options.format !== "json" && !options.quiet) {
648
+ // Each wraps to a narrow --width, as a note does.
643
649
  for (const warning of result.warnings ?? []) {
644
- console.error(`${cliStyles.warning("warning:")} ${hinted(warning)}`);
650
+ console.error(wrapToTerminal(`${cliStyles.warning("warning:")} ${hinted(warning)}`));
645
651
  }
646
652
  }
647
653
  // CSV says what its rows do not in closing `# note:` lines; NDJSON has no place for one among its rows,
@@ -139,7 +139,8 @@ export interface CliTickerOptions {
139
139
  */
140
140
  export class CoinListingError extends ListingArgError {
141
141
  constructor(message: string, details: string, readonly hint: string) {
142
- super(message, `${details}\n${hint}`);
142
+ // A listing message that already ends in the coin note (`describeOtherListings`) keeps it once.
143
+ super(message, details.endsWith(hint) ? details : `${details}\n${hint}`);
143
144
  this.name = "CoinListingError";
144
145
  }
145
146
  }
package/src/cli/types.ts CHANGED
@@ -21,6 +21,8 @@ export type MarketContext = ConfigContext & {
21
21
  seriesCapabilities?: CapabilityInvoker;
22
22
  /** The catalog series charted as an expression (`FRED:DFII10`), in a report run without the plugin runtime. */
23
23
  findCatalogSeries?: (expression: string) => import("../series/types").SeriesDefinition | undefined;
24
+ /** The catalog series a bare word is an alias of (`hashrate`), in a report run without the plugin runtime. */
25
+ findCatalogSeriesNamed?: (word: string) => import("../series/types").SeriesDefinition | undefined;
24
26
  /** `--refresh`: prefetched data bypasses the local cache. */
25
27
  refresh?: boolean;
26
28
  /** Filings behind the statements and a bank's figures, read from Gloom Cloud; absent where the data service is a stand-in. */
@@ -49,6 +49,7 @@ function formatFullCurrencyValue(
49
49
  options: MarketFormatOptions = {},
50
50
  ): string | null {
51
51
  if (unit.trim() === INDEX_POINTS_UNIT) return formatMarketPrice(value, { ...options, assetCategory, priceBasis: "points" });
52
+ if (unit.trim() === "32nds") return formatMarketPrice(value, { ...options, assetCategory, priceBasis: "thirty-seconds" });
52
53
  const currency = unitCurrencyCode(unit);
53
54
  return currency ? formatMarketPriceWithCurrency(value, currency, { ...options, assetCategory }) : null;
54
55
  }
@@ -362,6 +363,8 @@ export function formatCompositeAxisValue(value: number, domain: CompositeAxisDom
362
363
  // Derived dimensions are included in the legend value. Axis labels stay
363
364
  // numeric so a narrow gutter cannot truncate USD/JPY into a false USD label.
364
365
  if (group.startsWith("derived-unit:")) return compact;
366
+ // Treasury prices keep their whole points and 32nds at every zoom level.
367
+ if (domain.unit.trim() === "32nds") return formatAxisPriceValue(value, domain);
365
368
  // Compact labels suit a wide view, but a zoomed one can hold several ticks
366
369
  // inside a single rounding step and print one repeated price down the gutter.
367
370
  // Where that happens the axis spends the digits needed to tell them apart,
@@ -257,7 +257,7 @@ export function ChartTableHeader(props: ChartTableHeaderProps) {
257
257
  return (
258
258
  <Box flexDirection="column" flexShrink={0}>
259
259
  {query}
260
- {layout.figureRows ? <StatGrid items={layout.figures} width={width} /> : null}
260
+ {layout.figureRows ? <StatGrid items={layout.figures} width={width} maxLines={layout.figureMaxLines} /> : null}
261
261
  {band}
262
262
  </Box>
263
263
  );
@@ -31,6 +31,8 @@ const CHART_SHARE = 0.4;
31
31
  const SHORT_BODY_ROWS = 12;
32
32
  /** The figures never take more than this share of the body. */
33
33
  const FIGURE_SHARE = 0.25;
34
+ /** The share of the body figures that wrap rather than cut a date may take, beyond FIGURE_SHARE. */
35
+ const WRAPPED_FIGURE_SHARE = 1 / 3;
34
36
 
35
37
  /**
36
38
  * The rows a kit table spends outside its body: the header, plus a horizontal
@@ -70,15 +72,28 @@ export interface ChartTableLayoutInput {
70
72
  export interface ChartTableLayout {
71
73
  figures: StatItem[];
72
74
  figureRows: number;
75
+ /** The lines the figures may take, so a whole-row cell too narrow for one line wraps within them (StatGrid's `maxLines`). */
76
+ figureMaxLines: number;
73
77
  mode: ChartBandMode;
74
78
  chartRows: number;
75
79
  }
76
80
 
77
- /** Drops figures from the end until they fit the row budget. */
78
- function fitFigures(figures: readonly StatItem[], width: number, maxRows: number): StatItem[] {
81
+ /** Rows `figures` take within `maxLines` (StatGrid's own count). */
82
+ function figureRows(figures: readonly StatItem[], width: number, maxLines: number): number {
83
+ return statGridRows([...figures], width, undefined, { maxLines });
84
+ }
85
+
86
+ /**
87
+ * The figures that fit the row budget a line each, dropped from the end, and
88
+ * the lines they may take: a whole-row figure too narrow for one line wraps
89
+ * onto a second rather than cut a date, into rows up to WRAPPED_FIGURE_SHARE
90
+ * of the body, the leading ones first; the rest keep one line, cut. Wrapping
91
+ * never costs a figure that fits a line. A budget of one row wraps none.
92
+ */
93
+ function fitFigures(figures: readonly StatItem[], width: number, maxRows: number, body: number): { figures: StatItem[]; maxLines: number } {
79
94
  const kept = [...figures];
80
- while (kept.length > 0 && statGridRows(kept, width) > maxRows) kept.pop();
81
- return kept;
95
+ while (kept.length > 0 && figureRows(kept, width, maxRows) > maxRows) kept.pop();
96
+ return { figures: kept, maxLines: maxRows > 1 ? Math.max(maxRows, Math.floor(body * WRAPPED_FIGURE_SHARE)) : maxRows };
82
97
  }
83
98
 
84
99
  /**
@@ -89,7 +104,9 @@ function fitFigures(figures: readonly StatItem[], width: number, maxRows: number
89
104
  * fewer), so the rows the pane is about never vanish.
90
105
  * - The figures take at most a quarter of the body, one row when it is short
91
106
  * or when a second row would cost the chart its place, and lose items from
92
- * the end rather than wrap.
107
+ * the end rather than spill; a whole-row figure wraps onto a second line
108
+ * rather than cut its detail, in rows up to a third of the body, never
109
+ * at the cost of a figure that fits a line.
93
110
  * - The chart takes 40% of what is left, or everything the table does not
94
111
  * need when all its rows fit, so no blank band opens under a short table.
95
112
  * - Below a readable chart the band becomes a one-row strip, then nothing.
@@ -125,11 +142,11 @@ function layoutWithFigureRows(input: ChartTableLayoutInput, body: number, figure
125
142
 
126
143
  // A table with no room for a single row after the figures gets the body back.
127
144
  const figureBudget = Math.min(figureCap, Math.max(0, body - chrome - Math.min(tableRows, 1)));
128
- const figures = figureBudget > 0 ? fitFigures(input.figures ?? [], width, figureBudget) : [];
129
- const figureRows = figures.length ? statGridRows(figures, width) : 0;
145
+ const { figures, maxLines: figureMaxLines } = figureBudget > 0 ? fitFigures(input.figures ?? [], width, figureBudget, body) : { figures: [], maxLines: 0 };
146
+ const shownRows = figures.length ? figureRows(figures, width, figureMaxLines) : 0;
130
147
 
131
- const rest = body - figureRows;
132
- const none: ChartTableLayout = { figures, figureRows, mode: "none", chartRows: 0 };
148
+ const rest = body - shownRows;
149
+ const none: ChartTableLayout = { figures, figureRows: shownRows, figureMaxLines, mode: "none", chartRows: 0 };
133
150
  if (!input.chart || width < CHART_MIN_WIDTH) return none;
134
151
 
135
152
  const minRows = input.chart.minRows ?? CHART_MIN_ROWS;
@@ -137,7 +154,7 @@ function layoutWithFigureRows(input: ChartTableLayoutInput, body: number, figure
137
154
  const maxRows = input.chart.maxRows ?? Number.POSITIVE_INFINITY;
138
155
  const room = rest - tableMin;
139
156
  const spare = rest - tableFit;
140
- const full = (rows: number): ChartTableLayout => ({ figures, figureRows, mode: "full", chartRows: Math.min(rows, maxRows) });
157
+ const full = (rows: number): ChartTableLayout => ({ figures, figureRows: shownRows, figureMaxLines, mode: "full", chartRows: Math.min(rows, maxRows) });
141
158
  if (room >= minRows) {
142
159
  // With every table row in view beside a readable chart, the chart takes
143
160
  // exactly the rest; otherwise its 40% share, never below what it draws in.
@@ -148,7 +165,7 @@ function layoutWithFigureRows(input: ChartTableLayoutInput, body: number, figure
148
165
  // instead of a strip over a blank band.
149
166
  if (spare >= compactRows) return full(spare);
150
167
  if (input.chart.strip !== false && room >= 1) {
151
- return { figures, figureRows, mode: "strip", chartRows: 1 };
168
+ return { figures, figureRows: shownRows, figureMaxLines, mode: "strip", chartRows: 1 };
152
169
  }
153
170
  return none;
154
171
  }
@@ -32,6 +32,8 @@ interface UseCommandBarActionRuntimeOptions {
32
32
  dispatch: Dispatch<AppAction>;
33
33
  focusedPaneId: string | null;
34
34
  onCheckForUpdates?: () => void | Promise<void>;
35
+ /** Settings' Run Setup Again; absent where setup cannot run. */
36
+ onRunSetupAgain?: () => Promise<void>;
35
37
  persistConfig: (nextConfig: AppState["config"]) => void;
36
38
  pluginRegistry: PluginRegistry;
37
39
  pushRoute: (route: CommandBarRoute) => void;
@@ -57,6 +59,7 @@ export function useCommandBarActionRuntime({
57
59
  dispatch,
58
60
  focusedPaneId,
59
61
  onCheckForUpdates,
62
+ onRunSetupAgain,
60
63
  persistConfig,
61
64
  pluginRegistry,
62
65
  pushRoute,
@@ -222,6 +225,7 @@ export function useCommandBarActionRuntime({
222
225
  focusTicker,
223
226
  notify,
224
227
  onCheckForUpdates,
228
+ onRunSetupAgain,
225
229
  openBuiltInWorkflow,
226
230
  openInlineConfirm,
227
231
  openModeRoute,
@@ -29,6 +29,7 @@ import { parseWindowModeCommandArg } from "../../layout-items";
29
29
  import type { CommandBarRoute } from "../../workflow/types";
30
30
  import { recordFunctionOpen } from "../../../../telemetry/usage-counts";
31
31
  import { reportTelemetryConfig } from "../../../../telemetry/live-config";
32
+ import { ATTENTION_OFF_MESSAGE } from "../../../../telemetry/attention-setting";
32
33
  import { NIGHTLY_CHANNEL_SUMMARY, withUpdateChannel } from "../../../../updater/channel";
33
34
  import { isPrereleaseVersion } from "../../../../utils/semver";
34
35
  import { VERSION } from "../../../../version";
@@ -53,6 +54,8 @@ export function runDirectCommandAction(options: {
53
54
  getState: () => AppState;
54
55
  notify: NotifyFn;
55
56
  onCheckForUpdates?: () => void | Promise<void>;
57
+ /** Settings' Run Setup Again; absent where setup cannot run. */
58
+ onRunSetupAgain?: () => Promise<void>;
56
59
  openBuiltInWorkflow: (actionId: string) => void;
57
60
  openInlineConfirm: OpenInlineConfirm;
58
61
  openModeRoute: (screen: "ticker-search" | "layout", initialQuery?: string) => void;
@@ -78,6 +81,7 @@ export function runDirectCommandAction(options: {
78
81
  getState,
79
82
  notify,
80
83
  onCheckForUpdates,
84
+ onRunSetupAgain,
81
85
  openBuiltInWorkflow,
82
86
  openInlineConfirm,
83
87
  openModeRoute,
@@ -329,47 +333,22 @@ export function runDirectCommandAction(options: {
329
333
  return;
330
334
  }
331
335
  case "toggle-crash-reports":
332
- case "toggle-usage-counts": {
333
- const key = command.id === "toggle-crash-reports" ? "crashReports" : "usage";
336
+ case "toggle-usage-counts":
337
+ case "toggle-attention-counts": {
338
+ const key = command.id === "toggle-crash-reports" ? "crashReports" : command.id === "toggle-usage-counts" ? "usage" : "attention";
339
+ // All three are on by default: only an explicit false is off.
334
340
  const enabled = state.config.telemetry?.[key] === false;
335
341
  const nextConfig = { ...state.config, telemetry: { ...state.config.telemetry, [key]: enabled } };
342
+ // Reported before React commits, so turning Attention Counts off drops unsent counts at once.
336
343
  reportTelemetryConfig(nextConfig.telemetry);
337
344
  dispatch({ type: "SET_CONFIG", config: nextConfig });
338
345
  persistConfig(nextConfig);
339
- notify(`${t(command.label)}: ${t(enabled ? "on" : "off")}`, { type: "success" });
346
+ notify(key === "attention" && !enabled
347
+ ? t(ATTENTION_OFF_MESSAGE)
348
+ : `${t(command.label)}: ${t(enabled ? "on" : "off")}`, { type: "success" });
340
349
  closeAll({ revertThemePreview: false });
341
350
  return;
342
351
  }
343
- case "toggle-attention-counts": {
344
- const setEnabled = (enabled: boolean) => {
345
- const current = getState().config;
346
- const nextConfig = { ...current, telemetry: { ...current.telemetry, attention: enabled } };
347
- // Clear unsent counts before React commits the settings update.
348
- reportTelemetryConfig(nextConfig.telemetry);
349
- dispatch({ type: "SET_CONFIG", config: nextConfig });
350
- persistConfig(nextConfig);
351
- notify(`${t(command.label)}: ${t(enabled ? "on" : "off")}`, { type: "success" });
352
- };
353
- if (state.config.telemetry?.attention === true) {
354
- setEnabled(false);
355
- closeAll({ revertThemePreview: false });
356
- } else {
357
- openInlineConfirm({
358
- confirmId: "enable-attention-counts",
359
- title: "Share Attention Counts?",
360
- body: [
361
- "While signed in with a verified account, share the tickers you open in DES, charts, quotes and options, and add to watchlists.",
362
- "Gloom uses your account to limit contributions. Published counts are delayed and rounded, and require at least 20 contributors. Your holdings, watchlist names and searches are not included.",
363
- "This is separate from Usage Counts and is off by default. Turn it off at any time to stop collection and discard unsent counts. Already published anonymous totals cannot be removed.",
364
- ],
365
- confirmLabel: "Share Counts",
366
- cancelLabel: "Keep Off",
367
- tone: "default",
368
- onConfirm: async () => { setEnabled(true); },
369
- });
370
- }
371
- return;
372
- }
373
352
  case "font-size-increase":
374
353
  case "font-size-decrease": {
375
354
  const delta = command.id === "font-size-increase" ? 1 : -1;
@@ -391,6 +370,12 @@ export function runDirectCommandAction(options: {
391
370
  void onCheckForUpdates?.();
392
371
  closeAll({ revertThemePreview: false });
393
372
  return;
373
+ case "run-setup-again":
374
+ closeAll({ revertThemePreview: false });
375
+ void onRunSetupAgain?.().catch((error: unknown) => {
376
+ notify(error instanceof Error ? error.message : String(error), { type: "error" });
377
+ });
378
+ return;
394
379
  case "release-channel": {
395
380
  // The runtime checks again as soon as the channel changes.
396
381
  const setChannel = (channel: "stable" | "nightly") => {
@@ -39,6 +39,8 @@ interface UseCommandBarDirectCommandRuntimeOptions {
39
39
  focusTicker: (symbol: string, options?: PinTickerOptions) => void;
40
40
  notify: (body: string, options?: { type?: "info" | "success" | "error" }) => void;
41
41
  onCheckForUpdates?: () => void | Promise<void>;
42
+ /** Settings' Run Setup Again; absent where setup cannot run. */
43
+ onRunSetupAgain?: () => Promise<void>;
42
44
  openBuiltInWorkflow: (actionId: string) => void;
43
45
  openInlineConfirm: OpenInlineConfirm;
44
46
  openModeRoute: (
@@ -66,6 +68,7 @@ export function useCommandBarDirectCommandRuntime({
66
68
  focusTicker,
67
69
  notify,
68
70
  onCheckForUpdates,
71
+ onRunSetupAgain,
69
72
  openBuiltInWorkflow,
70
73
  openInlineConfirm,
71
74
  openModeRoute,
@@ -136,6 +139,7 @@ export function useCommandBarDirectCommandRuntime({
136
139
  getState,
137
140
  notify,
138
141
  onCheckForUpdates,
142
+ onRunSetupAgain,
139
143
  openBuiltInWorkflow,
140
144
  openInlineConfirm,
141
145
  openModeRoute,
@@ -159,6 +163,7 @@ export function useCommandBarDirectCommandRuntime({
159
163
  getState,
160
164
  notify,
161
165
  onCheckForUpdates,
166
+ onRunSetupAgain,
162
167
  openBuiltInWorkflow,
163
168
  openInlineConfirm,
164
169
  openModeRoute,
@@ -276,7 +276,7 @@ export const commands: Command[] = [
276
276
  id: "toggle-attention-counts",
277
277
  prefix: "",
278
278
  label: "Attention Counts",
279
- description: "Opt in to sharing ticker research counts for Gloom Trending",
279
+ description: "Turn ticker research counts on or off (on by default)",
280
280
  category: "Config",
281
281
  },
282
282
 
@@ -309,6 +309,15 @@ export const commands: Command[] = [
309
309
  category: "Config",
310
310
  },
311
311
 
312
+ // Setup, from its first step on what the user has (components/onboarding/run-setup-again.ts)
313
+ {
314
+ id: "run-setup-again",
315
+ prefix: "",
316
+ label: "Run Setup Again",
317
+ description: "Rebuild Home and setup's desk tabs; your other layouts stay",
318
+ category: "Config",
319
+ },
320
+
312
321
  // Import/Export
313
322
  {
314
323
  id: "export-config",
@@ -109,6 +109,9 @@ export function createRootCommandItemBuilder({
109
109
  if (state.updateProgress?.phase === "ready") return "Update ready, restart to apply";
110
110
  if (state.updateProgress?.phase === "error") return failedUpdateLabel(state.updateProgress);
111
111
  return command.label;
112
+ // Unlike its neighbours it started out off, so the row says what the default is now.
113
+ case "toggle-attention-counts":
114
+ return `${command.label} (on by default)`;
112
115
  default:
113
116
  return command.label;
114
117
  }
@@ -175,9 +178,9 @@ export function createRootCommandItemBuilder({
175
178
  case "toggle-usage-counts":
176
179
  return state.config.telemetry?.usage === false ? "Currently off" : "Currently on";
177
180
  case "toggle-attention-counts":
178
- return state.config.telemetry?.attention === true
179
- ? "Currently on: share ticker research counts"
180
- : "Currently off: opt in to sharing ticker research counts";
181
+ return state.config.telemetry?.attention === false
182
+ ? "Currently off. Signed in, this turns it off on your other devices too"
183
+ : "Currently on: tickers you open go to Gloom, published only as hourly totals of 20+ people";
181
184
  case "release-channel":
182
185
  if (resolveUpdateChannel(state.config) === "nightly") return `Currently nightly: ${NIGHTLY_CHANNEL_SUMMARY}`;
183
186
  return isPrereleaseVersion(VERSION)
@@ -201,7 +204,7 @@ export function createRootCommandItemBuilder({
201
204
  case "toggle-usage-counts":
202
205
  return state.config.telemetry?.usage === false ? "off" : "on";
203
206
  case "toggle-attention-counts":
204
- return state.config.telemetry?.attention === true ? "on" : "off";
207
+ return state.config.telemetry?.attention === false ? "off" : "on";
205
208
  case "release-channel":
206
209
  return resolveUpdateChannel(state.config);
207
210
  default:
@@ -223,6 +226,8 @@ export function createRootCommandItemBuilder({
223
226
  return "update";
224
227
  case "release-channel":
225
228
  return "nightly stable beta prerelease update updates channel";
229
+ case "run-setup-again":
230
+ return "settings onboarding wizard guide first run start over desks holdings";
226
231
  default:
227
232
  return "";
228
233
  }
@@ -20,6 +20,21 @@ import { buildRootShortcutFeedback } from "./shortcut-feedback";
20
20
  import type { ShortcutIntent } from "./shortcuts";
21
21
  import { clampIndex } from "../../../../utils/math";
22
22
 
23
+ /** Written the way a symbol is: up to five letters and a share class (AAPL, nvda, BRK.B). */
24
+ const SYMBOL_SHAPED_QUERY = /^[A-Za-z]{1,5}(?:[.\-/][A-Za-z]{1,2})?$/;
25
+
26
+ /**
27
+ * Text that asks for a symbol rather than a local row: symbol-shaped, and in
28
+ * capitals or in no row's name. `nvda` matches Exposure only through its
29
+ * example input; `upd` is in Check for Updates.
30
+ */
31
+ function readsAsSymbol(text: string, rows: readonly ResultItem[]): boolean {
32
+ if (!SYMBOL_SHAPED_QUERY.test(text)) return false;
33
+ if (text === text.toUpperCase()) return true;
34
+ const wanted = text.toLowerCase();
35
+ return !rows.some((row) => isDefaultSelectable(row) && row.label.toLowerCase().includes(wanted));
36
+ }
37
+
23
38
  interface UseCommandBarRootRuntimeOptions {
24
39
  activeCollectionId: string | null;
25
40
  activePortfolio?: AppState["config"]["portfolios"][number];
@@ -154,6 +169,13 @@ export function useCommandBarRootRuntime({
154
169
  } {
155
170
  const previousRootSelectionContextRef = useRef<{ query: string; mode: string } | null>(null);
156
171
  const previousRootResultIdsRef = useRef<string[]>([]);
172
+ /**
173
+ * The row the untouched highlight rests on once the query's answer is in,
174
+ * and the answer it was on. A later answer that reorders the rows (a
175
+ * fresher search over a cached one, a broker's) moves the highlight with
176
+ * its row, so Enter opens the row that was highlighted.
177
+ */
178
+ const pinnedRootRowRef = useRef<{ id: string; answer: string | null } | null>(null);
157
179
  const activeMatch = matchPrefix(rootQuery, availableCommands);
158
180
 
159
181
  const tickerSearchRouteQuery = currentRoute?.kind === "mode" && currentRoute.screen === "ticker-search"
@@ -310,6 +332,13 @@ export function useCommandBarRootRuntime({
310
332
  writeTickerSearchCache,
311
333
  });
312
334
  const rootSearching = tickerSearching || providerSearching;
335
+ // A symbol whose search has not answered: the rows on screen are what the
336
+ // letters match locally (a pane whose example names AAPL), not the symbol,
337
+ // so none of them is highlighted and Enter waits for the answer, as it would
338
+ // get had the symbol been typed slowly. Lower case found in a row's name
339
+ // (`upd`, `list`) is that row and runs at once.
340
+ const rootSymbolAnswerPending = !rootResultsCurrent && rootPlainTickerSearchArg !== null
341
+ && (rootTickerReading !== null || rootListingChoice !== null || readsAsSymbol(rootPlainTickerSearchArg, orderedRootResults));
313
342
 
314
343
  useLayoutEffect(() => {
315
344
  if (currentRoute) return;
@@ -326,6 +355,7 @@ export function useCommandBarRootRuntime({
326
355
  previousRootSelectionContextRef.current = { query: rootQuery, mode: rootModeKind };
327
356
  if (selectionContextChanged) {
328
357
  rootSelectionNavigatedRef.current = false;
358
+ pinnedRootRowRef.current = null;
329
359
  }
330
360
 
331
361
  setRootSelectedIdx((current) => {
@@ -338,15 +368,23 @@ export function useCommandBarRootRuntime({
338
368
  if (shiftedIdx >= 0) return shiftedIdx;
339
369
  return clampIndex(current, resultIds.length);
340
370
  }
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.
371
+ const pinnedIdx = pinnedRootRowRef.current ? resultIds.indexOf(pinnedRootRowRef.current.id) : -1;
372
+ if (pinnedIdx >= 0) return pinnedIdx;
373
+ // Untouched, the selection follows the best row on offer until the
374
+ // answer is in. When every row is an offer or a placeholder, none of
375
+ // them is the answer and none is selected: Enter must not take one of
376
+ // them for the ticker still being searched.
345
377
  const defaultIdx = orderedRootResults.findIndex(isDefaultSelectable);
346
- if (defaultIdx < 0) return -1;
347
- return clampIndex(Math.max(rootResultModel.initialIdx, defaultIdx), resultIds.length);
378
+ if (defaultIdx < 0 || rootSymbolAnswerPending) return -1;
379
+ const selected = clampIndex(Math.max(rootResultModel.initialIdx, defaultIdx), resultIds.length);
380
+ pinnedRootRowRef.current = rootResultsCurrent && resultIds[selected]
381
+ ? { id: resultIds[selected]!, answer: activeRootProviderResultsKey }
382
+ : null;
383
+ return selected;
348
384
  });
349
385
  }, [
386
+ activeRootProviderResultsKey,
387
+ rootResultsCurrent,
350
388
  activeMatch?.command.id,
351
389
  currentRoute,
352
390
  orderedRootResults,
@@ -354,13 +392,20 @@ export function useCommandBarRootRuntime({
354
392
  rootQuery,
355
393
  rootResultModel.initialIdx,
356
394
  rootSelectionNavigatedRef,
395
+ rootSymbolAnswerPending,
357
396
  setRootHoveredIdx,
358
397
  setRootSelectedIdx,
359
398
  ]);
360
399
 
361
400
  useEffect(() => {
362
401
  if (!activeRootProviderResultsKey) return;
402
+ // A later answer that only reorders the rows leaves the highlight on its row.
403
+ const pinned = pinnedRootRowRef.current;
404
+ if (pinned && pinned.answer !== activeRootProviderResultsKey
405
+ && orderedRootResults.some((item) => item.id === pinned.id)) return;
363
406
  setRootSelectedIdx(0);
407
+ const top = orderedRootResults[0];
408
+ pinnedRootRowRef.current = top ? { id: top.id, answer: activeRootProviderResultsKey } : null;
364
409
  setRootHoveredIdx(null);
365
410
  nativeListScrollRef.current?.scrollTo(0);
366
411
  }, [activeRootProviderResultsKey, nativeListScrollRef, setRootHoveredIdx, setRootSelectedIdx]);