@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
@@ -5,6 +5,7 @@ import type { KpiObservation } from "../../../api-client/company-kpis";
5
5
  import { listingAbroad } from "../../../api-client/paths";
6
6
  import { ChartTableHeader, DataTableStackView, DetailScrollBody, EmptyState, KeyValueRow, PaneStatusBody, QueryBar, SectionHeading,
7
7
  useChartTableSelection, usePaneMenuItems, usePaneNoticeFooter, usePaneStatusFooter, usePaneTabs, type DataTableCell, type PaneHint } from "../../../components";
8
+ import { useCompanyDataFooter } from "../../../components/layout/pane/status-footer";
8
9
  import { usePaneRefreshKey } from "../../../components/data-table/table-pane";
9
10
  import { useAsyncResource, useAutoRefresh, usePaneSettingValue, usePluginAppActions, usePluginPaneState } from "../../../public/react";
10
11
  import { usePaneTickerIdentity } from "../../../state/hooks/pane-ticker";
@@ -20,7 +21,7 @@ import { Blurred, LockedOverlay, UpgradeLabel } from "../shared/locked-rows";
20
21
  import { isCloudSessionRequired, useResearchCloudSession } from "../shared/research-cloud-session";
21
22
  import { cachedCompanyData, loadCompanyData, KPI_UNAVAILABLE, validateCompanyData, type CompanyDataset, type CompanyMode } from "./client";
22
23
  import { useCompanyEvidence } from "./screenshot-evidence";
