@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
@@ -13,7 +13,10 @@ import { useThemeColors } from "../../../theme/theme-context";
13
13
  import type { PaneProps } from "../../../types/plugin";
14
14
  import { Box } from "../../../ui";
15
15
  import { loadMacroDayEarnings, loadMacroDayHistory, loadMacroReleases, macroDayEarningsNotice } from "./client";
16
- import { macroDayEventLabel, macroDayStats, matchesSurprise, projectMacroDays, type MacroDayEvent, type MacroDayModel } from "./model";
16
+ import {
17
+ formatMacroDayMove, macroDayEventLabel, macroDayMoveDecimals, macroDayStats, matchesSurprise, projectMacroDays, type MacroDayEvent,
18
+ type MacroDayModel, type MacroDayMoveBasis,
19
+ } from "./model";
17
20
  import { MACRO_EVENT_KINDS, MACRO_EVENT_LABELS, MACRO_SURPRISE_LABELS, MACRO_SURPRISES, staleMacroReleasesNotice, type MacroEventKind,
18
21
  type MacroSurprise } from "./releases";
19
22
 
@@ -21,17 +24,13 @@ export const LOOKBACK_OPTIONS = [{ value: "1", label: "1Y" }, { value: "2", labe
21
24
  const RELEASE_OPTIONS = [{ value: "all", label: "All" }, ...MACRO_EVENT_KINDS.map((kind) => ({ value: kind, label: MACRO_EVENT_LABELS[kind] }))];
22
25
  const SURPRISE_OPTIONS = [{ value: "all", label: "All" }, ...MACRO_SURPRISES.map((surprise) => ({ value: surprise, label: MACRO_SURPRISE_LABELS[surprise] }))];
23
26
 
24
- function pct(value: number | null | undefined, signed = true): string {
25
- if (value == null) return "--";
26
- const fixed = (value * 100).toFixed(2);
27
- return /[1-9]/.test(fixed) ? `${signed && value > 0 ? "+" : ""}${fixed}%` : "0.00%";
28
- }
29
27
  const times = (value: number | null | undefined) => value == null ? "--" : `${value.toFixed(1)}x`;
30
28
 
31
29
  const EARNINGS_MARK = "earnings";
32
30
  const DATE: DataTableColumn = { id: "date", label: "Date", width: 19, align: "left" };
33
- const MOVE: DataTableColumn[] = [
34
- { id: "move", label: "Move", width: 9, align: "right" },
31
+ /** The move and its multiple of a normal day's; a yield's move is in basis points, and its header says so. */
32
+ const moveColumns = (basis: MacroDayMoveBasis): DataTableColumn[] => [
33
+ { id: "move", label: basis === "yield" ? "Move bp" : "Move", width: 9, align: "right" },
35
34
  { id: "multiple", label: "vs Normal", width: 10, align: "right" },
36
35
  ];
37
36
  /** What each release came out with, under the labels the economic calendar gives them. */
@@ -48,23 +47,25 @@ function figureColumns(rows: readonly MacroDayEvent[]): DataTableColumn[] {
48
47
  * With every release listed, the release's name, widened to take the earnings marker where a row has one. With one
49
48
  * selected, a column only for the marker: every row would repeat the name.
50
49
  */
51
- export function columnsFor(release: string, rows: readonly MacroDayEvent[]): DataTableColumn[] {
50
+ export function columnsFor(release: string, rows: readonly MacroDayEvent[], basis: MacroDayMoveBasis = "return"): DataTableColumn[] {
52
51
  const marked = rows.some((event) => event.earnings);
53
- const values = [...figureColumns(rows), ...MOVE];
52
+ const values = [...figureColumns(rows), ...moveColumns(basis)];
54
53
  if (release === "all") return [DATE, { id: "release", label: "Release", width: marked ? 15 : 8, align: "left" }, ...values];
55
54
  return marked ? [DATE, { id: "release", label: "Note", width: EARNINGS_MARK.length, align: "left" }, ...values] : [DATE, ...values];
56
55
  }
57
56
 
58
- /** By the sign the two-decimal text shows: an average that rounds to 0.00% is neither up nor down. */
59
- export const moveTone = (value: number | null) => value == null || !/[1-9]/.test((value * 100).toFixed(2)) ? undefined
60
- : value > 0 ? "positive" as const : "negative" as const;
57
+ /** By the sign the text shows: an average that rounds to 0.00% (or 0.0bp) is neither up nor down. */
58
+ export const moveTone = (value: number | null, basis: MacroDayMoveBasis = "return") =>
59
+ value == null || !/[1-9]/.test((value * 100).toFixed(macroDayMoveDecimals(basis))) ? undefined
60
+ : value > 0 ? "positive" as const : "negative" as const;
61
61
 
62
62
  /** The figures over the days a surprise filter keeps: the same four as one release's. */
63
63
  function surpriseFigures(model: MacroDayModel, rows: readonly MacroDayEvent[]): StatItem[] {
64
64
  const stats = macroDayStats(rows, model.normal.meanAbs);
65
+ const pct = (value: number | null, signed = true) => formatMacroDayMove(value, model.basis, { signed });
65
66
  return [
66
67
  { id: "abs", label: "Avg |move|", value: pct(stats.meanAbs, false), detail: stats.count ? `${times(stats.multiple)} normal` : undefined },
67
- { id: "mean", label: "Avg move", value: pct(stats.mean), tone: moveTone(stats.mean) },
68
+ { id: "mean", label: "Avg move", value: pct(stats.mean), tone: moveTone(stats.mean, model.basis) },
68
69
  { id: "up", label: "Up", value: stats.hitRate == null ? "--" : `${Math.round(stats.hitRate * 100)}%`,
69
70
  detail: stats.count ? `${Math.round(stats.hitRate! * stats.count)} of ${stats.count}` : undefined },
70
71
  { id: "normal", label: "Normal day", value: pct(model.normal.meanAbs, false), detail: "avg |move|" },
@@ -72,6 +73,7 @@ function surpriseFigures(model: MacroDayModel, rows: readonly MacroDayEvent[]):
72
73
  }
73
74
 
74
75
  function figures(model: MacroDayModel, release: string): StatItem[] {
76
+ const pct = (value: number | null, signed = true) => formatMacroDayMove(value, model.basis, { signed });
75
77
  const normal: StatItem = { id: "normal", label: "Normal day", value: pct(model.normal.meanAbs, false), detail: "avg |move|" };
76
78
  if (release === "all") {
77
79
  return [...model.kinds.map((kind): StatItem => {
@@ -84,7 +86,7 @@ function figures(model: MacroDayModel, release: string): StatItem[] {
84
86
  const stats = model.byKind[release as MacroEventKind];
85
87
  return [
86
88
  { id: "abs", label: "Avg |move|", value: pct(stats.meanAbs, false), detail: stats.count ? `${times(stats.multiple)} normal` : undefined },
87
- { id: "mean", label: "Avg move", value: pct(stats.mean), tone: moveTone(stats.mean) },
89
+ { id: "mean", label: "Avg move", value: pct(stats.mean), tone: moveTone(stats.mean, model.basis) },
88
90
  { id: "up", label: "Up", value: stats.hitRate == null ? "--" : `${Math.round(stats.hitRate * 100)}%`,
89
91
  detail: stats.count ? `${Math.round(stats.hitRate! * stats.count)} of ${stats.count}` : undefined },
90
92
  normal,
@@ -123,7 +125,7 @@ export function MacroDayPane({ width, height, focused }: PaneProps) {
123
125
  && matchesSurprise(event, surprise)) : [], [model, release, surprise]);
124
126
  const stats = useMemo(() => !model?.events.length ? [] : surprise === "all" ? figures(model, release) : surpriseFigures(model, rows),
125
127
  [model, release, surprise, rows]);
126
- const columns = useMemo(() => columnsFor(release, rows), [release, rows]);
128
+ const columns = useMemo(() => columnsFor(release, rows, model?.basis), [model?.basis, release, rows]);
127
129
 
128
130
  usePaneNoticeFooter({ registrationId: "macro-day-notices", focused,
129
131
  notices: [...new Set([identityError, history.error, history.data?.error, releasesNotice, earningsNotice].filter((value): value is string => !!value))] });
@@ -141,9 +143,14 @@ export function MacroDayPane({ width, height, focused }: PaneProps) {
141
143
  : event.figures?.surprise === "cool" ? colors.negative : undefined };
142
144
  if (column.id === "forecast") return { text: event.figures?.forecast ?? "" };
143
145
  if (column.id === "prior") return { text: event.figures?.prior ?? "" };
144
- if (column.id === "move") return { text: pct(event.move), value: event.move * 100, color: shownPriceColor(event.move * 100, 2, colors) };
146
+ if (column.id === "move") {
147
+ const basis = model?.basis ?? "return";
148
+ // Under a column headed bp, a yield's move is the number alone.
149
+ return { text: formatMacroDayMove(event.move, basis, { unit: false }), value: event.move * 100,
150
+ color: shownPriceColor(event.move * 100, macroDayMoveDecimals(basis), colors) };
151
+ }
145
152
  return { text: times(event.multiple), value: event.multiple };
146
- }, [colors, release]);
153
+ }, [colors, model?.basis, release]);
147
154
 
148
155
  const contentHeight = Math.max(4, height - 1 - statGridRows(stats, width));
149
156
  const key = (event: MacroDayEvent) => `${event.kind}:${event.date}`;
@@ -162,7 +169,8 @@ export function MacroDayPane({ width, height, focused }: PaneProps) {
162
169
  getItemKey={key} emptyStateTitle={surprise === "all" ? "No release days." : `No ${MACRO_SURPRISE_LABELS[surprise].toLowerCase()} prints.`} sortColumnId={null} sortDirection="desc" selectedTextOverridesCellColor
163
170
  selection={{ kind: "id", selectedId: selectedRow, getId: key, onChange: (id) => setSelectedRow(id) }}
164
171
  getExportMetadata={() => [["symbol", model.symbol], ["from", model.start ?? ""], ["to", model.asOf ?? ""],
165
- ["units", "percent, close to close on the release day"]]}
172
+ ["units", model.basis === "yield" ? "percentage points of yield, close to close on the release day"
173
+ : "percent, close to close on the release day"]]}
166
174
  renderCell={renderCell} />
167
175
  </> : null}
168
176
  </PaneStatusBody>}
@@ -5,9 +5,9 @@ export const MACRO_RELEASE_TABLE: {
5
5
  coveredThrough: string;
6
6
  days: Record<"cpi" | "jobs" | "fomc", Record<number, readonly string[]>>;
7
7
  } = {
8
- checkedAt: "2026-10-15",
8
+ checkedAt: "2026-10-28",
9
9
  /** The last day the lists are complete for: later sessions are left out, release day or not. */
10
- coveredThrough: "2026-10-14",
10
+ coveredThrough: "2026-10-27",
11
11
  days: {
12
12
  // BLS Consumer Price Index, 8:30 ET.
13
13
  cpi: {
@@ -1,4 +1,5 @@
1
1
  import { apiClient } from "../../../api-client";
2
+ import { createTtlCache } from "../../../data/ttl-cache";
2
3
  import type { MarketHeatmapResult, MarketHeatmapUniverseId } from "../../../api-client/market-discovery";
3
4
  export type { MarketHeatmapUniverseId } from "../../../api-client/market-discovery";
4
5
 
@@ -39,8 +40,7 @@ const MAX_MARKET_HEATMAP_COUNT = 500;
39
40
  export function heatmapCheckIntervalMs(board: Pick<MarketHeatmapResult, "access"> | null | undefined): number {
40
41
  return board?.access === "realtime" ? 15_000 : 60_000;
41
42
  }
42
- const activeFetches = new Map<string, Promise<MarketHeatmapBoard>>();
43
- const memoryCache = new Map<string, { expiresAt: number; result: MarketHeatmapBoard }>();
43
+ const boards = createTtlCache<MarketHeatmapBoard>({ ttlMs: heatmapCheckIntervalMs });
44
44
 
45
45
  /**
46
46
  * One tile per symbol. A company listed on two exchanges (KHC on NASDAQ and
@@ -75,43 +75,18 @@ async function loadMarketHeatmap(universe: MarketHeatmapUniverseId, count: numbe
75
75
  };
76
76
  }
77
77
 
78
- export async function fetchMarketHeatmap(
78
+ export function fetchMarketHeatmap(
79
79
  universe: MarketHeatmapUniverseId,
80
80
  options?: MarketHeatmapFetchOptions,
81
81
  sources?: MarketHeatmapSources,
82
82
  ): Promise<MarketHeatmapBoard> {
83
83
  const count = Math.max(1, Math.min(MAX_MARKET_HEATMAP_COUNT, Math.round(options?.count ?? DEFAULT_COUNT)));
84
- const cacheKey = `${universe}:${count}`;
85
- const useCache = options?.cache !== false && !sources?.client;
86
- const now = Date.now();
87
- const cached = memoryCache.get(cacheKey);
88
- if (useCache && !options?.forceRefresh && cached && cached.expiresAt > now) {
89
- return cached.result;
90
- }
91
-
92
- if (useCache) {
93
- const active = activeFetches.get(cacheKey);
94
- if (active) return active;
95
- }
96
-
97
- const fetchPromise = loadMarketHeatmap(universe, count, sources)
98
- .then((result) => {
99
- if (useCache) {
100
- memoryCache.set(cacheKey, { result, expiresAt: Date.now() + heatmapCheckIntervalMs(result) });
101
- }
102
- return result;
103
- })
104
- .finally(() => {
105
- if (activeFetches.get(cacheKey) === fetchPromise) {
106
- activeFetches.delete(cacheKey);
107
- }
108
- });
109
-
110
- if (useCache) activeFetches.set(cacheKey, fetchPromise);
111
- return fetchPromise;
84
+ const load = () => loadMarketHeatmap(universe, count, sources);
85
+ // A board asked for uncached, or through another client, neither reads nor fills the shared cache.
86
+ if (options?.cache === false || sources?.client) return load();
87
+ return boards.load(`${universe}:${count}`, load, { force: options?.forceRefresh });
112
88
  }
113
89
 
114
90
  export function resetMarketHeatmapCache(): void {
115
- activeFetches.clear();
116
- memoryCache.clear();
91
+ boards.clear();
117
92
  }
@@ -6,6 +6,7 @@ import type {
6
6
  } from "../../../api-client/market-movers";
7
7
  import type { DataProvider } from "../../../types/data-provider";
8
8
  import { logFailure } from "../../../utils/log-failure";
9
+ import { savedCopyMark } from "../../../data/saved-copies";
9
10
  import {
10
11
  CATEGORY_MAP,
11
12
  moverRegularSession,
@@ -24,6 +25,7 @@ import {
24
25
  type PreferredMoversOptions,
25
26
  type TrendingSymbol,
26
27
  } from "./screener";
28
+ import { meetsMinFloat } from "./min-float";
27
29
 
28
30
  export interface MarketMoverTabResult extends MarketMoversResult {
29
31
  tab: ScreenerTabId;
@@ -100,14 +102,19 @@ export function marketMoverQuoteError(result: Pick<MarketMoverTabResult, "unavai
100
102
  export async function loadMarketMoverTab(
101
103
  tab: ScreenerTabId,
102
104
  provider: DataProvider | null,
103
- options?: Pick<PreferredMoversOptions, "forceRefresh" | "metadataWaitMs"> & { basis?: MoverRankBasis },
105
+ options?: Pick<PreferredMoversOptions, "forceRefresh" | "metadataWaitMs"> & {
106
+ basis?: MoverRankBasis;
107
+ /** Keep the rows with a float of at least this many shares. Trending carries no float and ignores it. */
108
+ minFloat?: number | null;
109
+ },
104
110
  dependencies: MarketMoverTabDependencies = defaultDependencies,
105
111
  ): Promise<MarketMoverTabResult> {
106
- const { basis, ...fetchOptions } = options ?? {};
112
+ const { basis, minFloat = null, ...fetchOptions } = options ?? {};
107
113
  if (tab === "trending") {
108
114
  const trending = await dependencies.fetchTrending(LIST_ROWS, fetchOptions);
109
115
  const hydrated = await hydrateTrending(trending.data, provider);
110
116
  return {
117
+ ...savedCopyMark(trending),
111
118
  tab,
112
119
  ...hydrated,
113
120
  source: "gloom",
@@ -116,13 +123,15 @@ export async function loadMarketMoverTab(
116
123
  }
117
124
  const category = CATEGORY_MAP[tab];
118
125
  // The server picks the candidates on the basis the list ranks by, so after the close they are the session's movers.
119
- const result = await dependencies.fetchPreferred(category, category === "most_actives" ? LIST_ROWS : MOVER_CANDIDATES, {
126
+ // A minimum float drops rows, so Most Active then reads as many candidates as the price lists.
127
+ const result = await dependencies.fetchPreferred(category, category === "most_actives" && minFloat == null ? LIST_ROWS : MOVER_CANDIDATES, {
120
128
  ...fetchOptions,
121
129
  session: basis ?? "regular",
122
130
  });
123
131
  // Each row read as `ticker` reads it, then ranked by the move its Chg% shows.
124
132
  const { quotes, rankedBy } = rankMoverQuotes(category, result.quotes.map(moverRegularSession), basis);
125
- return { ...result, quotes: quotes.slice(0, LIST_ROWS), rankedBy, tab };
133
+ const kept = quotes.filter((quote) => meetsMinFloat(quote.floatShares, minFloat));
134
+ return { ...result, quotes: kept.slice(0, LIST_ROWS), rankedBy, tab };
126
135
  }
127
136
 
128
137
  /** The server refused the list for the account's plan (gaps, without Pro). */
@@ -136,12 +145,23 @@ export class SessionMoversAccessError extends Error {
136
145
  export async function loadSessionMovers(
137
146
  view: CloudSessionMoversCategory,
138
147
  side: CloudSessionMoversSide,
139
- options?: { forceRefresh?: boolean },
148
+ options?: { forceRefresh?: boolean; minFloat?: number | null },
140
149
  ): Promise<CloudSessionMoversPayload> {
141
- const response = await apiClient.getCloudMarketScreener(view, 25, options?.forceRefresh ? "refresh" : "cache-first", side);
150
+ const minFloat = options?.minFloat ?? null;
151
+ // A minimum float drops rows, so it reads the most the server lists.
152
+ const response = await apiClient.getCloudMarketScreener(view, minFloat == null ? LIST_ROWS : MOVER_CANDIDATES, options?.forceRefresh ? "refresh" : "cache-first", side);
142
153
  if (response.reasonCode === "PRO_REQUIRED") throw new SessionMoversAccessError();
143
154
  if ((response.status === "success" || response.status === "partial") && response.data) {
144
- return response.stale ? { ...response.data, stale: true } : response.data;
155
+ const payload = response.stale ? { ...response.data, stale: true } : response.data;
156
+ return minFloat == null ? payload : { ...payload, items: withMinFloat(payload.items, minFloat) };
145
157
  }
146
158
  throw new Error("Session movers temporarily unavailable");
147
159
  }
160
+
161
+ /** The rows with a float of at least `minFloat`, the first LIST_ROWS of them, ranked again from 1. */
162
+ export function withMinFloat<T extends { rank: number; floatShares: number | null }>(items: readonly T[], minFloat: number): T[] {
163
+ return items
164
+ .filter((item) => meetsMinFloat(item.floatShares, minFloat))
165
+ .slice(0, LIST_ROWS)
166
+ .map((item, index) => ({ ...item, rank: index + 1 }));
167
+ }
@@ -14,6 +14,8 @@ import { createRows, fiftyTwoWeekPositionPercent, formatMoverPrice, MOVER_RANKIN
14
14
  import { EXTENDED_SESSION_LABELS, type ExtendedSession } from "../../../market-data/market/status";
15
15
  import type { HeadlessPaneColumn } from "../../../types/headless";
16
16
  import { isSessionTab, resolveSide } from "./session";
17
+ import { hasPartialFloat, minFloatShares, normalizeMinFloat, PARTIAL_FLOAT_NOTE } from "./min-float";
18
+ import { savedCopyMark } from "../../../data/saved-copies";
17
19
 
18
20
  const rowPrice = (value: unknown, row: Record<string, unknown>) => formatMoverPrice(typeof value === "number" ? value : null, typeof row.currency === "string" ? row.currency : "", moverReferencePrice(row as unknown as MarketMoverRow));
19
21
 
@@ -69,15 +71,19 @@ function sessionColumns(view: CloudSessionMoversCategory) {
69
71
  ];
70
72
  }
71
73
 
74
+ const FLOAT_COLUMN: HeadlessPaneColumn = { key: "floatShares", header: "Float", align: "right", format: compact };
75
+
72
76
  export interface MarketMoversHeadlessDependencies {
73
77
  load(
74
78
  args: HeadlessPaneLoadArgs,
75
79
  tab: ScreenerTabId,
76
80
  provider: DataProvider,
81
+ minFloat: number | null,
77
82
  ): Promise<MarketMoverTabResult>;
78
83
  loadSession(
79
84
  view: CloudSessionMoversCategory,
80
85
  side: CloudSessionMoversSide,
86
+ minFloat: number | null,
81
87
  ): Promise<CloudSessionMoversPayload>;
82
88
  }
83
89
 
@@ -89,8 +95,8 @@ export interface MarketMoversHeadlessDependencies {
89
95
  const ONE_SHOT_METADATA_WAIT_MS = 10_000;
90
96
 
91
97
  const defaultDependencies: MarketMoversHeadlessDependencies = {
92
- load: (_args, tab, provider) => loadMarketMoverTab(tab, provider, { metadataWaitMs: ONE_SHOT_METADATA_WAIT_MS }),
93
- loadSession: (view, side) => loadSessionMovers(view, side),
98
+ load: (_args, tab, provider, minFloat) => loadMarketMoverTab(tab, provider, { metadataWaitMs: ONE_SHOT_METADATA_WAIT_MS, minFloat }),
99
+ loadSession: (view, side, minFloat) => loadSessionMovers(view, side, { minFloat }),
94
100
  };
95
101
 
96
102
  export function createMarketMoversHeadless(
@@ -128,6 +134,14 @@ export function createMarketMoversHeadless(
128
134
  ],
129
135
  defaultValue: "up",
130
136
  pluginState: { pluginId: "market-overview", key: "sessionSide" },
137
+ }, {
138
+ key: "min-float",
139
+ description: "Keep the rows whose float (the one EQS serves) is at least this many shares, such as 10M or 500k; any lifts it. A row with no float is left out. Trending carries no float and ignores it.",
140
+ type: "string",
141
+ placeholder: "shares",
142
+ example: "--min-float 10M",
143
+ normalize: normalizeMinFloat,
144
+ pluginState: { pluginId: "market-overview", key: "minFloat" },
131
145
  }],
132
146
  columns: COLUMNS,
133
147
  describe: (args) => isSessionTab(String(args.options.list))
@@ -135,21 +149,26 @@ export function createMarketMoversHeadless(
135
149
  : `Market Movers | ${String(args.options.list)}`,
136
150
  async load(args, ctx) {
137
151
  const tab = args.options.list as TabId;
152
+ const minFloatValue = typeof args.options["min-float"] === "string" ? args.options["min-float"] : null;
153
+ const minFloat = minFloatShares(minFloatValue);
138
154
  if (isSessionTab(tab)) {
139
155
  const side = resolveSide(tab, String(args.options.side));
140
- const payload = await dependencies.loadSession(tab, side);
156
+ const payload = await dependencies.loadSession(tab, side, minFloat);
141
157
  return {
142
158
  columns: sessionColumns(tab),
143
159
  rows: payload.items.map((item) => ({
144
160
  ...item,
145
161
  events: item.catalysts.map((catalyst) => CATALYST_LABEL[catalyst] ?? catalyst).join(" "),
146
162
  })),
147
- metadata: { list: tab, side, session: payload.session, phase: payload.phase, asOf: payload.asOf, stale: payload.stale === true },
163
+ ...(hasPartialFloat(payload.items) ? { notes: [PARTIAL_FLOAT_NOTE] } : {}),
164
+ metadata: { list: tab, side, session: payload.session, phase: payload.phase, asOf: payload.asOf, stale: payload.stale === true, minFloat },
148
165
  // A Pro account's list is the live board; the free one says it is delayed.
149
166
  ...(payload.access === "delayed" ? {} : { freshness: { status: "live" as const } }),
150
167
  };
151
168
  }
152
- const result = await dependencies.load(args, tab, ctx.marketData);
169
+ // Trending carries no float, so a minimum does not apply to it.
170
+ const floatFiltered = minFloat != null && tab !== "trending";
171
+ const result = await dependencies.load(args, tab, ctx.marketData, floatFiltered ? minFloat : null);
153
172
  const ranked = createRows(result.quotes);
154
173
  // Last and Change % are the regular session; an extended print is a column of its own.
155
174
  const extendedSessions = moverExtendedSessions(ranked);
@@ -168,16 +187,19 @@ export function createMarketMoversHeadless(
168
187
  // Beside an extended column, say which move the order follows.
169
188
  const rankedBy = result.rankedBy ?? null;
170
189
  const quoteError = marketMoverQuoteError(result);
190
+ const columns = [...COLUMNS.slice(0, 5), ...extendedSessions.map(extendedColumn), ...COLUMNS.slice(5), ...(floatFiltered ? [FLOAT_COLUMN] : [])];
191
+ const notes = [
192
+ ...(rankedBy && rankedBy !== "volume" && extendedSessions.length > 0 ? [`Ranked by ${MOVER_RANKING_LABELS[rankedBy]}.`] : []),
193
+ ...(minFloat != null && !floatFiltered ? ["Trending carries no float, so --min-float does not apply to it."] : []),
194
+ ...(floatFiltered && hasPartialFloat(rows) ? [PARTIAL_FLOAT_NOTE] : []),
195
+ ];
171
196
  return {
172
- ...(extendedSessions.length > 0
173
- ? { columns: [...COLUMNS.slice(0, 5), ...extendedSessions.map(extendedColumn), ...COLUMNS.slice(5)] }
174
- : {}),
197
+ ...(columns.length !== COLUMNS.length ? { columns } : {}),
175
198
  rows,
176
199
  ...(quoteError ? { complete: false, unavailableSymbols: result.unavailableSymbols, errors: [quoteError] } : {}),
177
- ...(rankedBy && rankedBy !== "volume" && extendedSessions.length > 0
178
- ? { notes: [`Ranked by ${MOVER_RANKING_LABELS[rankedBy]}.`] }
179
- : {}),
180
- metadata: { list: tab, source: result.source, stale: result.stale, rankedBy },
200
+ ...(notes.length > 0 ? { notes } : {}),
201
+ // A list served from a saved copy ends with when it was saved.
202
+ metadata: { ...savedCopyMark(result), list: tab, source: result.source, stale: result.stale, rankedBy, minFloat: floatFiltered ? minFloat : null },
181
203
  // A Pro account's Gainers, Losers and Most Active are the real-time lists.
182
204
  ...(result.source === "cloud" ? { freshness: { status: "live" as const } } : {}),
183
205
  };
@@ -1,7 +1,7 @@
1
1
  import { Box, useUiCapabilities } from "../../../ui";
2
2
  import { nextHeaderSort } from "../../../utils/sort-values";
3
3
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
4
- import { DataTableView, EmptyState, usePaneFooter, usePaneTabs, type DataTableKeyEvent } from "../../../components";
4
+ import { DataTableView, EmptyState, QueryBar, usePaneFooter, usePaneTabs, type DataTableKeyEvent } from "../../../components";
5
5
  import { handleRefreshKey } from "../../../components/data-table/table-pane";
6
6
  import type { PaneProps } from "../../../types/plugin";
7
7
  import type { PluginModule } from "../plugin-module";
@@ -41,6 +41,7 @@ import { loadMarketMoverTab, marketMoverQuoteError } from "./client";
41
41
  import { summaryFooterSegments } from "./footer";
42
42
  import { isSessionTab, resolveActiveTab, usSessionAt, type UsSession } from "./session";
43
43
  import { SessionMoversBody } from "./session-body";
44
+ import { ANY_FLOAT, minFloatEmptyTitle, minFloatFilter, minFloatShares, partialFloatFooter } from "./min-float";
44
45
  import { marketMoversHeadless } from "./headless";
45
46
  import { buildMarketMoverColumns, renderMarketMoverCell } from "./table";
46
47
  import type { ExtendedSession } from "../../../market-data/market/status";
@@ -152,16 +153,21 @@ function ScreenerMoversBody({ activeTab, focused, width, summaryQuotes, liveStre
152
153
  }) {
153
154
  const dataProvider = useAssetData();
154
155
  const { pinTicker } = usePluginTickerActions();
156
+ const [minFloatValue, setMinFloatValue] = usePluginPaneState<string>("minFloat", ANY_FLOAT);
157
+ // Trending carries no float, so the minimum is not offered there.
158
+ const floatFiltered = activeTab !== "trending";
159
+ const minFloat = floatFiltered ? minFloatShares(minFloatValue) : null;
160
+ const listKey = `${activeTab}:${minFloat ?? ""}`;
155
161
  const [quotes, setQuotes] = useState<MoverQuote[]>([]);
156
- const [loadedTab, setLoadedTab] = useState<TabId | null>(null);
157
- const visibleQuotes = loadedTab === activeTab ? quotes : NO_QUOTES;
162
+ const [loadedKey, setLoadedKey] = useState<string | null>(null);
163
+ const visibleQuotes = loadedKey === listKey ? quotes : NO_QUOTES;
158
164
  // The first load starts before the effect runs; an empty board is not "no data".
159
165
  const [loading, setLoading] = useState(true);
160
166
  const [loadError, setLoadError] = useState<string | null>(null);
161
167
  const [selectedSymbol, setSelectedSymbol] = useState<string | null>(null);
162
168
  const [sortPreference, setSortPreference] = useState<MarketMoverSortPreference>(DEFAULT_SORT_PREFERENCE);
163
169
  const [moversStale, setMoversStale] = useState(false);
164
- const [lastSuccess, setLastSuccess] = useState<{ tab: ScreenerTabId; at: number } | null>(null);
170
+ const [lastSuccess, setLastSuccess] = useState<{ key: string; at: number } | null>(null);
165
171
 
166
172
  const fetchGenRef = useRef(0);
167
173
 
@@ -192,8 +198,8 @@ function ScreenerMoversBody({ activeTab, focused, width, summaryQuotes, liveStre
192
198
  // A pre-market or after-hours column only while a row has such a print.
193
199
  const extendedSessionsKey = moverExtendedSessions(rankedRows).join(",");
194
200
  const columns = useMemo(
195
- () => buildMarketMoverColumns(width, extendedSessionsKey ? extendedSessionsKey.split(",") as ExtendedSession[] : []),
196
- [extendedSessionsKey, width],
201
+ () => buildMarketMoverColumns(width, extendedSessionsKey ? extendedSessionsKey.split(",") as ExtendedSession[] : [], { float: minFloat != null }),
202
+ [extendedSessionsKey, minFloat, width],
197
203
  );
198
204
  const rows = useMemo(() => sortRows(rankedRows, sortPreference), [rankedRows, sortPreference]);
199
205
  const selectedIdx = selectedSymbol
@@ -223,6 +229,7 @@ function ScreenerMoversBody({ activeTab, focused, width, summaryQuotes, liveStre
223
229
  try {
224
230
  const result = await loadMarketMoverTab(tab, dataProvider, {
225
231
  forceRefresh: options?.forceRefresh,
232
+ minFloat,
226
233
  });
227
234
  if (fetchGenRef.current !== gen) return;
228
235
 
@@ -230,12 +237,12 @@ function ScreenerMoversBody({ activeTab, focused, width, summaryQuotes, liveStre
230
237
  // A partial list shows only its current rows; a total failure keeps this list's prior rows.
231
238
  if (!quoteError || result.quotes.length > 0) {
232
239
  setQuotes(result.quotes);
233
- setLoadedTab(tab);
240
+ setLoadedKey(`${tab}:${minFloat ?? ""}`);
234
241
  if (!options?.background) setSelectedSymbol(null);
235
242
  }
236
243
  setMoversStale(result.stale);
237
244
  setLoadError(quoteError);
238
- if (!quoteError && !result.stale) setLastSuccess({ tab, at: Date.now() });
245
+ if (!quoteError && !result.stale) setLastSuccess({ key: `${tab}:${minFloat ?? ""}`, at: Date.now() });
239
246
  } catch {
240
247
  if (fetchGenRef.current !== gen) return;
241
248
  setMoversStale(true);
@@ -244,7 +251,7 @@ function ScreenerMoversBody({ activeTab, focused, width, summaryQuotes, liveStre
244
251
  finally {
245
252
  if (fetchGenRef.current === gen && !options?.background) setLoading(false);
246
253
  }
247
- }, [dataProvider]);
254
+ }, [dataProvider, minFloat]);
248
255
 
249
256
  useEffect(() => {
250
257
  setSelectedSymbol(null);
@@ -254,7 +261,7 @@ function ScreenerMoversBody({ activeTab, focused, width, summaryQuotes, liveStre
254
261
  const backgroundRefresh = useCallback(() => {
255
262
  void loadTab(activeTab, { background: true });
256
263
  }, [activeTab, loadTab]);
257
- useAutoRefresh(lastSuccess?.tab === activeTab ? lastSuccess.at : null, backgroundRefresh);
264
+ useAutoRefresh(lastSuccess?.key === listKey ? lastSuccess.at : null, backgroundRefresh);
258
265
 
259
266
  const openSymbol = useCallback((row: MarketMoverRow) => {
260
267
  pinTicker(moverKey(row), { floating: true, paneType: TICKER_RESEARCH_PANE_ID, instrument: null });
@@ -281,16 +288,21 @@ function ScreenerMoversBody({ activeTab, focused, width, summaryQuotes, liveStre
281
288
  id: "feed",
282
289
  parts: [{ text: feedStatus, tone: feedStatus === "live" ? "value" as const : "muted" as const }],
283
290
  }] : []),
284
- ...(moversStale && loadedTab === activeTab ? [{
291
+ ...(moversStale && loadedKey === listKey ? [{
285
292
  id: "stale",
286
293
  parts: [{ text: "stale", tone: "muted" as const }],
287
294
  }] : []),
295
+ // The FLOAT column shows while a minimum is set; a float the source does not date says so once.
296
+ ...partialFloatFooter(columns.some((column) => column.id === "floatShares"), rows),
288
297
  ],
289
- }), [activeTab, feedStatus, loadedTab, loadError, loading, moversStale, summaryQuotes]);
298
+ }), [columns, feedStatus, listKey, loadedKey, loadError, loading, moversStale, rows, summaryQuotes]);
290
299
 
291
300
  return (
292
301
  <DataTableView<MarketMoverRow, MarketMoverColumn>
293
302
  focused={focused}
303
+ rootBefore={floatFiltered ? (
304
+ <QueryBar width={Math.max(1, width - 2)} filters={[minFloatFilter(minFloatValue, setMinFloatValue)]} />
305
+ ) : undefined}
294
306
  selection={{
295
307
  kind: "id",
296
308
  selectedId: selectedSymbol,
@@ -298,7 +310,7 @@ function ScreenerMoversBody({ activeTab, focused, width, summaryQuotes, liveStre
298
310
  onChange: (symbol) => setSelectedSymbol(symbol),
299
311
  }}
300
312
  onRootKeyDown={handleTableKeyDown}
301
- resetScrollKey={activeTab}
313
+ resetScrollKey={listKey}
302
314
  sortable
303
315
  columns={columns}
304
316
  items={rows}
@@ -309,7 +321,7 @@ function ScreenerMoversBody({ activeTab, focused, width, summaryQuotes, liveStre
309
321
  onActivate={openSymbol}
310
322
  renderCell={renderCell}
311
323
  selectedTextOverridesCellColor
312
- emptyStateTitle={loading ? "Loading movers..." : loadError ?? "No movers returned."}
324
+ emptyStateTitle={loading ? "Loading movers..." : loadError ?? (minFloat != null ? minFloatEmptyTitle(minFloatValue) : "No movers returned.")}
313
325
  emptyContent={loadError ? (
314
326
  <Box paddingX={1} paddingY={1}>
315
327
  <EmptyState title={loadError} message="Try again in a moment." />
@@ -0,0 +1,74 @@
1
+ import type { PaneFooterSegment } from "../../../components";
2
+ import type { QueryBarSelectFilter } from "../../../components/ui/query-bar";
3
+ import { formatCompact } from "../../../utils/format";
4
+
5
+ /** No minimum: every row, with or without a float. */
6
+ export const ANY_FLOAT = "any";
7
+ /** The minimums the pane's menu offers; `--min-float` takes any other. */
8
+ const PRESETS = ["1M", "5M", "10M", "20M", "50M", "100M"] as const;
9
+ const SCALE: Record<string, number> = { k: 1e3, m: 1e6, b: 1e9 };
10
+
11
+ /** Shares in `text` (10M, 500k, 2.5M, 25000000), or null when it is not a share count. */
12
+ function parseShares(text: string): number | null {
13
+ const match = /^((?:\d+(?:\.\d*)?|\.\d+))([kmb])?$/i.exec(text.trim().replace(/[_,]/g, ""));
14
+ if (!match) return null;
15
+ const value = Number(match[1]) * (match[2] ? SCALE[match[2].toLowerCase()]! : 1);
16
+ return Number.isFinite(value) ? value : null;
17
+ }
18
+
19
+ /** The minimum a saved or typed value sets, in shares; null for none (`any`, 0 or a value that is not a count). */
20
+ export function minFloatShares(value: string | null | undefined): number | null {
21
+ if (!value || value.trim().toLowerCase() === ANY_FLOAT) return null;
22
+ const shares = parseShares(value);
23
+ return shares != null && shares > 0 ? shares : null;
24
+ }
25
+
26
+ /** `--min-float` as the pane keeps it: `10M` for 10m or 10000000, `any` for none. Throws on a value that is not a share count. */
27
+ export function normalizeMinFloat(value: string): string {
28
+ if (value.trim().toLowerCase() === ANY_FLOAT) return ANY_FLOAT;
29
+ const shares = parseShares(value);
30
+ if (shares == null) throw new Error(`--min-float takes a share count such as 10M or 500k, or any; got "${value}".`);
31
+ return shares > 0 ? formatCompact(shares) : ANY_FLOAT;
32
+ }
33
+
34
+ /** A row passes with a float at or above the minimum; a row whose float is not known does not. */
35
+ export function meetsMinFloat(floatShares: number | null | undefined, minimum: number | null): boolean {
36
+ return minimum == null || (floatShares != null && floatShares >= minimum);
37
+ }
38
+
39
+ /** The pane's Min float menu: Any and the presets, with a minimum set from the CLI kept as its own choice. */
40
+ export function minFloatFilter(value: string, onChange: (value: string) => void): QueryBarSelectFilter {
41
+ const current = minFloatShares(value) == null ? ANY_FLOAT : value;
42
+ const presets: string[] = [...PRESETS];
43
+ const options = [ANY_FLOAT, ...(presets.includes(current) || current === ANY_FLOAT ? [] : [current]), ...presets]
44
+ .map((option) => ({ value: option, label: option === ANY_FLOAT ? "Any" : option }));
45
+ return {
46
+ id: "min-float",
47
+ label: "Min float",
48
+ title: "Minimum float",
49
+ value: current,
50
+ defaultValue: ANY_FLOAT,
51
+ options,
52
+ onChange,
53
+ };
54
+ }
55
+
56
+ /** What a list says under its Float column while a shown float is partial. */
57
+ export const PARTIAL_FLOAT_NOTE = "Float is partial: the source does not say when it measured it.";
58
+
59
+ /** Whether any row's float is partial, which the list then says once. */
60
+ export function hasPartialFloat(rows: readonly { floatShares?: number | null; floatSharesState?: string }[]): boolean {
61
+ return rows.some((row) => row.floatShares != null && row.floatSharesState === "partial");
62
+ }
63
+
64
+ /** The footer's word for a FLOAT column of partial floats, or none. */
65
+ export function partialFloatFooter(shown: boolean, rows: readonly { floatShares?: number | null; floatSharesState?: string }[]): PaneFooterSegment[] {
66
+ return shown && hasPartialFloat(rows)
67
+ ? [{ id: "float-partial", parts: [{ text: "FLOAT", tone: "label" }, { text: "partial, undated", tone: "muted" }] }]
68
+ : [];
69
+ }
70
+
71
+ /** The empty list's title while a minimum hides every row. */
72
+ export function minFloatEmptyTitle(value: string): string {
73
+ return `No movers with a float of ${value} or more.`;
74
+ }
@@ -50,7 +50,8 @@ type MarketMoverColumnId =
50
50
  | "volume"
51
51
  | "volumeRatio"
52
52
  | "range"
53
- | "marketCap";
53
+ | "marketCap"
54
+ | "floatShares";
54
55
  export type MarketMoverColumn = DataTableColumn & { id: MarketMoverColumnId };
55
56
  /**
56
57
  * A list row. Price and change are the regular session's, as `ticker` reads
@@ -123,6 +124,8 @@ function getSortValue(
123
124
  return fiftyTwoWeekPositionPercent(row.price, row.fiftyTwoWeekLow, row.fiftyTwoWeekHigh);
124
125
  case "marketCap":
125
126
  return row.marketCap ?? null;
127
+ case "floatShares":
128
+ return row.floatShares ?? null;
126
129
  }
127
130
  }
128
131