@gloom-sh/terminal 0.18.5 → 0.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (570) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/package.json +1 -1
  4. package/patches/electrobun@1.18.1.patch +163 -8
  5. package/src/api-client/app-attention.ts +3 -0
  6. package/src/api-client/beneficial-owners.ts +3 -0
  7. package/src/api-client/company-kpis.ts +3 -0
  8. package/src/api-client/cot.ts +6 -2
  9. package/src/api-client/credit-documents.ts +6 -0
  10. package/src/api-client/data.ts +19 -7
  11. package/src/api-client/equity-screener.ts +24 -0
  12. package/src/api-client/fund-facts.ts +45 -4
  13. package/src/api-client/hiring.ts +3 -0
  14. package/src/api-client/index.ts +15 -0
  15. package/src/api-client/ipo.ts +24 -1
  16. package/src/api-client/market-discovery.ts +10 -0
  17. package/src/api-client/market-movers.ts +7 -0
  18. package/src/api-client/members.ts +1 -1
  19. package/src/api-client/mna.ts +3 -0
  20. package/src/api-client/paths.ts +9 -1
  21. package/src/api-client/perps.ts +43 -0
  22. package/src/api-client/research-activity.ts +3 -2
  23. package/src/api-client/roadmap.ts +85 -0
  24. package/src/api-client/types.ts +82 -2
  25. package/src/app/pane-runtime/plugin-bindings.ts +9 -5
  26. package/src/app/runtime/broker-import.ts +0 -2
  27. package/src/app/runtime/plugin-bindings.ts +73 -56
  28. package/src/architecture/core-builtin-allowlist.json +2 -2
  29. package/src/architecture/pane-tabs-allowlist.json +3 -0
  30. package/src/architecture/ttl-caches-allowlist.json +19 -0
  31. package/src/brokers/sync-broker-instance.ts +2 -8
  32. package/src/capabilities/chart-series.ts +1 -1
  33. package/src/cli/commands/crypto-hints.ts +40 -24
  34. package/src/cli/commands/market.ts +107 -38
  35. package/src/cli/commands/news-search.ts +22 -0
  36. package/src/cli/commands/options-chain.ts +48 -7
  37. package/src/cli/commands/options-leaps.ts +9 -3
  38. package/src/cli/commands/overview.ts +147 -32
  39. package/src/cli/commands/search.ts +21 -1
  40. package/src/cli/commands/themes.ts +11 -2
  41. package/src/cli/commands/ticker.ts +87 -36
  42. package/src/cli/company-name-hint.ts +41 -0
  43. package/src/cli/earnings-calendar.ts +1 -1
  44. package/src/cli/errors.ts +25 -2
  45. package/src/cli/glossary.ts +31 -2
  46. package/src/cli/history-rows.ts +57 -9
  47. package/src/cli/index-roots.ts +0 -7
  48. package/src/cli/index.ts +2 -0
  49. package/src/cli/listing-arg.ts +235 -60
  50. package/src/cli/output-file.ts +68 -0
  51. package/src/cli/pane-functions/capabilities.ts +4 -0
  52. package/src/cli/pane-functions/catalog.ts +80 -7
  53. package/src/cli/pane-functions/discovery.ts +21 -1
  54. package/src/cli/pane-functions/dom.ts +13 -3
  55. package/src/cli/pane-functions/freshness.ts +93 -4
  56. package/src/cli/pane-functions/function-help.ts +20 -10
  57. package/src/cli/pane-functions/headless.ts +17 -7
  58. package/src/cli/pane-functions/index.ts +54 -16
  59. package/src/cli/pane-functions/market-session.ts +66 -4
  60. package/src/cli/pane-functions/options.ts +19 -1
  61. package/src/cli/pane-functions/resolver.ts +19 -8
  62. package/src/cli/pane-functions/screenshot-assessment.ts +6 -4
  63. package/src/cli/pane-functions/screenshot-types.ts +5 -0
  64. package/src/cli/quote-answer.ts +22 -11
  65. package/src/cli/saved-copy-persistence.ts +88 -0
  66. package/src/cli/ticker-resolution.ts +3 -2
  67. package/src/components/chart/composite/composite-chart.tsx +4 -0
  68. package/src/components/chart/composite/interactions.ts +6 -0
  69. package/src/components/chart/composite/legend.tsx +45 -7
  70. package/src/components/chart/composite/panel-gestures.ts +33 -0
  71. package/src/components/chart/composite/panel-surface.tsx +52 -3
  72. package/src/components/chart/composite/rasterizer.ts +24 -13
  73. package/src/components/chart/composite/scene.ts +39 -4
  74. package/src/components/chart/composite/series-cues.ts +14 -0
  75. package/src/components/chart/composite/text-renderer.ts +9 -2
  76. package/src/components/chart/composite/tool-catalog.ts +36 -0
  77. package/src/components/chart/composite/tools.ts +203 -44
  78. package/src/components/chart/composite/use-composite-chart-keyboard.ts +11 -4
  79. package/src/components/command-bar/assist/inventory.ts +2 -1
  80. package/src/components/command-bar/commands/direct/index.ts +28 -13
  81. package/src/components/command-bar/list/model.ts +5 -0
  82. package/src/components/command-bar/pane-templates/items.ts +3 -2
  83. package/src/components/command-bar/routes/root/runtime.ts +6 -7
  84. package/src/components/command-bar/selection-runtime.ts +52 -3
  85. package/src/components/command-bar/surface/index.tsx +2 -0
  86. package/src/components/command-bar/workflow/collection-actions.ts +3 -3
  87. package/src/components/layout/pane/capture-stamp.ts +2 -2
  88. package/src/components/layout/pane/footer/index.tsx +22 -6
  89. package/src/components/layout/pane/footer/model.ts +83 -15
  90. package/src/components/layout/pane/status-footer.ts +35 -2
  91. package/src/components/layout/status-widget-space.ts +9 -0
  92. package/src/components/onboarding/onboarding-wizard.tsx +4 -1
  93. package/src/components/onboarding/portfolio-step/positions-panel.tsx +12 -7
  94. package/src/components/onboarding/wizard-broker-sync.ts +0 -1
  95. package/src/components/onboarding/wizard-positions.ts +3 -3
  96. package/src/components/ticker/list-table-view.tsx +8 -21
  97. package/src/components/ui/choice-dialog.tsx +23 -18
  98. package/src/components/ui/choice-groups.ts +18 -0
  99. package/src/components/ui/data-table/opentui/index.tsx +9 -7
  100. package/src/components/ui/data-table/opentui/model.ts +26 -1
  101. package/src/components/ui/data-table/types.ts +2 -0
  102. package/src/components/ui/display.tsx +18 -3
  103. package/src/components/ui/icon.tsx +4 -1
  104. package/src/components/ui/list-view.tsx +8 -4
  105. package/src/components/ui/query-bar-keys.tsx +2 -0
  106. package/src/components/ui/query-bar.tsx +1 -0
  107. package/src/components/ui/select-button.tsx +5 -1
  108. package/src/components/ui/select-field.tsx +1 -0
  109. package/src/components/ui/table-layout.ts +8 -5
  110. package/src/core/app-runtime.ts +2 -0
  111. package/src/core/app-services.ts +2 -2
  112. package/src/data/config/chart-settings.ts +1 -1
  113. package/src/data/json-ticker-repository.ts +17 -8
  114. package/src/data/plugin-cache.ts +5 -3
  115. package/src/data/saved-copies.ts +8 -0
  116. package/src/data/ticker-repository.ts +16 -9
  117. package/src/data/ticker-writes.ts +81 -0
  118. package/src/data/ttl-cache.ts +165 -0
  119. package/src/docs/snapshot.json +103 -142
  120. package/src/i18n/es.ts +20 -6
  121. package/src/i18n/ja.ts +20 -6
  122. package/src/i18n/ko.ts +20 -6
  123. package/src/i18n/zh-cn.ts +20 -6
  124. package/src/i18n/zh-tw.ts +20 -6
  125. package/src/market-data/depositary-pair.ts +27 -3
  126. package/src/market-data/fund-facts.ts +50 -0
  127. package/src/market-data/market/change-basis.ts +16 -3
  128. package/src/market-data/market/format.ts +25 -0
  129. package/src/market-data/market/freshness.ts +2 -1
  130. package/src/market-data/options-alternatives.ts +81 -4
  131. package/src/market-data/quotes/metadata.ts +7 -0
  132. package/src/news/aggregator.ts +119 -9
  133. package/src/news/cloud-session.ts +21 -0
  134. package/src/news/hooks.ts +1 -0
  135. package/src/news/news-model.ts +1 -0
  136. package/src/news/types.ts +17 -0
  137. package/src/plugins/builtin/alerts/alert-engine.ts +20 -1
  138. package/src/plugins/builtin/alerts/alert-log.ts +33 -0
  139. package/src/plugins/builtin/alerts/calendar-events.ts +10 -4
  140. package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
  141. package/src/plugins/builtin/alerts/cli.ts +362 -80
  142. package/src/plugins/builtin/alerts/command.ts +25 -1
  143. package/src/plugins/builtin/alerts/event-rows.ts +24 -3
  144. package/src/plugins/builtin/alerts/events-pane.tsx +34 -10
  145. package/src/plugins/builtin/alerts/fired-log.ts +73 -9
  146. package/src/plugins/builtin/alerts/format.ts +75 -5
  147. package/src/plugins/builtin/alerts/headless.ts +34 -12
  148. package/src/plugins/builtin/alerts/history-rows.ts +3 -2
  149. package/src/plugins/builtin/alerts/history.ts +1 -1
  150. package/src/plugins/builtin/alerts/index.tsx +79 -16
  151. package/src/plugins/builtin/alerts/metric-command.ts +77 -4
  152. package/src/plugins/builtin/alerts/metric-data.ts +54 -13
  153. package/src/plugins/builtin/alerts/metric-model.ts +163 -14
  154. package/src/plugins/builtin/alerts/metric-parity-fixture.json +1 -1
  155. package/src/plugins/builtin/alerts/pane.tsx +61 -23
  156. package/src/plugins/builtin/alerts/quotes.ts +6 -0
  157. package/src/plugins/builtin/alerts/storage.ts +12 -8
  158. package/src/plugins/builtin/alerts/types.ts +4 -0
  159. package/src/plugins/builtin/analytics/index.tsx +2 -1
  160. package/src/plugins/builtin/analytics/risk-client.ts +16 -5
  161. package/src/plugins/builtin/analytics/risk-fx.ts +70 -15
  162. package/src/plugins/builtin/analytics/risk-headless.ts +27 -0
  163. package/src/plugins/builtin/analytics/risk-model.ts +55 -9
  164. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
  165. package/src/plugins/builtin/attention/index.tsx +2 -1
  166. package/src/plugins/builtin/attention/listing.ts +14 -0
  167. package/src/plugins/builtin/broker-manager/index.tsx +7 -3
  168. package/src/plugins/builtin/buildout/cells.tsx +12 -4
  169. package/src/plugins/builtin/buildout/format.ts +38 -0
  170. package/src/plugins/builtin/buildout/model/normalizers.ts +36 -1
  171. package/src/plugins/builtin/buildout/pane/index.tsx +4 -1
  172. package/src/plugins/builtin/buildout/table-model.ts +23 -2
  173. package/src/plugins/builtin/builtin-plugin-meta.ts +6 -2
  174. package/src/plugins/builtin/cds/client.ts +142 -30
  175. package/src/plugins/builtin/cds/headless.ts +35 -8
  176. package/src/plugins/builtin/cds/model.ts +145 -18
  177. package/src/plugins/builtin/cds/pane.tsx +16 -5
  178. package/src/plugins/builtin/changelog/index.tsx +9 -6
  179. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +3 -0
  180. package/src/plugins/builtin/chart-composer/cli-options.ts +4 -0
  181. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +7 -0
  182. package/src/plugins/builtin/chart-composer/editor/controller.ts +2 -2
  183. package/src/plugins/builtin/chart-composer/follow-binding.ts +14 -2
  184. package/src/plugins/builtin/chart-composer/headless-schema.ts +7 -1
  185. package/src/plugins/builtin/chart-composer/headless.ts +6 -1
  186. package/src/plugins/builtin/chart-composer/index.tsx +5 -6
  187. package/src/plugins/builtin/chart-composer/pane.tsx +37 -8
  188. package/src/plugins/builtin/chart-composer/settings.ts +27 -0
  189. package/src/plugins/builtin/chat/content/command-suggestions.ts +1 -0
  190. package/src/plugins/builtin/chat/controller/index.ts +5 -1
  191. package/src/plugins/builtin/chat/controller/notifications.ts +33 -3
  192. package/src/plugins/builtin/chat/controller/state.ts +25 -2
  193. package/src/plugins/builtin/chat/controller/unread-people.ts +89 -23
  194. package/src/plugins/builtin/chat/controller/utils.ts +7 -2
  195. package/src/plugins/builtin/chat/pane-links/commands.ts +7 -1
  196. package/src/plugins/builtin/chat/status-preview.ts +79 -0
  197. package/src/plugins/builtin/chat/status-widget.tsx +147 -10
  198. package/src/plugins/builtin/chat/unread-badge.ts +51 -10
  199. package/src/plugins/builtin/clinical-trials/client.ts +49 -4
  200. package/src/plugins/builtin/clinical-trials/model.ts +86 -31
  201. package/src/plugins/builtin/clinical-trials/pane.tsx +61 -49
  202. package/src/plugins/builtin/clinical-trials/sponsor-listing.ts +90 -0
  203. package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +95 -15
  204. package/src/plugins/builtin/clinical-trials/trial-pages.ts +50 -0
  205. package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
  206. package/src/plugins/builtin/company-attention/building.tsx +5 -1
  207. package/src/plugins/builtin/company-attention/client.ts +8 -9
  208. package/src/plugins/builtin/company-attention/evidence.ts +4 -1
  209. package/src/plugins/builtin/company-attention/headless.ts +2 -1
  210. package/src/plugins/builtin/company-attention/moves.tsx +5 -1
  211. package/src/plugins/builtin/company-attention/pane.tsx +12 -2
  212. package/src/plugins/builtin/company-attention/signals.tsx +5 -1
  213. package/src/plugins/builtin/company-kpis/headless.ts +8 -3
  214. package/src/plugins/builtin/company-kpis/model.ts +73 -5
  215. package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
  216. package/src/plugins/builtin/composite-plugins.ts +2 -1
  217. package/src/plugins/builtin/correlation/headless.ts +13 -5
  218. package/src/plugins/builtin/correlation/history.ts +22 -0
  219. package/src/plugins/builtin/cot/client.ts +22 -18
  220. package/src/plugins/builtin/cot/headless.ts +15 -10
  221. package/src/plugins/builtin/cot/model.ts +14 -1
  222. package/src/plugins/builtin/cot/pane.tsx +22 -14
  223. package/src/plugins/builtin/credit-boards/client.ts +6 -10
  224. package/src/plugins/builtin/credit-conditions/headless.ts +8 -2
  225. package/src/plugins/builtin/credit-conditions/index.tsx +5 -1
  226. package/src/plugins/builtin/credit-conditions/model.ts +4 -0
  227. package/src/plugins/builtin/credit-conditions/series.ts +3 -1
  228. package/src/plugins/builtin/credit-documents/detail.tsx +2 -2
  229. package/src/plugins/builtin/credit-documents/export.ts +173 -0
  230. package/src/plugins/builtin/credit-documents/headless.ts +10 -10
  231. package/src/plugins/builtin/credit-documents/model.ts +104 -14
  232. package/src/plugins/builtin/credit-documents/pane.tsx +12 -5
  233. package/src/plugins/builtin/custom-view/sources.ts +2 -0
  234. package/src/plugins/builtin/dividend-yield/client.ts +131 -23
  235. package/src/plugins/builtin/dividend-yield/headless.ts +44 -16
  236. package/src/plugins/builtin/dividend-yield/model.ts +8 -5
  237. package/src/plugins/builtin/dividend-yield/pane.tsx +38 -13
  238. package/src/plugins/builtin/dividend-yield/ranking.ts +37 -14
  239. package/src/plugins/builtin/dividend-yield/types.ts +20 -2
  240. package/src/plugins/builtin/dividend-yield/view.ts +30 -2
  241. package/src/plugins/builtin/docs/targets.ts +5 -1
  242. package/src/plugins/builtin/doe/headless.ts +3 -2
  243. package/src/plugins/builtin/doe/pane.tsx +13 -3
  244. package/src/plugins/builtin/earnings/board-model.ts +16 -15
  245. package/src/plugins/builtin/earnings/board.tsx +62 -71
  246. package/src/plugins/builtin/earnings/headless.ts +40 -4
  247. package/src/plugins/builtin/earnings/scope.ts +31 -43
  248. package/src/plugins/builtin/earnings-calls/calendar.tsx +2 -2
  249. package/src/plugins/builtin/earnings-calls/data.ts +18 -6
  250. package/src/plugins/builtin/earnings-calls/format.ts +31 -2
  251. package/src/plugins/builtin/earnings-calls/headless.ts +20 -5
  252. package/src/plugins/builtin/earnings-calls/model.ts +6 -1
  253. package/src/plugins/builtin/earnings-calls/pane.tsx +12 -4
  254. package/src/plugins/builtin/econ/calendar-model.ts +48 -1
  255. package/src/plugins/builtin/econ/calendar-source.ts +4 -2
  256. package/src/plugins/builtin/econ/detail-view.tsx +2 -2
  257. package/src/plugins/builtin/econ/figures.ts +48 -0
  258. package/src/plugins/builtin/econ/index.tsx +14 -5
  259. package/src/plugins/builtin/econ/print-watch.ts +3 -1
  260. package/src/plugins/builtin/econ/types.ts +2 -0
  261. package/src/plugins/builtin/econ-statistics/client.ts +11 -0
  262. package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
  263. package/src/plugins/builtin/econ-statistics/headless.ts +16 -6
  264. package/src/plugins/builtin/econ-statistics/pane.tsx +8 -4
  265. package/src/plugins/builtin/econ-statistics/stats.ts +39 -9
  266. package/src/plugins/builtin/equity-screener/categories.ts +179 -0
  267. package/src/plugins/builtin/equity-screener/client.ts +4 -2
  268. package/src/plugins/builtin/equity-screener/headless.ts +144 -13
  269. package/src/plugins/builtin/equity-screener/ideas.ts +49 -0
  270. package/src/plugins/builtin/equity-screener/model.ts +99 -10
  271. package/src/plugins/builtin/equity-screener/pane.tsx +68 -13
  272. package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
  273. package/src/plugins/builtin/futures/use-board-terms.ts +42 -28
  274. package/src/plugins/builtin/futures/use-front-returns.ts +45 -28
  275. package/src/plugins/builtin/fx-matrix/client.ts +6 -3
  276. package/src/plugins/builtin/fx-matrix/headless.ts +1 -1
  277. package/src/plugins/builtin/fx-matrix/index.tsx +4 -2
  278. package/src/plugins/builtin/fx-matrix/live-legs.ts +42 -3
  279. package/src/plugins/builtin/gpu/headless.ts +8 -2
  280. package/src/plugins/builtin/help/function-index.ts +4 -1
  281. package/src/plugins/builtin/help/index.tsx +62 -59
  282. package/src/plugins/builtin/help/shortcut-model.ts +1 -1
  283. package/src/plugins/builtin/help/shortcut-table.tsx +5 -6
  284. package/src/plugins/builtin/help/table-cells.tsx +13 -2
  285. package/src/plugins/builtin/holders/beneficial-client.ts +5 -1
  286. package/src/plugins/builtin/holders/beneficial-view.tsx +3 -0
  287. package/src/plugins/builtin/holders/fund-holders.ts +51 -0
  288. package/src/plugins/builtin/holders/headless.ts +22 -3
  289. package/src/plugins/builtin/holders/pane.tsx +3 -0
  290. package/src/plugins/builtin/holders/report-rows.ts +4 -1
  291. package/src/plugins/builtin/insider/headless.ts +2 -2
  292. package/src/plugins/builtin/ipo-calendar/client.ts +18 -2
  293. package/src/plugins/builtin/ipo-calendar/headless.ts +75 -11
  294. package/src/plugins/builtin/ipo-calendar/listing-ipo.ts +160 -0
  295. package/src/plugins/builtin/ipo-calendar/model.ts +98 -2
  296. package/src/plugins/builtin/iv-history/client.ts +52 -11
  297. package/src/plugins/builtin/iv-history/format.ts +36 -23
  298. package/src/plugins/builtin/iv-history/headless.ts +33 -18
  299. package/src/plugins/builtin/iv-history/index.tsx +2 -2
  300. package/src/plugins/builtin/iv-history/model.ts +163 -27
  301. package/src/plugins/builtin/iv-history/pane.tsx +10 -6
  302. package/src/plugins/builtin/iv-history/screen-pane.tsx +22 -15
  303. package/src/plugins/builtin/iv-history/universe.ts +5 -1
  304. package/src/plugins/builtin/jobs/client.ts +25 -29
  305. package/src/plugins/builtin/kelly-sizer/model.ts +12 -8
  306. package/src/plugins/builtin/kelly-sizer/pane.tsx +9 -9
  307. package/src/plugins/builtin/kelly-sizer/sections.tsx +19 -10
  308. package/src/plugins/builtin/kelly-sizer/types.ts +10 -4
  309. package/src/plugins/builtin/macro-day/headless.ts +12 -5
  310. package/src/plugins/builtin/macro-day/index.tsx +1 -1
  311. package/src/plugins/builtin/macro-day/model.ts +39 -5
  312. package/src/plugins/builtin/macro-day/pane.tsx +27 -19
  313. package/src/plugins/builtin/macro-day/release-table.ts +2 -2
  314. package/src/plugins/builtin/market-heatmap/data.ts +8 -33
  315. package/src/plugins/builtin/market-movers/client.ts +27 -7
  316. package/src/plugins/builtin/market-movers/headless.ts +34 -12
  317. package/src/plugins/builtin/market-movers/index.tsx +26 -14
  318. package/src/plugins/builtin/market-movers/min-float.ts +74 -0
  319. package/src/plugins/builtin/market-movers/model.ts +4 -1
  320. package/src/plugins/builtin/market-movers/screener.ts +22 -4
  321. package/src/plugins/builtin/market-movers/session-body.tsx +13 -8
  322. package/src/plugins/builtin/market-movers/session-table.ts +4 -3
  323. package/src/plugins/builtin/market-movers/table.ts +17 -5
  324. package/src/plugins/builtin/members/breadth-pane.tsx +2 -1
  325. package/src/plugins/builtin/mna/headless.ts +2 -0
  326. package/src/plugins/builtin/mna/pane.tsx +3 -1
  327. package/src/plugins/builtin/news/headless.ts +3 -1
  328. package/src/plugins/builtin/news/index.tsx +4 -0
  329. package/src/plugins/builtin/news/wire/categories.ts +19 -3
  330. package/src/plugins/builtin/news/wire/industry-pane.tsx +1 -0
  331. package/src/plugins/builtin/news/wire/news/footer.ts +31 -5
  332. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +6 -1
  333. package/src/plugins/builtin/news/wire/news/table.tsx +2 -2
  334. package/src/plugins/builtin/openfda/client.ts +14 -2
  335. package/src/plugins/builtin/openfda/index.tsx +3 -2
  336. package/src/plugins/builtin/openfda/model.ts +9 -0
  337. package/src/plugins/builtin/openfda/pane.tsx +42 -6
  338. package/src/plugins/builtin/options/analytics.ts +29 -5
  339. package/src/plugins/builtin/options/enrichment.ts +17 -5
  340. package/src/plugins/builtin/options/table.ts +5 -2
  341. package/src/plugins/builtin/options/types.ts +2 -0
  342. package/src/plugins/builtin/options/view.tsx +17 -4
  343. package/src/plugins/builtin/options-positioning/client.ts +11 -0
  344. package/src/plugins/builtin/options-positioning/headless.ts +21 -8
  345. package/src/plugins/builtin/options-positioning/index.tsx +1 -1
  346. package/src/plugins/builtin/options-positioning/model.ts +78 -9
  347. package/src/plugins/builtin/options-positioning/pane.tsx +21 -6
  348. package/src/plugins/builtin/options-scenario/client.ts +116 -47
  349. package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +142 -10
  350. package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +206 -55
  351. package/src/plugins/builtin/options-scenario/csp-screen.ts +63 -23
  352. package/src/plugins/builtin/options-scenario/csp.ts +8 -2
  353. package/src/plugins/builtin/options-scenario/index.tsx +9 -7
  354. package/src/plugins/builtin/options-scenario/strategy-screen.ts +532 -0
  355. package/src/plugins/builtin/pe-band/model.ts +12 -49
  356. package/src/plugins/builtin/perps/cascade-tabs.tsx +161 -0
  357. package/src/plugins/builtin/perps/cascade.ts +236 -0
  358. package/src/plugins/builtin/perps/client.ts +56 -2
  359. package/src/plugins/builtin/perps/headless.ts +65 -9
  360. package/src/plugins/builtin/perps/index.tsx +7 -5
  361. package/src/plugins/builtin/perps/model.ts +18 -4
  362. package/src/plugins/builtin/perps/pane.tsx +19 -22
  363. package/src/plugins/builtin/perps/status-line.ts +25 -0
  364. package/src/plugins/builtin/plugin-module.ts +5 -1
  365. package/src/plugins/builtin/portfolio-list/cached-account.ts +3 -1
  366. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +2 -8
  367. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +4 -4
  368. package/src/plugins/builtin/portfolio-list/cli/render.ts +7 -5
  369. package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
  370. package/src/plugins/builtin/portfolio-list/pane/index.tsx +6 -7
  371. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +5 -2
  372. package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +3 -2
  373. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +0 -14
  374. package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +5 -2
  375. package/src/plugins/builtin/portfolio-list/table.tsx +15 -9
  376. package/src/plugins/builtin/primary-dealers/client.ts +13 -38
  377. package/src/plugins/builtin/realized-vol/client.ts +13 -5
  378. package/src/plugins/builtin/realized-vol/evidence.ts +29 -4
  379. package/src/plugins/builtin/realized-vol/headless.ts +8 -4
  380. package/src/plugins/builtin/realized-vol/index.tsx +2 -2
  381. package/src/plugins/builtin/realized-vol/model.ts +37 -6
  382. package/src/plugins/builtin/realized-vol/pane.tsx +11 -9
  383. package/src/plugins/builtin/relative-rotation/client.ts +18 -4
  384. package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
  385. package/src/plugins/builtin/relative-rotation/index.tsx +2 -1
  386. package/src/plugins/builtin/relative-rotation/model.ts +16 -40
  387. package/src/plugins/builtin/relative-rotation/pane.tsx +3 -2
  388. package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -2
  389. package/src/plugins/builtin/research/event-model.ts +40 -12
  390. package/src/plugins/builtin/research/headless.ts +1 -1
  391. package/src/plugins/builtin/research/index.tsx +7 -3
  392. package/src/plugins/builtin/research/relative-valuation-headless.ts +13 -26
  393. package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
  394. package/src/plugins/builtin/research/relative-valuation-pane.tsx +59 -20
  395. package/src/plugins/builtin/research/relative-valuation-peers.ts +217 -20
  396. package/src/plugins/builtin/research/relative-valuation-settings.ts +44 -0
  397. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -3
  398. package/src/plugins/builtin/reverse-dcf/model.ts +61 -6
  399. package/src/plugins/builtin/reverse-dcf/pane.tsx +7 -4
  400. package/src/plugins/builtin/risk-factors/data.ts +4 -2
  401. package/src/plugins/builtin/risk-factors/headless.ts +5 -2
  402. package/src/plugins/builtin/roadmap/index.tsx +28 -0
  403. package/src/plugins/builtin/roadmap/model.ts +67 -0
  404. package/src/plugins/builtin/roadmap/pane.tsx +244 -0
  405. package/src/plugins/builtin/scanner/flow-model.ts +50 -4
  406. package/src/plugins/builtin/scanner/flow-pane.tsx +367 -65
  407. package/src/plugins/builtin/scanner/flow-session.ts +146 -0
  408. package/src/plugins/builtin/scanner/headless.ts +204 -35
  409. package/src/plugins/builtin/scanner/index.ts +16 -5
  410. package/src/plugins/builtin/scanner/live-headless.ts +48 -5
  411. package/src/plugins/builtin/scanner/live-model.ts +11 -0
  412. package/src/plugins/builtin/scanner/live-pane.tsx +3 -3
  413. package/src/plugins/builtin/scanner/lock-note.ts +3 -2
  414. package/src/plugins/builtin/seasonality/headless.ts +10 -1
  415. package/src/plugins/builtin/sec/fund-facts-view.tsx +5 -2
  416. package/src/plugins/builtin/sec/fund-facts.ts +116 -34
  417. package/src/plugins/builtin/sec/headless.ts +4 -2
  418. package/src/plugins/builtin/sec/index.tsx +3 -0
  419. package/src/plugins/builtin/series-inventory.ts +2 -0
  420. package/src/plugins/builtin/shared/cloud-resource.ts +16 -7
  421. package/src/plugins/builtin/shared/cloud-upgrade.ts +4 -3
  422. package/src/plugins/builtin/shared/earnings-calendar.ts +6 -2
  423. package/src/plugins/builtin/shared/earnings-report.ts +2 -2
  424. package/src/plugins/builtin/shared/headless-market-data.ts +2 -2
  425. package/src/plugins/builtin/shared/job-posting.ts +1 -0
  426. package/src/plugins/builtin/shared/members-menu.tsx +3 -2
  427. package/src/plugins/builtin/shared/pro-lock-note.ts +12 -12
  428. package/src/plugins/builtin/shared/report-freshness.ts +11 -2
  429. package/src/plugins/builtin/shared/research-cells.tsx +1 -1
  430. package/src/plugins/builtin/shared/series-cache.ts +4 -4
  431. package/src/plugins/builtin/shared/ticker-choice.tsx +2 -1
  432. package/src/plugins/builtin/shared/ticker-company.ts +77 -0
  433. package/src/plugins/builtin/shared/use-quote-board.ts +2 -1
  434. package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
  435. package/src/plugins/builtin/shared/volatility/pricing.ts +19 -0
  436. package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
  437. package/src/plugins/builtin/themes/model.ts +16 -7
  438. package/src/plugins/builtin/themes/pane.tsx +5 -5
  439. package/src/plugins/builtin/thirteenf/api.ts +12 -1
  440. package/src/plugins/builtin/thirteenf/data.ts +7 -7
  441. package/src/plugins/builtin/thirteenf/headless.ts +31 -4
  442. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +43 -18
  443. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +3 -2
  444. package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +62 -0
  445. package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +83 -46
  446. package/src/plugins/builtin/ticker-detail/headless.ts +31 -16
  447. package/src/plugins/builtin/ticker-detail/overview/model.ts +11 -3
  448. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +2 -1
  449. package/src/plugins/builtin/ticker-detail/pane.tsx +14 -8
  450. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -1
  451. package/src/plugins/builtin/ticker-detail/settings.ts +1 -0
  452. package/src/plugins/builtin/time-sales/pane.tsx +31 -19
  453. package/src/plugins/builtin/treasury-auctions/headless.ts +21 -12
  454. package/src/plugins/builtin/treasury-auctions/pane.tsx +9 -3
  455. package/src/plugins/builtin/treasury-daily/client.ts +41 -5
  456. package/src/plugins/builtin/treasury-daily/headless.ts +2 -2
  457. package/src/plugins/builtin/treasury-daily/index.tsx +1 -1
  458. package/src/plugins/builtin/treasury-daily/model.ts +50 -3
  459. package/src/plugins/builtin/treasury-daily/pane.tsx +81 -2
  460. package/src/plugins/builtin/vol-surface/client.ts +3 -13
  461. package/src/plugins/builtin/vol-surface/headless.ts +3 -3
  462. package/src/plugins/builtin/vol-surface/index.tsx +1 -1
  463. package/src/plugins/builtin/vol-surface/pane.tsx +7 -5
  464. package/src/plugins/builtin/volatility/index.tsx +2 -2
  465. package/src/plugins/builtin/volatility/pane.tsx +7 -4
  466. package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
  467. package/src/plugins/builtin/yield-curve/client.ts +61 -1
  468. package/src/plugins/builtin/yield-curve/headless.ts +9 -6
  469. package/src/plugins/builtin/yield-curve/index.tsx +34 -10
  470. package/src/plugins/builtin/yield-curve/swaps.tsx +9 -5
  471. package/src/plugins/catalog-browser.ts +2 -1
  472. package/src/plugins/catalog-manifest.ts +2 -1
  473. package/src/plugins/host-link.ts +1 -1
  474. package/src/plugins/marketplace/model.ts +18 -5
  475. package/src/portfolio/column-values.ts +11 -7
  476. package/src/portfolio/futures-positions.ts +6 -3
  477. package/src/portfolio/position-quantity.ts +15 -9
  478. package/src/remote/resources.ts +2 -1
  479. package/src/renderers/dom/data-table/row.tsx +26 -0
  480. package/src/renderers/dom/desktop/icons.tsx +7 -0
  481. package/src/renderers/dom/desktop/list-view.tsx +13 -10
  482. package/src/renderers/dom/desktop/query-bar.tsx +4 -2
  483. package/src/renderers/dom/desktop/select-field.tsx +4 -2
  484. package/src/renderers/dom/host/chart-surface.tsx +21 -0
  485. package/src/renderers/electrobun/bun/desktop/app-name.ts +3 -3
  486. package/src/renderers/electrobun/bun/desktop/backend.ts +21 -10
  487. package/src/renderers/electrobun/bun/desktop/host-requests.ts +15 -0
  488. package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
  489. package/src/renderers/electrobun/bun/desktop/installer-folder.ts +731 -0
  490. package/src/renderers/electrobun/bun/desktop/relaunch.ts +102 -10
  491. package/src/renderers/electrobun/bun/desktop/update.ts +115 -14
  492. package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
  493. package/src/renderers/electrobun/bun/desktop/windows-script.ts +92 -0
  494. package/src/renderers/electrobun/bun/index.ts +26 -2
  495. package/src/renderers/electrobun/bun/window/create.ts +43 -4
  496. package/src/renderers/electrobun/bun/window/events.ts +20 -0
  497. package/src/renderers/electrobun/bun/window/windows-custom-chrome.ts +9 -2
  498. package/src/renderers/electrobun/bun/window/windows-native.ts +78 -0
  499. package/src/renderers/electrobun/bun/window/windows-quit.ts +57 -0
  500. package/src/renderers/electrobun/shared/app-name.ts +13 -0
  501. package/src/renderers/electrobun/shared/protocol.ts +12 -0
  502. package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
  503. package/src/renderers/electrobun/view/main.tsx +24 -1
  504. package/src/renderers/electrobun/view/news-source.ts +5 -1
  505. package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
  506. package/src/renderers/electrobun/view/ui-host.tsx +8 -0
  507. package/src/renderers/electrobun/view/view-ids.ts +85 -0
  508. package/src/renderers/opentui/start.tsx +5 -0
  509. package/src/series/registry.ts +1 -0
  510. package/src/series/types.ts +6 -0
  511. package/src/series/validate.ts +1 -0
  512. package/src/sources/gloomberb-cloud/fred-series.ts +3 -1
  513. package/src/sources/gloomberb-cloud/index.ts +43 -8
  514. package/src/sources/gloomberb-cloud/news.ts +47 -7
  515. package/src/sources/provider-router/financials.ts +15 -3
  516. package/src/sources/provider-router/index.ts +8 -5
  517. package/src/state/ticker-cleanup.ts +50 -0
  518. package/src/sync/controller.ts +4 -3
  519. package/src/sync/core-contributors.ts +90 -31
  520. package/src/telemetry/stall-reports-dom.ts +38 -0
  521. package/src/telemetry/stall-reports-node.ts +75 -0
  522. package/src/telemetry/stall-reports.ts +214 -0
  523. package/src/test-support/preload.ts +14 -0
  524. package/src/theme/themes.ts +10 -0
  525. package/src/tickers/company-name-listing.ts +53 -13
  526. package/src/tickers/company-name.ts +50 -4
  527. package/src/tickers/index-roots.ts +7 -0
  528. package/src/tickers/listing-fate.ts +2 -2
  529. package/src/tickers/listing-venues.ts +15 -3
  530. package/src/tickers/search/index.ts +6 -2
  531. package/src/tickers/search/ranking.ts +5 -1
  532. package/src/tickers/search/upsert.ts +13 -0
  533. package/src/tickers/terminal-codes.ts +19 -0
  534. package/src/tickers/ticker-changes.ts +8 -4
  535. package/src/time-series/alignment.ts +1 -1
  536. package/src/time-series/economic.ts +18 -4
  537. package/src/time-series/field-catalog.ts +1 -1
  538. package/src/time-series/filed-trailing-eps.ts +197 -0
  539. package/src/time-series/fundamentals.ts +44 -1
  540. package/src/time-series/ice-credit.ts +3 -0
  541. package/src/time-series/price-comparison.ts +30 -2
  542. package/src/time-series/resolve-context.ts +3 -1
  543. package/src/time-series/resolve.ts +56 -4
  544. package/src/time-series/session-bar.ts +11 -0
  545. package/src/time-series/spec.ts +3 -2
  546. package/src/time-series/studies.ts +8 -2
  547. package/src/time-series/total-return.ts +160 -0
  548. package/src/time-series/transforms.ts +2 -3
  549. package/src/time-series/types.ts +22 -1
  550. package/src/types/company-from.ts +40 -0
  551. package/src/types/data-provider.ts +7 -0
  552. package/src/types/financials.ts +59 -1
  553. package/src/types/headless.ts +27 -3
  554. package/src/types/news-source.ts +1 -0
  555. package/src/types/plugin.ts +9 -0
  556. package/src/ui/host.tsx +3 -0
  557. package/src/updater/failure.ts +4 -1
  558. package/src/utils/cli-output.ts +42 -1
  559. package/src/utils/crypto-pair.ts +165 -7
  560. package/src/utils/declared-dividends.ts +1 -1
  561. package/src/utils/forward-dividend.ts +2 -0
  562. package/src/utils/fundamentals.ts +60 -8
  563. package/src/utils/fx-market-hours.ts +26 -3
  564. package/src/utils/main-thread-stall.ts +35 -2
  565. package/src/utils/market-capitalization.ts +24 -1
  566. package/src/utils/price-history.ts +7 -1
  567. package/src/utils/shell-word.ts +8 -0
  568. package/src/utils/trailing-dividend.ts +53 -4
  569. package/src/version.ts +2 -2
  570. package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
