@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
@@ -79,6 +79,14 @@ function builtInPanePrefixes(paneId: string): string[] {
79
79
  return command ? getCommandPrefixes(command) : [];
80
80
  }
81
81
 
82
+ /**
83
+ * What a user types for a function, as the catalog lists it and every error names it: a template's
84
+ * shortcut or id, a pane opened by a built-in command (LAY) by that command's mnemonic, else the pane's id.
85
+ */
86
+ export function paneFunctionCode(template: PaneTemplateDef | undefined, pane: PaneDef): string {
87
+ return template?.shortcut?.prefix ?? template?.id ?? builtInPanePrefixes(pane.id)[0] ?? pane.id;
88
+ }
89
+
82
90
  function registerResolverToken(
83
91
  lookup: Map<string, PaneTemplateDef | PaneDef>,
84
92
  token: string | undefined,
@@ -195,7 +203,8 @@ export function nearFunctionCodes(
195
203
  const near = new Map<PaneTemplateDef, string>();
196
204
  for (const template of registry.paneTemplates.values()) {
197
205
  const prefix = template.shortcut?.prefix;
198
- if (!prefix) continue;
206
+ // An unlisted function is never offered for a typo.
207
+ if (!prefix || template.listed === false) continue;
199
208
  const code = [prefix, ...template.shortcut?.aliases ?? []].find((candidate) => {
200
209
  const known = normalizeLookupToken(candidate);
201
210
  return (!commandWord || known[0] === wanted[0]) && typoDistance(wanted, known) === 1;
@@ -215,6 +224,7 @@ export function suggestedFunctionCodes(registry: PaneFunctionCatalog, name: stri
215
224
  const wanted = normalizeLookupToken(name);
216
225
  if (!wanted) return [];
217
226
  return [...registry.paneTemplates.values()].flatMap((template) => {
227
+ if (template.listed === false) return [];
218
228
  const pane = registry.panes.get(template.paneId);
219
229
  const words = pane ? getHeadlessPaneDefinition(template, pane)?.discovery?.suggestFor ?? [] : [];
220
230
  const code = template.shortcut?.prefix;
@@ -277,9 +287,15 @@ async function buildTemplateCatalogEntry(
277
287
  };
278
288
  }
279
289
 
290
+ /**
291
+ * The functions `gloom catalog` and search list. An unlisted one (`listed:
292
+ * false`) is left out, and its pane is not listed in its place; `unlisted`
293
+ * keeps it, for its own help (`fn ATTN --help`).
294
+ */
280
295
  export async function buildPaneCatalogEntries(
281
296
  registry: PaneFunctionCatalog,
282
297
  context: MarketContext,
298
+ { unlisted = false }: { unlisted?: boolean } = {},
283
299
  ): Promise<PaneCatalogEntry[]> {
284
300
  const entries: PaneCatalogEntry[] = [];
285
301
  const templatedPaneIds = new Set<string>();
@@ -288,6 +304,7 @@ export async function buildPaneCatalogEntries(
288
304
  const pane = registry.panes.get(template.paneId);
289
305
  if (!pane) continue;
290
306
  templatedPaneIds.add(pane.id);
307
+ if (template.listed === false && !unlisted) continue;
291
308
  entries.push(await buildTemplateCatalogEntry(template, pane, context));
292
309
  }
293
310
 
@@ -296,7 +313,7 @@ export async function buildPaneCatalogEntries(
296
313
  const [shortcut, ...aliases] = builtInPanePrefixes(pane.id);
297
314
  const argument = paneFunctionArgument(undefined, pane);
298
315
  entries.push({
299
- token: shortcut ?? pane.id,
316
+ token: paneFunctionCode(undefined, pane),
300
317
  label: pane.name,
301
318
  description: `Open the ${pane.name} pane.`,
302
319
  paneId: pane.id,
@@ -333,6 +350,14 @@ const ALL_WORDS_WITH_FLAGS_BAND = 1000;
333
350
  const PER_MATCHED_WORD = 100;
334
351
  /** Loose matches (no function carries every word) listed before the rest are left to a refined query. */
335
352
  const LOOSE_MATCH_LIMIT = 5;
353
+ /** Loose matches a word of the query names (in the function's code, name or aliases) are all listed, up to this many. */
354
+ const LOOSE_NAMED_LIMIT = 8;
355
+ /** A query word found in what the function is called (its code, name or aliases) outweighs one found in its keywords or description. */
356
+ const PER_WORD_IN_NAME = 3;
357
+ /** A query word that begins a function's code (`vol` for VOLS, `fx` for FXC) is the start of typing it. */
358
+ const PER_WORD_STARTING_CODE = 3;
359
+ /** A query word that is one of the function's own keywords (`vol` for the volatility functions, which also say volume's) names it. */
360
+ const PER_WORD_IS_KEYWORD = 3;
336
361
 
337
362
  function phraseBand(entry: PaneCatalogEntry, phrase: string): number {
338
363
  const lower = (values: Array<string | undefined>) => values
@@ -375,6 +400,12 @@ function nameAndDescription(entry: PaneCatalogEntry): string {
375
400
  return nameAndDescriptionFields(entry).join(" ");
376
401
  }
377
402
 
403
+ /** What a function is called: its code, name and aliases, apart from the keywords and description that say what it covers. */
404
+ function identityFields(entry: PaneCatalogEntry): string[] {
405
+ return [entry.token, entry.label, entry.paneName, entry.shortcut, ...entry.aliases]
406
+ .filter((value): value is string => !!value).map((value) => value.toLowerCase().replace(/\s+/g, " "));
407
+ }
408
+
378
409
  function flagText(entry: PaneCatalogEntry): string {
379
410
  return [
380
411
  entry.argKind,
@@ -394,12 +425,14 @@ interface CatalogMatchScore {
394
425
  terms: number;
395
426
  /** Those found in the function's name, keywords or description. */
396
427
  named: number;
428
+ /** Those found in what the function is called: its code, name or aliases. */
429
+ called: number;
397
430
  }
398
431
 
399
432
  function scorePaneCatalogEntry(entry: PaneCatalogEntry, query: string): CatalogMatchScore {
400
433
  const terms = queryTerms(query);
401
- if (terms.length === 0) return { score: 1, terms: 0, named: 0 };
402
- const none = { score: 0, terms: terms.length, named: 0 };
434
+ if (terms.length === 0) return { score: 1, terms: 0, named: 0, called: 0 };
435
+ const none = { score: 0, terms: terms.length, named: 0, called: 0 };
403
436
 
404
437
  const exactTokens = [
405
438
  entry.token,
@@ -414,6 +447,8 @@ function scorePaneCatalogEntry(entry: PaneCatalogEntry, query: string): CatalogM
414
447
  const named = nameAndDescription(entry);
415
448
  const flags = flagText(entry);
416
449
  const wholeQuery = query.toLowerCase().trim().replace(/\s+/g, " ");
450
+ const called = identityFields(entry);
451
+ const codes = [entry.token, entry.shortcut, ...entry.aliases].filter((value): value is string => !!value).map((value) => normalizeLookupToken(value));
417
452
 
418
453
  let score = containsPhrase(`${named} ${flags}`, wholeQuery) ? 12 : 0;
419
454
  // A query that is one of the function's own keywords ("hedge" for OSA) beats one found inside a
@@ -423,9 +458,16 @@ function scorePaneCatalogEntry(entry: PaneCatalogEntry, query: string): CatalogM
423
458
  if (keywords.includes(wholeQuery)) score += 4;
424
459
  let matchedTerms = 0;
425
460
  let matchedNamed = 0;
461
+ let matchedCalled = 0;
426
462
  let matchedFlagKeys = 0;
427
463
  for (const term of terms) {
428
464
  const normalized = normalizeLookupToken(term);
465
+ if (called.some((field) => containsPhrase(field, term))) {
466
+ score += PER_WORD_IN_NAME;
467
+ matchedCalled += 1;
468
+ }
469
+ if (codes.some((code) => normalized.length >= 2 && code.startsWith(normalized))) score += PER_WORD_STARTING_CODE;
470
+ if (keywords.includes(term)) score += PER_WORD_IS_KEYWORD;
429
471
  // A term starts a word: "rate" is found in "rates" but not in "separate", nor "budget" in "budgetBps".
430
472
  if (exactTokens.includes(normalized)) {
431
473
  score += 8;
@@ -455,12 +497,13 @@ function scorePaneCatalogEntry(entry: PaneCatalogEntry, query: string): CatalogM
455
497
  score += Math.round((matchedTerms / terms.length) * 8);
456
498
  if (entry.capability.botSafe) score += 2;
457
499
  // One word has no phrase to prefer: it keeps the plain term scores.
458
- if (terms.length === 1) return { score, terms: 1, named: matchedNamed };
500
+ if (terms.length === 1) return { score, terms: 1, named: matchedNamed, called: matchedCalled };
459
501
  const allWords = matchedNamed === terms.length ? ALL_WORDS_BAND : matchedTerms === terms.length ? ALL_WORDS_WITH_FLAGS_BAND : 0;
460
502
  return {
461
503
  score: score + matchedTerms * PER_MATCHED_WORD + (phraseBand(entry, wholeQuery) || allWords),
462
504
  terms: terms.length,
463
505
  named: matchedNamed,
506
+ called: matchedCalled,
464
507
  };
465
508
  }
466
509
 
@@ -535,8 +578,14 @@ export function selectPaneCatalogMatches(ranked: PaneCatalogEntry[], query: stri
535
578
  const shown = every(complete).slice(0, limit);
536
579
  return { shown, hidden: ranked.length - shown.length, loose: false };
537
580
  }
581
+ // No function carries every word: the ones a word names come first, the code or name of a function being what a
582
+ // reader types ("fx vol" is FXC, whose name has fx, and VOLS, whose name has vol), ahead of one that mentions a word
583
+ // in its keywords ("implied fx" in ADR) or its flags, and however many of the words a weaker match has.
538
584
  const named = scored.filter(({ named }) => named > 0);
539
- const shown = every(named.length > 0 ? named : scored).slice(0, Math.min(limit, LOOSE_MATCH_LIMIT));
585
+ const pool = named.length > 0 ? named : scored;
586
+ const leading = pool.filter((item) => item.called > 0).slice(0, LOOSE_NAMED_LIMIT);
587
+ const rest = pool.filter((item) => !leading.includes(item)).slice(0, Math.max(0, LOOSE_MATCH_LIMIT - leading.length));
588
+ const shown = every([...leading, ...rest]).slice(0, limit);
540
589
  return { shown, hidden: ranked.length - shown.length, loose: true };
541
590
  }
542
591
 
@@ -576,7 +625,7 @@ function optionDescription(option: PaneFunctionOptionDef): string {
576
625
  const choices = optionChoices(option);
577
626
  const parts = [option.description.replace(/\.$/, "")];
578
627
  // An enum that also reads other values (a bar size of any length) lists its values as examples.
579
- if (choices.join("|").length > MAX_INLINE_CHOICES) parts.push(`${option.normalize ? "Such as" : "One of"} ${choices.join(", ")}`);
628
+ if (choices.join("|").length > MAX_INLINE_CHOICES) parts.push(`${option.list ? "Each one of" : option.normalize ? "Such as" : "One of"} ${choices.join(", ")}`);
580
629
  if (option.defaultValue !== undefined && option.defaultValue !== "") parts.push(`Default ${String(option.defaultValue)}`);
581
630
  return `${parts.join(". ")}.`;
582
631
  }
@@ -587,6 +636,26 @@ export function limitationSentence(text: string): string {
587
636
  return !trimmed || /[.!?]["')\]]*$/.test(trimmed) ? trimmed : `${trimmed}.`;
588
637
  }
589
638
 
639
+ /**
640
+ * Names as lines of at most `width`, each name whole and the names apart by
641
+ * semicolons, since a name may hold a comma (Furnishings, Fixtures & Appliances).
642
+ */
643
+ function packNames(names: readonly string[], width: number): string[] {
644
+ const lines: string[] = [];
645
+ let line = "";
646
+ names.forEach((name, index) => {
647
+ const word = index < names.length - 1 ? `${name};` : name;
648
+ if (line && line.length + 1 + word.length > width) {
649
+ lines.push(line);
650
+ line = word;
651
+ } else {
652
+ line = line ? `${line} ${word}` : word;
653
+ }
654
+ });
655
+ if (line) lines.push(line);
656
+ return lines;
657
+ }
658
+
590
659
  function renderCatalogEntry(entry: PaneCatalogEntry): string {
591
660
  const width = Math.min(cliTerminalWidth() ?? CATALOG_TEXT_WIDTH, CATALOG_TEXT_WIDTH);
592
661
  const capability = entry.capability;
@@ -620,6 +689,10 @@ function renderCatalogEntry(entry: PaneCatalogEntry): string {
620
689
  ));
621
690
  }
622
691
 
692
+ for (const { title, items } of capability.reference ?? []) {
693
+ lines.push("", renderSection(title), ...packNames(items, width - 2).map((line) => ` ${line}`));
694
+ }
695
+
623
696
  lines.push("", renderSection("Examples"));
624
697
  if (capability.reportReadiness !== "unsupported") {
625
698
  lines.push(...wrapCommandLine(`gloom fn ${invocation}`, width));
@@ -12,7 +12,9 @@ import type {
12
12
  PluginPersistence,
13
13
  } from "../../types/plugin";
14
14
  import { createPluginTeamState } from "../../plugins/team-state";
15
+ import { bindPluginContextNamespaces } from "../../plugins/registry/plugin-state";
15
16
  import type { MarketContext } from "../types";
17
+ import { createCommandPluginPersistence } from "../saved-copy-persistence";
16
18
  import type { PaneFunctionCatalog } from "./catalog";
17
19
 
18
20
  export interface PaneDiscoveryOptions {
@@ -173,23 +175,41 @@ export async function createPaneCatalog(context: MarketContext, plugins: GloomPl
173
175
  brokers,
174
176
  });
175
177
 
178
+ // Each plugin's caches save what a command fetches where the app keeps them, so a failed refresh answers from them.
179
+ const resources = context.persistence?.resources;
180
+ const persistences: Array<ReturnType<typeof createCommandPluginPersistence>> = [];
181
+ const commandPersistence = (stateId: string) => {
182
+ const persistence = createCommandPluginPersistence(resources, `plugin:${stateId}`);
183
+ persistences.push(persistence);
184
+ return persistence;
185
+ };
186
+
176
187
  const destroy = () => {
177
188
  let failure: unknown;
178
189
  for (const plugin of setupPlugins.splice(0).reverse()) {
179
190
  try { plugin.dispose?.(); } catch (error) { failure ??= error; }
180
191
  }
192
+ // A plugin that keeps its persistence past dispose must not reach a store the command closes.
193
+ for (const persistence of persistences.splice(0)) persistence.release();
181
194
  brokers.clear();
182
195
  if (context.resolveBroker === resolveBroker) context.resolveBroker = previousResolver;
183
196
  if (failure) throw failure;
184
197
  };
185
198
 
199
+ const namespaced = (stateId: string) => ({
200
+ persistence: commandPersistence(stateId),
201
+ resume: discoveryContext.resume,
202
+ configState: discoveryContext.configState,
203
+ teamState: discoveryContext.teamState,
204
+ });
186
205
  try {
187
206
  for (const plugin of plugins) {
188
207
  for (const pane of plugin.panes ?? []) panes.set(pane.id, pane);
189
208
  for (const template of plugin.paneTemplates ?? []) paneTemplates.set(template.id, template);
190
209
  if (plugin.setup) {
191
210
  setupPlugins.push(plugin);
192
- await plugin.setup(discoveryContext);
211
+ const stateId = plugin.stateId ?? plugin.id;
212
+ await plugin.setup(bindPluginContextNamespaces({ ...discoveryContext, ...namespaced(stateId) }, namespaced));
193
213
  }
194
214
  }
195
215
  } catch (error) {
@@ -1,6 +1,6 @@
1
1
  import type { DesktopPaneShotRenderedRow } from "../desktop-pane-shot";
2
2
  import { cliStyles, cliTerminalWidth, renderSection, renderTable, wrapText } from "../../utils/cli-output";
3
- import { formatUtcTime } from "../../utils/utc-time";
3
+ import { formatUtcTime, parseReportTime } from "../../utils/utc-time";
4
4
  import type { MarketContext } from "../types";
5
5
  import type { PaneFunctionReport } from "./report";
6
6
  import type { ResolvedPaneFunction } from "./resolver";
@@ -83,12 +83,22 @@ function domCellKeys(row: DesktopPaneShotRenderedRow): string[] {
83
83
  });
84
84
  }
85
85
 
86
+ const DAY_MS = 86_400_000;
87
+ /** A time of day in what a cell draws: `11:27`, `9am`. */
88
+ const DRAWN_CLOCK = /\d{1,2}:\d{2}|\b\d{1,2}\s?[ap]m\b/i;
89
+
86
90
  /**
87
91
  * A pane may shorten a time to fit ("Wed 11:27", "Oct 2"); the report prints
88
- * the instant behind it with its date and zone instead.
92
+ * the instant behind it with its date and zone instead. An instant at exactly
93
+ * 00:00 UTC that the cell draws without a time of day (a filing's date, an
94
+ * all-day event) is a day, not a midnight: it prints as the date, as a
95
+ * date-only value does. The CSV and JSON keep the instant.
89
96
  */
90
97
  function cellText(cell: DesktopPaneShotRenderedRow["cells"][number]): string {
91
- return cell.instant ? formatUtcTime(cell.instant) || cell.text : cell.text;
98
+ if (!cell.instant) return cell.text;
99
+ const time = parseReportTime(cell.instant);
100
+ if (time && !time.dateOnly && time.time % DAY_MS === 0 && !DRAWN_CLOCK.test(cell.text)) return formatUtcTime({ ...time, dateOnly: true });
101
+ return formatUtcTime(cell.instant) || cell.text;
92
102
  }
93
103
 
94
104
  interface DomTable {
@@ -11,7 +11,15 @@ import type {
11
11
  import { localTimeSuffix, parseReportTime, type ReportTime } from "../../utils/utc-time";
12
12
  import { isRecord } from "../../utils/guards";
13
13
  import { latestRegularSessionClose, readIndexSession } from "../../market-data/market/freshness";
14
- import { reportMarketSession, reportVenueSessionDates, type ReportMarket, type SessionObservation, type VenueSessionDate } from "./market-session";
14
+ import {
15
+ reportMarketSession,
16
+ reportVenueMarkets,
17
+ reportVenueSessionDates,
18
+ type ReportMarket,
19
+ type SessionObservation,
20
+ type VenueMarket,
21
+ type VenueSessionDate,
22
+ } from "./market-session";
15
23
  import { savedCopyShownAt } from "../../data/saved-copies";
16
24
  import { releasedText } from "../../utils/release-date";
17
25
 
@@ -77,6 +85,13 @@ export interface ReportFreshness {
77
85
  tradingDayMarket?: string;
78
86
  /** Where the venues of a quote report stand now, from their session calendars. */
79
87
  market?: ReportMarket;
88
+ /**
89
+ * With a declared `datesEachVenue`, when the rows trade on two or three
90
+ * markets that do not all stand alike: where each stands and when it opens,
91
+ * which the status line gives in place of `market` (`LSE opens 08:00 UTC ·
92
+ * US 13:30 UTC`).
93
+ */
94
+ markets?: VenueMarket[];
80
95
  /**
81
96
  * With a declared `datesEachVenue`, when the rows trade on more than one
82
97
  * market: each market's session date, which the status line gives in place
@@ -108,6 +123,8 @@ export interface ReportFreshness {
108
123
  ageOnly?: true;
109
124
  /** What the newest observation is, said before its time: `newest story Tue 29 Sep 2026 20:17 UTC`. */
110
125
  asOfLabel?: string;
126
+ /** The line says when the report was retrieved after the newest observation's date (`retrieved Wed 28 Oct 2026 19:07 UTC`). */
127
+ retrievedToo?: true;
111
128
  /** What the figures are, said after the status: `funding = current estimate, pays 08:00 UTC`. */
112
129
  note?: string;
113
130
  /** Shorter forms of `note`, in the order a capture's line too long for it tries them. */
@@ -534,6 +551,9 @@ function resolveFreshness({ declared, signals, observations, now }: FreshnessInp
534
551
  }
535
552
  const { market, closeDate, closeMarket } = reportMarketSession(signals.sessions, now);
536
553
  const venueSessions = declared.datesEachVenue && !declaredAsOf ? reportVenueSessionDates(signals.sessions, now) : [];
554
+ // Each market's own state and open, when they are not all one: `LSE opens 08:00 UTC · US 13:30 UTC`.
555
+ const venueMarkets = declared.datesEachVenue ? reportVenueMarkets(signals.sessions, now) : [];
556
+ const markets = venueMarkets.length > 0 && !sameMarketText(venueMarkets, now) ? venueMarkets : [];
537
557
  // The close wording dates the newest observation, so only when that is what `asOf` is.
538
558
  const asOfClose = closeDate && asOf && !declaredAsOf ? closeDate : null;
539
559
  const tradingDayMarket = asOfClose ? closeMarket
@@ -584,6 +604,7 @@ function resolveFreshness({ declared, signals, observations, now }: FreshnessInp
584
604
  ...(asOfClose ? { asOfClose } : {}),
585
605
  ...(tradingDayMarket ? { tradingDayMarket } : {}),
586
606
  ...(market ? { market } : {}),
607
+ ...(markets.length > 0 ? { markets } : {}),
587
608
  ...(venueSessions.length > 0 ? { venueSessions } : {}),
588
609
  ...(lagDays != null ? { lagDays } : {}),
589
610
  ...(observation ? { observation } : {}),
@@ -591,6 +612,7 @@ function resolveFreshness({ declared, signals, observations, now }: FreshnessInp
591
612
  ...(oldestAgeMinutes != null ? { oldestAgeMinutes } : {}),
592
613
  ...(status === "unreported" && declared.ageOnly ? { ageOnly: true as const } : {}),
593
614
  ...(asOfLabel ? { asOfLabel } : {}),
615
+ ...(asOf && declared.retrievedToo ? { retrievedToo: true as const } : {}),
594
616
  ...(note ? { note } : {}),
595
617
  ...(noteVariants.length > 0 ? { noteVariants } : {}),
596
618
  ...(schedule ? { schedule } : {}),
@@ -857,6 +879,21 @@ function venueSessionsText(sessions: readonly VenueSessionDate[]): string {
857
879
  return groups.map(({ markets, date, close }) => `${listText(markets)} ${dayText(date)} ${close ? "close" : "session"}`).join(", ");
858
880
  }
859
881
 
882
+ /**
883
+ * An as-of the feed stamped at exactly 00:00:00.000 UTC reads as the day it
884
+ * names, as a date-only value does: a daily close, a filing's date or a
885
+ * published report's has no time of day, so `Mon 26 Oct 2026 00:00 UTC` would
886
+ * claim one. Quotes keep their time, as does anything a feed or a venue's
887
+ * sessions date, and bars of a size under a day, whose midnight bar opens then.
888
+ */
889
+ function asOfAsDay(freshness: ReportFreshness, time: ReportTime | null): ReportTime | null {
890
+ if (!time || time.dateOnly || time.time % DAY_MS !== 0) return time;
891
+ const feed = freshness.status === "live" || freshness.status === "delayed" || freshness.feed != null;
892
+ const intradayBars = freshness.basis === "intraday bars";
893
+ return feed || intradayBars || freshness.market || freshness.sources?.length || freshness.oldestAgeMinutes != null
894
+ ? time : { ...time, dateOnly: true };
895
+ }
896
+
860
897
  /**
861
898
  * The dated as-of: a session close, a UTC time, a date, or when the report was
862
899
  * retrieved; with the oldest observation unless `short`, or when the line says
@@ -868,7 +905,7 @@ function asOfText(freshness: ReportFreshness, short: boolean, local: boolean, un
868
905
  if (freshness.venueSessions?.length) return venueSessionsText(freshness.venueSessions);
869
906
  const market = freshness.tradingDayMarket;
870
907
  const period = freshness.periodicity;
871
- const time = parseReportTime(freshness.asOf);
908
+ const time = asOfAsDay(freshness, parseReportTime(freshness.asOf));
872
909
  const reference = freshness.referencePeriod;
873
910
  // `Sep 2026, released Tue 20 Oct`: the period the figure covers, then when it was released.
874
911
  const released = reference ? releasedText(freshness.asOf, reference) : null;
@@ -949,6 +986,53 @@ function marketText(market: ReportMarket, now: number, short: boolean, local: bo
949
986
  }
950
987
  }
951
988
 
989
+ /** Whether every market of a mixed report reads the same (`markets closed until Mon`): one line then says it for all. */
990
+ function sameMarketText(markets: readonly VenueMarket[], now: number): boolean {
991
+ return new Set(markets.map((market) => marketText(market, now, false, false))).size === 1;
992
+ }
993
+
994
+ /**
995
+ * Where each market of a report that mixes them stands, one after another:
996
+ * the open ones first, then those in an extended session, then the closed ones
997
+ * by when they open. `markets closed · LSE opens 08:00 UTC · US 13:30 UTC`
998
+ * while all are closed, else each its own (`LSE open · US pre-market, opens
999
+ * 13:30 UTC`). A time is the regular session's open, as `marketText` says it;
1000
+ * `short` leaves out `markets closed`.
1001
+ */
1002
+ function marketsText(markets: readonly VenueMarket[], now: number, short: boolean, local: boolean): string {
1003
+ const opensAt = (market: VenueMarket) => market.reopensAt ? Date.parse(market.reopensAt) : Number.POSITIVE_INFINITY;
1004
+ const rank = (market: VenueMarket) => market.state === "open" ? 0 : market.state === "closed" ? 2 : 1;
1005
+ const ordered = [...markets].sort((left, right) => rank(left) - rank(right) || opensAt(left) - opensAt(right));
1006
+ // `LSE opens 08:00 UTC · US 13:30 UTC`: a verb is said once for the timed opens that follow each other
1007
+ // with it; a week's first trade after a session's open says its own (`reopens 22:00 UTC`).
1008
+ let previousVerb: string | null = null;
1009
+ const parts = ordered.map((market) => {
1010
+ const reopen = reopenText(market, now, local);
1011
+ const verb = market.state === "closed" && reopen?.time ? market.firstTrade ? "reopens" : "opens" : null;
1012
+ const said = (() => {
1013
+ switch (market.state) {
1014
+ case "open":
1015
+ return `${market.market} open`;
1016
+ case "after-hours":
1017
+ return `${market.market} after hours`;
1018
+ case "pre-market":
1019
+ return reopen?.time ? `${market.market} pre-market, opens ${reopen.text}` : `${market.market} pre-market`;
1020
+ case "closed":
1021
+ if (!reopen) return `${market.market} closed`;
1022
+ if (!reopen.time) return `${market.market} closed until ${reopen.text}`;
1023
+ return `${market.market} ${verb === previousVerb ? "" : `${verb} `}${reopen.text}`;
1024
+ default: {
1025
+ const _exhaustive: never = market.state;
1026
+ return _exhaustive;
1027
+ }
1028
+ }
1029
+ })();
1030
+ previousVerb = verb;
1031
+ return said;
1032
+ });
1033
+ return ordered.every((market) => market.state === "closed") && !short ? ["markets closed", ...parts].join(" · ") : parts.join(" · ");
1034
+ }
1035
+
952
1036
  interface StatusLineDetail {
953
1037
  /** Leave out the oldest observation. */
954
1038
  asOf?: "short";
@@ -980,14 +1064,19 @@ function statusLineParts(freshness: ReportFreshness, detail: StatusLineDetail =
980
1064
  const undated = detail.date === "none";
981
1065
  return [
982
1066
  ...(freshness.sources?.length ? sourcesAsOfParts(freshness, freshness.sources, undated)
983
- : freshness.unreached && !freshness.asOf ? []
1067
+ // Undated, a saved copy is not "retrieved" now: its own part says when it was saved.
1068
+ : (freshness.unreached || freshness.savedAt) && !freshness.asOf ? []
984
1069
  : [asOfText(freshness, detail.asOf === "short", local, undated)]),
1070
+ // A list dated by its newest filing says when it was read as well.
1071
+ ...(freshness.retrievedToo && freshness.asOf && !freshness.savedAt ? [`retrieved ${timeText(freshness.retrievedAt, local)}`] : []),
985
1072
  ...(freshness.schedule && detail.schedule !== "none"
986
1073
  ? [typeof detail.schedule === "number" ? freshness.scheduleVariants?.[detail.schedule] ?? freshness.schedule : freshness.schedule] : []),
987
1074
  ...(freshness.oldestAgeMinutes != null ? [`oldest ${ageText(freshness.oldestAgeMinutes)}`] : []),
988
1075
  ...(freshness.savedAt ? [savedCopyText(freshness.savedAt, freshness.retrievedAt, local)]
989
1076
  : [statusText(detail.basis === "none" ? { ...freshness, basis: undefined } : freshness, detail.status === "short")].filter(Boolean)),
990
- ...(freshness.market && detail.market !== "none" ? [marketText(freshness.market, now, detail.market === "short", local)] : []),
1077
+ ...(detail.market === "none" ? []
1078
+ : freshness.markets?.length ? [marketsText(freshness.markets, now, detail.market === "short", local)]
1079
+ : freshness.market ? [marketText(freshness.market, now, detail.market === "short", local)] : []),
991
1080
  ...(freshness.note && detail.note !== "none" ? [typeof detail.note === "number" ? freshness.noteVariants?.[detail.note] ?? freshness.note : freshness.note] : []),
992
1081
  ];
993
1082
  }
@@ -234,9 +234,10 @@ export const FUNCTION_HELP: Readonly<Record<string, FunctionHelp>> = {
234
234
  bloomberg: ["RV"],
235
235
  },
236
236
  EQS: {
237
- summary: "Screen covered stocks on valuation, growth, margins, liquidity, volatility, short interest, insider activity and 13F holders, then rank the matches by any metric. "
238
- + "ATR% is the 14-session average true range as a percent of the last close. IV rank is a rank, not an IV percentile: 0 is the lowest 30-day implied volatility of the past 52 weeks and 100 the highest.",
239
- usage: ["EQS"],
237
+ summary: "Screen covered stocks on valuation, growth, margins, liquidity, volatility, technicals, float and ownership, short interest, insider activity and 13F holders, then rank the matches by any metric. "
238
+ + "ATR% is the 14-session average true range as a percent of the last close. IV rank is a rank, not an IV percentile: 0 is the lowest 30-day implied volatility of the past 52 weeks and 100 the highest. IV percentile, IV30 change in vol points, HV20 and HV rank screen premium to sell. "
239
+ + "Technical fields read the last completed session: RSI 14, price vs SMA 20, 50 and 200, trading days since a MACD or golden cross and since a new 20-day or 52-week high or low, and Bollinger width percentile (0 is a six-month squeeze). Ideas holds ready technical screens.",
240
+ usage: ["EQS", "gloom fn EQS --preset golden-cross"],
240
241
  keys: [key("a", "dd criterion"), key("m", "etric"), key("t", "check ticker"), key("s", "ave screen"), key("o", "pen research")],
241
242
  data: same("Daily snapshot, statements as filed"),
242
243
  bloomberg: ["EQS"],
@@ -285,8 +286,10 @@ export const FUNCTION_HELP: Readonly<Record<string, FunctionHelp>> = {
285
286
  },
286
287
  CSP: {
287
288
  summary: "Which names pay most to sell a put? For each, the put nearest a delta (0.30) on the expiry nearest a date or a number of days out (30), "
288
- + "ranked by annualised yield on cash at the bid, with % out of the money, delta, IV rank, the spread (marked wide, never left out) and an earnings flag.",
289
- usage: ["CSP JPM, GS, KO", "gloom fn CSP JPM GS KO AAPL --delta 0.20 --days 45"],
289
+ + "ranked by annualised yield on cash at the bid, with % out of the money, delta, IV rank, the spread (marked wide, never left out) and an earnings flag. "
290
+ + "--strategy screens covered calls, verticals, iron condors, straddles, strangles and calendars the same way, with the trade, credit or debit, "
291
+ + "max gain and loss per contract, break-evens, probability of profit, and an income trade's annualised return or a debit trade's reward to risk.",
292
+ usage: ["CSP JPM, GS, KO", "gloom fn CSP JPM GS KO AAPL --delta 0.20 --days 45", "gloom fn CSP JPM GS KO --strategy iron-condor"],
290
293
  keys: [key("Enter", " open in OSA")],
291
294
  data: OPTIONS,
292
295
  bloomberg: [],
@@ -713,8 +716,8 @@ export const FUNCTION_HELP: Readonly<Record<string, FunctionHelp>> = {
713
716
 
714
717
  // Watch markets
715
718
  MOST: {
716
- summary: "Today's top gainers, losers, most active and trending tickers with price, change, volume against average and market cap, and the pre-market and after-hours movers. On Pro, the gaps at the open.",
717
- usage: ["MOST"],
719
+ summary: "Today's top gainers, losers, most active and trending tickers with price, change, volume against average and market cap, and the pre-market and after-hours movers. On Pro, the gaps at the open. Min float keeps the names with at least that many shares in the float.",
720
+ usage: ["MOST", "gloom fn MOST --list gainers --min-float 10M"],
718
721
  keys: [TABS, OPEN],
719
722
  data: { free: DELAYED, pro: REAL_TIME },
720
723
  bloomberg: ["MOST"],
@@ -902,9 +905,9 @@ export const FUNCTION_HELP: Readonly<Record<string, FunctionHelp>> = {
902
905
  bloomberg: [],
903
906
  },
904
907
  FLOW: {
905
- summary: "Sweeps, blocks and large premium prints as they hit the tape, with ticker, strike, expiry and premium.",
906
- usage: ["FLOW"],
907
- keys: [OPEN],
908
+ summary: "Sweeps, blocks and large premium prints, and the session's net premium tide.",
909
+ usage: ["FLOW", "FLOW NVDA"],
910
+ keys: [TABS, OPEN],
908
911
  data: pro(REAL_TIME),
909
912
  access: "pro",
910
913
  bloomberg: ["FLOW"],
@@ -1423,6 +1426,13 @@ export const FUNCTION_HELP: Readonly<Record<string, FunctionHelp>> = {
1423
1426
  data: null,
1424
1427
  bloomberg: [],
1425
1428
  },
1429
+ ROADMAP: {
1430
+ summary: "What is being built, what is planned and what shipped, with each item's votes. Pro members vote on what is building or planned, once per item, and can take the vote back.",
1431
+ usage: ["ROADMAP"],
1432
+ keys: [OPEN, key("v", "ote")],
1433
+ data: null,
1434
+ bloomberg: [],
1435
+ },
1426
1436
  HELP: {
1427
1437
  summary: "Help and the function reference. HELP with a function opens its card, F1 opens the focused pane's, and HELP HELP reaches support.",
1428
1438
  usage: ["HELP", "HELP OMON", "HELP HELP"],
@@ -42,6 +42,7 @@ import type { ResolvedPaneFunction } from "./resolver";
42
42
  import { isRecord } from "../../utils/guards";
43
43
  import { splitSymbolWords } from "../symbol-args";
44
44
  import { formatUtcTime, localTimeSuffix } from "../../utils/utc-time";
45
+ import { formatCompact } from "../../utils/format";
45
46
  import { deriveHeadlessFreshness, formatFreshnessLine, staleRowsText, withSavedCopy, type ReportFreshness } from "./freshness";
46
47
  import { DATA_SERVICE_UNREACHABLE_MESSAGE } from "../../utils/network-failure";
47
48
  import { accessGateStatus, errorsAccessGate, isProLock, reportGateText } from "./access-gate";
@@ -235,7 +236,12 @@ export function createHeadlessPaneContext(
235
236
  command, noun: "a quote", retry: (key) => `gloom ${command} ${argumentWith(key)}`,
236
237
  }, { refresh: !!context.refresh });
237
238
  if (!read) return null;
238
- return { symbol: read.symbol, ...read.exchange ? { exchange: read.exchange } : {}, ...read.name ? { name: read.name } : {} };
239
+ return {
240
+ symbol: read.symbol,
241
+ ...read.exchange ? { exchange: read.exchange } : {},
242
+ ...read.name ? { name: read.name } : {},
243
+ ...read.coinNote ? { coinNote: read.coinNote } : {},
244
+ };
239
245
  },
240
246
  };
241
247
  }
@@ -369,8 +375,8 @@ export function serializeHeadlessPaneResult(
369
375
  }
370
376
  case "series": {
371
377
  const series = result as HeadlessSeriesResult;
372
- // `textDecimals` only shapes the text report; JSON keeps every digit and does not carry it.
373
- return { ...common, series: series.series.map(({ textDecimals: _text, ...kept }) => kept), ...(series.stats ? { stats: series.stats } : {}) };
378
+ // `textDecimals` and `textCompact` only shape the text report; JSON keeps every digit and does not carry them.
379
+ return { ...common, series: series.series.map(({ textDecimals: _decimals, textCompact: _compact, ...kept }) => kept), ...(series.stats ? { stats: series.stats } : {}) };
374
380
  }
375
381
  case "snapshot": {
376
382
  const snapshot = result as HeadlessSnapshotResult;
@@ -380,6 +386,7 @@ export function serializeHeadlessPaneResult(
380
386
  asOf: snapshot.asOf,
381
387
  columns: serializableColumns(columns),
382
388
  items: snapshot.items,
389
+ ...(snapshot.empty && snapshot.items.length === 0 ? { empty: snapshot.empty } : {}),
383
390
  };
384
391
  }
385
392
  default: {
@@ -495,14 +502,16 @@ function seriesPointExportDate(series: HeadlessSeries, date: SeriesPointDate, da
495
502
  * points, multiples and
496
503
  * ratios (P/E, EV/EBITDA, one series over another, VIX 3M / VIX) to two
497
504
  * decimals, which a ratio under 0.1 trades for three significant digits so it
498
- * is not flattened to 0.03, and a 0 to 100 score to a tenth of a point. The
499
- * exports keep every digit. Any other unit prints as loaded, without float
500
- * noise.
505
+ * is not flattened to 0.03, and a 0 to 100 score to a tenth of a point. A
506
+ * company's reported total (`textCompact`) reads as the tables print one:
507
+ * 63.33B. The exports keep every digit. Any other unit prints as loaded,
508
+ * without float noise.
501
509
  */
502
510
  function seriesValue(series: HeadlessSeries, value: number | null): string {
503
511
  if (value == null) return "-";
504
512
  if (!Number.isFinite(value)) return displayNumber(value);
505
513
  if (series.textDecimals != null) return value.toFixed(series.textDecimals);
514
+ if (series.textCompact) return formatCompact(value);
506
515
  const unit = series.unit?.trim().toLowerCase();
507
516
  if (unit === "%" || unit === "pp") return value.toFixed(2);
508
517
  if (unit === "x" || unit === "ratio") return value !== 0 && Math.abs(value) < 0.1 ? value.toPrecision(3) : value.toFixed(2);
@@ -701,6 +710,7 @@ function emptyReportReason(
701
710
  const bundleReason = definition.shape === "bundle" ? (result as HeadlessBundleResult).empty : undefined;
702
711
  if (bundleReason) return bundleReason;
703
712
  const ownReason = definition.shape === "rows" ? !!(result as HeadlessRowsResult).empty
713
+ : definition.shape === "snapshot" ? !!(result as HeadlessSnapshotResult).empty
704
714
  : definition.shape === "bundle" && (result as HeadlessBundleResult).sections.some((section) => "empty" in section && !!section.empty && !isLockedSection(section));
705
715
  const { failed } = splitErrors(result);
706
716
  if (ownReason || failed.length) return null;
@@ -909,7 +919,7 @@ export function renderHeadlessPaneText(
909
919
  case "snapshot": {
910
920
  const snapshot = result as HeadlessSnapshotResult;
911
921
  // The closing source line carries the snapshot's as-of, so it is not printed twice.
912
- lines.push(renderRows(snapshot.items, undefined, definition.columns));
922
+ lines.push(renderRows(snapshot.items, undefined, definition.columns, snapshot.empty));
913
923
  break;
914
924
  }
915
925
  default: {