@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,6 +1,6 @@
1
1
  import { fredSeriesUrl } from "../../../sources/gloomberb-cloud/fred-series";
2
2
  import type { SeriesDefinition } from "../../../series/types";
3
- import { ICE_CREDIT_SERIES } from "../../../time-series/ice-credit";
3
+ import { ICE_CREDIT_SERIES, ICE_HISTORY_LIMIT_NOTICE } from "../../../time-series/ice-credit";
4
4
 
5
5
  /** Corporate bond option-adjusted spreads by rating and region, and the effective yields of the US indexes. */
6
6
  export const CREDIT_SPREAD_SERIES: readonly SeriesDefinition[] = ICE_CREDIT_SERIES.map((series) => ({
@@ -11,6 +11,8 @@ export const CREDIT_SPREAD_SERIES: readonly SeriesDefinition[] = ICE_CREDIT_SERI
11
11
  group: "Credit",
12
12
  tags: [series.measure, ...series.tags, "corporate bonds"],
13
13
  publisher: "FRED",
14
+ // A fixed note on every ICE series. G and CRD read the same sentence from each series' FRED notes instead, so there it lapses if FRED lifts the limit.
15
+ limitation: ICE_HISTORY_LIMIT_NOTICE,
14
16
  url: fredSeriesUrl(series.seriesId),
15
17
  expression: `FRED:${series.seriesId}`,
16
18
  }));
@@ -6,7 +6,7 @@ import { useThemeColors } from "../../../theme/theme-context";
6
6
  import { Box, Text, TextAttributes, useRendererHost, useUiCapabilities, type ScrollBoxRenderable } from "../../../ui";
7
7
  import { quoteBlockStyle } from "../../../components/ui/quote";
8
8
  import { fetchCreditInstrument } from "./client";
9
- import { comparatorSymbol, covenantNumber, covenantTerms, creditPercent, factColumns, factValue, fieldLabel, hasAmendments, headroomTone, NO_VALUE, sentence, structuredFields } from "./model";
9
+ import { comparatorSymbol, covenantNumber, covenantReason, covenantTerms, creditPercent, factColumns, factValue, fieldLabel, hasAmendments, headroomTone, NO_VALUE, sentence, structuredFields } from "./model";
10
10
 
11
11
  const LABEL_WIDTH = 16;
12
12
  const MATCH = { exact: "Exact", whitespace: "Whitespace normalized", nfkc_whitespace: "Unicode and whitespace normalized" } as Record<string, string>;