23
- import { allGuidance, allObservations, basisLabel, changeText, changeTone, companyCellText, companyColumns, dimensionLabel, directionLabel, directionTone, guideGroupKey, guidanceSeries, fiscalLabel,
24
+ import { allGuidance, allObservations, basisLabel, changeText, changeTone, companyCellText, companyColumns, companyEmptyState, dimensionLabel, directionLabel, directionTone, fitCompanyColumns, guideGroupKey, guidanceSeries, fiscalLabel,
24
25
  isGuidance, exactObservation, observationValue, observationChange, observationChart, periodOrder, outcomeLabel, QUIET_COLUMNS, rangeText, scopeWords, seriesDetail, seriesLabel, seriesLabels, seriesName,
25
26
  seriesOrder, unitLabel, valueText, companyPreviewNote, type CompanyRow } from "./model";
26
27
  import { PriceSparkline } from "../../../components/price-sparkline/view";
@@ -47,7 +48,7 @@ function DisclosureEvidence({ row, sourceId, onSource, width }: { row: CompanyRo
47
48
  <KeyValueRow label="Revision" value={`${row.revision}${row.current ? " · current" : " · superseded"}${!isGuidance(row) && (row.conflict || row.contested) ? " · conflicting disclosures" : ""}`} labelWidth={18} />
48
49
  {row.revisionReason ? <KeyValueRow label="Correction" value={row.revisionReason} labelWidth={18} /> : null}
49
50
  {isGuidance(row) ? <>
50
- <KeyValueRow label="Issued" value={row.issuedDate} detail={directionLabel(row.direction)} labelWidth={18} />
51
+ <KeyValueRow label="Issued" value={row.issuedDate} detail={directionLabel(row)} labelWidth={18} />
51
52
  <KeyValueRow label="Source range" value={row.rangeText} labelWidth={18} />
52
53
  {row.conditions ? <KeyValueRow label="Conditions" value={row.conditions} labelWidth={18} /> : null}
53
54
  <KeyValueRow label="Later actual" value={row.actual ? valueText(row, row.actual.value) : "Pending"} detail={row.actual ? outcomeLabel(row) : undefined} labelWidth={18} />
@@ -130,7 +131,7 @@ function CompanyView({ symbol, exchange, name, mode, width, height, focused }: P
130
131
  : sort.column === "actual" ? guide?.actual?.value ?? null : sort.column === "outcome" ? guide ? outcomeLabel(guide) : null
131
132
  : sort.column === "basis" ? row.basis : sort.column === "unit" ? unitLabel(row) : sort.column === "scope" ? dimensionLabel(row.dimensions)
132
133
  : sort.column === "published" ? row.evidence[0]!.publishedAt : sort.column === "revision" ? row.revision
133
- : sort.column === "quote" ? row.evidence[0]!.quote : sort.column === "change" ? guide ? guide.direction : observationChange(row as KpiObservation, observations).value
134
+ : sort.column === "quote" ? row.evidence[0]!.quote : sort.column === "change" ? guide ? directionLabel(guide) : observationChange(row as KpiObservation, observations).value
134
135
  : periodOrder(row);
135
136
  };
136
137
  const direction = sort.direction === "asc" ? 1 : -1;
@@ -158,8 +159,13 @@ function CompanyView({ symbol, exchange, name, mode, width, height, focused }: P
158
159
  }
159
160
  return map;
160
161
  }, [kpis]);
161
- const columns = useMemo(() => withoutQuietColumns(companyColumns(mode === "guidance", tab === "evidence", tab === "history", mode === "kpis" && tab === "table" && trends.size > 0 && width >= 100, desktop),
162
- rows, (row, id) => id === "basis" ? basisLabel(row.basis) : id === "scope" ? dimensionLabel(row.dimensions) || "Consolidated" : "", QUIET_COLUMNS), [mode, tab, trends, rows, width, desktop]);
162
+ const columns = useMemo(() => {
163
+ const shown = withoutQuietColumns(companyColumns(mode === "guidance", tab === "evidence", tab === "history", mode === "kpis" && tab === "table" && trends.size > 0 && width >= 100, desktop),
164
+ rows, (row, id) => id === "basis" ? basisLabel(row.basis) : id === "scope" ? dimensionLabel(row.dimensions) || "Consolidated" : "", QUIET_COLUMNS);
165
+ // Evidence is a wide ledger that scrolls; the other tabs fit the pane.
166
+ return tab === "evidence" ? shown : fitCompanyColumns(shown, width, tab === "history");
167
+ }, [mode, tab, trends, rows, width, desktop]);
168
+ const emptyState = companyEmptyState(mode, symbol ?? "", { tab, hasRows: allRows.length > 0 });
163
169
  const metricOptions = useMemo(() => [{ value: "all", label: "All metrics" }, ...[...new Map(allRows.map((row) => [row.metricId, { value: row.metricId, label: row.metric.name }])).values()]], [allRows]);
164
170
  const kpiChart = useMemo(() => observationChart(rows.filter((row): row is KpiObservation => !isGuidance(row)), colors.warning), [rows, colors.warning]);
165
171
  const series = useMemo(() => tab !== "chart" ? [] : mode === "kpis" ? kpiChart.series
@@ -190,6 +196,8 @@ function CompanyView({ symbol, exchange, name, mode, width, height, focused }: P
190
196
  usePaneNoticeFooter({ registrationId: "company-kpis:notices", focused, notices: [resource.data?.refreshError,
191
197
  data?.truncated && data.access === "full" ? "The response reached its stored-record limit. Narrow the metric or date range in a CLI or REST query." : null,
192
198
  data?.coverage.conflicts ? "Conflicting disclosures are retained in Evidence and excluded from comparable trends." : null].filter((value): value is string => !!value) });
199
+ // A receipt's or foreign line's disclosures are its company's, read from the home line.
200
+ useCompanyDataFooter(data?.companyFrom);
193
201
  if (!symbol) return <EmptyState title="Select a ticker." />;
194
202
  if (signInWall) return <SignInWall placement="company-kpis-signin" action={mode === "guidance" ? "view company guidance" : "view company KPIs"} needsVerification={session.needsVerification} />;
195
203
  const bodyHeight = Math.max(3, height - tabRows);
@@ -212,7 +220,7 @@ function CompanyView({ symbol, exchange, name, mode, width, height, focused }: P
212
220
  return { text: "", content: <PriceSparkline priceHistory={points} width={column.width} period="all" trend={tone === "muted" ? "neutral" : tone} /> };
213
221
  }
214
222
  if (column.id === "change") {
215
- if (guide) return { text: directionLabel(guide.direction), color: toneColor(directionTone(guide.direction), colors), keepColorWhenSelected: guide.direction === "raised" || guide.direction === "cut" };
223
+ if (guide) return { text: directionLabel(guide), color: toneColor(directionTone(guide), colors), keepColorWhenSelected: guide.direction === "raised" || guide.direction === "cut" };
216
224
  const change = observationChange(row as KpiObservation, observations);
217
225
  return { text: changeText(change), value: change.value, color: toneColor(changeTone(row as KpiObservation, change.value), colors) };
218
226
  }
@@ -244,7 +252,7 @@ function CompanyView({ symbol, exchange, name, mode, width, height, focused }: P
244
252
  detailScrollRef={detailScrollRef} detailContent={openRow ? <DetailScrollBody ref={detailScrollRef} resetScrollKey={openRow.id}><DisclosureEvidence row={openRow} sourceId={sourceId} onSource={setSource} width={width} /></DetailScrollBody> : null}
245
253
  sortColumnId={sort.column} sortDirection={sort.direction} onHeaderClick={(column) => setSort((previous) => ({ column, direction: previous.column === column && previous.direction === "desc" ? "asc" : "desc" }))}
246
254
  resetScrollKey={`${mode}:${symbol}:${tab}:${metric}:${activePlot}`}
247
- emptyStateTitle={tab === "history" ? "Actuals have not been matched to these guidance periods yet." : "No disclosed observations for this selection yet."}
255
+ emptyStateTitle={emptyState.title} emptyStateHint={emptyState.hint}
248
256
  rootBefore={<ChartTableHeader width={width} height={bodyHeight} tableRows={items.length} tableColumns={columns} query={query}
249
257
  chart={tab === "chart" ? { series, ...(mode === "kpis" ? { xAxis: kpiChart.xAxis, viewport: kpiChart.viewport } : {}), empty: preview ? "Full history is available with Pro." : "History is accumulating. Two comparable disclosures are needed for a trend.",
250
258
  formatValue: (value) => selected ? `${valueText(selected, value)}${selected.currency ? ` ${unitLabel(selected)}` : ""}` : String(value),
@@ -14,6 +14,7 @@ import { portfolioAnalyticsModule } from "./analytics";
14
14
  import { brokerTradingModule } from "./broker-trading";
15
15
  import { brokerManagerModule } from "./broker-manager";
16
16
  import { changelogModule } from "./changelog";
17
+ import { roadmapModule } from "./roadmap";
17
18
  import { connectionsModule } from "./connections";
18
19
  import { pluginMarketplaceModule } from "./plugin-marketplace";
19
20
  import { relativeRotationModule } from "./relative-rotation";
@@ -116,7 +117,7 @@ import { FIXED_INCOME_HOSTS } from "./fixed-income/model";
116
117
 
117
118
  export const applicationPlugin = composeBuiltinPlugin({
118
119
  ...applicationPluginMeta,
119
- modules: [layoutManagerModule, pluginMarketplaceModule, helpModule, changelogModule, connectionsModule, docsModule],
120
+ modules: [layoutManagerModule, pluginMarketplaceModule, helpModule, changelogModule, connectionsModule, docsModule, roadmapModule],
120
121
  });
121
122
 
122
123
  export const portfolioPlugin = composeBuiltinPlugin({
@@ -8,8 +8,9 @@ import { buildCorrelationMatrix, buildCorrelationSeries, buildGeoCorrelationSeri
8
8
  import { geoSeriesToken, loadGeoCorrelationHistory } from "./geo";
9
9
  import { buildRelationshipAnalysis, DEFAULT_RELATIONSHIP_SECOND_SYMBOL } from "./relationship/model";
10
10
  import { paneSchemas } from "./headless-schema";
11
- import { CORRELATION_RETURN_BASIS, GEO_CORRELATION_CHANGE_BASIS, loadCorrelationHistory } from "./history";
11
+ import { CORRELATION_RETURN_BASIS, GEO_CORRELATION_CHANGE_BASIS, loadCorrelationHistory, unfinishedBarFreshness } from "./history";
12
12
  import type { HeadlessPaneContext } from "../../../types/headless";
13
+ import type { PricePoint } from "../../../types/financials";
13
14
  import { newestReportTime } from "../../../utils/utc-time";
14
15
  import { DAILY_CLOSES } from "../shared/report-freshness";
15
16
 
@@ -34,9 +35,13 @@ async function loadListedHistory(ctx: HeadlessPaneContext, key: string, range: T
34
35
  }
35
36
 
36
37
  /** A ticker's daily closes, or a map series' daily values read straight from the Cloud client. */
37
- async function loadCorrelationInput(ctx: HeadlessPaneContext, key: string, range: TimeRange): Promise<CorrelationSeries> {
38
+ async function loadCorrelationInput(ctx: HeadlessPaneContext, key: string, range: TimeRange, onBars?: (bars: PricePoint[]) => void): Promise<CorrelationSeries> {
38
39
  const geo = geoSeriesToken(key);
39
- if (!geo) return buildCorrelationSeries(key, await loadHistory(ctx, key, range));
40
+ if (!geo) {
41
+ const bars = await loadHistory(ctx, key, range);
42
+ onBars?.(bars);
43
+ return buildCorrelationSeries(key, bars);
44
+ }
40
45
  const request = <T,>(path: string, init?: RequestInit) => ctx.apiClient.geo<T>(path, { ...init, signal: init?.signal ?? ctx.signal });
41
46
  return buildGeoCorrelationSeries(key, await loadGeoCorrelationHistory(request, geo, range, ctx.signal));
42
47
  }
@@ -53,7 +58,8 @@ export const correlationHeadless: HeadlessPaneDefinition<"rows"> = {
53
58
  ],
54
59
  async load({ symbols, options }, ctx) {
55
60
  const range = (options.rangePreset ?? "1Y") as TimeRange;
56
- const loaded = await loadHeadlessSymbols(symbols, ctx, (symbol) => loadCorrelationInput(ctx, symbol, range));
61
+ const bars = new Map<string, PricePoint[]>();
62
+ const loaded = await loadHeadlessSymbols(symbols, ctx, (symbol) => loadCorrelationInput(ctx, symbol, range, (points) => bars.set(symbol, points)));
57
63
  const bySymbol = new Map(loaded.entries.map(({ symbol, data }) => [symbol, data]));
58
64
  const matrix = buildCorrelationMatrix(symbols, bySymbol);
59
65
  const rows = symbols.flatMap((left, index) => symbols.slice(index + 1).map((right) => {
@@ -68,8 +74,9 @@ export const correlationHeadless: HeadlessPaneDefinition<"rows"> = {
68
74
  ? "Inconsistent OHLC history"
69
75
  : row.sampleSize < 5 ? "Insufficient shared return observations" : "Zero return variance",
70
76
  }));
77
+ const asOf = newestReportTime(symbols.map((symbol) => bySymbol.get(symbol)?.prices.at(-1)?.dateKey));
71
78
  return {
72
- freshness: { asOf: newestReportTime(symbols.map((symbol) => bySymbol.get(symbol)?.prices.at(-1)?.dateKey)) },
79
+ freshness: { asOf, ...unfinishedBarFreshness([...bars.values()], asOf?.slice(0, 10)) },
73
80
  rows, unavailableSymbols,
74
81
  errors: [
75
82
  ...loaded.errors,
@@ -132,6 +139,7 @@ export const relationshipHeadless: HeadlessPaneDefinition<"series"> = {
132
139
  })),
133
140
  { key: "returnCount", label: "Shared returns", value: analysis.stats?.sampleSize ?? analysis.returns.length },
134
141
  ],
142
+ freshness: unfinishedBarFreshness([...histories.values()], analysis.aligned.at(-1)?.dateKey) ?? {},
135
143
  unavailableSymbols: [...new Set(unavailableSymbols)],
136
144
  errors: [...loaded.errors, ...(analysis.unavailableReason ? [analysis.unavailableReason] : []),
137
145
  ...(analysis.correlationUnavailableReason ? [analysis.correlationUnavailableReason] : [])],
@@ -2,6 +2,9 @@ import type { AssetDataProvider, MarketDataRequestContext } from "../../../types
2
2
  import type { TimeRange } from "../../../time-series/range";
3
3
  import type { ManualChartResolution } from "../../../time-series/resolution";
4
4
  import { clipPriceHistoryToRange } from "../../../time-series/history-window";
5
+ import { DAY_SO_FAR_TEXT } from "../../../time-series/session-bar";
6
+ import type { PricePoint } from "../../../types/financials";
7
+ import type { HeadlessPaneFreshness } from "../../../types/headless";
5
8
 
6
9
  /** Risk views need daily closes even when a chart preset defaults to intraday or weekly bars. */
7
10
  export const CORRELATION_HISTORY_RESOLUTION: ManualChartResolution = "1d";
@@ -16,6 +19,25 @@ export async function loadCorrelationHistory(
16
19
  return clipPriceHistoryToRange(points, range);
17
20
  }
18
21
 
22
+ /**
23
+ * What an analysis of daily closes says when its newest day is a bar the source
24
+ * has not finished: a coin's UTC day or a future's session under way
25
+ * (`partial`), or a future's last trade before its settlement (`unsettled`).
26
+ * That day is no close, so the line calls the bars daily bars and says so, as
27
+ * `history` does, instead of dating a close by it. Nothing when `day` (the
28
+ * newest date the analysis uses) is a finished bar in every history.
29
+ */
30
+ export function unfinishedBarFreshness(
31
+ histories: ReadonlyArray<ReadonlyArray<Pick<PricePoint, "date" | "partial" | "unsettled">> | undefined>,
32
+ day: string | null | undefined,
33
+ ): Pick<HeadlessPaneFreshness, "basis" | "note"> | null {
34
+ if (!day) return null;
35
+ const bars = histories.flatMap((history) => history?.filter((point) => new Date(point.date).toISOString().slice(0, 10) === day) ?? []);
36
+ if (bars.some((bar) => bar.partial)) return { basis: "daily bars", note: `the newest bar is ${DAY_SO_FAR_TEXT}` };
37
+ if (bars.some((bar) => bar.unsettled)) return { basis: "daily bars", note: "the newest bar is not yet settled" };
38
+ return null;
39
+ }
40
+
19
41
  export const CORRELATION_RETURN_BASIS = "Local-price close-to-close returns between shared UTC dates; cash distributions and FX conversion are excluded, and exchange closing times may differ.";
20
42
 
21
43
  export const GEO_CORRELATION_CHANGE_BASIS = "Map series (GEO:) use the change in daily value between the same shared UTC dates, because counts such as transits can be zero.";
@@ -1,5 +1,5 @@
1
1
  import { apiClient } from "../../../api-client";
2
- import type { CotBoardPayload, CotClass, CotClassSummary, CotContractPayload, CotFamily, CotPayloadBase } from "../../../api-client/cot";
2
+ import type { CotBoardPayload, CotClass, CotClassSummary, CotContractPayload, CotFamily, CotPayloadBase, CotPositions } from "../../../api-client/cot";
3
3
  import { createPluginCache } from "../../../data/plugin-cache";
4
4
  import { cachedCloudResource, loadCloudResource, unavailableOnServer } from "../shared/cloud-resource";
5
5
  import type { PricePoint } from "../../../types/financials";
@@ -12,8 +12,8 @@ const date = (value: unknown): value is string => typeof value === "string" && /
12
12
  const number = (value: unknown) => value === null || typeof value === "number" && Number.isFinite(value);
13
13
  const count = (value: unknown) => value === null || typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
14
14
  const code = (value: unknown) => typeof value === "string" && /^[0-9A-Z]{5}[0-9A-Z+]$/.test(value);
15
- function base(data: CotPayloadBase, family: CotFamily) {
16
- if (!data || data.source !== "CFTC" || data.scope !== "futures-only" || data.reportFamily !== family
15
+ function base(data: CotPayloadBase, family: CotFamily, positions: CotPositions) {
16
+ if (!data || data.source !== "CFTC" || data.scope !== positions || data.reportFamily !== family
17
17
  || !Array.isArray(data.gaps) || !data.gaps.every((gap) => typeof gap === "string")
18
18
  || !Array.isArray(data.classes) || data.classes.some((row) => !COT_CLASSES[family].some((option) => option.value === row.id))
19
19
  || data.asOf !== null && !date(data.asOf) || !Number.isFinite(Date.parse(data.generatedAt))
@@ -33,8 +33,8 @@ function summary(row: CotClassSummary, family: CotFamily) {
33
33
  }
34
34
  }
35
35
  }
36
- function validateCotBoard(data: CotBoardPayload, family: CotFamily, traderClass: CotClass) {
37
- base(data, family);
36
+ function validateCotBoard(data: CotBoardPayload, family: CotFamily, traderClass: CotClass, positions: CotPositions) {
37
+ base(data, family, positions);
38
38
  if (!Array.isArray(data.rows) || data.traderClass !== traderClass) throw new Error("The server returned an invalid COT board");
39
39
  const seen = new Set<string>();
40
40
  for (const row of data.rows) {
@@ -44,8 +44,8 @@ function validateCotBoard(data: CotBoardPayload, family: CotFamily, traderClass:
44
44
  }
45
45
  return data;
46
46
  }
47
- export function validateCotContract(data: CotContractPayload, family: CotFamily, contractCode: string) {
48
- base(data, family);
47
+ export function validateCotContract(data: CotContractPayload, family: CotFamily, contractCode: string, positions: CotPositions = "futures-only") {
48
+ base(data, family, positions);
49
49
  if (data.contract !== null && data.contract.contractCode !== contractCode || !Array.isArray(data.positions) || !Array.isArray(data.history)) {
50
50
  throw new Error("The server returned an invalid COT contract");
51
51
  }
@@ -63,30 +63,34 @@ export function validateCotContract(data: CotContractPayload, family: CotFamily,
63
63
  return data;
64
64
  }
65
65
  const unavailable = (error: unknown) => unavailableOnServer(error, "COT history is not available yet.", [404, 503]);
66
- export async function fetchCotBoard(family: CotFamily, traderClass: CotClass, client: Pick<typeof apiClient, "getCloudCotBoard"> = apiClient) {
67
- try { return validateCotBoard(await client.getCloudCotBoard(family, traderClass), family, traderClass); }
66
+ export async function fetchCotBoard(family: CotFamily, traderClass: CotClass, client: Pick<typeof apiClient, "getCloudCotBoard"> = apiClient, positions: CotPositions = "futures-only") {
67
+ try { return validateCotBoard(await client.getCloudCotBoard(family, traderClass, positions), family, traderClass, positions); }
68
68
  catch (error) { throw unavailable(error); }
69
69
  }
70
- const cotBoardKey = (family: CotFamily, traderClass: CotClass) => `${family}:${traderClass}`;
70
+ // Futures-only boards keep the key they always had, so a cached board still opens.
71
+ const cotBoardKey = (family: CotFamily, traderClass: CotClass, positions: CotPositions) =>
72
+ positions === "combined" ? `${family}:${traderClass}:combined` : `${family}:${traderClass}`;
71
73
  const presentCotBoard = (payload: CotBoardPayload, stale: boolean, refreshError: string | null) => ({
72
74
  ...payload, gaps: [...payload.gaps, ...(refreshError ? [refreshError] : [])],
73
75
  status: stale && payload.status === "available" ? "partial" as const : payload.status,
74
76
  });
75
- export function cachedCotBoard(family: CotFamily, traderClass: CotClass) {
76
- const cached = cachedCloudResource(cotBoardCache, cotBoardKey(family, traderClass), (payload) => validateCotBoard(payload, family, traderClass));
77
+ export function cachedCotBoard(family: CotFamily, traderClass: CotClass, positions: CotPositions = "futures-only") {
78
+ const cached = cachedCloudResource(cotBoardCache, cotBoardKey(family, traderClass, positions), (payload) => validateCotBoard(payload, family, traderClass, positions));
77
79
  return cached ? presentCotBoard(cached.payload, false, null) : null;
78
80
  }
79
- export async function loadCotBoard(family: CotFamily, traderClass: CotClass, force = false) {
80
- const { payload, stale, refreshError } = await loadCloudResource(cotBoardCache, cotBoardKey(family, traderClass), () => fetchCotBoard(family, traderClass), { force });
81
+ export async function loadCotBoard(family: CotFamily, traderClass: CotClass, force = false, positions: CotPositions = "futures-only") {
82
+ const { payload, stale, refreshError } = await loadCloudResource(cotBoardCache, cotBoardKey(family, traderClass, positions),
83
+ () => fetchCotBoard(family, traderClass, apiClient, positions), { force });
81
84
  return presentCotBoard(payload, stale, refreshError);
82
85
  }
83
- export async function fetchCotContract(contractCode: string, family: CotFamily, client: Pick<typeof apiClient, "getCloudCotContract"> = apiClient) {
84
- try { return validateCotContract(await client.getCloudCotContract(contractCode, family), family, contractCode); }
86
+ export async function fetchCotContract(contractCode: string, family: CotFamily, client: Pick<typeof apiClient, "getCloudCotContract"> = apiClient, positions: CotPositions = "futures-only") {
87
+ try { return validateCotContract(await client.getCloudCotContract(contractCode, family, positions), family, contractCode, positions); }
85
88
  catch (error) { throw unavailable(error); }
86
89
  }
87
90
  export interface CotDetailData { payload: CotContractPayload; price: PricePoint[]; priceSymbol: string | null; priceAsOf: string | null; priceWarning: string | null }
88
- export async function loadCotDetail(contractCode: string, family: CotFamily, client: Pick<typeof apiClient, "getCloudCotContract" | "getCloudHistory"> = apiClient): Promise<CotDetailData> {
89
- const payload = await fetchCotContract(contractCode, family, client);
91
+ export async function loadCotDetail(contractCode: string, family: CotFamily, client: Pick<typeof apiClient, "getCloudCotContract" | "getCloudHistory"> = apiClient,
92
+ positions: CotPositions = "futures-only"): Promise<CotDetailData> {
93
+ const payload = await fetchCotContract(contractCode, family, client, positions);
90
94
  const mapping = cotPriceMapping(contractCode);
91
95
  // An unmapped market simply has no price panel; that is not a data failure.
92
96
  if (!mapping?.priceSymbol) return { payload, price: [], priceSymbol: null, priceAsOf: null, priceWarning: null };
@@ -1,7 +1,7 @@
1
1
  import type { CotClass, CotClassSummary, CotContractPayload, CotHistoryPoint } from "../../../api-client/cot";
2
2
  import type { HeadlessPaneColumn, HeadlessPaneDefinition } from "../../../types/plugin";
3
3
  import { fetchCotBoard, fetchCotContract } from "./client";
4
- import { COT_MAJOR_CODES, COT_SCOPES, cotClass, cotContractCode, cotContractUnitText, cotInteger, cotMarketName, cotScope, cotUnknownMarketMessage } from "./model";
4
+ import { COT_CLASSES, COT_FAMILIES, COT_MAJOR_CODES, COT_POSITIONS, COT_SCOPES, cotClass, cotContractCode, cotContractUnitText, cotFamily, cotInteger, cotMarketName, cotPositions, cotPositionsText, cotScope, cotUnknownMarketMessage } from "./model";
5
5
  import { COMMODITIES_VIEW_STATE } from "../commodities-energy/view-state";
6
6
  import { REPORT_ROWS } from "../shared/headless-options";
7
7
 
@@ -24,9 +24,11 @@ function cotPositionRow(row: CotClassSummary) {
24
24
  */
25
25
  function cotContractTitle(data: CotContractPayload, code: string): string {
26
26
  const units = cotContractUnitText(data.contract?.contractUnits);
27
- return [data.contract ? cotMarketName(data.contract.marketName) : code, data.asOf ? `report date ${data.asOf}` : null, units]
28
- .filter(Boolean).join(" · ");
27
+ return [data.contract ? cotMarketName(data.contract.marketName) : code, data.asOf ? `report date ${data.asOf}` : null, units,
28
+ data.scope === "combined" ? cotPositionsText(data.scope) : null].filter(Boolean).join(" · ");
29
29
  }
30
+ /** Every trader class of every report, each once, in the reports' order. */
31
+ const TRADER_CLASSES = [...new Set(Object.values(COT_CLASSES).flat().map(({ value }) => value))];
30
32
 
31
33
  /** The weekly reports newest first, the newest `limit` of them when given. */
32
34
  function cotWeeklyRows(history: readonly CotHistoryPoint[], limit: number | null): CotHistoryPoint[] {
@@ -47,22 +49,25 @@ export const cotHeadless: HeadlessPaneDefinition<"bundle"> = {
47
49
  // Tuesday positions published the Friday after; two weeks without a new report means one was missed.
48
50
  freshness: { source: "CFTC", status: "not-a-feed", basis: "weekly report", maxAgeMinutes: 14 * 24 * 60 },
49
51
  argument: { kind: "free-text", optional: true, placeholder: "code or name", description: "Futures root (KC), commodity name (coffee) or CFTC market code; omit for the extremes board." },
50
- options: [{ key: "report", type: "enum", values: [{ value: "legacy" }, { value: "disaggregated" }], defaultValue: "legacy", description: "CFTC report family." },
51
- { key: "traderClass", type: "enum", values: ["noncommercial", "commercial", "producer", "swap", "managed-money", "other-reportable", "nonreportable"].map((value) => ({ value })), description: "Trader class for the extremes board; defaults by report family.",
52
+ options: [{ key: "report", type: "enum", values: COT_FAMILIES.map(({ value }) => ({ value })), defaultValue: "legacy", description: "CFTC report: legacy, disaggregated, or tff (Traders in Financial Futures)." },
53
+ { key: "traderClass", type: "enum", values: TRADER_CLASSES.map((value) => ({ value })), description: "Trader class for the extremes board; defaults by report family.",
52
54
  pluginState: { pluginId: COMMODITIES_VIEW_STATE.cot, key: "class" } },
55
+ { key: "positions", type: "enum", values: COT_POSITIONS.map(({ value }) => ({ value })), defaultValue: "futures-only", description: "Futures positions only, or futures and options combined, as in the pane.",
56
+ pluginState: { pluginId: COMMODITIES_VIEW_STATE.cot, key: "positions" } },
53
57
  { key: "scope", type: "enum", values: COT_SCOPES.map(({ value }) => ({ value })), defaultValue: "major", description: "Markets on the extremes board, as in the pane.",
54
58
  pluginState: { pluginId: COMMODITIES_VIEW_STATE.cot, key: "scope" } },
55
59
  // Weekly reports come newest first, so --tail asks for the same newest n as --limit.
56
60
  { key: "limit", type: "integer", aliases: ["tail", "weeks", "rows"], minimum: 1, maximum: 2000, shot: REPORT_ROWS,
57
61
  description: "Newest weekly reports a market lists; every one by default." }],
58
- discovery: { aliases: ["COT", "CFTC"], dataRequirements: ["Gloom Cloud CFTC history"], limitations: ["Futures-only positions aggregate all maturities.", "Legacy and disaggregated classes differ.", "Observation date is not publication date.", "Some markets have short or suppressed histories."] },
62
+ discovery: { aliases: ["COT", "CFTC"], dataRequirements: ["Gloom Cloud CFTC history"], limitations: ["Positions aggregate all maturities; combined adds options, delta-adjusted.", "Legacy, disaggregated and TFF classes differ.", "Observation date is not publication date.", "Some markets have short or suppressed histories."] },
59
63
  describe: "CFTC positioning",
60
64
  async load(args, ctx) {
61
- const family = args.options.report === "disaggregated" ? "disaggregated" : "legacy";
65
+ const family = cotFamily(args.options.report);
66
+ const positions = cotPositions(args.options.positions);
62
67
  if (args.argument) {
63
68
  const code = cotContractCode(args.argument);
64
69
  if (!code) throw new Error(cotUnknownMarketMessage(args.argument));
65
- const data = await fetchCotContract(code, family, ctx.apiClient);
70
+ const data = await fetchCotContract(code, family, ctx.apiClient, positions);
66
71
  const classes = data.positions.map((row) => row.id);
67
72
  const limit = args.options.limit == null ? null : Number(args.options.limit);
68
73
  const weeks = cotWeeklyRows(data.history, limit);
@@ -80,9 +85,9 @@ export const cotHeadless: HeadlessPaneDefinition<"bundle"> = {
80
85
  const traderClass = cotClass(family, args.options.traderClass);
81
86
  if (args.options.traderClass && traderClass !== args.options.traderClass) throw new Error("Trader class does not belong to this report family");
82
87
  const scope = cotScope(args.options.scope);
83
- const data = await fetchCotBoard(family, traderClass, ctx.apiClient);
88
+ const data = await fetchCotBoard(family, traderClass, ctx.apiClient, positions);
84
89
  const rows = data.rows.filter((row) => scope === "all" || COT_MAJOR_CODES.has(row.contractCode));
85
- return { sections: [{ title: "Positioning extremes",
90
+ return { sections: [{ title: positions === "combined" ? `Positioning extremes · ${cotPositionsText(positions)}` : "Positioning extremes",
86
91
  columns: [{ key: "marketName", header: "Market" }, { key: "contractCode", header: "Code" }, ...POSITION_COLUMNS,
87
92
  { key: "openInterest", header: "Open interest", align: "right", format: count }, { key: "reportDate", header: "As of" }],
88
93
  rows: rows.map((row) => ({ contractCode: row.contractCode, marketName: cotMarketName(row.marketName), reportDate: row.reportDate, openInterest: row.openInterest, ...cotPositionRow(row.position) })) }],
@@ -1,4 +1,4 @@
1
- import type { CotClass, CotContractPayload, CotFamily, CotHistoryPoint } from "../../../api-client/cot";
1
+ import type { CotClass, CotContractPayload, CotFamily, CotHistoryPoint, CotPositions } from "../../../api-client/cot";
2
2
  import { staticSeries } from "../../../components/chart/static/series";
3
3
  import type { ResolvedSeries } from "../../../time-series/types";
4
4
  import type { PricePoint } from "../../../types/financials";
@@ -10,7 +10,20 @@ import { bestNameMatch, FUTURES_CONTRACTS, futuresBoardContract, futuresBoardCon
10
10
  export const COT_CLASSES: Record<CotFamily, Array<{ value: CotClass; label: string }>> = {
11
11
  legacy: [{ value: "noncommercial", label: "Noncommercial" }, { value: "commercial", label: "Commercial" }, { value: "nonreportable", label: "Nonreportable" }],
12
12
  disaggregated: [{ value: "managed-money", label: "Managed Money" }, { value: "producer", label: "Producer/Merchant" }, { value: "swap", label: "Swap Dealers" }, { value: "other-reportable", label: "Other Reportables" }, { value: "nonreportable", label: "Nonreportable" }],
13
+ tff: [{ value: "leveraged-funds", label: "Leveraged Funds" }, { value: "asset-manager", label: "Asset Manager/Institutional" }, { value: "dealer", label: "Dealer/Intermediary" }, { value: "other-reportable", label: "Other Reportables" }, { value: "nonreportable", label: "Nonreportable" }],
13
14
  };
15
+ /** The three CFTC reports, as the pane's tabs and `--report` name them. */
16
+ export const COT_FAMILIES: ReadonlyArray<{ value: CotFamily; label: string }> = [
17
+ { value: "legacy", label: "Legacy" }, { value: "disaggregated", label: "Disaggregated" }, { value: "tff", label: "Financial (TFF)" },
18
+ ];
19
+ export function cotFamily(value: unknown): CotFamily { return value === "disaggregated" || value === "tff" ? value : "legacy"; }
20
+ /** Futures alone, as before, or futures and options combined. */
21
+ export const COT_POSITIONS: ReadonlyArray<{ value: CotPositions; label: string }> = [
22
+ { value: "futures-only", label: "Futures only" }, { value: "combined", label: "Futures + options" },
23
+ ];
24
+ export function cotPositions(value: unknown): CotPositions { return value === "combined" ? "combined" : "futures-only"; }
25
+ /** How a status line names the positions counted: "futures only" or "futures + options". */
26
+ export const cotPositionsText = (positions: CotPositions) => positions === "combined" ? "futures + options" : "futures only";
14
27
  const ROOTS: Record<string, { code: string; exchange: string; priceSymbol: string | null; name: string; crypto?: true }> = {
15
28
  ZN: { code: "043602", exchange: "CBT", priceSymbol: "ZN=F", name: "10-Year T-Note" },
16
29
  ZQ: { code: "045601", exchange: "CBT", priceSymbol: "ZQ=F", name: "30-Day Fed Funds" },
@@ -6,16 +6,15 @@ import { useThemeColors } from "../../../theme/theme-context";
6
6
  import type { PaneProps } from "../../../types/plugin";
7
7
  import type { ResolvedSeries } from "../../../time-series/types";
8
8
  import type { CompositeAxisDomain } from "../../../components/chart/composite/types";
9
- import type { CotBoardRow, CotClass, CotClassSummary, CotFamily } from "../../../api-client/cot";
9
+ import type { CotBoardRow, CotClass, CotClassSummary, CotFamily, CotPositions } from "../../../api-client/cot";
10
10
  import { isAccessDenied } from "../../../api-client/errors";
11
11
  import { Box } from "../../../ui";
12
12
  import { useAutoRefresh } from "../../../react/auto-refresh";
13
13
  import { compareSortValues, nextHeaderSort, type SortPreference } from "../../../utils/sort-values";
14
14
  import { useResearchCloudSession } from "../shared/research-cloud-session";
15
15
  import { cachedCotBoard, loadCotBoard, loadCotDetail } from "./client";
16
- import { COT_CLASSES, COT_MAJOR_CODES, COT_SCOPES, cotChartSeries, cotClass, cotContractUnitText, cotInteger, cotLegendValue, cotMarketName, cotScope, type CotScope } from "./model";
16
+ import { COT_CLASSES, COT_FAMILIES, COT_MAJOR_CODES, COT_POSITIONS, COT_SCOPES, cotChartSeries, cotClass, cotContractUnitText, cotFamily, cotInteger, cotLegendValue, cotMarketName, cotPositions, cotPositionsText, cotScope, type CotScope } from "./model";
17
17
 
18
- const FAMILIES = [{ value: "legacy", label: "Legacy" }, { value: "disaggregated", label: "Disaggregated" }];
19
18
  const BOARD_COLUMNS: DataTableColumn[] = [
20
19
  { id: "name", label: "MARKET", width: 30, align: "left", flexGrow: 1 },
21
20
  { id: "code", label: "CODE", width: 7, align: "left" },
@@ -44,8 +43,9 @@ const cotDetailTitle = (name: string | undefined, code: string | null) => name ?
44
43
 
45
44
  export function CotPane(props: PaneProps) {
46
45
  const pane = usePaneInstance();
47
- const [family, setFamily] = usePluginPaneState<CotFamily>("report", pane?.settings?.report === "disaggregated" ? "disaggregated" : "legacy");
48
- const { strip: tabStrip, rows: tabRows } = usePaneTabs({ tabs: FAMILIES, activeValue: family, onSelect: (value) => setFamily(value as CotFamily), focused: props.focused, dense: true });
46
+ const [storedFamily, setFamily] = usePluginPaneState<CotFamily>("report", cotFamily(pane?.settings?.report));
47
+ const family = cotFamily(storedFamily);
48
+ const { strip: tabStrip, rows: tabRows } = usePaneTabs({ tabs: [...COT_FAMILIES], activeValue: family, onSelect: (value) => setFamily(value as CotFamily), focused: props.focused, dense: true });
49
49
  return <Box width={props.width} height={props.height} flexDirection="column">
50
50
  {tabStrip}
51
51
  <CotBoard key={family} {...props} height={Math.max(3, props.height - tabRows)} family={family} initialCode={pane?.params?.code ?? null} />
@@ -63,14 +63,18 @@ function CotBoard({ width, height, focused, family, initialCode }: PaneProps & {
63
63
  const open = storedOpen || null;
64
64
  const [storedScope, setScope] = usePluginPaneState<CotScope>("scope", "major");
65
65
  const scope = cotScope(storedScope);
66
+ // Futures only, as the pane always read, unless futures and options combined is picked.
67
+ const [storedPositions, setPositions] = usePluginPaneState<CotPositions>("positions", "futures-only");
68
+ const positions = cotPositions(storedPositions);
66
69
  const [query, setQuery] = useState("");
67
70
  const { active: searching, focus: focusSearch, searchProps } = useQueryBarSearch();
68
71
  const [sort, setSort] = useState<SortPreference<string>>({ columnId: null, direction: "desc" });
69
72
  const control = useRef<SelectControl>(null);
70
73
  const scopeControl = useRef<SelectControl>(null);
71
- const loader = useCallback((force: boolean) => loadCotBoard(family, traderClass, force), [family, traderClass, session.requestKey]);
72
- const resource = useAsyncResource(loader, { initialData: () => cachedCotBoard(family, traderClass), clearOnError: isAccessDenied });
73
- const data = resource.data?.traderClass === traderClass ? resource.data : null;
74
+ const positionsControl = useRef<SelectControl>(null);
75
+ const loader = useCallback((force: boolean) => loadCotBoard(family, traderClass, force, positions), [family, traderClass, positions, session.requestKey]);
76
+ const resource = useAsyncResource(loader, { initialData: () => cachedCotBoard(family, traderClass, positions), clearOnError: isAccessDenied });
77
+ const data = resource.data?.traderClass === traderClass && resource.data.scope === positions ? resource.data : null;
74
78
  const rows = useMemo(() => {
75
79
  // A typed query searches every market; the scope only shapes the unsearched board.
76
80
  const result = (data?.rows ?? []).filter((row) => query ? `${row.marketName} ${row.contractCode}`.toLowerCase().includes(query.toLowerCase())
@@ -90,15 +94,17 @@ function CotBoard({ width, height, focused, family, initialCode }: PaneProps & {
90
94
  if (!boardShown) return;
91
95
  if (event.name === "c") { event.preventDefault(); control.current?.open(); }
92
96
  if (event.name === "s") { event.preventDefault(); scopeControl.current?.open(); }
97
+ if (event.name === "p") { event.preventDefault(); positionsControl.current?.open(); }
93
98
  });
94
99
  usePaneFooter("cot:actions", () => ({ hints: boardShown ? [
95
100
  { id: "class", key: "c", label: "class", onPress: () => control.current?.open() },
96
101
  { id: "scope", key: "s", label: "cope", onPress: () => scopeControl.current?.open() },
102
+ { id: "positions", key: "p", label: "ositions", onPress: () => positionsControl.current?.open() },
97
103
  { id: "search", key: "/", label: "search", onPress: focusSearch },
98
104
  ] : [] }), [boardShown, focusSearch]);
99
105
  usePaneNoticeFooter({ registrationId: "cot:board-notices", focused, enabled: !open, notices: data?.gaps ?? [] });
100
106
  usePaneStatusFooter({ registrationId: "cot:board", enabled: !open, loading: resource.loading, error: resource.error,
101
- info: data ? [{ id: "as-of", parts: [{ text: `CFTC futures only · ${data.asOf ?? "--"} · contracts`, tone: "muted" }] }] : [] });
107
+ info: data ? [{ id: "as-of", parts: [{ text: `CFTC ${cotPositionsText(positions)} · ${data.asOf ?? "--"} · contracts`, tone: "muted" }] }] : [] });
102
108
  // A class change reloads under the query bar, which stays.
103
109
  return <DataTableStackView<CotBoardRow> columns={BOARD_COLUMNS} items={rows} focused={focused && !searching}
104
110
  status={data ? undefined : { loading: resource.loading, error: resource.error, empty: !resource.loading && !resource.error, subject: "COT history" }}
@@ -118,11 +124,12 @@ function CotBoard({ width, height, focused, family, initialCode }: PaneProps & {
118
124
  filters={[
119
125
  { id: "class", label: "Class", value: traderClass, options: COT_CLASSES[family], onChange: setClass, controlRef: control },
120
126
  { id: "scope", label: "Scope", value: scope, defaultValue: "major", options: [...COT_SCOPES], onChange: setScope, controlRef: scopeControl },
127
+ { id: "positions", label: "Positions", value: positions, defaultValue: "futures-only", options: [...COT_POSITIONS], onChange: setPositions, controlRef: positionsControl },
121
128
  ]} />}
122
129
  emptyStateTitle={data ? query ? "No matching COT markets." : "No major markets in this report; switch the scope to all markets." : ""} detailOpen={!!open} onBack={() => setOpen("")}
123
130
  detailTitle={cotDetailTitle(data?.rows.find((row) => row.contractCode === open)?.marketName, open)}
124
- detailContent={open ? <CotDetail key={`${family}:${open}`} width={width} height={Math.max(3, height - 1)} focused={focused}
125
- code={open} family={family} traderClass={traderClass} onClassChange={setClass} /> : null} />;
131
+ detailContent={open ? <CotDetail key={`${family}:${positions}:${open}`} width={width} height={Math.max(3, height - 1)} focused={focused}
132
+ code={open} family={family} positions={positions} traderClass={traderClass} onClassChange={setClass} /> : null} />;
126
133
  }
127
134
 
128
135
  /**
@@ -147,10 +154,10 @@ function CotChart({ series, width, height }: { series: ResolvedSeries[]; width:
147
154
  remoteKind="cot-history" />;
148
155
  }
149
156
 
150
- function CotDetail({ width, height, focused, code, family, traderClass, onClassChange }: Pick<PaneProps, "width" | "height" | "focused"> & { code: string; family: CotFamily; traderClass: CotClass; onClassChange: (value: CotClass) => void }) {
157
+ function CotDetail({ width, height, focused, code, family, positions, traderClass, onClassChange }: Pick<PaneProps, "width" | "height" | "focused"> & { code: string; family: CotFamily; positions: CotPositions; traderClass: CotClass; onClassChange: (value: CotClass) => void }) {
151
158
  const colors = useThemeColors();
152
159
  const session = useResearchCloudSession();
153
- const loader = useCallback(() => loadCotDetail(code, family), [code, family, session.requestKey]);
160
+ const loader = useCallback(() => loadCotDetail(code, family, undefined, positions), [code, family, positions, session.requestKey]);
154
161
  const resource = useAsyncResource(loader, { clearOnError: isAccessDenied });
155
162
  const data = resource.data;
156
163
  const payload = data?.payload;
@@ -178,7 +185,8 @@ function CotDetail({ width, height, focused, code, family, traderClass, onClassC
178
185
  usePaneNoticeFooter({ registrationId: "cot:detail-notices", focused, notices: [...(payload?.gaps ?? []), ...(data?.priceWarning ? [data.priceWarning] : []), ...partialRanks] });
179
186
  usePaneStatusFooter({ registrationId: "cot:detail", loading: resource.loading, error: resource.error,
180
187
  // The unit the net is counted in, as `fn COT` titles it: what one contract is, where the report says.
181
- info: payload ? [{ id: "as-of", parts: [{ text: `CFTC ${payload.asOf ?? "--"} · ${cotContractUnitText(payload.contract?.contractUnits) ?? "contracts"}${data?.priceAsOf ? ` · ${data.priceSymbol} ${data.priceAsOf}` : ""}`, tone: "muted" }] }] : [] });
188
+ // Futures and options combined says so; futures only reads as it always has.
189
+ info: payload ? [{ id: "as-of", parts: [{ text: `CFTC ${positions === "combined" ? `${cotPositionsText(positions)} · ` : ""}${payload.asOf ?? "--"} · ${cotContractUnitText(payload.contract?.contractUnits) ?? "contracts"}${data?.priceAsOf ? ` · ${data.priceSymbol} ${data.priceAsOf}` : ""}`, tone: "muted" }] }] : [] });
182
190
  return <PaneStatusBody loading={resource.loading && !data} error={!data ? resource.error : null} empty={!!payload && !payload.contract} subject="COT contract">
183
191
  {payload && data ? <DataTableView columns={POSITION_COLUMNS} items={payload.positions} focused={focused} rootWidth={width} rootHeight={height}
184
192
  rootBefore={<ChartTableHeader width={width} height={height} tableRows={payload.positions.length} chart={chart}
@@ -5,7 +5,9 @@ import type {
5
5
  SovrBoardPayload,
6
6
  } from "../../../api-client/credit-boards";
7
7
  import { createPluginCache } from "../../../data/plugin-cache";
8
+ import { createTtlCache } from "../../../data/ttl-cache";
8
9
  import type { PricePoint } from "../../../types/financials";
10
+ import { logFailure } from "../../../utils/log-failure";
9
11
  import { cachedCloudResource, loadCloudResource, unavailableOnServer } from "../shared/cloud-resource";
10
12
  import { currencyMoveFromHistory, currencyPair } from "./model";
11
13
 
@@ -84,18 +86,12 @@ export type PriceHistoryLoader = (symbol: string) => Promise<PricePoint[]>;
84
86
  const FX_HISTORY_TTL_MS = 30 * 60_000;
85
87
  /** Twenty-odd currencies at once; a few requests at a time keep the feed from refusing them. */
86
88
  const FX_HISTORY_CONCURRENCY = 4;
87
- const fxMoves = new Map<string, { at: number; move: Promise<number | null> }>();
89
+ const fxMoves = createTtlCache<number | null>({ ttlMs: FX_HISTORY_TTL_MS });
88
90
 
91
+ /** A load that fails reads as unknown for everyone waiting on it and is asked again next time, never remembered as unknown. */
89
92
  function currencyMove(symbol: string, dollarsPerUnit: boolean, loadHistory: PriceHistoryLoader): Promise<number | null> {
90
- const cached = fxMoves.get(symbol);
91
- if (cached && Date.now() - cached.at <= FX_HISTORY_TTL_MS) return cached.move;
92
- const move = loadHistory(symbol).then((history) => currencyMoveFromHistory(history, dollarsPerUnit));
93
- fxMoves.set(symbol, { at: Date.now(), move });
94
- // A failed load is retried next time rather than remembered as unknown.
95
- return move.catch(() => {
96
- fxMoves.delete(symbol);
97
- return null;
98
- });
93
+ return fxMoves.load(symbol, async () => currencyMoveFromHistory(await loadHistory(symbol), dollarsPerUnit))
94
+ .catch(logFailure("credit-boards", "Read a currency's price history failed", null));
99
95
  }
100
96
 
101
97
  /**
@@ -7,7 +7,7 @@ import {
7
7
  type CreditConditionsLoadResult,
8
8
  type CreditSeriesLoader,
9
9
  } from "./client";
10
- import { creditRegion, creditSeriesIn } from "./model";
10
+ import { creditRegion, creditSeriesIn, type CreditConditionRow } from "./model";
11
11
 
12
12
  const COLUMNS = [
13
13
  { key: "label", header: "Index" },
@@ -22,6 +22,11 @@ const COLUMNS = [
22
22
  { key: "date", header: "As of" },
23
23
  ];
24
24
 
25
+ /** What the publisher caps the indexes' history at, said once for the report. */
26
+ function historyLimits(rows: readonly CreditConditionRow[]): string[] {
27
+ return [...new Set(rows.flatMap((row) => row.historyLimit ? [row.historyLimit] : []))];
28
+ }
29
+
25
30
  interface CreditConditionsHeadlessDependencies {
26
31
  load(args: HeadlessPaneLoadArgs, loader: CreditSeriesLoader): Promise<CreditConditionsLoadResult>;
27
32
  }
@@ -57,7 +62,8 @@ function createCreditConditionsHeadless(
57
62
  );
58
63
  return {
59
64
  // The year of history feeds the pane's chart and rank, not the report rows.
60
- rows: result.rows.map(({ history: _history, ...row }) => row),
65
+ rows: result.rows.map(({ history: _history, historyLimit: _historyLimit, ...row }) => row),
66
+ notes: historyLimits(result.rows),
61
67
  errors: result.errors,
62
68
  metadata: { stale: result.stale },
63
69
  };
@@ -110,10 +110,14 @@ export function CreditConditionsPane({ paneId, focused, width, height }: PanePro
110
110
  const partial = rows.length > 0 && rows.length < series.length;
111
111
  // Each row carries its own date in the AS OF column; a spread between
112
112
  // indexes from different sessions is a limitation behind the warning.
113
+ const historyLimits = useMemo(() => [...new Set(rows.flatMap((row) => row.historyLimit ? [row.historyLimit] : []))], [rows]);
113
114
  usePaneNoticeFooter({
114
115
  registrationId: "credit-conditions:notices",
115
116
  focused,
116
- notices: mixedDates ? ["The indexes carry different observation dates, so the spreads are not one session."] : [],
117
+ notices: [
118
+ ...mixedDates ? ["The indexes carry different observation dates, so the spreads are not one session."] : [],
119
+ ...historyLimits,
120
+ ],
117
121
  });
118
122
  const footerInfo = useMemo<PaneFooterSegment[]>(() => [
119
123
  ...(rows.length > 0 ? [DELAYED_SEGMENT] : []),
@@ -1,5 +1,6 @@
1
1
  import type { CloudFredSeriesPayload } from "../../../api-client";
2
2
  import { historyStatistics } from "../../../components/chart/curve/model";
3
+ import { fredHistoryLimitNotice } from "../../../time-series/economic";
3
4
  import { ICE_SPREAD_SERIES, type IceCreditSeries } from "../../../time-series/ice-credit";
4
5
 
5
6
  export type CreditSeriesDefinition = Pick<IceCreditSeries, "seriesId" | "label" | "region">;
@@ -30,6 +31,8 @@ export interface CreditConditionRow {
30
31
  dailyChangeBp: number | null;
31
32
  date: string;
32
33
  stale: boolean;
34
+ /** The publisher's cap on how far back the series goes, when FRED states one. */
35
+ historyLimit: string | null;
33
36
  /** Observations in the year to `date`, in basis points, oldest first. */
34
37
  history: Array<{ date: string; valueBp: number }>;
35
38
  /** Midrank of the latest spread within that year. */
@@ -85,6 +88,7 @@ export function normalizeCreditSeries(
85
88
  dailyChangeBp: previous ? roundTenth((latest.value - previous.value) * 100) : null,
86
89
  date: latest.date,
87
90
  stale,
91
+ historyLimit: fredHistoryLimitNotice(definition.seriesId, info),
88
92
  history,
89
93
  // One observation ranks nothing.
90
94
  percentile1Y: year.count >= 2 ? year.percentile : null,