@gloom-sh/terminal 0.18.5 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (436) hide show
  1. package/package.json +1 -1
  2. package/patches/electrobun@1.18.1.patch +140 -7
  3. package/src/api-client/cot.ts +6 -2
  4. package/src/api-client/credit-documents.ts +6 -0
  5. package/src/api-client/data.ts +17 -6
  6. package/src/api-client/equity-screener.ts +24 -0
  7. package/src/api-client/fund-facts.ts +45 -4
  8. package/src/api-client/index.ts +11 -0
  9. package/src/api-client/market-discovery.ts +10 -0
  10. package/src/api-client/market-movers.ts +7 -0
  11. package/src/api-client/members.ts +1 -1
  12. package/src/api-client/perps.ts +43 -0
  13. package/src/api-client/research-activity.ts +3 -2
  14. package/src/api-client/types.ts +70 -2
  15. package/src/app/pane-runtime/plugin-bindings.ts +9 -5
  16. package/src/app/runtime/broker-import.ts +0 -2
  17. package/src/app/runtime/plugin-bindings.ts +73 -56
  18. package/src/architecture/pane-tabs-allowlist.json +3 -0
  19. package/src/architecture/ttl-caches-allowlist.json +19 -0
  20. package/src/brokers/sync-broker-instance.ts +2 -8
  21. package/src/capabilities/chart-series.ts +1 -1
  22. package/src/cli/commands/crypto-hints.ts +40 -24
  23. package/src/cli/commands/market.ts +72 -26
  24. package/src/cli/commands/options-chain.ts +48 -7
  25. package/src/cli/commands/options-leaps.ts +9 -3
  26. package/src/cli/commands/overview.ts +61 -12
  27. package/src/cli/commands/search.ts +21 -1
  28. package/src/cli/commands/themes.ts +11 -2
  29. package/src/cli/commands/ticker.ts +55 -34
  30. package/src/cli/company-name-hint.ts +41 -0
  31. package/src/cli/earnings-calendar.ts +1 -1
  32. package/src/cli/errors.ts +8 -0
  33. package/src/cli/glossary.ts +25 -2
  34. package/src/cli/history-rows.ts +57 -9
  35. package/src/cli/index.ts +2 -0
  36. package/src/cli/listing-arg.ts +180 -52
  37. package/src/cli/output-file.ts +68 -0
  38. package/src/cli/pane-functions/capabilities.ts +3 -0
  39. package/src/cli/pane-functions/catalog.ts +80 -7
  40. package/src/cli/pane-functions/dom.ts +13 -3
  41. package/src/cli/pane-functions/freshness.ts +86 -3
  42. package/src/cli/pane-functions/function-help.ts +13 -10
  43. package/src/cli/pane-functions/headless.ts +17 -7
  44. package/src/cli/pane-functions/index.ts +49 -14
  45. package/src/cli/pane-functions/market-session.ts +66 -4
  46. package/src/cli/pane-functions/options.ts +19 -1
  47. package/src/cli/pane-functions/resolver.ts +19 -8
  48. package/src/cli/pane-functions/screenshot-assessment.ts +6 -4
  49. package/src/cli/pane-functions/screenshot-types.ts +5 -0
  50. package/src/cli/quote-answer.ts +22 -11
  51. package/src/cli/ticker-resolution.ts +3 -2
  52. package/src/components/chart/composite/composite-chart.tsx +4 -0
  53. package/src/components/chart/composite/interactions.ts +6 -0
  54. package/src/components/chart/composite/legend.tsx +45 -7
  55. package/src/components/chart/composite/panel-gestures.ts +33 -0
  56. package/src/components/chart/composite/panel-surface.tsx +52 -3
  57. package/src/components/chart/composite/rasterizer.ts +24 -13
  58. package/src/components/chart/composite/scene.ts +39 -4
  59. package/src/components/chart/composite/series-cues.ts +14 -0
  60. package/src/components/chart/composite/text-renderer.ts +9 -2
  61. package/src/components/chart/composite/tool-catalog.ts +36 -0
  62. package/src/components/chart/composite/tools.ts +203 -44
  63. package/src/components/chart/composite/use-composite-chart-keyboard.ts +11 -4
  64. package/src/components/command-bar/assist/inventory.ts +2 -1
  65. package/src/components/command-bar/commands/direct/index.ts +28 -13
  66. package/src/components/command-bar/pane-templates/items.ts +3 -2
  67. package/src/components/command-bar/workflow/collection-actions.ts +3 -3
  68. package/src/components/onboarding/onboarding-wizard.tsx +4 -1
  69. package/src/components/onboarding/portfolio-step/positions-panel.tsx +12 -7
  70. package/src/components/onboarding/wizard-broker-sync.ts +0 -1
  71. package/src/components/onboarding/wizard-positions.ts +3 -3
  72. package/src/components/ticker/list-table-view.tsx +4 -1
  73. package/src/components/ui/choice-dialog.tsx +23 -18
  74. package/src/components/ui/choice-groups.ts +18 -0
  75. package/src/components/ui/data-table/opentui/index.tsx +9 -7
  76. package/src/components/ui/data-table/opentui/model.ts +26 -1
  77. package/src/components/ui/data-table/types.ts +2 -0
  78. package/src/components/ui/list-view.tsx +8 -4
  79. package/src/components/ui/query-bar-keys.tsx +2 -0
  80. package/src/components/ui/query-bar.tsx +1 -0
  81. package/src/components/ui/select-button.tsx +5 -1
  82. package/src/components/ui/select-field.tsx +1 -0
  83. package/src/components/ui/table-layout.ts +8 -5
  84. package/src/core/app-runtime.ts +2 -0
  85. package/src/core/app-services.ts +2 -2
  86. package/src/data/config/chart-settings.ts +1 -1
  87. package/src/data/json-ticker-repository.ts +17 -8
  88. package/src/data/ticker-repository.ts +16 -9
  89. package/src/data/ticker-writes.ts +81 -0
  90. package/src/data/ttl-cache.ts +165 -0
  91. package/src/docs/snapshot.json +71 -137
  92. package/src/i18n/es.ts +18 -2
  93. package/src/i18n/ja.ts +18 -2
  94. package/src/i18n/ko.ts +18 -2
  95. package/src/i18n/zh-cn.ts +18 -2
  96. package/src/i18n/zh-tw.ts +18 -2
  97. package/src/market-data/depositary-pair.ts +2 -2
  98. package/src/market-data/fund-facts.ts +50 -0
  99. package/src/market-data/market/change-basis.ts +16 -3
  100. package/src/market-data/market/format.ts +25 -0
  101. package/src/market-data/market/freshness.ts +2 -1
  102. package/src/market-data/options-alternatives.ts +81 -4
  103. package/src/news/aggregator.ts +113 -9
  104. package/src/news/cloud-session.ts +21 -0
  105. package/src/news/hooks.ts +1 -0
  106. package/src/news/news-model.ts +1 -0
  107. package/src/news/types.ts +11 -0
  108. package/src/plugins/builtin/alerts/alert-engine.ts +20 -1
  109. package/src/plugins/builtin/alerts/alert-log.ts +33 -0
  110. package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
  111. package/src/plugins/builtin/alerts/cli.ts +336 -77
  112. package/src/plugins/builtin/alerts/command.ts +25 -1
  113. package/src/plugins/builtin/alerts/events-pane.tsx +19 -4
  114. package/src/plugins/builtin/alerts/fired-log.ts +73 -9
  115. package/src/plugins/builtin/alerts/format.ts +75 -5
  116. package/src/plugins/builtin/alerts/headless.ts +24 -6
  117. package/src/plugins/builtin/alerts/history-rows.ts +3 -2
  118. package/src/plugins/builtin/alerts/history.ts +1 -1
  119. package/src/plugins/builtin/alerts/index.tsx +79 -16
  120. package/src/plugins/builtin/alerts/metric-command.ts +77 -4
  121. package/src/plugins/builtin/alerts/metric-data.ts +54 -13
  122. package/src/plugins/builtin/alerts/metric-model.ts +163 -14
  123. package/src/plugins/builtin/alerts/metric-parity-fixture.json +1 -1
  124. package/src/plugins/builtin/alerts/pane.tsx +61 -23
  125. package/src/plugins/builtin/alerts/quotes.ts +6 -0
  126. package/src/plugins/builtin/alerts/storage.ts +12 -8
  127. package/src/plugins/builtin/alerts/types.ts +4 -0
  128. package/src/plugins/builtin/analytics/index.tsx +2 -1
  129. package/src/plugins/builtin/analytics/risk-client.ts +16 -5
  130. package/src/plugins/builtin/analytics/risk-fx.ts +70 -15
  131. package/src/plugins/builtin/analytics/risk-headless.ts +27 -0
  132. package/src/plugins/builtin/analytics/risk-model.ts +55 -9
  133. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
  134. package/src/plugins/builtin/attention/index.tsx +2 -1
  135. package/src/plugins/builtin/attention/listing.ts +14 -0
  136. package/src/plugins/builtin/broker-manager/index.tsx +7 -3
  137. package/src/plugins/builtin/buildout/cells.tsx +12 -4
  138. package/src/plugins/builtin/buildout/format.ts +38 -0
  139. package/src/plugins/builtin/buildout/model/normalizers.ts +36 -1
  140. package/src/plugins/builtin/buildout/pane/index.tsx +4 -1
  141. package/src/plugins/builtin/buildout/table-model.ts +23 -2
  142. package/src/plugins/builtin/builtin-plugin-meta.ts +6 -2
  143. package/src/plugins/builtin/changelog/index.tsx +9 -6
  144. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +3 -0
  145. package/src/plugins/builtin/chart-composer/cli-options.ts +4 -0
  146. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +7 -0
  147. package/src/plugins/builtin/chart-composer/editor/controller.ts +2 -2
  148. package/src/plugins/builtin/chart-composer/headless-schema.ts +7 -1
  149. package/src/plugins/builtin/chart-composer/headless.ts +6 -1
  150. package/src/plugins/builtin/chart-composer/index.tsx +1 -1
  151. package/src/plugins/builtin/chart-composer/pane.tsx +37 -8
  152. package/src/plugins/builtin/chart-composer/settings.ts +27 -0
  153. package/src/plugins/builtin/chat/content/command-suggestions.ts +1 -0
  154. package/src/plugins/builtin/chat/pane-links/commands.ts +7 -1
  155. package/src/plugins/builtin/clinical-trials/model.ts +0 -9
  156. package/src/plugins/builtin/clinical-trials/pane.tsx +15 -22
  157. package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +8 -6
  158. package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
  159. package/src/plugins/builtin/company-attention/building.tsx +5 -1
  160. package/src/plugins/builtin/company-attention/client.ts +8 -9
  161. package/src/plugins/builtin/company-attention/evidence.ts +4 -1
  162. package/src/plugins/builtin/company-attention/moves.tsx +5 -1
  163. package/src/plugins/builtin/company-attention/pane.tsx +9 -2
  164. package/src/plugins/builtin/company-attention/signals.tsx +5 -1
  165. package/src/plugins/builtin/correlation/headless.ts +13 -5
  166. package/src/plugins/builtin/correlation/history.ts +22 -0
  167. package/src/plugins/builtin/cot/client.ts +22 -18
  168. package/src/plugins/builtin/cot/headless.ts +15 -10
  169. package/src/plugins/builtin/cot/model.ts +14 -1
  170. package/src/plugins/builtin/cot/pane.tsx +22 -14
  171. package/src/plugins/builtin/credit-boards/client.ts +6 -10
  172. package/src/plugins/builtin/credit-conditions/headless.ts +8 -2
  173. package/src/plugins/builtin/credit-conditions/index.tsx +5 -1
  174. package/src/plugins/builtin/credit-conditions/model.ts +4 -0
  175. package/src/plugins/builtin/credit-conditions/series.ts +3 -1
  176. package/src/plugins/builtin/credit-documents/detail.tsx +2 -2
  177. package/src/plugins/builtin/credit-documents/export.ts +173 -0
  178. package/src/plugins/builtin/credit-documents/headless.ts +7 -7
  179. package/src/plugins/builtin/credit-documents/model.ts +104 -14
  180. package/src/plugins/builtin/credit-documents/pane.tsx +12 -5
  181. package/src/plugins/builtin/custom-view/sources.ts +2 -0
  182. package/src/plugins/builtin/dividend-yield/client.ts +131 -23
  183. package/src/plugins/builtin/dividend-yield/headless.ts +44 -16
  184. package/src/plugins/builtin/dividend-yield/model.ts +8 -5
  185. package/src/plugins/builtin/dividend-yield/pane.tsx +38 -13
  186. package/src/plugins/builtin/dividend-yield/ranking.ts +37 -14
  187. package/src/plugins/builtin/dividend-yield/types.ts +20 -2
  188. package/src/plugins/builtin/dividend-yield/view.ts +30 -2
  189. package/src/plugins/builtin/docs/targets.ts +5 -1
  190. package/src/plugins/builtin/earnings/board-model.ts +16 -15
  191. package/src/plugins/builtin/earnings/board.tsx +62 -71
  192. package/src/plugins/builtin/earnings/headless.ts +40 -4
  193. package/src/plugins/builtin/earnings/scope.ts +31 -43
  194. package/src/plugins/builtin/earnings-calls/calendar.tsx +2 -2
  195. package/src/plugins/builtin/earnings-calls/format.ts +31 -2
  196. package/src/plugins/builtin/earnings-calls/headless.ts +7 -2
  197. package/src/plugins/builtin/earnings-calls/model.ts +6 -1
  198. package/src/plugins/builtin/earnings-calls/pane.tsx +5 -4
  199. package/src/plugins/builtin/econ/types.ts +2 -0
  200. package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
  201. package/src/plugins/builtin/econ-statistics/headless.ts +6 -4
  202. package/src/plugins/builtin/econ-statistics/stats.ts +36 -9
  203. package/src/plugins/builtin/equity-screener/categories.ts +179 -0
  204. package/src/plugins/builtin/equity-screener/client.ts +4 -2
  205. package/src/plugins/builtin/equity-screener/headless.ts +144 -13
  206. package/src/plugins/builtin/equity-screener/ideas.ts +49 -0
  207. package/src/plugins/builtin/equity-screener/model.ts +99 -10
  208. package/src/plugins/builtin/equity-screener/pane.tsx +68 -13
  209. package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
  210. package/src/plugins/builtin/futures/use-board-terms.ts +42 -28
  211. package/src/plugins/builtin/futures/use-front-returns.ts +45 -28
  212. package/src/plugins/builtin/fx-matrix/client.ts +6 -3
  213. package/src/plugins/builtin/fx-matrix/headless.ts +1 -1
  214. package/src/plugins/builtin/fx-matrix/index.tsx +4 -2
  215. package/src/plugins/builtin/fx-matrix/live-legs.ts +42 -3
  216. package/src/plugins/builtin/gpu/headless.ts +8 -2
  217. package/src/plugins/builtin/help/function-index.ts +4 -1
  218. package/src/plugins/builtin/help/index.tsx +1 -0
  219. package/src/plugins/builtin/help/shortcut-model.ts +1 -1
  220. package/src/plugins/builtin/holders/fund-holders.ts +51 -0
  221. package/src/plugins/builtin/holders/headless.ts +18 -1
  222. package/src/plugins/builtin/iv-history/client.ts +52 -11
  223. package/src/plugins/builtin/iv-history/format.ts +36 -23
  224. package/src/plugins/builtin/iv-history/headless.ts +33 -18
  225. package/src/plugins/builtin/iv-history/index.tsx +2 -2
  226. package/src/plugins/builtin/iv-history/model.ts +163 -27
  227. package/src/plugins/builtin/iv-history/pane.tsx +10 -6
  228. package/src/plugins/builtin/iv-history/screen-pane.tsx +22 -15
  229. package/src/plugins/builtin/iv-history/universe.ts +5 -1
  230. package/src/plugins/builtin/jobs/client.ts +25 -29
  231. package/src/plugins/builtin/kelly-sizer/model.ts +12 -8
  232. package/src/plugins/builtin/kelly-sizer/pane.tsx +9 -9
  233. package/src/plugins/builtin/kelly-sizer/sections.tsx +19 -10
  234. package/src/plugins/builtin/kelly-sizer/types.ts +10 -4
  235. package/src/plugins/builtin/macro-day/release-table.ts +2 -2
  236. package/src/plugins/builtin/market-heatmap/data.ts +8 -33
  237. package/src/plugins/builtin/market-movers/client.ts +25 -7
  238. package/src/plugins/builtin/market-movers/headless.ts +32 -12
  239. package/src/plugins/builtin/market-movers/index.tsx +26 -14
  240. package/src/plugins/builtin/market-movers/min-float.ts +74 -0
  241. package/src/plugins/builtin/market-movers/model.ts +4 -1
  242. package/src/plugins/builtin/market-movers/screener.ts +14 -0
  243. package/src/plugins/builtin/market-movers/session-body.tsx +13 -8
  244. package/src/plugins/builtin/market-movers/session-table.ts +4 -3
  245. package/src/plugins/builtin/market-movers/table.ts +17 -5
  246. package/src/plugins/builtin/members/breadth-pane.tsx +2 -1
  247. package/src/plugins/builtin/news/index.tsx +1 -0
  248. package/src/plugins/builtin/news/wire/categories.ts +19 -3
  249. package/src/plugins/builtin/news/wire/industry-pane.tsx +1 -0
  250. package/src/plugins/builtin/news/wire/news/footer.ts +31 -5
  251. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +6 -1
  252. package/src/plugins/builtin/news/wire/news/table.tsx +2 -2
  253. package/src/plugins/builtin/openfda/client.ts +14 -2
  254. package/src/plugins/builtin/openfda/index.tsx +3 -2
  255. package/src/plugins/builtin/openfda/model.ts +9 -0
  256. package/src/plugins/builtin/openfda/pane.tsx +42 -6
  257. package/src/plugins/builtin/options/analytics.ts +29 -5
  258. package/src/plugins/builtin/options/enrichment.ts +17 -5
  259. package/src/plugins/builtin/options/table.ts +5 -2
  260. package/src/plugins/builtin/options/types.ts +2 -0
  261. package/src/plugins/builtin/options/view.tsx +17 -4
  262. package/src/plugins/builtin/options-positioning/client.ts +11 -0
  263. package/src/plugins/builtin/options-positioning/headless.ts +21 -8
  264. package/src/plugins/builtin/options-positioning/index.tsx +1 -1
  265. package/src/plugins/builtin/options-positioning/model.ts +78 -9
  266. package/src/plugins/builtin/options-positioning/pane.tsx +21 -6
  267. package/src/plugins/builtin/options-scenario/client.ts +116 -47
  268. package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +142 -10
  269. package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +206 -55
  270. package/src/plugins/builtin/options-scenario/csp-screen.ts +63 -23
  271. package/src/plugins/builtin/options-scenario/csp.ts +8 -2
  272. package/src/plugins/builtin/options-scenario/index.tsx +9 -7
  273. package/src/plugins/builtin/options-scenario/strategy-screen.ts +532 -0
  274. package/src/plugins/builtin/pe-band/model.ts +12 -49
  275. package/src/plugins/builtin/perps/cascade-tabs.tsx +161 -0
  276. package/src/plugins/builtin/perps/cascade.ts +236 -0
  277. package/src/plugins/builtin/perps/client.ts +56 -2
  278. package/src/plugins/builtin/perps/headless.ts +65 -9
  279. package/src/plugins/builtin/perps/index.tsx +7 -5
  280. package/src/plugins/builtin/perps/model.ts +18 -4
  281. package/src/plugins/builtin/perps/pane.tsx +19 -22
  282. package/src/plugins/builtin/perps/status-line.ts +25 -0
  283. package/src/plugins/builtin/plugin-module.ts +5 -1
  284. package/src/plugins/builtin/portfolio-list/cached-account.ts +3 -1
  285. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +2 -8
  286. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +4 -4
  287. package/src/plugins/builtin/portfolio-list/cli/render.ts +7 -5
  288. package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
  289. package/src/plugins/builtin/portfolio-list/pane/index.tsx +4 -1
  290. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +5 -2
  291. package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +3 -2
  292. package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +3 -1
  293. package/src/plugins/builtin/portfolio-list/table.tsx +11 -3
  294. package/src/plugins/builtin/primary-dealers/client.ts +13 -38
  295. package/src/plugins/builtin/realized-vol/index.tsx +2 -2
  296. package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
  297. package/src/plugins/builtin/relative-rotation/client.ts +18 -4
  298. package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
  299. package/src/plugins/builtin/relative-rotation/index.tsx +2 -1
  300. package/src/plugins/builtin/relative-rotation/model.ts +16 -40
  301. package/src/plugins/builtin/relative-rotation/pane.tsx +3 -2
  302. package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -2
  303. package/src/plugins/builtin/research/event-model.ts +10 -8
  304. package/src/plugins/builtin/research/headless.ts +1 -1
  305. package/src/plugins/builtin/research/relative-valuation-headless.ts +5 -1
  306. package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
  307. package/src/plugins/builtin/research/relative-valuation-pane.tsx +7 -5
  308. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -3
  309. package/src/plugins/builtin/reverse-dcf/model.ts +61 -6
  310. package/src/plugins/builtin/reverse-dcf/pane.tsx +7 -4
  311. package/src/plugins/builtin/scanner/flow-model.ts +50 -4
  312. package/src/plugins/builtin/scanner/flow-pane.tsx +367 -65
  313. package/src/plugins/builtin/scanner/flow-session.ts +146 -0
  314. package/src/plugins/builtin/scanner/headless.ts +204 -35
  315. package/src/plugins/builtin/scanner/index.ts +16 -5
  316. package/src/plugins/builtin/scanner/live-headless.ts +27 -3
  317. package/src/plugins/builtin/scanner/live-model.ts +11 -0
  318. package/src/plugins/builtin/scanner/live-pane.tsx +3 -3
  319. package/src/plugins/builtin/scanner/lock-note.ts +3 -2
  320. package/src/plugins/builtin/seasonality/headless.ts +10 -1
  321. package/src/plugins/builtin/sec/fund-facts-view.tsx +5 -2
  322. package/src/plugins/builtin/sec/fund-facts.ts +116 -34
  323. package/src/plugins/builtin/sec/headless.ts +2 -1
  324. package/src/plugins/builtin/series-inventory.ts +2 -0
  325. package/src/plugins/builtin/shared/cloud-resource.ts +3 -2
  326. package/src/plugins/builtin/shared/cloud-upgrade.ts +4 -3
  327. package/src/plugins/builtin/shared/earnings-calendar.ts +6 -2
  328. package/src/plugins/builtin/shared/earnings-report.ts +2 -2
  329. package/src/plugins/builtin/shared/job-posting.ts +1 -0
  330. package/src/plugins/builtin/shared/members-menu.tsx +3 -2
  331. package/src/plugins/builtin/shared/pro-lock-note.ts +12 -12
  332. package/src/plugins/builtin/shared/report-freshness.ts +3 -2
  333. package/src/plugins/builtin/shared/ticker-choice.tsx +2 -1
  334. package/src/plugins/builtin/shared/ticker-company.ts +77 -0
  335. package/src/plugins/builtin/shared/use-quote-board.ts +2 -1
  336. package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
  337. package/src/plugins/builtin/shared/volatility/pricing.ts +19 -0
  338. package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
  339. package/src/plugins/builtin/themes/model.ts +16 -7
  340. package/src/plugins/builtin/themes/pane.tsx +5 -5
  341. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +43 -18
  342. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +3 -2
  343. package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +62 -0
  344. package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +83 -46
  345. package/src/plugins/builtin/ticker-detail/headless.ts +31 -16
  346. package/src/plugins/builtin/ticker-detail/overview/model.ts +1 -1
  347. package/src/plugins/builtin/ticker-detail/pane.tsx +3 -2
  348. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -1
  349. package/src/plugins/builtin/ticker-detail/settings.ts +1 -0
  350. package/src/plugins/builtin/time-sales/pane.tsx +31 -19
  351. package/src/plugins/builtin/treasury-daily/client.ts +41 -5
  352. package/src/plugins/builtin/treasury-daily/headless.ts +2 -2
  353. package/src/plugins/builtin/treasury-daily/index.tsx +1 -1
  354. package/src/plugins/builtin/treasury-daily/model.ts +50 -3
  355. package/src/plugins/builtin/treasury-daily/pane.tsx +81 -2
  356. package/src/plugins/builtin/vol-surface/client.ts +3 -13
  357. package/src/plugins/builtin/vol-surface/headless.ts +3 -3
  358. package/src/plugins/builtin/vol-surface/index.tsx +1 -1
  359. package/src/plugins/builtin/vol-surface/pane.tsx +7 -5
  360. package/src/plugins/builtin/volatility/index.tsx +2 -2
  361. package/src/plugins/builtin/volatility/pane.tsx +2 -2
  362. package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
  363. package/src/plugins/catalog-manifest.ts +2 -1
  364. package/src/plugins/marketplace/model.ts +18 -5
  365. package/src/portfolio/futures-positions.ts +6 -3
  366. package/src/portfolio/position-quantity.ts +15 -9
  367. package/src/remote/resources.ts +2 -1
  368. package/src/renderers/dom/data-table/row.tsx +26 -0
  369. package/src/renderers/dom/desktop/list-view.tsx +13 -10
  370. package/src/renderers/dom/desktop/query-bar.tsx +4 -2
  371. package/src/renderers/dom/desktop/select-field.tsx +4 -2
  372. package/src/renderers/dom/host/chart-surface.tsx +21 -0
  373. package/src/renderers/electrobun/bun/desktop/app-name.ts +3 -3
  374. package/src/renderers/electrobun/bun/desktop/backend.ts +12 -0
  375. package/src/renderers/electrobun/bun/desktop/host-requests.ts +7 -0
  376. package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
  377. package/src/renderers/electrobun/bun/desktop/update.ts +115 -14
  378. package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
  379. package/src/renderers/electrobun/shared/app-name.ts +13 -0
  380. package/src/renderers/electrobun/shared/protocol.ts +10 -0
  381. package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
  382. package/src/renderers/electrobun/view/main.tsx +24 -1
  383. package/src/renderers/electrobun/view/news-source.ts +3 -1
  384. package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
  385. package/src/renderers/electrobun/view/view-ids.ts +85 -0
  386. package/src/renderers/opentui/start.tsx +5 -0
  387. package/src/series/registry.ts +1 -0
  388. package/src/series/types.ts +6 -0
  389. package/src/series/validate.ts +1 -0
  390. package/src/sources/gloomberb-cloud/index.ts +1 -0
  391. package/src/sources/gloomberb-cloud/news.ts +47 -7
  392. package/src/sources/provider-router/financials.ts +15 -3
  393. package/src/sources/provider-router/index.ts +8 -5
  394. package/src/state/ticker-cleanup.ts +50 -0
  395. package/src/sync/controller.ts +4 -3
  396. package/src/sync/core-contributors.ts +90 -31
  397. package/src/telemetry/stall-reports-dom.ts +38 -0
  398. package/src/telemetry/stall-reports-node.ts +75 -0
  399. package/src/telemetry/stall-reports.ts +214 -0
  400. package/src/test-support/preload.ts +14 -0
  401. package/src/theme/themes.ts +10 -0
  402. package/src/tickers/company-name.ts +44 -3
  403. package/src/tickers/listing-venues.ts +15 -3
  404. package/src/tickers/terminal-codes.ts +19 -0
  405. package/src/time-series/alignment.ts +1 -1
  406. package/src/time-series/economic.ts +18 -4
  407. package/src/time-series/field-catalog.ts +1 -1
  408. package/src/time-series/filed-trailing-eps.ts +197 -0
  409. package/src/time-series/fundamentals.ts +44 -1
  410. package/src/time-series/ice-credit.ts +3 -0
  411. package/src/time-series/price-comparison.ts +30 -2
  412. package/src/time-series/resolve-context.ts +3 -1
  413. package/src/time-series/resolve.ts +56 -4
  414. package/src/time-series/session-bar.ts +11 -0
  415. package/src/time-series/spec.ts +3 -2
  416. package/src/time-series/studies.ts +8 -2
  417. package/src/time-series/total-return.ts +160 -0
  418. package/src/time-series/transforms.ts +2 -3
  419. package/src/time-series/types.ts +22 -1
  420. package/src/types/financials.ts +32 -1
  421. package/src/types/headless.ts +21 -3
  422. package/src/types/news-source.ts +1 -0
  423. package/src/types/plugin.ts +9 -0
  424. package/src/ui/host.tsx +3 -0
  425. package/src/updater/failure.ts +4 -1
  426. package/src/utils/cli-output.ts +42 -1
  427. package/src/utils/crypto-pair.ts +44 -0
  428. package/src/utils/forward-dividend.ts +2 -0
  429. package/src/utils/fundamentals.ts +24 -3
  430. package/src/utils/fx-market-hours.ts +26 -3
  431. package/src/utils/main-thread-stall.ts +35 -2
  432. package/src/utils/market-capitalization.ts +24 -1
  433. package/src/utils/price-history.ts +7 -1
  434. package/src/utils/shell-word.ts +8 -0
  435. package/src/utils/trailing-dividend.ts +53 -4
  436. package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