@@ -1,21 +1,32 @@
1
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
- import type { ScannerFlowEvent } from "../../../api-client";
1
+ import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react";
2
+ import type { ScannerFlowEvent, ScannerFlowSession } from "../../../api-client";
3
3
  import {
4
+ ChartTableHeader,
4
5
  DataTableView,
5
6
  PaneStatusBody,
6
7
  QueryBar,
8
+ scalarPoint,
9
+ staticSeries,
10
+ useChartTableSelection,
11
+ usePaneTabs,
7
12
  useTableLoadMore,
13
+ type ChartTableChart,
8
14
  type DataTableCell,
9
15
  type DataTableColumn,
10
16
  type DataTableKeyEvent,
11
17
  type PaneFooterSegment,
12
18
  } from "../../../components";
19
+ import { fitChartTableColumns, formatCompactAxis } from "../../../components/chart-table";
20
+ import { usePaneRefreshKey } from "../../../components/data-table/table-pane";
21
+ import type { CompositeAxisDomain } from "../../../components/chart/composite/types";
22
+ import { useAsyncResource, useAutoRefresh, usePluginAppActions, usePluginPaneState } from "../../../public/react";
13
23
  import { useAppSelector, usePaneSettingValue, usePaneStateValue } from "../../../state/app/context";