@@ -83,7 +83,7 @@ export function CreditInstrumentDetail({ symbol, instrument, width, height, focu
83
83
  const columns = useMemo(() => factColumns(view, width), [view, width]);
84
84
  const query = <QueryBar width={width} view={{ value: view, options: [{ value: "terms", label: "Current terms" }, { value: "history", label: "Amendment history" }], onChange: (value) => { setView(value); setOpen(null); } }} />;
85
85
  // Why headroom is missing stays beside the figures it explains.
86
- const reason = covenant?.reason && covenant.status !== "compliant" && covenant.status !== "breach" ? covenant.reason : null;
86
+ const reason = covenant ? covenantReason(covenant, data.facts) : null;
87
87
  const header = covenant ? <Box flexDirection="column">
88
88
  <ChartTableHeader width={width} height={height - (reason ? 2 : 0)} tableRows={rows.length} tableColumns={columns} query={query} figures={covenantFigures(covenant, data.facts.find((fact) => fact.id === covenant.id))} />
89
89
  {reason ? <Box paddingX={1} width={width}><Notice tone="muted">{reason}</Notice></Box> : null}
@@ -0,0 +1,173 @@
1
+ import type { CreditDocumentsPayload, CreditFact, CreditInstrument, CreditObligor, CreditScreenPayload } from "../../../api-client/credit-documents";
2
+ import type { HeadlessBundleSection, HeadlessPaneColumn, HeadlessPaneRow } from "../../../types/plugin";
3
+ import { capitalCell, capitalColumns, comparatorSymbol, covenantFact, covenantNumber, creditAmount, creditCurrencies, creditMaturityRows, creditPercent, factValue, fieldLabel,
4
+ instrumentOutstanding, isClosed, maturityShares, NO_VALUE, outstandingText, screenSignal, sentence } from "./model";
5
+
6
+ /** A column the text report leaves out: the exports keep it, for ids and figures a spreadsheet reads whole. */
7
+ type ExportColumn = HeadlessPaneColumn & { exportOnly?: boolean };
8
+ type Section = Extract<HeadlessBundleSection, { rows: HeadlessPaneRow[] }>;
9
+
10
+ const blank = (text: string) => text === NO_VALUE ? null : text;
11
+ const name = (obligor: CreditObligor | null | undefined) => obligor?.name ?? null;
12
+ const number = (value: unknown) => typeof value === "number" ? value : null;
13
+ const wide = (key: string, header: string, extra: Partial<HeadlessPaneColumn> = {}): ExportColumn => ({ key, header, shrink: true, minWidth: 16, ...extra });
14
+ const OBLIGOR: ExportColumn = { key: "obligor", header: "Obligor", minWidth: 16 };
15
+ const INSTRUMENT_ID: ExportColumn = { key: "instrumentId", header: "Instrument ID", stackPriority: 9 };
16
+
17
+ /**
18
+ * A table of flat records: the exports keep every column; the text report
19
+ * prints the pane's, and leaves out the obligor when the filing names none.
20
+ */
21
+ function table(title: string, columns: readonly ExportColumn[], rows: HeadlessPaneRow[], { textKeys, empty }: { textKeys?: readonly string[]; empty?: string } = {}): Section {
22
+ const named = rows.some((row) => row.obligor != null);
23
+ const plain = (column: ExportColumn): HeadlessPaneColumn => { const { exportOnly: _, ...rest } = column; return rest; };
24
+ return { title, columns: columns.map(plain), rows,
25
+ textColumns: columns.filter((column) => !column.exportOnly && (column.key !== "obligor" || named) && (!textKeys || textKeys.includes(column.key))).map(plain),
26
+ ...(empty ? { empty } : {}) };
27
+ }
28
+
29
+ const CAPITAL: ExportColumn[] = [
30
+ { key: "name", header: "Instrument", minWidth: 20, textFormat: (value, row) => isClosed({ lifecycle: row.lifecycle as string | null }) ? `${value} (${row.lifecycle})` : String(value) },
31
+ { key: "lifecycle", header: "Status", exportOnly: true },
32
+ { key: "facilityType", header: "Type", format: (value) => value ? fieldLabel(String(value)) : NO_VALUE },
33
+ { key: "coupon", header: "Coupon/Margin" },
34
+ { key: "maturity", header: "Maturity" },
35
+ { key: "outstanding", header: "Outstanding", align: "right", textFormat: (value, row) => outstandingText(number(value), number(row.commitment)) },
36
+ { key: "commitment", header: "Commitment", align: "right", exportOnly: true },
37
+ { key: "currency", header: "CCY" },
38
+ { key: "ranking", header: "Rank", minWidth: 12 },
39
+ { key: "controlPut", header: "CoC put", align: "right" },
40
+ OBLIGOR,
41
+ INSTRUMENT_ID,
42
+ ];
43
+ /** The pane's capital column behind each export column; the others are not in the pane's own list. */
44
+ const PANE_COLUMN: Record<string, string> = { name: "name", facilityType: "facility", coupon: "coupon", maturity: "maturity", outstanding: "principal", currency: "currency", ranking: "ranking", controlPut: "control" };
45
+
46
+ function capitalRows(data: CreditDocumentsPayload, instruments: readonly CreditInstrument[]): HeadlessPaneRow[] {
47
+ return instruments.map((row) => ({
48
+ name: row.name, lifecycle: row.lifecycle ?? null, facilityType: row.facilityType,
49
+ coupon: blank(capitalCell(row, "coupon", data)), maturity: row.maturity, outstanding: instrumentOutstanding(row), commitment: row.commitment,
50
+ currency: row.currency, ranking: blank(capitalCell(row, "ranking", data)), controlPut: blank(capitalCell(row, "control", data)),
51
+ obligor: name(row.obligor), instrumentId: row.id,
52
+ }));
53
+ }
54
+ /** The instruments as the Capital tab lists them; a column the pane leaves out as undisclosed stays out of the text report. */
55
+ export function capitalSection(data: CreditDocumentsPayload, title = "Capital structure", instruments: readonly CreditInstrument[] = data.instruments): Section {
56
+ const shown = new Set(capitalColumns(data, Number.MAX_SAFE_INTEGER).map((column) => column.id));
57
+ return table(title, CAPITAL, capitalRows(data, instruments), { textKeys: CAPITAL.map((column) => column.key).filter((key) => !(key in PANE_COLUMN) || shown.has(PANE_COLUMN[key]!)) });
58
+ }
59
+
60
+ /** The unit a covenant row's figures are in, read from the fact its columns carry. */
61
+ const figureUnit = (row: HeadlessPaneRow) => ({ unit: typeof row.unit === "string" ? row.unit : null, currency: typeof row.currency === "string" ? row.currency : null });
62
+ const COVENANTS: ExportColumn[] = [
63
+ wide("metric", "Covenant"),
64
+ { key: "threshold", header: "Limit", align: "right", textFormat: (value, row) => `${comparatorSymbol(row.comparator as string, row.inclusive as boolean | null ?? undefined)} ${covenantNumber(number(value), figureUnit(row))}` },
65
+ { key: "comparator", header: "Comparator", exportOnly: true },
66
+ { key: "inclusive", header: "Inclusive", exportOnly: true },
67
+ { key: "unit", header: "Unit", exportOnly: true },
68
+ { key: "currency", header: "CCY", exportOnly: true },
69
+ { key: "current", header: "Reported", align: "right", textFormat: (value, row) => covenantNumber(number(value), figureUnit(row)) },
70
+ { key: "headroomPercent", header: "Headroom", align: "right", format: (value) => creditPercent(number(value)) },
71
+ { key: "status", header: "Status", format: (value) => fieldLabel(String(value)) },
72
+ { key: "asOf", header: "As of" },
73
+ { key: "testDate", header: "Test date", exportOnly: true },
74
+ wide("instrument", "Instrument"),
75
+ OBLIGOR,
76
+ { key: "reason", header: "Reason", underRow: true },
77
+ { key: "id", header: "Covenant ID", exportOnly: true },
78
+ { key: "instrumentId", header: "Instrument ID", exportOnly: true },
79
+ ];
80
+ export function covenantSection(data: CreditDocumentsPayload): Section {
81
+ return table("Covenants", COVENANTS, data.covenants.map((row) => {
82
+ const fact = covenantFact(data, row);
83
+ return { metric: sentence(row.metric), threshold: row.threshold, comparator: row.comparator, inclusive: row.inclusive ?? null, unit: fact?.unit ?? null, currency: fact?.currency ?? null,
84
+ current: row.current, headroomPercent: row.headroomPercent, status: row.status, asOf: row.asOf, testDate: row.testDate, instrument: row.instrumentName,
85
+ obligor: name(row.obligor), reason: row.reason, id: row.id, instrumentId: row.instrumentId };
86
+ }));
87
+ }
88
+
89
+ function maturityColumns(preview: boolean): ExportColumn[] {
90
+ return [
91
+ { key: "year", header: "Year" },
92
+ { key: "currency", header: "CCY" },
93
+ { key: "principal", header: "Principal", align: "right", textFormat: (value) => creditAmount(number(value)) },
94
+ ...preview ? [] : [
95
+ { key: "share", header: "Share", align: "right", format: (value: unknown) => creditPercent(number(value)) } as ExportColumn,
96
+ { key: "cumulative", header: "Cumulative", align: "right", format: (value: unknown) => creditPercent(number(value)) } as ExportColumn,
97
+ ],
98
+ wide("instrument", "Instrument"),
99
+ OBLIGOR,
100
+ INSTRUMENT_ID,
101
+ ];
102
+ }
103
+ /** One row per instrument and year, in each currency: wall currencies are never added together. A preview wall is partial, so it has no shares. */
104
+ export function maturitySection(data: CreditDocumentsPayload): Section {
105
+ const preview = data.access === "preview";
106
+ const rows = creditCurrencies(data).flatMap((currency) => {
107
+ const buckets = creditMaturityRows(data, currency), shares = preview ? new Map() : maturityShares(buckets);
108
+ return buckets.flatMap((bucket) => bucket.instruments.map((instrument) => {
109
+ const share = shares.get(`${bucket.year}:${instrument.id}`);
110
+ return { year: bucket.year, currency, principal: instrument.principal, ...preview ? {} : { share: share?.share ?? null, cumulative: share?.cumulative ?? null },
111
+ instrument: instrument.name, obligor: name(bucket.obligor), instrumentId: instrument.id };
112
+ }));
113
+ });
114
+ return table("Maturity wall", maturityColumns(preview), rows);
115
+ }
116
+
117
+ const CONTROL: ExportColumn[] = [
118
+ wide("instrument", "Instrument", { minWidth: 20 }),
119
+ { key: "putPercent", header: "Put", align: "right", format: (value) => value == null ? "" : `${value}%` },
120
+ { key: "principal", header: "Principal", align: "right", textFormat: (value) => creditAmount(number(value)) },
121
+ { key: "currency", header: "CCY" },
122
+ wide("trigger", "Trigger", { minWidth: 24 }),
123
+ OBLIGOR,
124
+ INSTRUMENT_ID,
125
+ ];
126
+ export function controlSection(data: CreditDocumentsPayload): Section {
127
+ return table("Change of control", CONTROL, data.changeOfControl.map((row) => ({ instrument: row.instrumentName, putPercent: row.putPercent, principal: row.principal,
128
+ currency: row.currency, trigger: row.trigger, obligor: name(row.obligor), instrumentId: row.instrumentId })));
129
+ }
130
+
131
+ const SCREEN: ExportColumn[] = [
132
+ { key: "symbol", header: "Issuer" },
133
+ { key: "signal", header: "Signal" },
134
+ { key: "headroomPercent", header: "Headroom", align: "right", format: (value) => creditPercent(number(value)) },
135
+ { key: "date", header: "Date" },
136
+ wide("instrument", "Instrument", { minWidth: 20 }),
137
+ wide("condition", "Condition", { minWidth: 24 }),
138
+ { key: "currency", header: "CCY", exportOnly: true },
139
+ INSTRUMENT_ID,
140
+ ];
141
+ /** The risk screen's signals as the Screen tab lists them; the evidence behind each stays behind `--instrument`. */
142
+ export function screenSection(data: CreditScreenPayload): Section {
143
+ return table("Credit risk screen", SCREEN, data.rows.map((row) => ({ symbol: row.symbol, signal: screenSignal(row), headroomPercent: row.value, date: row.date, instrument: row.instrumentName,
144
+ condition: sentence(row.reason), currency: row.currency, instrumentId: row.instrumentId })));
145
+ }
146
+
147
+ const FACTS: ExportColumn[] = [
148
+ wide("term", "Term", { shrink: false }),
149
+ wide("value", "Disclosed value", { minWidth: 24 }),
150
+ { key: "amount", header: "Amount", exportOnly: true, align: "right" },
151
+ { key: "unit", header: "Unit", exportOnly: true },
152
+ { key: "asOf", header: "As of" },
153
+ { key: "filedAt", header: "Filed" },
154
+ { key: "status", header: "Revision", format: (value) => fieldLabel(String(value)) },
155
+ { key: "form", header: "Form", exportOnly: true },
156
+ { key: "confidence", header: "Confidence", exportOnly: true, align: "right" },
157
+ { key: "filingUrl", header: "Filing URL", exportOnly: true },
158
+ { key: "id", header: "Fact ID", exportOnly: true },
159
+ // The filing's own words belong to the instrument view; the text report prints them under their term.
160
+ { key: "quote", header: "Filing text", underRow: true },
161
+ ];
162
+ const factRow = (fact: CreditFact): HeadlessPaneRow => ({ term: fieldLabel(fact.field), value: factValue(fact), amount: number(fact.value), unit: fact.currency ?? fact.unit, asOf: fact.asOf, filedAt: fact.filedAt, status: fact.status,
163
+ form: fact.form, confidence: fact.confidence, filingUrl: fact.filingUrl, id: fact.id, quote: fact.quote });
164
+ /** An instrument's terms and amendments, each with the filing text behind it. Current terms are all in effect, so only the history names the revision. */
165
+ export function instrumentSections(data: CreditDocumentsPayload, instrument: CreditInstrument): Section[] {
166
+ const byTerm = (a: CreditFact, b: CreditFact) => a.field.localeCompare(b.field) || a.factKey.localeCompare(b.factKey);
167
+ const history = [...instrument.history ?? []].sort((a, b) => b.filedAt.localeCompare(a.filedAt) || a.field.localeCompare(b.field));
168
+ return [
169
+ capitalSection(data, instrument.name, [instrument]),
170
+ table("Current terms", FACTS, [...instrument.facts].sort(byTerm).map(factRow), { textKeys: FACTS.map((column) => column.key).filter((key) => key !== "status") }),
171
+ table("Amendment history", FACTS, history.map(factRow)),
172
+ ];
173
+ }
@@ -1,7 +1,8 @@
1
1
  import type { HeadlessPaneDefinition } from "../../../types/plugin";
2
2
  import type { CreditDocumentsPayload } from "../../../api-client/credit-documents";
3
3
  import { creditIssuerSymbol, fetchCreditDocuments, fetchCreditInstrument, fetchCreditScreen } from "./client";
4
- import { CREDIT_TABS } from "./model";
4
+ import { capitalSection, controlSection, covenantSection, instrumentSections, maturitySection, screenSection } from "./export";
5
+ import { CREDIT_TABS, creditEmptyCause } from "./model";
5
6
  import { SEC_FILINGS } from "../shared/report-freshness";
6
7
 
7
8
  export const creditHeadless: HeadlessPaneDefinition<"bundle"> = {
@@ -25,19 +26,18 @@ export const creditHeadless: HeadlessPaneDefinition<"bundle"> = {
25
26
  if (args.options.tab === "screen") {
26
27
  const data = await fetchCreditScreen({ headroomBelow: Number(args.options.headroom ?? 20), springingWithinMonths: Number(args.options.months ?? 12), limit: 100 }, ctx.apiClient);
27
28
  return { complete: data.lockedRows === 0 && !data.truncated, errors: [...(data.lockedRows ? ["More results require Gloom Pro"] : []), ...(data.truncated ? ["Screen reached the result limit"] : [])],
28
- sections: [{ title: "Credit risk screen", rows: data.rows.map((row) => ({ ...row })) }], metadata: { asOf: data.asOf, access: data.access, lockedRows: data.lockedRows } };
29
+ sections: [screenSection(data)], metadata: { asOf: data.asOf, access: data.access, lockedRows: data.lockedRows } };
29
30
  }
30
31
  const data = await fetchCreditDocuments(args.symbols[0]!, ctx.apiClient);
31
32
  const instrument = args.options.instrument ? await fetchCreditInstrument(data.symbol, String(args.options.instrument), ctx.apiClient) : null;
32
33
  const reason = instrument ? null : creditEmptyReason(data, args.symbols[0]!);
33
- // Nothing on file says why once, instead of four empty sections.
34
+ // Nothing on file says why once, as the report's one reason, instead of four empty sections and a generic "not covered".
34
35
  if (reason) {
35
- return { complete: false, errors: data.warnings, sections: [],
36
- metadata: { symbol: data.symbol, issuer: data.issuer, asOf: data.asOf, access: data.access, lockedRows: data.lockedRows, status: data.status, reason, notices: [reason] } };
36
+ return { complete: false, errors: data.warnings, sections: [], empty: reason,
37
+ metadata: { symbol: data.symbol, issuer: data.issuer, asOf: data.asOf, access: data.access, lockedRows: data.lockedRows, status: data.status, reason } };
37
38
  }
38
39
  return { complete: data.status === "available" && data.lockedRows === 0, errors: [...data.warnings, ...(data.lockedRows ? ["Full credit documents and history require Gloom Pro"] : [])],
39
- sections: instrument ? [{ title: instrument.name, rows: [{ ...instrument }] }, { title: "Current terms", rows: instrument.facts.map((row) => ({ ...row })) }, { title: "Amendment history", rows: (instrument.history ?? []).map((row) => ({ ...row })) }]
40
- : [{ title: "Capital structure", rows: data.instruments.map((row) => ({ ...row })) }, { title: "Covenants", rows: data.covenants.map((row) => ({ ...row })) }, { title: "Maturity wall", rows: data.maturities.map((row) => ({ ...row })) }, { title: "Change of control", rows: data.changeOfControl.map((row) => ({ ...row })) }],
40
+ sections: instrument ? instrumentSections(data, instrument) : [capitalSection(data), covenantSection(data), maturitySection(data), controlSection(data)],
41
41
  metadata: { symbol: data.symbol, issuer: data.issuer, asOf: data.asOf, access: data.access, lockedRows: data.lockedRows, status: data.status } };
42
42
  },
43
43
  };
@@ -55,10 +55,10 @@ export function creditEmptyReason(
55
55
  data: Pick<CreditDocumentsPayload, "symbol" | "issuer" | "instruments" | "covenants" | "maturities" | "changeOfControl" | "lockedRows">,
56
56
  requested: string,
57
57
  ): string | null {
58
- const rows = data.instruments.length + data.covenants.length + data.maturities.length + data.changeOfControl.length;
59
- if (rows > 0 || data.lockedRows > 0) return null;
58
+ const cause = creditEmptyCause(data);
59
+ if (!cause) return null;
60
60
  const symbol = requested.trim().toUpperCase();
61
- const why = data.issuer
61
+ const why = cause === "instruments" && data.issuer
62
62
  ? `No credit documents on file for ${data.issuer.name} (${symbol}): no debt instrument has been read from its filings yet.`
63
63
  : `No credit documents on file for ${symbol}: it is not an issuer the credit-document set covers.`;
64
64
  const example = creditIssuerSymbol(symbol) === CREDIT_EXAMPLE_SYMBOL ? "" : ` Try \`gloom fn CRDOC ${CREDIT_EXAMPLE_SYMBOL}\` for an issuer with documents.`;
@@ -54,12 +54,14 @@ function structuredSummary(fact: CreditFact): string | null {
54
54
  const low = finite(value.minimum), high = finite(value.maximum);
55
55
  return low !== null && high !== null ? [`${low}–${high}${unitSuffix(fact.unit)}`, words(value.condition)].filter(Boolean).join(" · ") : null;
56
56
  }
57
+ const VALUE_KINDS = new Set(["text", "date"]);
57
58
  export function factValue(fact: CreditFact): string {
58
59
  if (typeof fact.value === "number" && ["percent", "%"].includes(fact.unit ?? "")) return `${fact.value}%`;
59
60
  const summary = structuredSummary(fact);
60
61
  if (summary) return summary;
61
62
  const value = typeof fact.value === "number" && fact.currency ? creditAmount(fact.value) : typeof fact.value === "string" ? sentence(fact.value) : creditValue(fact.value);
62
- return [value, fact.currency ?? fact.unit].filter(Boolean).join(" ");
63
+ // `text` and `date` say what kind of value a fact holds, not what it is measured in.
64
+ return [value, fact.currency ?? (fact.unit && !VALUE_KINDS.has(fact.unit) ? fact.unit : null)].filter(Boolean).join(" ");
63
65
  }
64
66
 
65
67
  export interface CreditTermField { label: string; value: string; detail?: string }
@@ -98,13 +100,34 @@ function instrumentFact(row: CreditInstrument, field: string): CreditFact | unde
98
100
  return row.facts.find((fact) => fact.field === field);
99
101
  }
100
102
  const CLOSED = new Set(["repaid", "redeemed", "terminated", "cancelled", "matured"]);
101
- export const isClosed = (row: CreditInstrument) => !!row.lifecycle && CLOSED.has(row.lifecycle);
103
+ export const isClosed = (row: Pick<CreditInstrument, "lifecycle">) => !!row.lifecycle && CLOSED.has(row.lifecycle);
102
104
  /** What is owed now: a revolver's drawn balance, otherwise outstanding principal. */
103
105
  export const instrumentOutstanding = (row: CreditInstrument) => row.drawn ?? row.principal;
104
- const creditCoupon = (row: CreditInstrument) => {
106
+ /** A margin in basis points, from the unit the filing states it in; null when the unit does not say. */
107
+ function marginBasisPoints(margin: CreditFact): number | null {
108
+ const value = finite(margin.value);
109
+ if (value === null) return null;
110
+ if (margin.unit === "bps" || margin.unit === "bp") return value;
111
+ return margin.unit === "percent" || margin.unit === "%" ? Number((value * 100).toFixed(2)) : null;
112
+ }
113
+ /** SOFR is written `S`, as a floating spread is quoted; any other named base keeps its name. */
114
+ const baseLabel = (reference: string) => /\bSOFR\b/i.test(reference) ? "S" : reference;
115
+ /**
116
+ * What an instrument pays: a fixed coupon as its percentage, a floating margin
117
+ * as its spread over the base in basis points (`S+148`, `EURIBOR+175`). A
118
+ * margin with no named base is read as SOFR for a dollar or undated currency
119
+ * instrument, the market's base for dollar loans, and is left bare for any other.
120
+ */
121
+ export function creditCoupon(row: CreditInstrument): string {
105
122
  const coupon = instrumentFact(row, "coupon"), margin = instrumentFact(row, "margin"), reference = instrumentFact(row, "reference_rate");
106
- return coupon ? factValue(coupon) : [reference ? creditValue(reference.value) : null, margin ? factValue(margin) : null].filter(Boolean).join(" + ") || NO_VALUE;
107
- };
123
+ const named = reference ? words(typeof reference.value === "string" ? reference.value : creditValue(reference.value)) : null;
124
+ if (margin) {
125
+ const base = named ? baseLabel(named) : !row.currency || row.currency === "USD" ? "S" : "";
126
+ const basisPoints = marginBasisPoints(margin);
127
+ return basisPoints === null ? `${base}+${factValue(margin)}` : `${base}${basisPoints < 0 ? "-" : "+"}${Math.abs(basisPoints)}`;
128
+ }
129
+ return coupon ? factValue(coupon) : named ?? NO_VALUE;
130
+ }
108
131
  const rankText = (row: CreditInstrument) => {
109
132
  const fact = instrumentFact(row, "ranking") ?? instrumentFact(row, "security");
110
133
  return fact ? sentence(creditValue(fact.value)) : NO_VALUE;
@@ -114,6 +137,74 @@ function securityClass(row: CreditInstrument): "secured" | "unsecured" | null {
114
137
  const text = ["ranking", "security"].map((field) => { const fact = instrumentFact(row, field); return fact ? creditValue(fact.value).toLowerCase() : ""; }).join(" ");
115
138
  return /\bunsecured\b/.test(text) ? "unsecured" : /\bsecured\b/.test(text) ? "secured" : null;
116
139
  }
140
+ const reasonKey = (text: string) => text.trim().replace(/\.$/, "").toLowerCase();
141
+ /**
142
+ * The phrases the headroom check gives for a covenant it cannot compute, in plain
143
+ * words. The payload carries the phrase and no code, so each is matched whole.
144
+ * An empty line says nothing more than the missing figure, which comes first.
145
+ */
146
+ const PLAIN_REASONS: Record<string, string | ((limit: string) => string)> = {
147
+ "covenant boundary inclusivity is not established": (limit) => `The filing doesn't say if exactly ${limit} passes.`,
148
+ "springing test; activation conditions require review": "Only tested once a trigger is met; the filing's trigger needs a read.",
149
+ "covenant adjustments are not fully disclosed": "The filing does not spell out how the ratio is adjusted.",
150
+ "no reported value for this covenant definition": "",
151
+ "reported metric units or currency do not match the covenant": "The reported figure is in other units than the limit.",
152
+ "reported metric definition or borrower scope does not match the covenant": "The reported figure is defined differently from the covenant's ratio.",
153
+ "latest comparable financials are more than 190 days old": "The latest comparable financials are over 190 days old.",
154
+ "financial period is in the future": "The comparable financials are dated in the future.",
155
+ "financial period does not match the covenant test date": "The reported period is not the covenant's test date.",
156
+ "a positive threshold is required to express percentage headroom": "Percentage headroom needs a limit above zero.",
157
+ };
158
+ /**
159
+ * Why a covenant has no headroom, in a few plain words, or null when it has a
160
+ * figure or no reason. The missing reported figure comes first when the
161
+ * instrument's facts hold none for this covenant's metric; then what the
162
+ * covenant's own terms or the check say. A step-up in the filing's adjustment
163
+ * text is named with its limit. A phrase not known here is the payload's own,
164
+ * as it came.
165
+ */
166
+ export function covenantReason(row: Pick<CreditHeadroom, "id" | "status" | "reason" | "metric" | "threshold">, facts: readonly CreditFact[]): string | null {
167
+ const raw = row.status === "compliant" || row.status === "breach" ? null : row.reason?.trim();
168
+ if (!raw) return null;
169
+ const fact = facts.find((entry) => entry.id === row.id);
170
+ const limit = covenantNumber(row.threshold, fact);
171
+ const known = PLAIN_REASONS[reasonKey(raw)];
172
+ const reported = facts.some((entry) => entry.field === "reported_metric" && reasonKey(reportedMetricName(entry)) === reasonKey(row.metric));
173
+ // Said only with the instrument's facts in hand: with none loaded, there is nothing to show it has no figure.
174
+ const lead = known === "" || (facts.length > 0 && !reported) ? `No reported ${row.metric.trim()} to compare yet.` : null;
175
+ const stepUp = /step[- ]?up to ([\d.]+)/i.exec(raw);
176
+ let why: string | null;
177
+ if (typeof known === "function") why = known(limit);
178
+ else if (known !== undefined) why = known || null;
179
+ else if (stepUp) why = `The ${limit} limit can step up to ${covenantNumber(Number(stepUp[1]), fact)}${/acquisition/i.test(raw) ? " after permitted acquisitions" : " under conditions in the filing"}.`;
180
+ else why = `${sentence(raw)}${/[.!?]$/.test(raw) ? "" : "."}`;
181
+ return [lead, why].filter(Boolean).join(" ");
182
+ }
183
+ /** The metric a reported-figure fact is for, from its structured value. */
184
+ function reportedMetricName(fact: CreditFact): string {
185
+ return words(fields(fact.value)?.metric) ?? "";
186
+ }
187
+ /** What an issuer with no rows at all lacks: the issuer itself, or a debt instrument read from its filings. */
188
+ type CreditEmptyCause = "issuer" | "instruments";
189
+ /**
190
+ * Why a payload has nothing to show in any section. Null when a section has a
191
+ * row, or Pro holds some back: a preview that is partly locked is not empty.
192
+ */
193
+ export function creditEmptyCause(data: Pick<CreditDocumentsPayload, "issuer" | "instruments" | "covenants" | "maturities" | "changeOfControl" | "lockedRows">): CreditEmptyCause | null {
194
+ const rows = data.instruments.length + data.covenants.length + data.maturities.length + data.changeOfControl.length;
195
+ return rows > 0 || data.lockedRows > 0 ? null : data.issuer ? "instruments" : "issuer";
196
+ }
197
+ /** The pane's line for an empty payload; the CLI says the same with the issuer's name. */
198
+ export const CREDIT_EMPTY_TITLE: Record<CreditEmptyCause, string> = {
199
+ instruments: "No debt instrument has been read from this issuer's filings yet.",
200
+ issuer: "This is not an issuer the credit documents cover.",
201
+ };
202
+ /** The maintenance covenant fact a headroom row was computed from, which says its unit. */
203
+ export const covenantFact = (data: Pick<CreditDocumentsPayload, "instruments">, row: Pick<CreditHeadroom, "id" | "instrumentId">): CreditFact | undefined =>
204
+ data.instruments.find((item) => item.id === row.instrumentId)?.facts.find((item) => item.id === row.id);
205
+ /** What a screen row flags, in the words the pane and its exports use. */
206
+ export const screenSignal = (row: Pick<CreditScreenRow, "kind" | "value">) =>
207
+ row.kind === "springing_maturity" ? "Springing maturity" : row.value != null && row.value < 0 ? "Breach" : "Low headroom";
117
208
  export const screenRowId = (row: CreditScreenRow) => `${row.symbol}:${row.instrumentId}:${row.kind}:${row.date ?? ""}:${row.reason}`;
118
209
  /** Wall currencies, the one with the most instruments first so the wall opens on the issuer's main debt. */
119
210
  export function creditCurrencies(data: CreditDocumentsPayload): string[] {
@@ -208,14 +299,14 @@ type CapitalColumnId = "name" | "facility" | "coupon" | "maturity" | "principal"
208
299
  const CAPITAL_COLUMNS: Column<CapitalColumnId>[] = [
209
300
  { id: "name", label: "Instrument", width: 24, flexGrow: 1, align: "left" },
210
301
  { id: "facility", label: "Type", width: 12, align: "left" },
211
- { id: "coupon", label: "Coupon", width: 20, align: "left" },
302
+ { id: "coupon", label: "Coupon/Margin", width: 20, align: "left" },
212
303
  { id: "maturity", label: "Maturity", width: 10, align: "left" },
213
304
  { id: "principal", label: "Outstanding", width: 11, align: "right" },
214
305
  { id: "currency", label: "CCY", width: 3, align: "left" },
215
306
  { id: "ranking", label: "Rank", width: 26, align: "left" },
216
307
  { id: "control", label: "CoC put", width: 7, align: "right" },
217
308
  ];
218
- /** Text columns take the width their values need, up to the column's own width. */
309
+ /** Text columns take the width their values and their label need, up to the column's own width. */
219
310
  const FITTED_CAPITAL = new Set<CapitalColumnId>(["facility", "coupon", "ranking"]);
220
311
  const SPARSE_CAPITAL = new Set<CapitalColumnId>(["ranking", "control"]);
221
312
  export function capitalColumns(data: CreditDocumentsPayload, width: number): DataTableColumn[] {
@@ -223,20 +314,19 @@ export function capitalColumns(data: CreditDocumentsPayload, width: number): Dat
223
314
  const disclosed = (id: CapitalColumnId) => rows.filter((row) => capitalCell(row, id, data) !== NO_VALUE).length;
224
315
  // A column nothing discloses is left out; ranking and puts need most instruments.
225
316
  const columns = CAPITAL_COLUMNS.filter((column) => column.id === "name" || column.id === "principal" || (SPARSE_CAPITAL.has(column.id) ? disclosed(column.id) * 2 > rows.length : disclosed(column.id) > 0))
226
- .map((column) => FITTED_CAPITAL.has(column.id) ? { ...column, width: Math.min(column.width, Math.max(4, ...rows.map((row) => displayWidth(capitalCell(row, column.id, data))))) } : column);
317
+ .map((column) => FITTED_CAPITAL.has(column.id) ? { ...column, width: Math.min(column.width, Math.max(4, displayWidth(column.label), ...rows.map((row) => displayWidth(capitalCell(row, column.id, data))))) } : column);
227
318
  // One currency is said by the figures; several keep their column at every width.
228
319
  const single = new Set(rows.map((row) => row.currency).filter(Boolean)).size <= 1;
229
320
  return fitColumns(columns, width, ["control", "ranking", ...(single ? ["currency" as const] : []), "facility", "coupon", "maturity"]);
230
321
  }
322
+ /** A revolver reads as drawn of commitment; the strip carries the undrawn total. */
323
+ export const outstandingText = (outstanding: number | null, commitment: number | null): string =>
324
+ commitment != null && commitment !== outstanding ? `${creditAmount(outstanding)} of ${creditAmount(commitment)}` : creditAmount(outstanding);
231
325
  export function capitalCell(row: CreditInstrument, column: string, data: CreditDocumentsPayload): string {
232
326
  const control = data.changeOfControl.find((entry) => entry.instrumentId === row.id);
233
327
  switch (column) {
234
328
  case "name": return isClosed(row) ? `${row.name} (${row.lifecycle})` : row.name;
235
- case "principal": {
236
- // A revolver reads as drawn of commitment; the strip carries the undrawn total.
237
- const outstanding = creditAmount(instrumentOutstanding(row));
238
- return row.commitment != null && row.commitment !== instrumentOutstanding(row) ? `${outstanding} of ${creditAmount(row.commitment)}` : outstanding;
239
- }
329
+ case "principal": return outstandingText(instrumentOutstanding(row), row.commitment);
240
330
  case "currency": return row.currency ?? NO_VALUE;
241
331
  case "maturity": return row.maturity ?? NO_VALUE;
242
332
  case "coupon": return creditCoupon(row);
@@ -320,7 +410,7 @@ export function maturityAxis(rows: readonly CreditMaturityBucket[]) {
320
410
  }
321
411
 
322
412
  /** The source fact, not the magnitude, determines a covenant's unit. */
323
- export function covenantNumber(value: number | null, fact: CreditFact | undefined): string {
413
+ export function covenantNumber(value: number | null, fact: Pick<CreditFact, "currency" | "unit"> | undefined): string {
324
414
  if (value === null) return NO_VALUE;
325
415
  if (fact?.currency) return `${creditAmount(value)} ${fact.currency}`;
326
416
  if (fact?.unit === "ratio" || fact?.unit === "times") return `${value}x`;
@@ -2,7 +2,7 @@ import { useCallback, useMemo } from "react";
2
2
  import { isAccessDenied } from "../../../api-client/errors";
3
3
  import { usePlanAccess } from "../../../api-client/plan-access";
4
4
  import type { CreditDocumentsPayload, CreditHeadroom, CreditInstrument, CreditScreenPayload, CreditScreenRow } from "../../../api-client/credit-documents";
5
- import { ChartTableHeader, DataTableView, EmptyState, PageStackView, QueryBar, useChartTableSelection, usePaneFooter, usePaneNoticeFooter, usePaneStatusFooter, usePaneTabs, type DataTableCell, type DataTableColumn, type PaneHint } from "../../../components";
5
+ import { ChartTableHeader, DataTableView, EmptyState, Notice, PageStackView, QueryBar, useChartTableSelection, usePaneFooter, usePaneNoticeFooter, usePaneStatusFooter, usePaneTabs, type DataTableCell, type DataTableColumn, type PaneHint } from "../../../components";
6
6
  import { formatCompactAxis } from "../../../components/chart-table";
7
7
  import { usePaneRefreshKey } from "../../../components/data-table/table-pane";
8
8
  import { useAsyncResource, useAutoRefresh, usePaneSettingValue, usePluginAppActions, usePluginPaneState } from "../../../public/react";
@@ -19,7 +19,7 @@ import { cachedCredit, creditIssuerSymbol, CREDIT_UNAVAILABLE, fetchCreditScreen
19
19
  import { CreditInstrumentDetail } from "./detail";
20
20
  import { useCreditEvidence } from "./evidence";
21
21
  import { HeadroomGauge } from "./gauge";
22
- import { capitalCell, capitalColumns, capitalFigures, comparatorSymbol, covenantColumns, covenantUsage, CREDIT_TABS, creditAmount, covenantNumber, creditCurrencies, creditMaturityRows, creditPercent, creditTab, fieldLabel, headroomTone, instrumentOutstanding, isClosed, maturityAxis, maturityColumns, maturitySeries, maturityShares, NO_VALUE, screenColumns, screenRowId, sentence } from "./model";
22
+ import { capitalCell, capitalColumns, capitalFigures, comparatorSymbol, covenantColumns, covenantFact, covenantReason, covenantUsage, CREDIT_EMPTY_TITLE, creditEmptyCause, CREDIT_TABS, creditAmount, covenantNumber, creditCurrencies, creditMaturityRows, creditPercent, creditTab, fieldLabel, headroomTone, instrumentOutstanding, isClosed, maturityAxis, maturityColumns, maturitySeries, maturityShares, NO_VALUE, screenColumns, screenRowId, screenSignal, sentence } from "./model";
23
23
 
24
24
  type Row = { numbers?: Record<string, number | null>; id: string; values: Record<string, string>; instrumentId?: string; covenant?: CreditHeadroom; screen?: CreditScreenRow; year?: number; locked?: boolean; closed?: boolean };
25
25
  const rowId = (row: Row) => row.id;
@@ -70,10 +70,10 @@ function CreditView({ symbol, width, height, focused, accessKey, needsVerificati
70
70
  : tab === "maturities" ? maturityColumns(width, preview) : screenColumns(width), [tab, data, width, preview]);
71
71
  const figures = useMemo(() => tab === "capital" && data ? capitalFigures(data) : [], [tab, data]);
72
72
  const unsortedRows = useMemo<Row[]>(() => {
73
- if (tab === "screen") return (screenData?.rows ?? []).map((row) => ({ id: screenRowId(row), instrumentId: row.instrumentId, screen: row, numbers: {value:row.value}, values: { symbol: row.symbol, kind: row.kind === "springing_maturity" ? "Springing maturity" : row.value != null && row.value < 0 ? "Breach" : "Low headroom", value: creditPercent(row.value), date: row.date ?? NO_VALUE, instrument: row.instrumentName, reason: sentence(row.reason) } }));
73
+ if (tab === "screen") return (screenData?.rows ?? []).map((row) => ({ id: screenRowId(row), instrumentId: row.instrumentId, screen: row, numbers: {value:row.value}, values: { symbol: row.symbol, kind: screenSignal(row), value: creditPercent(row.value), date: row.date ?? NO_VALUE, instrument: row.instrumentName, reason: sentence(row.reason) } }));
74
74
  if (!data) return [];
75
75
  if (tab === "capital") return data.instruments.map((row) => ({ id: row.id, instrumentId: row.id, closed: isClosed(row), numbers: {principal:instrumentOutstanding(row)}, values: Object.fromEntries(["name", "facility", "coupon", "maturity", "principal", "currency", "ranking", "control"].map((id) => [id, capitalCell(row, id, data)])) }));
76
- if (tab === "covenants") return data.covenants.map((row) => { const fact=data.instruments.find((item) => item.id === row.instrumentId)?.facts.find((item) => item.id === row.id); return ({ id: row.id, instrumentId: row.instrumentId, covenant: row, numbers: {headroom:row.headroomPercent,usage:covenantUsage(row),threshold:row.threshold,current:row.current}, values: { metric: sentence(row.metric), headroom: creditPercent(row.headroomPercent), usage: "", threshold: `${comparatorSymbol(row.comparator, row.inclusive)} ${covenantNumber(row.threshold,fact)}`, current: covenantNumber(row.current,fact), status: fieldLabel(row.status), date: row.asOf, instrument: row.instrumentName } }); });
76
+ if (tab === "covenants") return data.covenants.map((row) => { const fact=covenantFact(data, row); return ({ id: row.id, instrumentId: row.instrumentId, covenant: row, numbers: {headroom:row.headroomPercent,usage:covenantUsage(row),threshold:row.threshold,current:row.current}, values: { metric: sentence(row.metric), headroom: creditPercent(row.headroomPercent), usage: "", threshold: `${comparatorSymbol(row.comparator, row.inclusive)} ${covenantNumber(row.threshold,fact)}`, current: covenantNumber(row.current,fact), status: fieldLabel(row.status), date: row.asOf, instrument: row.instrumentName } }); });
77
77
  const shares = preview ? new Map() : maturityShares(maturityRows);
78
78
  return maturityRows.flatMap((row) => row.instruments.map((instrument) => { const id = `${row.year}:${instrument.id}`, share = shares.get(id); return { id, instrumentId: instrument.id, year: row.year, numbers: {year:row.year,principal:instrument.principal,share:share?.share ?? null,cumulative:share?.cumulative ?? null}, values: { year: String(row.year), principal: creditAmount(instrument.principal), share: creditPercent(share?.share), cumulative: creditPercent(share?.cumulative), instruments: instrument.name } }; }));
79
79
  }, [data, screenData, tab, maturityRows, preview]);
@@ -85,11 +85,17 @@ function CreditView({ symbol, width, height, focused, accessKey, needsVerificati
85
85
  : left === right ? a.id.localeCompare(b.id) : (left < right ? -1 : 1)*(sort.direction === "asc" ? 1 : -1);
86
86
  }),[unsortedRows,sort]);
87
87
  const selected = rows.find((row) => row.id === selectedId) ?? rows[0] ?? null;
88
+ // The status says Uncomputable; the selected row says why, on the row, as the detail does.
89
+ const selectedCovenant = tab === "covenants" ? selected?.covenant : undefined;
90
+ const whyNoHeadroom = selectedCovenant ? covenantReason(selectedCovenant, data?.instruments.find((row) => row.id === selectedCovenant.instrumentId)?.facts ?? []) : null;
88
91
  const screenOpen = rows.find((row) => row.id === openId)?.screen;
89
92
  const openInstrument = data?.instruments.find((row) => row.id === openId) ?? (screenOpen ? { id: screenOpen.instrumentId, key: screenOpen.instrumentId, name: screenOpen.instrumentName, currency: screenOpen.currency, facilityType: null, principal: null, commitment: null, drawn: null, maturity: null, facts: screenOpen.evidence } : null);
90
93
  const [initialFact] = usePaneSettingValue<string | null>("fact", null);
91
94
  const [openFactId, setOpenFact] = usePluginPaneState<string | null>(`credit:fact:open:${openInstrument?.id ?? "none"}`, initialFact);
92
95
  const activeSymbol = screenOpen?.symbol ?? symbol;
96
+ // An issuer with nothing in any section says why once, in each tab, instead of a different empty line per tab.
97
+ const emptyCause = data ? creditEmptyCause(data) : null;
98
+ const nothingOnFile = emptyCause ? CREDIT_EMPTY_TITLE[emptyCause] : null;
93
99
  const activeCovenant = tab === "covenants" ? data?.covenants.find((row) => row.id === selected?.id && row.instrumentId === openInstrument?.id) ?? null : null;
94
100
  const locked = tab === "screen" ? screenData?.lockedRows ?? 0 : data?.lockedRows ?? 0;
95
101
  const items = useMemo<Row[]>(() => [...rows, ...Array.from({ length: Math.min(3, locked) }, (_, index) => ({ id: `locked:${index}`, values: {}, locked: true }))], [rows, locked]);
@@ -180,7 +186,8 @@ function CreditView({ symbol, width, height, focused, accessKey, needsVerificati
180
186
  getItemKey={rowId} selection={{ kind: "id", selectedId: selected?.id ?? null, getId: rowId, onChange: setSelected }}
181
187
  onActivate={(row) => row.locked ? openUpgrade() : setOpen(row.screen ? row.id : row.instrumentId ?? null)}
182
188
  renderCell={renderCell}
183
- emptyStateTitle={tab === "screen" ? "No supported credit signals match these thresholds." : tab === "covenants" ? "No financial maintenance covenants documented." : tab === "maturities" ? "No supported instrument maturity amounts." : CREDIT_UNAVAILABLE}
189
+ emptyStateTitle={tab === "screen" ? "No supported credit signals match these thresholds." : nothingOnFile ?? (tab === "covenants" ? "No financial maintenance covenants documented." : tab === "maturities" ? "No supported instrument maturity amounts." : CREDIT_EMPTY_TITLE.instruments)}
190
+ rootAfter={whyNoHeadroom ? <Box paddingX={1} flexShrink={0}><Notice tone="muted">{whyNoHeadroom}</Notice></Box> : undefined}
184
191
  emptyStateHint={tab === "screen" && (headroom < 100 || months < 36) ? "Raise either threshold to widen the screen." : undefined}
185
192
  selectedTextOverridesCellColor showHorizontalScrollbar freezeFirstColumn resetScrollKey={`${symbol}:${tab}:${selectedCurrency}`}
186
193
  bodyAfter={locked > 0 && desktop ? <LockedOverlay rows={Math.min(3, locked)} text={lockedText} onPress={openUpgrade} /> : undefined} />} />
@@ -30,6 +30,8 @@ export function listViewSources(catalog: ViewSourceCatalog): ViewSourceInfo[] {
30
30
  const pane = catalog.panes.get(template.paneId);
31
31
  if (!pane) continue;
32
32
  templated.add(pane.id);
33
+ // An unlisted function (`listed: false`) is not offered, nor its pane in its place.
34
+ if (template.listed === false) continue;
33
35
  const headless = template.headless ?? pane.headless;
34
36
  if (!headless) continue;
35
37
  sources.push({