@@ -0,0 +1,146 @@
1
+ import {
2
+ apiClient,
3
+ type ScannerFlowSession,
4
+ type ScannerFlowSessionStrike,
5
+ } from "../../../api-client";
6
+ import { signalWithTimeout } from "../../../utils/async-deadline";
7
+ import { formatFlowPremium } from "./flow-model";
8
+
9
+ /**
10
+ * The session's recorded prints summed by the server under the pane's
11
+ * filters: the net-premium tide through the day and, for one name, its
12
+ * premium by expiry and strike. A print at the ask counts as bought and one at
13
+ * the bid as sold; mid or unknown prints count as neither (docs/usage.md, Options flow).
14
+ */
15
+ export type FlowSessionLoader = (search: string, signal?: AbortSignal) => Promise<ScannerFlowSession>;
16
+
17
+ export const loadFlowSession: FlowSessionLoader = (search, signal) =>
18
+ apiClient.getScannerFlowSession(search, signalWithTimeout(signal, 20_000));
19
+
20
+ /** A summary is refetched this often while the tape is live; it is final once the session closes. */
21
+ export const FLOW_SESSION_LIVE_REFRESH_MS = 60_000;
22
+
23
+ /** One bucket of the tide: what it added, and the running totals through it. */
24
+ export interface FlowTideRow {
25
+ at: number;
26
+ /** Call premium bought at the ask less call premium sold at the bid, through this bucket. */
27
+ netCalls: number;
28
+ netPuts: number;
29
+ /** Net calls less net puts through this bucket: the tide. */
30
+ net: number;
31
+ /** What this bucket added to the tide. */
32
+ change: number;
33
+ callAsk: number;
34
+ callBid: number;
35
+ putAsk: number;
36
+ putBid: number;
37
+ }
38
+
39
+ /** The tide bucket by bucket, oldest first, each row with the running totals through it. */
40
+ export function flowTideRows(session: ScannerFlowSession | null | undefined): FlowTideRow[] {
41
+ let netCalls = 0;
42
+ let netPuts = 0;
43
+ return [...(session?.buckets ?? [])].sort((left, right) => left.at - right.at).map((bucket) => {
44
+ const calls = bucket.callAsk - bucket.callBid;
45
+ const puts = bucket.putAsk - bucket.putBid;
46
+ netCalls += calls;
47
+ netPuts += puts;
48
+ return {
49
+ at: bucket.at,
50
+ netCalls,
51
+ netPuts,
52
+ net: netCalls - netPuts,
53
+ change: calls - puts,
54
+ callAsk: bucket.callAsk,
55
+ callBid: bucket.callBid,
56
+ putAsk: bucket.putAsk,
57
+ putBid: bucket.putBid,
58
+ };
59
+ });
60
+ }
61
+
62
+ /** The buckets that moved the tide: one whose prints were all mid adds nothing to read. */
63
+ export function flowTideMoves(rows: readonly FlowTideRow[]): FlowTideRow[] {
64
+ return rows.filter((row) => row.callAsk !== 0 || row.callBid !== 0 || row.putAsk !== 0 || row.putBid !== 0);
65
+ }
66
+
67
+ /**
68
+ * The running totals at every bucket from the first to the last, so a line
69
+ * holds flat through a quiet stretch instead of sloping across it.
70
+ */
71
+ export function flowTidePoints(rows: readonly FlowTideRow[], bucketMs: number): Array<Pick<FlowTideRow, "at" | "netCalls" | "netPuts" | "net">> {
72
+ const points: Array<Pick<FlowTideRow, "at" | "netCalls" | "netPuts" | "net">> = [];
73
+ let previous: FlowTideRow | null = null;
74
+ for (const row of rows) {
75
+ if (previous && bucketMs > 0) {
76
+ for (let at = previous.at + bucketMs; at < row.at; at += bucketMs) {
77
+ points.push({ at, netCalls: previous.netCalls, netPuts: previous.netPuts, net: previous.net });
78
+ }
79
+ }
80
+ points.push({ at: row.at, netCalls: row.netCalls, netPuts: row.netPuts, net: row.net });
81
+ previous = row;
82
+ }
83
+ return points;
84
+ }
85
+
86
+ export interface FlowStrikeRow extends ScannerFlowSessionStrike {
87
+ id: string;
88
+ /** Net call premium less net put premium at this strike, by the same side rule as the tide. */
89
+ net: number;
90
+ }
91
+
92
+ export function flowStrikeRows(session: ScannerFlowSession | null | undefined): FlowStrikeRow[] {
93
+ return (session?.strikes ?? []).map((strike) => ({
94
+ ...strike,
95
+ id: `${strike.expiry}:${strike.strike}`,
96
+ net: strike.callAsk - strike.callBid - (strike.putAsk - strike.putBid),
97
+ }));
98
+ }
99
+
100
+ export type FlowStrikeSort = "expiry" | "strike" | "callPremium" | "callAsk" | "callBid" | "putPremium" | "putAsk" | "putBid" | "net";
101
+
102
+ /** The breakdown in a column's order; ties fall back to expiry, then strike. */
103
+ export function sortFlowStrikes(
104
+ rows: readonly FlowStrikeRow[],
105
+ column: FlowStrikeSort | null,
106
+ direction: "asc" | "desc",
107
+ ): FlowStrikeRow[] {
108
+ if (!column) return [...rows];
109
+ const sign = direction === "asc" ? 1 : -1;
110
+ const byContract = (left: FlowStrikeRow, right: FlowStrikeRow) =>
111
+ left.expiry.localeCompare(right.expiry) || left.strike - right.strike;
112
+ return [...rows].sort((left, right) => {
113
+ const primary = column === "expiry" ? left.expiry.localeCompare(right.expiry)
114
+ : column === "strike" ? left.strike - right.strike
115
+ : left[column] - right[column];
116
+ return primary * sign || byContract(left, right);
117
+ });
118
+ }
119
+
120
+ /** A premium that can go either way, with its sign: `+$1.2M`, `-$350K`. */
121
+ export function formatSignedFlowPremium(value: number): string {
122
+ if (!Number.isFinite(value) || Math.abs(value) < 0.5) return "$0";
123
+ return `${value > 0 ? "+" : "-"}${formatFlowPremium(Math.abs(value))}`;
124
+ }
125
+
126
+ /** An expiry as the table reads it: `10/30`, with the year when it is not this one. */
127
+ export function formatStrikeExpiry(expiry: string, now = Date.now()): string {
128
+ const [year, month, day] = expiry.split("-");
129
+ if (!year || !month || !day) return expiry;
130
+ return year === String(new Date(now).getFullYear()) ? `${month}/${day}` : `${month}/${day}/${year.slice(2)}`;
131
+ }
132
+
133
+ /** A bucket's start as the local wall clock, `10:35`. */
134
+ export function formatTideTime(at: number): string {
135
+ const date = new Date(at);
136
+ return `${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`;
137
+ }
138
+
139
+ /** The session's date as the chart's legend names it: `Mon 26 Oct`. */
140
+ export function formatFlowSessionDate(session: string | null | undefined): string | null {
141
+ if (!session) return null;
142
+ const date = new Date(`${session}T12:00:00Z`);
143
+ if (Number.isNaN(date.getTime())) return null;
144
+ return date.toLocaleDateString("en-US", { weekday: "short", day: "numeric", month: "short", timeZone: "UTC" })
145
+ .replace(/^(\w+), (\w+) (\d+)$/, "$1 $3 $2");
146
+ }
@@ -1,12 +1,17 @@
1
1
  import { ApiRequestError } from "../../../api-client/errors";