14
24
  import { colors } from "../../../theme/colors";
25
+ import type { ResolvedSeries } from "../../../time-series/types";
15
26
  import { TICKER_RESEARCH_PANE_ID } from "../../../types/config";
16
27
  import type { PaneProps } from "../../../types/plugin";
17
- import { Box, TextAttributes, type ScrollBoxRenderable } from "../../../ui";
18
- import { formatCompact, formatNumber } from "../../../utils/format";
28
+ import { Box, Text, TextAttributes, type ScrollBoxRenderable } from "../../../ui";
29
+ import { MISSING_VALUE, formatCompact, formatNumber } from "../../../utils/format";
19
30
  import { isPlainKey } from "../../../utils/keyboard";
20
31
  import { usePluginTickerActions } from "../../runtime";
21
32
  import { ScannerDeniedState } from "./denied";
@@ -28,6 +39,8 @@ import {
28
39
  flowEmptyState,
29
40
  flowHistoryQuery,
30
41
  flowRowsSpanDays,
42
+ flowScopeSymbol,
43
+ flowSessionSearch,
31
44
  keepFlowPrints,
32
45
  mergeFlowRows,
33
46
  formatFlowExpiry,
@@ -40,23 +53,51 @@ import {
40
53
  type FlowFilters,
41
54
  type FlowKind,
42
55
  type FlowMinPremium,
56
+ type FlowScope,
43
57
  type FlowSide,
44
58
  type FlowUniverse,
45
59
  type FlowVolOi,
46
60
  } from "./flow-model";
61
+ import {
62
+ FLOW_SESSION_LIVE_REFRESH_MS,
63
+ flowStrikeRows,
64
+ flowTideMoves,
65
+ flowTidePoints,
66
+ flowTideRows,
67
+ formatFlowSessionDate,
68
+ formatSignedFlowPremium,
69
+ formatStrikeExpiry,
70
+ formatTideTime,
71
+ loadFlowSession,
72
+ sortFlowStrikes,
73
+ type FlowStrikeRow,
74
+ type FlowStrikeSort,
75
+ type FlowTideRow,
76
+ } from "./flow-session";
77
+
78
+ type FlowTab = "prints" | "tide" | "strikes";
47
79
 
48
- function buildColumns(width: number, dated: boolean): DataTableColumn[] {
80
+ /** The tabs a pane offers: the strike breakdown is one name's. */
81
+ function flowTabs(symbol: string | null): Array<{ label: string; value: FlowTab }> {
82
+ return [
83
+ { label: "Prints", value: "prints" },
84
+ { label: "Tide", value: "tide" },
85
+ ...(symbol ? [{ label: "Strikes", value: "strikes" as const }] : []),
86
+ ];
87
+ }
88
+
89
+ function buildColumns(width: number, dated: boolean, ticker: boolean): DataTableColumn[] {
49
90
  // Prints from earlier days carry their date ("09/22 15:04"), so the time
50
- // column widens for them.
51
- const fixed = { time: dated ? 11 : 8, ticker: 7, type: 8, strike: 8, exp: 6, side: 5, size: 7, prem: 7, volOi: 6 };
91
+ // column widens for them. One name's pane names it in its title, not a column.
92
+ const fixed = { time: dated ? 11 : 8, ticker: ticker ? 7 : 0, type: 8, strike: 8, exp: 6, side: 5, size: 7, prem: 7, volOi: 6 };
52
93
  const total = Object.values(fixed).reduce((sum, value) => sum + value, 0);
53
94
  // Table chrome is one gap per column, the table's extra gutter where
54
95
  // right-aligned EXP meets left-aligned SIDE, two cells of padding, and the scrollbar.
55
- const slack = Math.max(0, width - total - 9 - 1 - 2 - 1);
96
+ const slack = Math.max(0, width - total - (ticker ? 9 : 8) - 1 - 2 - 1);
56
97
  return [
57
98
  { id: "time", label: "TIME", width: fixed.time, align: "left" },
58
- { id: "ticker", label: "TICKER", width: fixed.ticker + Math.min(4, slack), align: "left" },
59
- { id: "type", label: "TYPE", width: fixed.type, align: "left" },
99
+ ...(ticker ? [{ id: "ticker", label: "TICKER", width: fixed.ticker + Math.min(4, slack), align: "left" as const }] : []),
100
+ { id: "type", label: "TYPE", width: fixed.type + (ticker ? 0 : Math.min(4, slack)), align: "left" },
60
101
  { id: "strike", label: "STRIKE", width: fixed.strike, align: "right" },
61
102
  { id: "expiry", label: "EXP", width: fixed.exp, align: "right" },
62
103
  { id: "side", label: "SIDE", width: fixed.side, align: "left" },
@@ -66,14 +107,43 @@ function buildColumns(width: number, dated: boolean): DataTableColumn[] {
66
107
  ];
67
108
  }
68
109
 
110
+ const TIDE_COLUMNS: DataTableColumn[] = [
111
+ { id: "time", label: "TIME", width: 6, align: "left" },
112
+ { id: "netCalls", label: "NET CALLS", width: 10, align: "right" },
113
+ { id: "netPuts", label: "NET PUTS", width: 10, align: "right" },
114
+ { id: "net", label: "NET PREM", width: 10, align: "right" },
115
+ { id: "change", label: "CHG", width: 9, align: "right" },
116
+ ];
117
+
118
+ const STRIKE_COLUMNS: Array<DataTableColumn & { id: FlowStrikeSort }> = [
119
+ { id: "expiry", label: "EXP", width: 8, align: "left" },
120
+ { id: "strike", label: "STRIKE", width: 8, align: "right" },
121
+ { id: "callPremium", label: "CALLS", width: 8, align: "right" },
122
+ { id: "callAsk", label: "C ASK", width: 8, align: "right" },
123
+ { id: "callBid", label: "C BID", width: 8, align: "right" },
124
+ { id: "putPremium", label: "PUTS", width: 8, align: "right" },
125
+ { id: "putAsk", label: "P ASK", width: 8, align: "right" },
126
+ { id: "putBid", label: "P BID", width: 8, align: "right" },
127
+ { id: "net", label: "NET", width: 8, align: "right" },
128
+ ];
129
+ /** The totals go before the side columns that make up the breakdown. */
130
+ const STRIKE_DROP_ORDER: FlowStrikeSort[] = ["putPremium", "callPremium"];
131
+
69
132
  function sideColor(side: ScannerFlowEvent["side"]): string {
70
133
  if (side === "ask") return colors.positive;
71
134
  if (side === "bid") return colors.negative;
72
135
  return side === "mid" ? colors.text : colors.textDim;
73
136
  }
74
137
 
138
+ function signedColor(value: number): string {
139
+ return value > 0 ? colors.positive : value < 0 ? colors.negative : colors.textDim;
140
+ }
141
+
75
142
  // Stable table adapters so memoized rows survive feed ticks.
76
143
  const eventKey = (event: ScannerFlowEvent) => event.id;
144
+ const tideKey = (row: FlowTideRow) => String(row.at);
145
+ const tideDate = (row: FlowTideRow) => new Date(row.at);
146
+ const strikeKey = (row: FlowStrikeRow) => row.id;
77
147
 
78
148
  function renderCell(
79
149
  event: ScannerFlowEvent,
@@ -111,14 +181,84 @@ function renderCell(
111
181
  }
112
182
  }
113
183
 
184
+ function renderTideCell(row: FlowTideRow, column: DataTableColumn): DataTableCell {
185
+ if (column.id === "time") return { text: formatTideTime(row.at), value: new Date(row.at).toISOString(), color: colors.textDim };
186
+ const value = row[column.id as "netCalls" | "netPuts" | "net" | "change"];
187
+ return {
188
+ text: formatSignedFlowPremium(value),
189
+ value,
190
+ // Coloured by what it means for the name: put buying is bearish, put selling bullish.
191
+ color: signedColor(column.id === "netPuts" ? -value : value),
192
+ ...(column.id === "net" ? { attributes: TextAttributes.BOLD } : {}),
193
+ };
194
+ }
195
+
196
+ function renderStrikeCell(row: FlowStrikeRow, column: DataTableColumn): DataTableCell {
197
+ switch (column.id) {
198
+ case "expiry":
199
+ return { text: formatStrikeExpiry(row.expiry), value: row.expiry, color: colors.textDim };
200
+ case "strike":
201
+ return { text: formatNumber(row.strike, row.strike % 1 === 0 ? 0 : 2), value: row.strike, color: colors.textBright };
202
+ case "net":
203
+ return { text: formatSignedFlowPremium(row.net), value: row.net, color: signedColor(row.net), attributes: TextAttributes.BOLD };
204
+ default: {
205
+ const value = row[column.id as "callPremium" | "callAsk" | "callBid" | "putPremium" | "putAsk" | "putBid"];
206
+ const call = column.id.startsWith("call");
207
+ const total = column.id === "callPremium" || column.id === "putPremium";
208
+ return {
209
+ text: value > 0 ? formatFlowPremium(value) : MISSING_VALUE,
210
+ value,
211
+ color: value <= 0 ? colors.textDim : total ? (call ? colors.positive : colors.negative) : colors.text,
212
+ };
213
+ }
214
+ }
215
+ }
216
+
217
+ /** Amounts on the tide's axis, in one unit for the whole axis: `$12M`, `-$4.5M`. */
218
+ function formatPremiumAxis(value: number, domain: CompositeAxisDomain): string {
219
+ const text = formatCompactAxis(value, domain);
220
+ if (text === "0") return "$0";
221
+ return text.startsWith("-") ? `-$${text.slice(1)}` : `$${text}`;
222
+ }
223
+
224
+ /** The tide's lines read in the user's own clock, as the TIME columns do. */
225
+ const LOCAL_TIME_ZONE = Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
226
+
227
+ function tideSeries(rows: readonly FlowTideRow[], bucketMs: number): ResolvedSeries[] {
228
+ const points = flowTidePoints(rows, bucketMs);
229
+ const line = (id: string, label: string, color: string, value: (point: (typeof points)[number]) => number): ResolvedSeries => ({
230
+ ...staticSeries(points.map((point) => scalarPoint(new Date(point.at), value(point))), { id, label, color }),
231
+ timeBasis: { kind: "market", timeZone: LOCAL_TIME_ZONE, cadenceMs: bucketMs },
232
+ observationKind: "market",
233
+ });
234
+ // Named as the table's columns are; the net line is the tide itself.
235
+ return [
236
+ line("flow-net", "Net prem", colors.textBright, (point) => point.net),
237
+ line("flow-net-calls", "Net calls", colors.positive, (point) => point.netCalls),
238
+ line("flow-net-puts", "Net puts", colors.negative, (point) => point.netPuts),
239
+ ];
240
+ }
241
+
114
242
  function FlowPane({ focused, width, height }: PaneProps) {
115
243
  const feed = useFlowFeed();
116
244
  const { pinTicker } = usePluginTickerActions();
245
+ const { createPaneFromTemplate } = usePluginAppActions();
117
246
  const [selectedId, setSelectedId] = useState<string | null>(null);
118
247
  // Publish the selection on this pane, not the portfolio: moving the
119
248
  // portfolio's cursor scrolled it to a ticker the user never picked there.
120
249
  const [, setCursorSymbol] = usePaneStateValue<string | null>("cursorSymbol", null);
121
250
 
251
+ // `FLOW NVDA` is one name's flow; plain FLOW is the market.
252
+ const [symbolSetting] = usePaneSettingValue<string>("symbol", "");
253
+ const symbol = flowScopeSymbol(symbolSetting);
254
+ const tabItems = useMemo(() => flowTabs(symbol), [symbol]);
255
+ const [initialTab] = usePaneSettingValue<string>("tab", "prints");
256
+ const [savedTab, setTab] = usePluginPaneState<string>("flow:tab", initialTab);
257
+ const tab: FlowTab = tabItems.find((item) => item.value === savedTab)?.value ?? "prints";
258
+ // Signed out or without Pro, every tab is the same wall.
259
+ const { strip, rows: tabRows } = usePaneTabs(!feed.denied ? { tabs: tabItems, activeValue: tab, onSelect: setTab, focused, dense: true } : null);
260
+ const bodyHeight = Math.max(3, height - tabRows);
261
+
122
262
  const [minPremium, setMinPremium] = usePaneSettingValue<FlowMinPremium>("minPremium", DEFAULT_FLOW_FILTERS.minPremium);
123
263
  const [side, setSide] = usePaneSettingValue<FlowSide>("side", DEFAULT_FLOW_FILTERS.side);
124
264
  const [kind, setKind] = usePaneSettingValue<FlowKind>("kind", DEFAULT_FLOW_FILTERS.kind);
@@ -128,9 +268,10 @@ function FlowPane({ focused, width, height }: PaneProps) {
128
268
 
129
269
  const trackedSymbols = useAppSelector((state) => state.tickers);
130
270
  const watchlist = useMemo(
131
- () => new Set([...trackedSymbols.keys()].map((symbol) => symbol.toUpperCase())),
271
+ () => new Set([...trackedSymbols.keys()].map((entry) => entry.toUpperCase())),
132
272
  [trackedSymbols],
133
273
  );
274
+ const scope = useMemo<FlowScope>(() => ({ symbol, watchlist }), [symbol, watchlist]);
134
275
 
135
276
  const filters = useMemo<FlowFilters>(
136
277
  () => ({ minPremium, side, kind, volOi, expiry, universe }),
@@ -146,11 +287,11 @@ function FlowPane({ focused, width, height }: PaneProps) {
146
287
  return keptRef.current;
147
288
  }, [feed.payload?.events]);
148
289
  const liveRows = useMemo(
149
- () => filterFlowEvents(kept, filters, watchlist),
150
- [filters, kept, watchlist],
290
+ () => filterFlowEvents(kept, filters, scope),
291
+ [filters, kept, scope],
151
292
  );
152
293
 
153
- const historyQuery = useMemo(() => flowHistoryQuery(filters, watchlist), [filters, watchlist]);
294
+ const historyQuery = useMemo(() => flowHistoryQuery(filters, scope), [filters, scope]);
154
295
  const oldestKept = kept.at(-1);
155
296
  const oldestLive = useMemo(
156
297
  () => (oldestKept ? { at: oldestKept.at, id: oldestKept.id } : null),
@@ -159,32 +300,58 @@ function FlowPane({ focused, width, height }: PaneProps) {
159
300
  const history = useFlowHistory(historyQuery, oldestLive, !!feed.payload && !feed.denied);
160
301
  const events = useMemo(() => mergeFlowRows(liveRows, history.events), [history.events, liveRows]);
161
302
 
303
+ // The session summary is read only while Tide or Strikes is on screen: it
304
+ // loads when one first opens, follows the filters and refreshes while shown,
305
+ // and holds still behind Prints.
306
+ const sessionShown = tab !== "prints";
307
+ const sessionSearch = useMemo(() => flowSessionSearch(filters, scope), [filters, scope]);
308
+ const [shownSearch, setShownSearch] = useState<string | null>(sessionShown ? sessionSearch : null);
309
+ useEffect(() => {
310
+ if (sessionShown) setShownSearch(sessionSearch);
311
+ }, [sessionSearch, sessionShown]);
312
+ const sessionLoader = useMemo(
313
+ () => (shownSearch === null ? null : () => loadFlowSession(shownSearch)),
314
+ [shownSearch],
315
+ );
316
+ const session = useAsyncResource<ScannerFlowSession>(
317
+ sessionLoader && feed.payload && !feed.denied ? sessionLoader : null,
318
+ );
319
+ useAutoRefresh(session.updatedAt, () => void session.load(), {
320
+ intervalMs: sessionShown && feed.payload?.status === "live" ? FLOW_SESSION_LIVE_REFRESH_MS : null,
321
+ });
322
+
162
323
  const scrollRef = useRef<ScrollBoxRenderable | null>(null);
163
- const canLoadOlder = history.hasMore && !history.loading && !history.error && !!feed.payload && !feed.denied;
324
+ const canLoadOlder = tab === "prints" && history.hasMore && !history.loading && !history.error && !!feed.payload && !feed.denied;
164
325
  const onBodyScrollActivity = useTableLoadMore(scrollRef, canLoadOlder, history.loadMore);
165
326
  // A strict filter or a quiet tape should still fill the pane, including
166
327
  // before the open, when every row is recorded.
167
- const bodyRows = Math.max(1, height - 3);
328
+ const bodyRows = Math.max(1, bodyHeight - 3);
168
329
  useEffect(() => {
169
330
  if (canLoadOlder && events.length < bodyRows) history.loadMore();
170
331
  }, [bodyRows, canLoadOlder, events.length, history.loadMore]);
171
332
 
172
333
  const emptyState = useMemo(
173
- () => flowEmptyState(kept.length, liveRows.length, feed.payload?.status),
174
- [feed.payload?.status, kept.length, liveRows.length],
334
+ () => flowEmptyState(kept.length, liveRows.length, feed.payload?.status, symbol),
335
+ [feed.payload?.status, kept.length, liveRows.length, symbol],
175
336
  );
176
337
 
177
- const historyFooter = useMemo<PaneFooterSegment | null>(() => {
338
+ const statusFooter = useMemo<PaneFooterSegment | null>(() => {
339
+ if (tab !== "prints") {
340
+ // A failed refresh keeps the last summary on screen and says so here.
341
+ return session.error && session.data
342
+ ? { id: "flow-session", parts: [{ text: "session totals not refreshed", tone: "warning" }] }
343
+ : null;
344
+ }
178
345
  if (history.error) {
179
346
  return { id: "flow-history", parts: [{ text: "older prints unavailable", tone: "warning" }] };
180
347
  }
181
348
  if (history.loading) return { id: "flow-history", parts: [{ text: "loading older prints", tone: "muted" }] };
182
349
  return null;
183
- }, [history.error, history.loading]);
184
- useScannerStatusFooter("flow", feed, focused, historyFooter);
350
+ }, [history.error, history.loading, session.data, session.error, tab]);
351
+ useScannerStatusFooter("flow", feed, focused, statusFooter);
185
352
 
186
353
  const dated = useMemo(() => flowRowsSpanDays(events), [events]);
187
- const columns = useMemo(() => buildColumns(width, dated), [dated, width]);
354
+ const columns = useMemo(() => buildColumns(width, dated, !symbol), [dated, symbol, width]);
188
355
 
189
356
  const handleRootKeyDown = useCallback((event: DataTableKeyEvent) => {
190
357
  if (!isPlainKey(event, "r") || !history.error) return false;
@@ -193,6 +360,7 @@ function FlowPane({ focused, width, height }: PaneProps) {
193
360
  history.retry();
194
361
  return true;
195
362
  }, [history.error, history.retry]);
363
+ usePaneRefreshKey(() => void session.reload(), { focused, enabled: tab !== "prints" && !feed.denied });
196
364
 
197
365
  const handleSelect = useCallback((event: ScannerFlowEvent) => {
198
366
  setSelectedId(event.id);
@@ -203,57 +371,191 @@ function FlowPane({ focused, width, height }: PaneProps) {
203
371
  return <ScannerDeniedState reason={feed.deniedReason} placement="flow-wall" width={width} height={height} />;
204
372
  }
205
373
 
374
+ const queryBar = (
375
+ <QueryBar
376
+ width={width}
377
+ filters={[
378
+ { id: "premium", label: "Prem", value: minPremium, defaultValue: DEFAULT_FLOW_FILTERS.minPremium,
379
+ options: FLOW_FILTER_OPTIONS.minPremium, onChange: setMinPremium },
380
+ { id: "side", label: "Side", value: side, defaultValue: DEFAULT_FLOW_FILTERS.side,
381
+ options: FLOW_FILTER_OPTIONS.side, onChange: setSide },
382
+ { id: "kind", label: "Kind", value: kind, defaultValue: DEFAULT_FLOW_FILTERS.kind,
383
+ options: FLOW_FILTER_OPTIONS.kind, onChange: setKind },
384
+ { id: "voloi", label: "V/OI", value: volOi, defaultValue: DEFAULT_FLOW_FILTERS.volOi,
385
+ options: FLOW_FILTER_OPTIONS.volOi, onChange: setVolOi },
386
+ { id: "expiry", label: "Exp", value: expiry, defaultValue: DEFAULT_FLOW_FILTERS.expiry,
387
+ options: FLOW_FILTER_OPTIONS.expiry, onChange: setExpiry },
388
+ // One name's pane is already narrower than any universe.
389
+ ...(symbol ? [] : [{ id: "universe", label: "Univ", value: universe, defaultValue: DEFAULT_FLOW_FILTERS.universe,
390
+ options: FLOW_FILTER_OPTIONS.universe, onChange: setUniverse }]),
391
+ ]}
392
+ />
393
+ );
394
+
395
+ let body: ReactNode;
396
+ if (tab === "tide") {
397
+ body = (
398
+ <FlowTideView
399
+ focused={focused} width={width} height={bodyHeight} query={queryBar}
400
+ session={session.data} loading={session.loading} error={session.error}
401
+ onRetry={session.reload}
402
+ />
403
+ );
404
+ } else if (tab === "strikes" && symbol) {
405
+ body = (
406
+ <FlowStrikesView
407
+ focused={focused} width={width} height={bodyHeight} query={queryBar} symbol={symbol}
408
+ session={session.data} loading={session.loading} error={session.error}
409
+ onRetry={session.reload}
410
+ onOpenChain={(row) => createPaneFromTemplate("options-pane", { symbol, values: { expiration: row.expiry } })}
411
+ />
412
+ );
413
+ } else {
414
+ body = (
415
+ <>
416
+ {queryBar}
417
+ <DataTableView<ScannerFlowEvent>
418
+ focused={focused}
419
+ selection={{
420
+ kind: "id",
421
+ selectedId,
422
+ getId: (event) => event.id,
423
+ onChange: (_id, event) => handleSelect(event),
424
+ }}
425
+ rootWidth={width}
426
+ rootHeight={Math.max(2, bodyHeight - 1)}
427
+ columns={columns}
428
+ items={events}
429
+ sortColumnId={null}
430
+ sortDirection="desc"
431
+ getItemKey={eventKey}
432
+ onActivate={(event) => pinTicker(event.underlying, { floating: true, paneType: TICKER_RESEARCH_PANE_ID })}
433
+ renderCell={renderCell}
434
+ selectedTextOverridesCellColor
435
+ emptyContent={
436
+ !feed.payload
437
+ ? <PaneStatusBody loading loadingLabel="Waiting for the scanner..." />
438
+ : events.length === 0 && (history.loading || (!!symbol && history.hasMore && !history.error))
439
+ ? <PaneStatusBody loading loadingLabel="Loading recorded prints..." />
440
+ : undefined
441
+ }
442
+ emptyStateTitle={emptyState.title}
443
+ emptyStateHint={emptyState.hint}
444
+ onRootKeyDown={handleRootKeyDown}
445
+ onBodyScrollActivity={onBodyScrollActivity}
446
+ scrollRef={scrollRef}
447
+ />
448
+ </>
449
+ );
450
+ }
451
+
206
452
  return (
207
453
  <Box flexDirection="column" width={width} height={height}>
208
- <QueryBar
209
- width={width}
210
- filters={[
211
- { id: "premium", label: "Prem", value: minPremium, defaultValue: DEFAULT_FLOW_FILTERS.minPremium,
212
- options: FLOW_FILTER_OPTIONS.minPremium, onChange: setMinPremium },
213
- { id: "side", label: "Side", value: side, defaultValue: DEFAULT_FLOW_FILTERS.side,
214
- options: FLOW_FILTER_OPTIONS.side, onChange: setSide },
215
- { id: "kind", label: "Kind", value: kind, defaultValue: DEFAULT_FLOW_FILTERS.kind,
216
- options: FLOW_FILTER_OPTIONS.kind, onChange: setKind },
217
- { id: "voloi", label: "V/OI", value: volOi, defaultValue: DEFAULT_FLOW_FILTERS.volOi,
218
- options: FLOW_FILTER_OPTIONS.volOi, onChange: setVolOi },
219
- { id: "expiry", label: "Exp", value: expiry, defaultValue: DEFAULT_FLOW_FILTERS.expiry,
220
- options: FLOW_FILTER_OPTIONS.expiry, onChange: setExpiry },
221
- { id: "universe", label: "Univ", value: universe, defaultValue: DEFAULT_FLOW_FILTERS.universe,
222
- options: FLOW_FILTER_OPTIONS.universe, onChange: setUniverse },
223
- ]}
224
- />
225
- <DataTableView<ScannerFlowEvent>
454
+ {strip}
455
+ {body}
456
+ </Box>
457
+ );
458
+ }
459
+
460
+ interface SessionViewProps {
461
+ focused: boolean;
462
+ width: number;
463
+ height: number;
464
+ query: ReactNode;
465
+ session: ScannerFlowSession | null;
466
+ loading: boolean;
467
+ error: string | null;
468
+ onRetry: () => unknown;
469
+ }
470
+
471
+ /** Until the first summary answers, the body loads or says why it failed; after that a failed refresh is the footer's. */
472
+ function sessionStatus(props: SessionViewProps, subject: string) {
473
+ return props.session ? undefined : { loading: props.loading, error: props.error, subject, retry: { onRetry: props.onRetry } };
474
+ }
475
+
476
+ /**
477
+ * The net-premium tide through the session: call premium bought at the ask
478
+ * less call premium sold at the bid, less the same for puts, bucket by
479
+ * bucket. The chart draws the running totals; the table adds what each
480
+ * bucket moved them by.
481
+ */
482
+ function FlowTideView(props: SessionViewProps) {
483
+ const { focused, width, height, query, session } = props;
484
+ const rows = useMemo(() => flowTideRows(session), [session]);
485
+ // Newest first, like the prints; the chart keeps every bucket, the table the ones that moved it.
486
+ const tableRows = useMemo(() => flowTideMoves(rows).reverse(), [rows]);
487
+ const bucketMs = session?.bucketMs ?? 300_000;
488
+ const [selected, setSelected] = usePluginPaneState<string | null>("flow:tide-bucket", null);
489
+ const selectedId = tableRows.some((row) => tideKey(row) === selected) ? selected : tableRows[0] ? tideKey(tableRows[0]) : null;
490
+ const link = useChartTableSelection({
491
+ rows: tableRows, getId: tideKey, getDate: tideDate, selectedId, onSelect: setSelected, focused,
492
+ });
493
+ const series = useMemo(() => tideSeries(rows, bucketMs), [bucketMs, rows]);
494
+ const sessionDate = formatFlowSessionDate(session?.session);
495
+ // Two points are not a trend: under three buckets the table has the pane.
496
+ const chart: ChartTableChart | null = rows.length >= 3 ? {
497
+ series,
498
+ formatValue: (value) => formatSignedFlowPremium(value),
499
+ formatAxisValue: formatPremiumAxis,
500
+ viewport: { start: new Date(rows[0]!.at), end: new Date(rows.at(-1)!.at + bucketMs) },
501
+ ...(sessionDate ? { legendAccessory: <Text fg={colors.textMuted}>{sessionDate}</Text>, legendAccessoryWidth: sessionDate.length } : {}),
502
+ remoteKind: "flow-tide",
503
+ ...link,
504
+ } : !session && props.loading ? { loading: true } : null;
505
+ return (
506
+ <DataTableView<FlowTideRow>
507
+ focused={focused}
508
+ rootWidth={width}
509
+ rootHeight={height}
510
+ columns={TIDE_COLUMNS}
511
+ items={tableRows}
512
+ sortColumnId={null}
513
+ sortDirection="desc"
514
+ getItemKey={tideKey}
515
+ selection={{ kind: "id", selectedId, getId: tideKey, onChange: (id) => setSelected(id) }}
516
+ onActivate={(row) => setSelected(tideKey(row))}
517
+ renderCell={renderTideCell}
518
+ selectedTextOverridesCellColor
519
+ status={sessionStatus(props, "the session's net premium")}
520
+ emptyStateTitle="No prints under these filters this session."
521
+ rootBefore={<ChartTableHeader width={width} height={height} tableRows={tableRows.length} tableColumns={TIDE_COLUMNS} query={query} chart={chart} />}
522
+ />
523
+ );
524
+ }
525
+
526
+ /** One name's session premium by expiry and strike, split by side. Enter opens the chain at that expiry. */
527
+ function FlowStrikesView(props: SessionViewProps & { symbol: string; onOpenChain: (row: FlowStrikeRow) => void }) {
528
+ const { focused, width, height, query, session } = props;
529
+ const [sort, setSort] = usePluginPaneState<{ column: FlowStrikeSort | null; direction: "asc" | "desc" }>(
530
+ "flow:strike-sort", { column: null, direction: "desc" });
531
+ const rows = useMemo(() => sortFlowStrikes(flowStrikeRows(session), sort.column, sort.direction), [session, sort]);
532
+ const columns = useMemo(() => fitChartTableColumns(STRIKE_COLUMNS, width, STRIKE_DROP_ORDER), [width]);
533
+ const [selected, setSelected] = usePluginPaneState<string | null>("flow:strike", null);
534
+ const selectedId = rows.some((row) => row.id === selected) ? selected : rows[0]?.id ?? null;
535
+ return (
536
+ <>
537
+ {query}
538
+ <DataTableView<FlowStrikeRow, DataTableColumn & { id: FlowStrikeSort }>
226
539
  focused={focused}
227
- selection={{
228
- kind: "id",
229
- selectedId,
230
- getId: (event) => event.id,
231
- onChange: (_id, event) => handleSelect(event),
232
- }}
233
540
  rootWidth={width}
234
541
  rootHeight={Math.max(2, height - 1)}
235
542
  columns={columns}
236
- items={events}
237
- sortColumnId={null}
238
- sortDirection="desc"
239
- getItemKey={eventKey}
240
- onActivate={(event) => pinTicker(event.underlying, { floating: true, paneType: TICKER_RESEARCH_PANE_ID })}
241
- renderCell={renderCell}
543
+ items={rows}
544
+ sortColumnId={sort.column}
545
+ sortDirection={sort.direction}
546
+ onHeaderClick={(column) => setSort((current) => ({
547
+ column: column as FlowStrikeSort,
548
+ direction: current.column === column && current.direction === "desc" ? "asc" : "desc",
549
+ }))}
550
+ getItemKey={strikeKey}
551
+ selection={{ kind: "id", selectedId, getId: strikeKey, onChange: (id) => setSelected(id) }}
552
+ onActivate={props.onOpenChain}
553
+ renderCell={renderStrikeCell}
242
554
  selectedTextOverridesCellColor
243
- emptyContent={
244
- !feed.payload
245
- ? <PaneStatusBody loading loadingLabel="Waiting for the scanner..." />
246
- : events.length === 0 && history.loading
247
- ? <PaneStatusBody loading loadingLabel="Loading recorded prints..." />
248
- : undefined
249
- }
250
- emptyStateTitle={emptyState.title}
251
- emptyStateHint={emptyState.hint}
252
- onRootKeyDown={handleRootKeyDown}
253
- onBodyScrollActivity={onBodyScrollActivity}
254
- scrollRef={scrollRef}
555
+ status={sessionStatus(props, `${props.symbol} strikes`)}
556
+ emptyStateTitle={`No ${props.symbol} prints under these filters this session.`}
255
557
  />
256
- </Box>
558
+ </>
257
559
  );
258
560
  }
259
561