2
2
  import type {
3
+ ScannerFlowEvent,
4
+ ScannerFlowHistoryPage,
3
5
  ScannerFlowPayload,
6
+ ScannerFlowSession,
4
7
  ScannerHiloPayload,
5
8
  ScannerKind,
6
9
  ScannerPayload,
7
10
  } from "../../../api-client";
8
11
  import type {
12
+ HeadlessBundleSection,
9
13
  HeadlessPaneApiClient,
14
+ HeadlessPaneColumn,
10
15
  HeadlessPaneDefinition,
11
16
  HeadlessPaneFreshness,
12
17
  HeadlessPaneLoadArgs,
@@ -17,9 +22,20 @@ import {
17
22
  DEFAULT_FLOW_FILTERS,
18
23
  FLOW_FILTER_OPTIONS,
19
24
  filterFlowEvents,
25
+ flowHistoryQuery,
26
+ flowHistorySearch,
27
+ flowScopeSymbol,
28
+ flowSessionSearch,
29
+ formatFlowPremium,
30
+ formatFlowVolOi,
31
+ mergeFlowRows,
20
32
  type FlowFilters,
33
+ type FlowScope,
21
34
  } from "./flow-model";
22
- import { explainRefusal } from "../shared/cloud-denied";
35
+ import { flowStrikeRows, flowTideMoves, flowTideRows, formatSignedFlowPremium } from "./flow-session";
36
+ import { signalWithTimeout } from "../../../utils/async-deadline";
37
+ import { errorMessage } from "../../../utils/errors";
38
+ import { explainRefusal, refusedLock } from "../shared/cloud-denied";
23
39
  import { captureScannerEvent, SCANNER_SNAPSHOT_TIMEOUT_MS } from "../shared/scanner-capture";
24
40
  import { flowLockNote } from "./lock-note";
25
41
  import { buildHiloBarRows, filterHiloRows, formatHiloPrice, type HiloMinPrice, type HiloSort } from "./hilo-model";
@@ -175,15 +191,121 @@ function flowOption(key: FlowHeadlessFilterKey): HeadlessPaneOptionDef {
175
191
  };
176
192
  }
177
193
 
178
- function projectFlowSnapshot(
179
- payload: ScannerFlowPayload,
194
+ /** The pane's tabs, each a section of the report; left out, the report has every one. */
195
+ type FlowReportTab = "prints" | "tide" | "strikes";
196
+
197
+ /** Recorded prints of one name a report lists under the live ones. */
198
+ const FLOW_REPORT_RECORDED = 100;
199
+
200
+ /** The side rule, said once in every report that sums prints by side. */
201
+ export const FLOW_SIDE_RULE = "Net premium counts a print at the ask as bought and one at the bid as sold; mid or unknown prints count as neither.";
202
+
203
+ const utcTime = (value: unknown) => {
204
+ const time = Number(value);
205
+ return Number.isFinite(time) ? `${new Date(time).toISOString().slice(0, 19).replace("T", " ")} UTC` : "-";
206
+ };
207
+ const signedPremium = (value: unknown) => typeof value === "number" ? formatSignedFlowPremium(value) : "";
208
+ /** A side's premium in the text view: `$2.1M`, blank where none traded. */
209
+ const premium = (value: unknown) => typeof value === "number" && value > 0 ? formatFlowPremium(value) : "";
210
+
211
+ const PRINT_COLUMNS: HeadlessPaneColumn[] = [
212
+ { key: "at", header: "Time", format: utcTime },
213
+ { key: "underlying", header: "Ticker" },
214
+ { key: "right", header: "Right" },
215
+ { key: "kind", header: "Kind" },
216
+ { key: "strike", header: "Strike", align: "right" },
217
+ { key: "expiry", header: "Expiry" },
218
+ { key: "side", header: "Side" },
219
+ { key: "size", header: "Size", align: "right" },
220
+ { key: "premium", header: "Premium", align: "right", textFormat: premium },
221
+ { key: "volOi", header: "Vol/OI", align: "right", textFormat: (value) => formatFlowVolOi(typeof value === "number" ? value : null) },
222
+ ];
223
+
224
+ const TIDE_COLUMNS: HeadlessPaneColumn[] = [
225
+ { key: "at", header: "Time", format: utcTime },
226
+ { key: "callAsk", header: "Call ask", align: "right", description: "Call premium bought at the ask in the bucket." },
227
+ { key: "callBid", header: "Call bid", align: "right", description: "Call premium sold at the bid in the bucket." },
228
+ { key: "putAsk", header: "Put ask", align: "right", description: "Put premium bought at the ask in the bucket." },
229
+ { key: "putBid", header: "Put bid", align: "right", description: "Put premium sold at the bid in the bucket." },
230
+ { key: "netCalls", header: "Net calls", align: "right", textFormat: signedPremium, description: "Call ask less call bid premium through the bucket." },
231
+ { key: "netPuts", header: "Net puts", align: "right", textFormat: signedPremium, description: "Put ask less put bid premium through the bucket." },
232
+ { key: "net", header: "Net premium", align: "right", textFormat: signedPremium, description: "Net calls less net puts through the bucket: the tide." },
233
+ { key: "change", header: "Change", align: "right", textFormat: signedPremium, description: "What the bucket added to the net premium." },
234
+ ];
235
+ const TIDE_TEXT_COLUMNS = TIDE_COLUMNS.filter((column) => !["callAsk", "callBid", "putAsk", "putBid"].includes(column.key));
236
+
237
+ const STRIKE_COLUMNS: HeadlessPaneColumn[] = [
238
+ { key: "expiry", header: "Expiry" },
239
+ { key: "strike", header: "Strike", align: "right" },
240
+ { key: "callPremium", header: "Calls", align: "right", textFormat: premium, description: "All call premium, mid and unknown prints included." },
241
+ { key: "callAsk", header: "Call ask", align: "right", textFormat: premium },
242
+ { key: "callBid", header: "Call bid", align: "right", textFormat: premium },
243
+ { key: "putPremium", header: "Puts", align: "right", textFormat: premium, description: "All put premium, mid and unknown prints included." },
244
+ { key: "putAsk", header: "Put ask", align: "right", textFormat: premium },
245
+ { key: "putBid", header: "Put bid", align: "right", textFormat: premium },
246
+ { key: "net", header: "Net", align: "right", textFormat: signedPremium, description: "Net call premium less net put premium at the strike." },
247
+ { key: "prints", header: "Prints", align: "right" },
248
+ ];
249
+
250
+ interface FlowReportData {
251
+ payload: ScannerFlowPayload;
252
+ /** One name's recorded prints below the live tape; null for the market. */
253
+ recorded: ScannerFlowHistoryPage | null;
254
+ /** The session's prints summed; null when the report leaves the tide and strikes out. */
255
+ session: ScannerFlowSession | null;
256
+ }
257
+
258
+ /** Newest first, the buckets that moved the tide with the running totals through each, as the pane's table lists them. */
259
+ function tideReportRows(session: ScannerFlowSession | null): Array<Record<string, unknown>> {
260
+ return flowTideMoves(flowTideRows(session)).reverse().map((row) => ({ ...row }));
261
+ }
262
+
263
+ function projectFlowReport(
264
+ data: FlowReportData,
180
265
  filters: FlowFilters,
266
+ scope: FlowScope,
267
+ tab: FlowReportTab | null,
181
268
  now = Date.now(),
182
- ): HeadlessSnapshotResult {
183
- const events = filterFlowEvents(payload.events, filters, new Set(), now);
269
+ ): { sections: HeadlessBundleSection[]; asOf: string; metadata: Record<string, unknown>; notes?: string[]; errors?: string[]; freshness: HeadlessPaneFreshness } {
270
+ const { payload, recorded, session } = data;
271
+ const symbol = scope.symbol ?? null;
272
+ const live = filterFlowEvents(payload.events, filters, scope, now);
273
+ const prints: ScannerFlowEvent[] = recorded ? mergeFlowRows(live, recorded.events) : live;
274
+ const shows = (section: FlowReportTab) => !tab || tab === section;
275
+ const sessionDate = session?.session ?? null;
276
+ const sections: HeadlessBundleSection[] = [];
277
+ if (shows("prints")) {
278
+ sections.push({
279
+ title: symbol ? `${symbol} prints` : "Prints",
280
+ columns: PRINT_COLUMNS,
281
+ rows: prints.map((event) => ({ ...event })),
282
+ empty: symbol ? `No ${symbol} prints under these filters.` : "No prints on the tape under these filters.",
283
+ });
284
+ }
285
+ if (shows("tide")) {
286
+ sections.push({
287
+ title: sessionDate ? `Net premium through the ${sessionDate} session` : "Net premium through the session",
288
+ columns: TIDE_COLUMNS,
289
+ textColumns: TIDE_TEXT_COLUMNS,
290
+ rows: tideReportRows(session),
291
+ unit: "USD",
292
+ empty: "No prints under these filters this session.",
293
+ });
294
+ }
295
+ if (symbol && shows("strikes")) {
296
+ sections.push({
297
+ title: sessionDate ? `${symbol} by expiry and strike, ${sessionDate}` : `${symbol} by expiry and strike`,
298
+ columns: STRIKE_COLUMNS,
299
+ rows: flowStrikeRows(session).map(({ id: _id, ...row }) => row),
300
+ unit: "USD",
301
+ empty: `No ${symbol} prints under these filters this session.`,
302
+ });
303
+ }
304
+ const sums = shows("tide") || (!!symbol && shows("strikes"));
184
305
  return {
306
+ sections,
185
307
  asOf: new Date(payload.asOf).toISOString(),
186
- items: events.map((event) => ({ ...event })),
308
+ ...(sums ? { notes: [FLOW_SIDE_RULE] } : {}),
187
309
  ...(payload.status === "degraded" ? { errors: ["Scanner feed degraded"] } : {}),
188
310
  freshness: scannerFreshness(payload),
189
311
  metadata: {
@@ -192,6 +314,8 @@ function projectFlowSnapshot(
192
314
  delayMinutes: payload.delayMinutes,
193
315
  received: payload.events?.length ?? 0,
194
316
  filters,
317
+ ...(symbol ? { symbol } : {}),
318
+ ...(session ? { session: session.session, bucketMinutes: session.bucketMs / 60_000 } : {}),
195
319
  },
196
320
  };
197
321
  }
@@ -202,46 +326,91 @@ interface FlowHeadlessDependencies {
202
326
  client: Pick<HeadlessPaneApiClient, "subscribeScanner">,
203
327
  signal: AbortSignal,
204
328
  ): Promise<ScannerFlowPayload>;
329
+ recorded(search: string, client: Pick<HeadlessPaneApiClient, "getScannerFlowHistory">, signal: AbortSignal): Promise<ScannerFlowHistoryPage>;
330
+ session(search: string, client: Pick<HeadlessPaneApiClient, "getScannerFlowSession">, signal: AbortSignal): Promise<ScannerFlowSession>;
205
331
  }
206
332
 
207
- function createFlowHeadless(
208
- dependencies: FlowHeadlessDependencies = {
209
- load: (_args, client, signal) => loadScannerSnapshot<ScannerFlowPayload>(client, "flow", signal),
210
- },
211
- ): HeadlessPaneDefinition<"snapshot"> {
333
+ const flowDependencies: FlowHeadlessDependencies = {
334
+ load: (_args, client, signal) => loadScannerSnapshot<ScannerFlowPayload>(client, "flow", signal),
335
+ recorded: (search, client, signal) => client.getScannerFlowHistory(search, signalWithTimeout(signal, 20_000)),
336
+ session: (search, client, signal) => client.getScannerFlowSession(search, signalWithTimeout(signal, 20_000)),
337
+ };
338
+
339
+ export function createFlowHeadless(
340
+ dependencies: FlowHeadlessDependencies = flowDependencies,
341
+ ): HeadlessPaneDefinition<"bundle"> {
212
342
  const keys: FlowHeadlessFilterKey[] = ["minPremium", "side", "kind", "volOi", "expiry"];
213
343
  return {
214
- shape: "snapshot",
215
- argument: { kind: "none" },
216
- options: keys.map(flowOption),
217
- columns: [
344
+ shape: "bundle",
345
+ argument: {
346
+ kind: "ticker",
347
+ optional: true,
348
+ placeholder: "ticker",
349
+ description: "Optional ticker: its prints and its premium by expiry and strike. Omit for the whole market.",
350
+ },
351
+ options: [
352
+ ...keys.map(flowOption),
218
353
  {
219
- key: "at",
220
- header: "Time",
221
- format: (value: unknown) => {
222
- const time = Number(value);
223
- return Number.isFinite(time) ? `${new Date(time).toISOString().slice(0, 19).replace("T", " ")} UTC` : "-";
224
- },
354
+ key: "tab",
355
+ settingKey: "tab",
356
+ description: "One section: the prints, the net-premium tide through the session, or a ticker's strikes. Every section when left out.",
357
+ type: "enum",
358
+ values: [{ value: "prints" }, { value: "tide" }, { value: "strikes" }],
225
359
  },
226
- { key: "underlying", header: "Ticker" },
227
- { key: "right", header: "Right" },
228
- { key: "kind", header: "Kind" },
229
- { key: "strike", header: "Strike", align: "right" },
230
- { key: "expiry", header: "Expiry" },
231
- { key: "side", header: "Side" },
232
- { key: "size", header: "Size", align: "right" },
233
- { key: "premium", header: "Premium", align: "right" },
234
- { key: "volOi", header: "Vol/OI", align: "right" },
235
360
  ],
236
- describe: "Options Flow",
361
+ describe: (args) => {
362
+ const symbol = flowScopeSymbol(Array.isArray(args.argument) ? args.argument[0] : args.argument);
363
+ return symbol ? `Options Flow: ${symbol}` : "Options Flow";
364
+ },
237
365
  async load(args, ctx) {
238
- const payload = await dependencies.load(args, ctx.apiClient, ctx.signal)
239
- .catch(async (error: unknown) => { throw await explainRefusal(error, flowLockNote, { symbol: null }); });
240
- return projectFlowSnapshot(payload, {
366
+ const symbol = flowScopeSymbol(Array.isArray(args.argument) ? args.argument[0] : args.argument);
367
+ const tab = typeof args.options.tab === "string" && args.options.tab ? args.options.tab as FlowReportTab : null;
368
+ if (tab === "strikes" && !symbol) throw new Error("--tab strikes needs a ticker: gloom fn FLOW <ticker> --tab strikes.");
369
+ const filters: FlowFilters = {
241
370
  ...DEFAULT_FLOW_FILTERS,
242
371
  ...(args.options as Partial<FlowFilters>),
243
372
  universe: "active",
244
- });
373
+ };
374
+ const scope: FlowScope = { symbol, watchlist: new Set() };
375
+ const refuse = async (error: unknown): Promise<never> => {
376
+ throw await explainRefusal(error, flowLockNote, { symbol, context: ctx });
377
+ };
378
+ // The live feed answers the entitlement first, as the pane's does.
379
+ const payload = await dependencies.load(args, ctx.apiClient, ctx.signal).catch(refuse);
380
+ // The recorded prints and the session's sums add to the tape: one that
381
+ // fails (a timeout, a server without the route) is said under errors and
382
+ // the rest of the report stands. A wall still refuses the whole read.
383
+ const failures: string[] = [];
384
+ const extra = async <T>(load: Promise<T> | null, what: string): Promise<T | null> => {
385
+ if (!load) return null;
386
+ try {
387
+ return await load;
388
+ } catch (error) {
389
+ if (ctx.signal.aborted || refusedLock(error)) return refuse(error);
390
+ failures.push(`${what} unavailable: ${errorMessage(error)}`);
391
+ return null;
392
+ }
393
+ };
394
+ const wantsPrints = !tab || tab === "prints";
395
+ const wantsSession = !tab || tab === "tide" || tab === "strikes";
396
+ const [recorded, session] = await Promise.all([
397
+ extra(symbol && wantsPrints
398
+ ? dependencies.recorded(flowHistorySearch(flowHistoryQuery(filters, scope, undefined, FLOW_REPORT_RECORDED)), ctx.apiClient, ctx.signal)
399
+ : null, "Recorded prints"),
400
+ extra(wantsSession ? dependencies.session(flowSessionSearch(filters, scope), ctx.apiClient, ctx.signal) : null, "Session totals"),
401
+ ]);
402
+ // Asked for the session's sums alone, a failure to read them is the report's.
403
+ if (!wantsPrints && failures.length > 0) throw new Error(failures[0]);
404
+ const report = projectFlowReport({ payload, recorded, session }, filters, scope, wantsSession && !session ? "prints" : tab);
405
+ const errors = [...report.errors ?? [], ...failures];
406
+ return {
407
+ sections: report.sections,
408
+ ...(report.notes ? { notes: report.notes } : {}),
409
+ ...(errors.length > 0 ? { errors } : {}),
410
+ ...(symbol ? { symbols: [symbol] } : {}),
411
+ freshness: report.freshness,
412
+ metadata: { asOf: report.asOf, ...report.metadata },
413
+ };
245
414
  },
246
415
  };
247
416
  }
@@ -1,6 +1,6 @@
1
1
  import type { PaneSettingsDef } from "../../../types/plugin";
2
2
  import type { PluginModule } from "../plugin-module";
3
- import { DEFAULT_FLOW_FILTERS, FLOW_FILTER_OPTIONS } from "./flow-model";
3
+ import { DEFAULT_FLOW_FILTERS, FLOW_FILTER_OPTIONS, flowScopeSymbol } from "./flow-model";
4
4
  import FlowPane from "./flow-pane";
5
5
  import HiloPane from "./hilo-pane";
6
6
  import LiveScanPane from "./live-pane";
@@ -123,11 +123,22 @@ export const scannerModule: PluginModule = {
123
123
  id: "scanner-flow-pane",
124
124
  paneId: FLOW_PANE_ID,
125
125
  label: "Options Flow",
126
- description: "Unusual options activity: sweeps, blocks, and large premium prints.",
127
- keywords: ["flow", "options", "sweep", "block", "unusual", "premium", "scanner"],
128
- shortcut: { prefix: "FLOW" },
126
+ description: "Unusual options activity: sweeps, blocks, large premium prints, and the net premium tide.",
127
+ keywords: ["flow", "options", "sweep", "block", "unusual", "premium", "scanner", "tide", "net premium", "market tide"],
128
+ shortcut: { prefix: "FLOW", argKind: "ticker", argPlaceholder: "ticker", argOptional: true, openWithoutArg: true },
129
129
  headless: flowHeadless,
130
- createInstance: () => ({ settings: { ...DEFAULT_FLOW_FILTERS } }),
130
+ // One window per name, and one for the whole market.
131
+ createInstance: (_context, options) => {
132
+ const symbol = flowScopeSymbol(options?.symbol ?? options?.arg);
133
+ return symbol
134
+ ? {
135
+ instanceId: `${FLOW_PANE_ID}:${symbol}`,
136
+ title: `FLOW ${symbol}`,
137
+ binding: { kind: "fixed" as const, symbol },
138
+ settings: { ...DEFAULT_FLOW_FILTERS, symbol },
139
+ }
140
+ : { settings: { ...DEFAULT_FLOW_FILTERS } };
141
+ },
131
142
  },
132
143
  {
133
144
  id: LIVE_SCAN_TEMPLATE_ID,
@@ -4,7 +4,18 @@ import type {
4
4
  HeadlessPaneFreshness,
5
5
  HeadlessSnapshotResult,
6
6
  } from "../../../types/plugin";
7
- import { conditionColumnLabel, conditionText, DEFAULT_LIVE_SCAN, LIVE_SCAN_SECTORS, parseUniverseValue, readLiveScan, type LiveScan } from "./live-model";
7
+ import { formatPercentRaw } from "../../../utils/format";
8
+ import {
9
+ conditionColumnLabel,
10
+ conditionText,
11
+ DEFAULT_LIVE_SCAN,
12
+ formatMultiple,
13
+ LIVE_SCAN_SECTORS,
14
+ parseUniverseValue,
15
+ readLiveScan,
16
+ warmingWindowsText,
17
+ type LiveScan,
18
+ } from "./live-model";
8
19
  import { LIVE_SCAN_INDEXES } from "../../../api-client/live-scan";
9
20
 
10
21
  /** `all`, an index (`sp500`) or a sector (`Energy`), as `--universe` takes it. */
@@ -39,9 +50,21 @@ function freshness(payload: LiveScanPayload): HeadlessPaneFreshness {
39
50
  return payload.access === "realtime" ? { status: "live" } : { status: "delayed", delayMinutes: payload.delayMinutes };
40
51
  }
41
52
 
53
+ /** Why a scan lists nothing, as the pane says it: still starting, warming up, closed, or no match. */
54
+ function emptyText(payload: LiveScanPayload): string {
55
+ if (payload.status === "starting") return "The scanner is starting. Matches appear within a minute.";
56
+ if (payload.status === "closed") return "Nothing matched at the close. The scan runs again at the open.";
57
+ const warming = warmingWindowsText(payload);
58
+ if (warming) return `The scanner is warming up: conditions over ${warming} match nothing until that much of the session has been watched.`;
59
+ return "Nothing matches right now.";
60
+ }
61
+
62
+ const number = (value: unknown) => (typeof value === "number" ? value : undefined);
63
+
42
64
  function projectLiveScan(payload: LiveScanPayload, scan: LiveScan): HeadlessSnapshotResult {
43
65
  return {
44
66
  asOf: new Date(payload.asOf ?? payload.takenAt ?? Date.now()).toISOString(),
67
+ empty: emptyText(payload),
45
68
  items: payload.matches.map((match) => ({
46
69
  symbol: match.symbol,
47
70
  exchange: match.exchange ?? null,
@@ -64,6 +87,7 @@ function projectLiveScan(payload: LiveScanPayload, scan: LiveScan): HeadlessSnap
64
87
  scanned: payload.scanned,
65
88
  matched: payload.matched,
66
89
  conditions: scan.conditions.map(conditionText),
90
+ ...(payload.warmingWindows?.length ? { warmingWindows: payload.warmingWindows } : {}),
67
91
  },
68
92
  };
69
93
  }
@@ -94,8 +118,8 @@ export const liveScanHeadless: HeadlessPaneDefinition<"snapshot"> = {
94
118
  { key: "symbol", header: "Symbol" },
95
119
  { key: "name", header: "Name" },
96
120
  { key: "price", header: "Price", align: "right" },
97
- { key: "changePercent", header: "Chg %", align: "right" },
98
- { key: "relativeVolume", header: "RVol", align: "right" },
121
+ { key: "changePercent", header: "Chg %", align: "right", format: (value) => formatPercentRaw(number(value)) },
122
+ { key: "relativeVolume", header: "RVol", align: "right", format: (value) => formatMultiple(number(value)) },
99
123
  { key: "volume", header: "Volume", align: "right" },
100
124
  ],
101
125
  describe: "Live Scanner",
@@ -5,6 +5,7 @@ import {
5
5
  type LiveScanDirection,
6
6
  type LiveScanIndex,
7
7
  type LiveScanMatch,
8
+ type LiveScanPayload,
8
9
  type LiveScanWindow,
9
10
  type LiveScanWireDefinition,
10
11
  } from "../../../api-client/live-scan";
@@ -208,6 +209,16 @@ export function formatMultiple(value: number | null | undefined): string {
208
209
  return value >= 10 ? `${Math.round(value)}x` : `${value.toFixed(1)}x`;
209
210
  }
210
211
 
212
+ /**
213
+ * The windows a running scan's conditions cannot measure yet (the scanner
214
+ * started recently), as a list to read: `5 min, 15 min`. Empty when there are
215
+ * none, or when the scanner is not running a session.
216
+ */
217
+ export function warmingWindowsText(payload: LiveScanPayload | null): string {
218
+ const windows = payload?.status === "live" || payload?.status === "degraded" ? payload.warmingWindows : [];
219
+ return (windows ?? []).map((window) => `${window} min`).join(", ");
220
+ }
221
+
211
222
  /** A time of day the scanner saw something, to the second. */
212
223
  export function formatScanTime(at: number | null | undefined): string {
213
224
  if (at == null || !Number.isFinite(at)) return "--";
@@ -60,6 +60,7 @@ import {
60
60
  universeLabel,
61
61
  universeOptions,
62
62
  universeValue,
63
+ warmingWindowsText,
63
64
  type LiveScan,
64
65
  type SavedLiveScan,
65
66
  } from "./live-model";
@@ -194,9 +195,8 @@ function emptyState(payload: LiveScanPayload | null): { title: string; hint?: st
194
195
  }
195
196
 
196
197
  function warmingNotice(payload: LiveScanPayload | null): string | null {
197
- const windows = payload?.status === "live" || payload?.status === "degraded" ? payload.warmingWindows : [];
198
- if (!windows?.length) return null;
199
- const list = windows.map((window) => `${window} min`).join(", ");
198
+ const list = warmingWindowsText(payload);
199
+ if (!list) return null;
200
200
  return `The scanner started recently: conditions over ${list} match nothing until that much of the session has been watched.`;
201
201
  }
202
202
 
@@ -5,12 +5,13 @@ import { proLockNote } from "../shared/pro-lock-note";
5
5
  * FLOW is Pro whole: the one scanner with no delayed tier to fall back to.
6
6
  * The options chain needs no account and lists each contract's day volume and
7
7
  * open interest, the raw counts behind a sweep or a block, without the prints.
8
- * FLOW takes no ticker, so the route names none to check.
8
+ * FLOW takes a ticker only to narrow the market to one name, so the note
9
+ * repeats it only when the reader gave one.
9
10
  */
10
11
  export function flowLockNote(lock: PaneLock): Promise<string> {
11
12
  return proLockNote(lock, {
12
13
  token: "FLOW",
13
- argument: "none",
14
+ argument: "optional",
14
15
  does: "it streams real-time sweeps, blocks and large premium prints across US options exchanges",
15
16
  shows: "it",
16
17
  free: [{ text: (ticker, company) => `gloom options ${ticker} lists ${company}'s option chain, each contract with its volume and open interest.` }],
@@ -1,6 +1,7 @@
1
1
  import type { HeadlessPaneDefinition } from "../../../types/plugin";
2
2
  import { resolveHeadlessInstrument } from "../shared/headless-market-data";
3
3
  import { loadSeasonalityHistory } from "./client";
4
+ import { closedHistoryBarDay, formingHistoryBar } from "../ticker-detail/data-panes/history-bars";
4
5
  import { MONTH_LABELS, projectSeasonality, projectWeekdays, TURN_OF_MONTH_LABELS } from "./model";
5
6
 
6
7
  const percent = (digits: number) => (value: unknown) => typeof value === "number" ? `${(value * 100).toFixed(digits)}%` : "--";
@@ -9,7 +10,7 @@ const statColumns = (digits: number, countHeader: string) => [{ key: "mean", hea
9
10
 
10
11
  export const seasonalityHeadless: HeadlessPaneDefinition<"bundle"> = {
11
12
  shape: "bundle", argument: { kind: "ticker", description: "Ticker" },
12
- // Dated by the month of its last monthly close.
13
+ // Dated by its last monthly close, or by how far the running month's bar runs (`month to date, through Fri 23 Oct 2026`).
13
14
  freshness: { status: "not-a-feed", basis: "monthly closes", maxAgeMinutes: 45 * 24 * 60 },
14
15
  describe: (args) => `SEAS ${args.symbols[0] ?? ""}`,
15
16
  discovery: { screenshotReadiness: "live-dom", limitations: ["Local-price closes; dividends and FX are excluded.",
@@ -46,6 +47,13 @@ export const seasonalityHeadless: HeadlessPaneDefinition<"bundle"> = {
46
47
  };
47
48
  }
48
49
  const model = projectSeasonality(history.history, { symbol: instrument.symbol, lookbackYears: Number(args.options.lookbackYears) || 10 });
50
+ // Monthly bars are dated by their month's first day, which is no close while the month runs.
51
+ const bar = model.asOf?.toISOString().slice(0, 10);
52
+ // The venue whose sessions say whether the month still runs: the key's, or for a bare symbol its quote's.
53
+ const quote = bar && !instrument.exchange ? await ctx.marketData.getQuote(instrument.symbol, "").catch(() => null) : null;
54
+ const venue = instrument.exchange || quote?.listingExchangeName || quote?.exchangeName || undefined;
55
+ const forming = formingHistoryBar("1mo", bar, venue, Date.now());
56
+ const closed = forming ? null : closedHistoryBarDay("1mo", bar, venue, Date.now());
49
57
  const monthColumns = MONTH_LABELS.map((label, month) => ({ key: `m${month}`, header: label, format: percent(1) }));
50
58
  return {
51
59
  sections: [
@@ -57,6 +65,7 @@ export const seasonalityHeadless: HeadlessPaneDefinition<"bundle"> = {
57
65
  complete: !history.stale && !history.error && model.years.length > 0,
58
66
  unavailableSymbols: model.years.length ? [] : [instrument.symbol],
59
67
  ...common,
68
+ ...(forming ? { freshness: { periodToDate: forming } } : closed ? { freshness: { asOf: closed } } : {}),
60
69
  metadata: { unit: "decimal return, local-price close to close", asOf: model.asOf?.toISOString() ?? null, stale: history.stale,
61
70
  fetchedAt: history.fetchedAt, averagePath: model.averagePath, methodology: "docs/research-data.md#seasonality" },
62
71
  };