@gloom-sh/terminal 0.19.1 → 0.19.3

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 (554) hide show
  1. package/README.md +7 -1
  2. package/package.json +2 -1
  3. package/patches/electrobun@1.18.1.patch +108 -1
  4. package/src/account/upgrade-dialog.tsx +4 -23
  5. package/src/api-client/coalesce.ts +118 -0
  6. package/src/api-client/credit-boards.ts +13 -0
  7. package/src/api-client/crypto-etf-flows.ts +6 -0
  8. package/src/api-client/crypto-markets.ts +40 -0
  9. package/src/api-client/data.ts +201 -14
  10. package/src/api-client/equity-screener.ts +10 -0
  11. package/src/api-client/errors.ts +58 -0
  12. package/src/api-client/gpu.ts +29 -0
  13. package/src/api-client/index.ts +5 -2
  14. package/src/api-client/plan-access.ts +10 -1
  15. package/src/api-client/plan-rules.ts +35 -0
  16. package/src/api-client/research-activity.ts +1 -32
  17. package/src/api-client/types.ts +21 -1
  18. package/src/app/attention-notice.tsx +94 -0
  19. package/src/app/runtime/startup.ts +3 -1
  20. package/src/app.tsx +23 -1
  21. package/src/architecture/core-builtin-allowlist.json +2 -1
  22. package/src/capabilities/chart-series.ts +2 -1
  23. package/src/capabilities/factories.ts +1 -0
  24. package/src/cli/ascii-output.ts +42 -0
  25. package/src/cli/base-converter.ts +10 -2
  26. package/src/cli/command-options.ts +16 -1
  27. package/src/cli/commands/crypto-hints.ts +23 -4
  28. package/src/cli/commands/market.ts +101 -27
  29. package/src/cli/commands/news-search.ts +24 -5
  30. package/src/cli/commands/overview.ts +14 -10
  31. package/src/cli/commands/search.ts +84 -25
  32. package/src/cli/commands/system.ts +22 -30
  33. package/src/cli/commands/ticker.ts +49 -9
  34. package/src/cli/commands/version.ts +33 -0
  35. package/src/cli/context.ts +6 -9
  36. package/src/cli/data-dir.ts +7 -0
  37. package/src/cli/desktop-pane-shot-api-policy.ts +85 -0
  38. package/src/cli/desktop-pane-shot-browser.ts +320 -0
  39. package/src/cli/desktop-pane-shot-routes.ts +26 -1
  40. package/src/cli/desktop-pane-shot.ts +196 -256
  41. package/src/cli/early-command.ts +42 -0
  42. package/src/cli/earnings-calendar.ts +9 -0
  43. package/src/cli/entry.ts +19 -13
  44. package/src/cli/errors.ts +12 -2
  45. package/src/cli/glossary.ts +2 -1
  46. package/src/cli/help.ts +1 -1
  47. package/src/cli/history-export.ts +22 -2
  48. package/src/cli/history-rows.ts +189 -11
  49. package/src/cli/index.ts +18 -1
  50. package/src/cli/known-gaps.ts +51 -5
  51. package/src/cli/listing-arg.ts +63 -10
  52. package/src/cli/options.ts +1 -1
  53. package/src/cli/output-file.ts +60 -13
  54. package/src/cli/pane-functions/catalog.ts +14 -3
  55. package/src/cli/pane-functions/data.ts +1 -1
  56. package/src/cli/pane-functions/dom.ts +45 -0
  57. package/src/cli/pane-functions/freshness.ts +46 -9
  58. package/src/cli/pane-functions/function-help.ts +17 -9
  59. package/src/cli/pane-functions/headless.ts +22 -14
  60. package/src/cli/pane-functions/index.ts +6 -2
  61. package/src/cli/pane-functions/options.ts +6 -2
  62. package/src/cli/pane-functions/resolver.ts +27 -5
  63. package/src/cli/pane-functions/screenshot-assessment.ts +33 -0
  64. package/src/cli/pane-functions/screenshot-bridge.ts +94 -13
  65. package/src/cli/pane-functions/screenshot-http.ts +70 -0
  66. package/src/cli/pane-functions/screenshot-render.ts +19 -5
  67. package/src/cli/quote-answer.ts +49 -6
  68. package/src/cli/registry.ts +41 -13
  69. package/src/cli/result.ts +12 -6
  70. package/src/cli/ticker-resolution.ts +2 -1
  71. package/src/cli/types.ts +2 -0
  72. package/src/components/chart/composite/format.ts +3 -0
  73. package/src/components/chart-table/header.tsx +1 -1
  74. package/src/components/chart-table/layout.ts +28 -11
  75. package/src/components/command-bar/action-runtime.ts +4 -0
  76. package/src/components/command-bar/commands/direct/index.ts +18 -33
  77. package/src/components/command-bar/commands/direct/runtime.ts +5 -0
  78. package/src/components/command-bar/commands/registry.ts +10 -1
  79. package/src/components/command-bar/routes/root/command-items.ts +9 -4
  80. package/src/components/command-bar/routes/root/runtime.ts +51 -6
  81. package/src/components/command-bar/selection-runtime.ts +12 -29
  82. package/src/components/command-bar/surface/index.tsx +9 -3
  83. package/src/components/company-logo.tsx +2 -1
  84. package/src/components/layout/detached-pane-shell.tsx +2 -2
  85. package/src/components/layout/header.tsx +10 -5
  86. package/src/components/layout/market-summary.ts +25 -7
  87. package/src/components/layout/pane/footer/index.tsx +8 -2
  88. package/src/components/layout/pane/footer/model.ts +73 -6
  89. package/src/components/layout/pane/header.tsx +2 -2
  90. package/src/components/layout/pane/status-footer.ts +2 -1
  91. package/src/components/market-board/index.tsx +3 -2
  92. package/src/components/onboarding/desks-step.tsx +12 -4
  93. package/src/components/onboarding/first-run-workspace.ts +81 -4
  94. package/src/components/onboarding/onboarding-frame.tsx +4 -2
  95. package/src/components/onboarding/onboarding-keyboard.ts +8 -5
  96. package/src/components/onboarding/onboarding-wizard.tsx +60 -26
  97. package/src/components/onboarding/portfolio-step/positions-panel.tsx +7 -2
  98. package/src/components/onboarding/run-setup-again.ts +24 -0
  99. package/src/components/onboarding/use-onboarding-progress.ts +33 -15
  100. package/src/components/onboarding/use-onboarding-upgrade.ts +2 -6
  101. package/src/components/onboarding/wizard-model.ts +57 -3
  102. package/src/components/onboarding/wizard-positions.ts +1 -1
  103. package/src/components/ui/delayed-mark.tsx +56 -0
  104. package/src/components/ui/display.tsx +16 -5
  105. package/src/components/ui/external-link.tsx +3 -6
  106. package/src/components/ui/icon.tsx +16 -3
  107. package/src/components/ui/logo-mark.tsx +8 -5
  108. package/src/components/ui/stat-grid.tsx +263 -19
  109. package/src/core/app-services.ts +11 -2
  110. package/src/data/app-persistence.ts +9 -7
  111. package/src/data/config/store/node.ts +11 -5
  112. package/src/data/config/store/normalize.ts +16 -3
  113. package/src/data/plugin-cache.ts +15 -0
  114. package/src/data/plugin-state-store.ts +81 -24
  115. package/src/data/profile-permissions.ts +47 -0
  116. package/src/data/resource-store.ts +165 -90
  117. package/src/data/session-store.ts +31 -8
  118. package/src/data/sqlite/access.ts +324 -0
  119. package/src/data/sqlite/checkpointer.ts +285 -0
  120. package/src/data/sqlite/database.ts +80 -4
  121. package/src/data/sqlite/retry.ts +50 -9
  122. package/src/data/ticker-store.ts +57 -23
  123. package/src/docs/snapshot.json +191 -78
  124. package/src/i18n/es.ts +18 -2
  125. package/src/i18n/ja.ts +18 -2
  126. package/src/i18n/ko.ts +18 -2
  127. package/src/i18n/zh-cn.ts +18 -2
  128. package/src/i18n/zh-tw.ts +18 -2
  129. package/src/layout/desk-keys.ts +4 -1
  130. package/src/layout/desks.ts +38 -5
  131. package/src/layout-marketplace/gallery-desktop.tsx +45 -16
  132. package/src/market-data/batched-reads.ts +16 -0
  133. package/src/market-data/coordinator/auxiliary.ts +4 -2
  134. package/src/market-data/coordinator/index.ts +11 -2
  135. package/src/market-data/futures-board.ts +1 -1
  136. package/src/market-data/futures-contract.ts +3 -1
  137. package/src/market-data/market/index-level.ts +6 -4
  138. package/src/market-data/published-apac-sessions.ts +6 -0
  139. package/src/market-data/realized-volatility.ts +4 -2
  140. package/src/news/aggregator.ts +7 -4
  141. package/src/news/news-model.ts +11 -0
  142. package/src/news/story-id.ts +22 -0
  143. package/src/news/ticker-symbols.ts +17 -1
  144. package/src/notifications/app-notifier.ts +35 -14
  145. package/src/plugins/auto-update-node.ts +10 -6
  146. package/src/plugins/builtin/account-management/form-components.tsx +1 -1
  147. package/src/plugins/builtin/account-management/index.tsx +2 -3
  148. package/src/plugins/builtin/account-management/model.ts +1 -1
  149. package/src/plugins/builtin/alerts/cli-forms.ts +15 -1
  150. package/src/plugins/builtin/alerts/events-pane.tsx +7 -5
  151. package/src/plugins/builtin/alerts/events.ts +10 -2
  152. package/src/plugins/builtin/alerts/index.tsx +5 -5
  153. package/src/plugins/builtin/alerts/research-builder.ts +5 -1
  154. package/src/plugins/builtin/alerts/research-rules.ts +18 -0
  155. package/src/plugins/builtin/alerts/targets.ts +4 -1
  156. package/src/plugins/builtin/attention/headless.ts +1 -1
  157. package/src/plugins/builtin/attention/index.tsx +2 -4
  158. package/src/plugins/builtin/attention/listing.ts +1 -1
  159. package/src/plugins/builtin/attention/series.ts +1 -1
  160. package/src/plugins/builtin/awards/index.tsx +1 -2
  161. package/src/plugins/builtin/backtest/client.ts +1 -0
  162. package/src/plugins/builtin/backtest/index.tsx +1 -2
  163. package/src/plugins/builtin/backtest/rules.ts +3 -1
  164. package/src/plugins/builtin/backtest/window.ts +8 -7
  165. package/src/plugins/builtin/broker-manager/detail.tsx +16 -3
  166. package/src/plugins/builtin/broker-manager/index.tsx +1 -1
  167. package/src/plugins/builtin/buildout/cells.tsx +5 -0
  168. package/src/plugins/builtin/buildout/company/flow.tsx +367 -0
  169. package/src/plugins/builtin/buildout/company/id.ts +2 -0
  170. package/src/plugins/builtin/buildout/company/model.ts +247 -0
  171. package/src/plugins/builtin/buildout/company/pane.tsx +317 -0
  172. package/src/plugins/builtin/buildout/data-runtime.ts +31 -10
  173. package/src/plugins/builtin/buildout/facilities.ts +309 -0
  174. package/src/plugins/builtin/buildout/format.ts +9 -0
  175. package/src/plugins/builtin/buildout/index.tsx +51 -5
  176. package/src/plugins/builtin/buildout/model/index.ts +62 -10
  177. package/src/plugins/builtin/buildout/model/normalizers.ts +5 -0
  178. package/src/plugins/builtin/buildout/model/types.ts +11 -2
  179. package/src/plugins/builtin/buildout/pane/index.tsx +91 -20
  180. package/src/plugins/builtin/buildout/pane/status.tsx +1 -1
  181. package/src/plugins/builtin/buildout/pane/themes.tsx +110 -0
  182. package/src/plugins/builtin/buildout/table-model.ts +26 -2
  183. package/src/plugins/builtin/builtin-plugin-meta.ts +16 -0
  184. package/src/plugins/builtin/cds/headless.ts +10 -2
  185. package/src/plugins/builtin/cds/model.ts +7 -3
  186. package/src/plugins/builtin/central-bank-rates/index.tsx +1 -2
  187. package/src/plugins/builtin/central-bank-rates/pane.tsx +2 -1
  188. package/src/plugins/builtin/central-bank-rates/series.ts +2 -3
  189. package/src/plugins/builtin/changelog/index.tsx +3 -2
  190. package/src/plugins/builtin/chart-composer/cli-options.ts +16 -0
  191. package/src/plugins/builtin/chart-composer/headless-schema.ts +10 -0
  192. package/src/plugins/builtin/chart-composer/headless.ts +88 -21
  193. package/src/plugins/builtin/chart-composer/index.tsx +2 -3
  194. package/src/plugins/builtin/chart-composer/pane.tsx +30 -2
  195. package/src/plugins/builtin/chart-composer/series-expression.ts +3 -2
  196. package/src/plugins/builtin/chart-composer/session-range.ts +5 -2
  197. package/src/plugins/builtin/chart-composer/studies.ts +1 -1
  198. package/src/plugins/builtin/chat/controller/index.ts +1 -1
  199. package/src/plugins/builtin/chat/controller/messages.ts +15 -4
  200. package/src/plugins/builtin/chat/ghost-user.ts +8 -0
  201. package/src/plugins/builtin/chat/index.tsx +5 -5
  202. package/src/plugins/builtin/clinical-trials/index.tsx +1 -2
  203. package/src/plugins/builtin/cloud/company-picker.tsx +1 -1
  204. package/src/plugins/builtin/cloud/index.tsx +1 -2
  205. package/src/plugins/builtin/cloud/plugin.tsx +6 -4
  206. package/src/plugins/builtin/cloud/team/module.tsx +1 -1
  207. package/src/plugins/builtin/cloud/thesis/module.tsx +1 -1
  208. package/src/plugins/builtin/cloud/trial-offer-status-widget.tsx +24 -1
  209. package/src/plugins/builtin/cloud/upgrade-status-widget.tsx +39 -15
  210. package/src/plugins/builtin/comment-letters/index.tsx +1 -2
  211. package/src/plugins/builtin/commodity-balances/client.ts +4 -1
  212. package/src/plugins/builtin/commodity-balances/index.tsx +3 -3
  213. package/src/plugins/builtin/commodity-balances/pane.tsx +2 -2
  214. package/src/plugins/builtin/congress-trades/aggregates.ts +13 -2
  215. package/src/plugins/builtin/congress-trades/detail.tsx +20 -4
  216. package/src/plugins/builtin/congress-trades/headless.ts +2 -0
  217. package/src/plugins/builtin/congress-trades/model.ts +92 -64
  218. package/src/plugins/builtin/congress-trades/table.tsx +14 -2
  219. package/src/plugins/builtin/connections/index.ts +2 -3
  220. package/src/plugins/builtin/cot/client.ts +28 -2
  221. package/src/plugins/builtin/cot/headless.ts +4 -1
  222. package/src/plugins/builtin/cot/index.tsx +5 -4
  223. package/src/plugins/builtin/cot/model.ts +58 -7
  224. package/src/plugins/builtin/cot/pane.tsx +6 -2
  225. package/src/plugins/builtin/cpi/client.ts +6 -5
  226. package/src/plugins/builtin/cpi/index.tsx +1 -2
  227. package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
  228. package/src/plugins/builtin/credit-boards/client.ts +7 -2
  229. package/src/plugins/builtin/credit-boards/headless.ts +25 -12
  230. package/src/plugins/builtin/credit-boards/model.ts +29 -0
  231. package/src/plugins/builtin/credit-boards/series.ts +3 -4
  232. package/src/plugins/builtin/credit-boards/sovr-pane.tsx +9 -4
  233. package/src/plugins/builtin/credit-conditions/client.ts +22 -14
  234. package/src/plugins/builtin/crypto-board/bitcoin-network.ts +143 -0
  235. package/src/plugins/builtin/crypto-board/client.ts +5 -5
  236. package/src/plugins/builtin/crypto-board/headless.ts +4 -3
  237. package/src/plugins/builtin/crypto-board/index.tsx +4 -2
  238. package/src/plugins/builtin/crypto-board/series.ts +6 -4
  239. package/src/plugins/builtin/crypto-etf-flows/client.ts +9 -2
  240. package/src/plugins/builtin/crypto-etf-flows/headless.ts +61 -6
  241. package/src/plugins/builtin/crypto-etf-flows/index.tsx +1 -2
  242. package/src/plugins/builtin/crypto-etf-flows/model.ts +14 -1
  243. package/src/plugins/builtin/crypto-etf-flows/pane.tsx +8 -5
  244. package/src/plugins/builtin/debt-maturities/headless.ts +4 -3
  245. package/src/plugins/builtin/debt-maturities/model.ts +17 -1
  246. package/src/plugins/builtin/debt-maturities/pane.tsx +18 -4
  247. package/src/plugins/builtin/debt-maturities/series.ts +3 -3
  248. package/src/plugins/builtin/debug/index.tsx +1 -1
  249. package/src/plugins/builtin/dividend-yield/client.ts +32 -2
  250. package/src/plugins/builtin/dividend-yield/headless.ts +58 -33
  251. package/src/plugins/builtin/dividend-yield/index.tsx +37 -2
  252. package/src/plugins/builtin/dividend-yield/pane.tsx +32 -13
  253. package/src/plugins/builtin/dividend-yield/ranking-reads.ts +73 -0
  254. package/src/plugins/builtin/dividend-yield/ranking-view.tsx +163 -0
  255. package/src/plugins/builtin/dividend-yield/ranking.ts +1 -1
  256. package/src/plugins/builtin/dividend-yield/view.ts +21 -0
  257. package/src/plugins/builtin/docs/index.tsx +2 -3
  258. package/src/plugins/builtin/doe/client.ts +6 -5
  259. package/src/plugins/builtin/doe/headless.ts +16 -3
  260. package/src/plugins/builtin/doe/index.tsx +5 -4
  261. package/src/plugins/builtin/doe/prices.ts +2 -3
  262. package/src/plugins/builtin/doe/series.ts +4 -4
  263. package/src/plugins/builtin/earnings/board.tsx +2 -1
  264. package/src/plugins/builtin/earnings/history.tsx +2 -1
  265. package/src/plugins/builtin/earnings-calls/calendar.tsx +10 -2
  266. package/src/plugins/builtin/earnings-calls/data.ts +19 -11
  267. package/src/plugins/builtin/earnings-calls/pane.tsx +27 -14
  268. package/src/plugins/builtin/earnings-ripple/client.ts +32 -7
  269. package/src/plugins/builtin/earnings-ripple/index.tsx +1 -2
  270. package/src/plugins/builtin/econ/calendar-model.ts +29 -6
  271. package/src/plugins/builtin/econ/detail-view.tsx +16 -2
  272. package/src/plugins/builtin/econ-statistics/index.tsx +1 -2
  273. package/src/plugins/builtin/equity-screener/client.ts +1 -1
  274. package/src/plugins/builtin/equity-screener/headless.ts +1 -0
  275. package/src/plugins/builtin/equity-screener/ideas.ts +30 -1
  276. package/src/plugins/builtin/equity-screener/index.tsx +1 -2
  277. package/src/plugins/builtin/equity-screener/model.ts +28 -4
  278. package/src/plugins/builtin/equity-screener/pane.tsx +5 -0
  279. package/src/plugins/builtin/equity-screener/screen-check.ts +23 -1
  280. package/src/plugins/builtin/estimate-revisions/index.tsx +1 -2
  281. package/src/plugins/builtin/executives/data.ts +6 -1
  282. package/src/plugins/builtin/executives/pane.tsx +9 -21
  283. package/src/plugins/builtin/exposure/holdings.ts +35 -13
  284. package/src/plugins/builtin/exposure/index.tsx +1 -2
  285. package/src/plugins/builtin/fear-greed/cache.ts +4 -0
  286. package/src/plugins/builtin/fear-greed/cli.ts +44 -0
  287. package/src/plugins/builtin/fear-greed/index.tsx +5 -53
  288. package/src/plugins/builtin/fear-greed/pane.tsx +2 -1
  289. package/src/plugins/builtin/fear-greed/series.ts +6 -4
  290. package/src/plugins/builtin/fixed-income/index.tsx +1 -2
  291. package/src/plugins/builtin/futures/board-reads.ts +25 -0
  292. package/src/plugins/builtin/futures/coalesced-bump.ts +44 -0
  293. package/src/plugins/builtin/futures/index.tsx +15 -0
  294. package/src/plugins/builtin/futures/model.ts +14 -0
  295. package/src/plugins/builtin/futures/use-board-terms.ts +22 -15
  296. package/src/plugins/builtin/futures/use-front-returns.ts +20 -14
  297. package/src/plugins/builtin/futures-curve/headless.ts +6 -1
  298. package/src/plugins/builtin/futures-curve/index.tsx +1 -2
  299. package/src/plugins/builtin/fx-matrix/index.tsx +2 -2
  300. package/src/plugins/builtin/gpu/board.tsx +51 -12
  301. package/src/plugins/builtin/gpu/client.ts +19 -3
  302. package/src/plugins/builtin/gpu/equities.tsx +5 -2
  303. package/src/plugins/builtin/gpu/headless.ts +14 -2
  304. package/src/plugins/builtin/gpu/index.tsx +1 -2
  305. package/src/plugins/builtin/gpu/model.ts +20 -2
  306. package/src/plugins/builtin/gpu/pane.tsx +1 -1
  307. package/src/plugins/builtin/gpu/series.ts +2 -3
  308. package/src/plugins/builtin/help/index.tsx +1 -1
  309. package/src/plugins/builtin/holders/beneficial-view.tsx +1 -2
  310. package/src/plugins/builtin/holders/index-holders.ts +24 -12
  311. package/src/plugins/builtin/holders/index.tsx +2 -3
  312. package/src/plugins/builtin/ipo-calendar/index.tsx +1 -2
  313. package/src/plugins/builtin/iv-history/index.tsx +1 -2
  314. package/src/plugins/builtin/jodi/client.ts +4 -1
  315. package/src/plugins/builtin/jodi/index.tsx +1 -2
  316. package/src/plugins/builtin/kelly-sizer/index.tsx +1 -2
  317. package/src/plugins/builtin/layout-manager/index.tsx +9 -8
  318. package/src/plugins/builtin/macro-day/pane.tsx +4 -2
  319. package/src/plugins/builtin/market-halts/index.tsx +1 -2
  320. package/src/plugins/builtin/market-heatmap/index.tsx +11 -2
  321. package/src/plugins/builtin/market-movers/client.ts +1 -0
  322. package/src/plugins/builtin/market-movers/index.tsx +16 -5
  323. package/src/plugins/builtin/market-movers/screener.ts +23 -6
  324. package/src/plugins/builtin/market-movers/session-body.tsx +6 -2
  325. package/src/plugins/builtin/market-valuation/index.tsx +1 -2
  326. package/src/plugins/builtin/members/index.tsx +1 -2
  327. package/src/plugins/builtin/money-markets/index.tsx +1 -2
  328. package/src/plugins/builtin/money-markets/pane.tsx +2 -1
  329. package/src/plugins/builtin/money-markets/series.ts +3 -5
  330. package/src/plugins/builtin/news/headless.ts +7 -0
  331. package/src/plugins/builtin/news/wire/index.tsx +1 -1
  332. package/src/plugins/builtin/news/wire/news/pop-out.tsx +9 -3
  333. package/src/plugins/builtin/notes/index.tsx +3 -6
  334. package/src/plugins/builtin/openfda/index.tsx +1 -2
  335. package/src/plugins/builtin/options/view.tsx +6 -5
  336. package/src/plugins/builtin/options-calculator/index.tsx +1 -2
  337. package/src/plugins/builtin/options-positioning/index.tsx +1 -2
  338. package/src/plugins/builtin/options-scenario/index.tsx +2 -4
  339. package/src/plugins/builtin/perps/index.tsx +1 -2
  340. package/src/plugins/builtin/plugin-marketplace/index.tsx +2 -3
  341. package/src/plugins/builtin/plugin-marketplace/pane.tsx +2 -1
  342. package/src/plugins/builtin/plugin-module.ts +16 -2
  343. package/src/plugins/builtin/portfolio-list/cli/render.ts +53 -3
  344. package/src/plugins/builtin/portfolio-list/index.tsx +1 -2
  345. package/src/plugins/builtin/portfolio-list/pane/index.tsx +11 -0
  346. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -2
  347. package/src/plugins/builtin/power/gas-client.ts +2 -1
  348. package/src/plugins/builtin/power/index.tsx +1 -2
  349. package/src/plugins/builtin/power/prices-client.ts +3 -2
  350. package/src/plugins/builtin/rate-path/index.tsx +1 -2
  351. package/src/plugins/builtin/realized-vol/headless.ts +2 -0
  352. package/src/plugins/builtin/realized-vol/index.tsx +1 -2
  353. package/src/plugins/builtin/realized-vol/model.ts +16 -2
  354. package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
  355. package/src/plugins/builtin/relative-rotation/index.tsx +1 -2
  356. package/src/plugins/builtin/research/analyst-headless.ts +33 -6
  357. package/src/plugins/builtin/research/analyst-model.ts +37 -2
  358. package/src/plugins/builtin/research/analyst-pane.tsx +25 -5
  359. package/src/plugins/builtin/research/index.tsx +5 -8
  360. package/src/plugins/builtin/research-search/index.tsx +1 -2
  361. package/src/plugins/builtin/revenue-breakdown/headless.ts +22 -8
  362. package/src/plugins/builtin/revenue-breakdown/model.ts +23 -0
  363. package/src/plugins/builtin/reverse-dcf/pane.tsx +4 -2
  364. package/src/plugins/builtin/risk-factors/data.ts +8 -4
  365. package/src/plugins/builtin/risk-factors/index.tsx +3 -1
  366. package/src/plugins/builtin/risk-factors/pane.tsx +5 -8
  367. package/src/plugins/builtin/roadmap/index.tsx +2 -3
  368. package/src/plugins/builtin/roadmap/pane.tsx +49 -2
  369. package/src/plugins/builtin/scanner/hilo-model.ts +18 -1
  370. package/src/plugins/builtin/scanner/hilo-pane.tsx +12 -2
  371. package/src/plugins/builtin/scanner/live-pane.tsx +7 -4
  372. package/src/plugins/builtin/scanner/lock-note.ts +26 -3
  373. package/src/plugins/builtin/sec/filing-content.ts +38 -6
  374. package/src/plugins/builtin/sec/headless.ts +5 -1
  375. package/src/plugins/builtin/sec/index.tsx +4 -2
  376. package/src/plugins/builtin/sec/model.ts +2 -0
  377. package/src/plugins/builtin/sectors/index.tsx +11 -5
  378. package/src/plugins/builtin/series-inventory.ts +2 -0
  379. package/src/plugins/builtin/shared/cloud-resource.ts +73 -8
  380. package/src/plugins/builtin/shared/cloud-upgrade.ts +24 -1
  381. package/src/plugins/builtin/shared/forming-bars.ts +29 -0
  382. package/src/plugins/builtin/shared/fred-points.ts +6 -1
  383. package/src/plugins/builtin/shared/fx-rate-freshness.ts +17 -0
  384. package/src/plugins/builtin/shared/pro-lock-note.ts +23 -8
  385. package/src/plugins/builtin/shared/series-detail-chart.tsx +4 -2
  386. package/src/plugins/builtin/shared/series-warning.ts +13 -4
  387. package/src/plugins/builtin/shared/use-quote-board.ts +5 -0
  388. package/src/plugins/builtin/short-interest/publications-pane.tsx +2 -1
  389. package/src/plugins/builtin/short-volume/pane.tsx +2 -1
  390. package/src/plugins/builtin/short-volume/series.ts +2 -3
  391. package/src/plugins/builtin/social-mentions/client.ts +15 -10
  392. package/src/plugins/builtin/social-mentions/index.tsx +1 -2
  393. package/src/plugins/builtin/social-mentions/pane.tsx +2 -1
  394. package/src/plugins/builtin/social-mentions/series.ts +2 -3
  395. package/src/plugins/builtin/supply-chain/client.ts +22 -1
  396. package/src/plugins/builtin/supply-chain/graph-client.ts +22 -1
  397. package/src/plugins/builtin/supply-chain/index.tsx +1 -2
  398. package/src/plugins/builtin/thirteenf/api.ts +127 -29
  399. package/src/plugins/builtin/thirteenf/data.ts +165 -35
  400. package/src/plugins/builtin/thirteenf/index.tsx +2 -4
  401. package/src/plugins/builtin/thirteenf/model.ts +2 -0
  402. package/src/plugins/builtin/thirteenf/overlap-pane.tsx +5 -1
  403. package/src/plugins/builtin/thirteenf/pane.tsx +41 -16
  404. package/src/plugins/builtin/thirteenf/types.ts +14 -0
  405. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +22 -6
  406. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +81 -11
  407. package/src/plugins/builtin/ticker-detail/financials/model.ts +35 -2
  408. package/src/plugins/builtin/ticker-detail/financials/ratios.ts +53 -18
  409. package/src/plugins/builtin/ticker-detail/headless.ts +70 -11
  410. package/src/plugins/builtin/ticker-detail/index.tsx +5 -10
  411. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +16 -6
  412. package/src/plugins/builtin/time-sales/index.tsx +1 -2
  413. package/src/plugins/builtin/time-sales/pane.tsx +3 -1
  414. package/src/plugins/builtin/trace-bonds/headless.ts +11 -2
  415. package/src/plugins/builtin/trace-bonds/model.ts +44 -10
  416. package/src/plugins/builtin/trace-bonds/pane.tsx +12 -7
  417. package/src/plugins/builtin/treasury-auctions/cache.ts +20 -8
  418. package/src/plugins/builtin/treasury-auctions/client.ts +3 -3
  419. package/src/plugins/builtin/treasury-auctions/headless.ts +3 -1
  420. package/src/plugins/builtin/treasury-auctions/pane.tsx +10 -4
  421. package/src/plugins/builtin/vol-surface/client.ts +18 -3
  422. package/src/plugins/builtin/vol-surface/index.tsx +1 -2
  423. package/src/plugins/builtin/volatility/client.ts +43 -16
  424. package/src/plugins/builtin/volatility/headless.ts +5 -2
  425. package/src/plugins/builtin/volatility/index.tsx +1 -2
  426. package/src/plugins/builtin/volatility/model.ts +17 -6
  427. package/src/plugins/builtin/volatility/pane.tsx +6 -2
  428. package/src/plugins/builtin/volatility/series.ts +2 -1
  429. package/src/plugins/builtin/world-indices/footer.ts +6 -1
  430. package/src/plugins/builtin/world-venue-map/client.ts +10 -1
  431. package/src/plugins/builtin/world-venue-map/index.ts +6 -2
  432. package/src/plugins/builtin/world-venue-map/layered-pane.tsx +48 -7
  433. package/src/plugins/builtin/world-venue-map/layers.ts +17 -0
  434. package/src/plugins/builtin/world-venue-map/local-layers.ts +141 -0
  435. package/src/plugins/builtin/world-venue-map/map-symbols.ts +18 -0
  436. package/src/plugins/builtin/world-venue-map/map.tsx +4 -2
  437. package/src/plugins/builtin/world-venue-map/scope.ts +63 -0
  438. package/src/plugins/builtin/yield-curve/client.ts +4 -6
  439. package/src/plugins/builtin/yield-curve/curves.ts +16 -2
  440. package/src/plugins/builtin/yield-curve/headless.ts +3 -3
  441. package/src/plugins/bundle.ts +52 -2
  442. package/src/plugins/catalog-browser.ts +2 -2
  443. package/src/plugins/catalog-cli.ts +20 -0
  444. package/src/plugins/host-link.ts +13 -1
  445. package/src/plugins/installer.ts +64 -42
  446. package/src/plugins/loader.ts +43 -11
  447. package/src/plugins/peer-imports.ts +201 -0
  448. package/src/plugins/peer-retry.ts +26 -29
  449. package/src/plugins/plugin-process.ts +128 -0
  450. package/src/plugins/redirection-guard.ts +45 -0
  451. package/src/remote/endpoint.ts +16 -3
  452. package/src/renderers/browser/main.tsx +3 -2
  453. package/src/renderers/cloudflare/http-proxy.ts +7 -123
  454. package/src/renderers/dom/desktop/icons.tsx +5 -2
  455. package/src/renderers/dom/host/image-surface.tsx +46 -21
  456. package/src/renderers/dom/styles.css +7 -0
  457. package/src/renderers/dom/toast-host.tsx +2 -0
  458. package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
  459. package/src/renderers/electrobun/bun/desktop/detached-windows.ts +23 -1
  460. package/src/renderers/electrobun/bun/desktop/initialization.ts +12 -3
  461. package/src/renderers/electrobun/bun/desktop/rpc-timing.ts +88 -0
  462. package/src/renderers/electrobun/bun/desktop/update.ts +20 -4
  463. package/src/renderers/electrobun/bun/desktop/view-ids.ts +8 -4
  464. package/src/renderers/electrobun/bun/external-plugins.ts +4 -0
  465. package/src/renderers/electrobun/bun/window/attention.ts +54 -0
  466. package/src/renderers/electrobun/bun/window/create.ts +6 -2
  467. package/src/renderers/electrobun/bun/window/native-timing.ts +48 -0
  468. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +82 -4
  469. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +13 -5
  470. package/src/renderers/electrobun/view/desktop/window/snapshot.ts +3 -0
  471. package/src/renderers/electrobun/view/main.tsx +7 -2
  472. package/src/renderers/electrobun/view/remote/asset-data-client.ts +1 -0
  473. package/src/renderers/electrobun/view/ui-host.tsx +9 -2
  474. package/src/renderers/opentui/browser-open.ts +10 -4
  475. package/src/renderers/opentui/image/surface.tsx +1 -1
  476. package/src/renderers/opentui/start.tsx +12 -6
  477. package/src/renderers/opentui/toast-host.tsx +2 -1
  478. package/src/series/registry.ts +18 -0
  479. package/src/series/resolve.ts +2 -1
  480. package/src/series/standalone.ts +10 -1
  481. package/src/series/types.ts +12 -0
  482. package/src/series/validate.ts +11 -2
  483. package/src/sources/gloomberb-cloud/fred-series.ts +5 -3
  484. package/src/sources/gloomberb-cloud/index.ts +28 -5
  485. package/src/sources/gloomberb-cloud/news.ts +24 -3
  486. package/src/sources/gloomberb-cloud/normalizers.ts +17 -2
  487. package/src/sources/provider-errors.ts +26 -3
  488. package/src/sources/provider-router/history.ts +5 -0
  489. package/src/sources/provider-router/index.ts +13 -0
  490. package/src/sync/core-contributors.ts +32 -0
  491. package/src/telemetry/attention-counts.ts +64 -6
  492. package/src/telemetry/attention-setting.ts +33 -0
  493. package/src/telemetry/crash-reports-node.ts +3 -1
  494. package/src/telemetry/stall-clocks.ts +93 -0
  495. package/src/telemetry/stall-reports-node.ts +25 -9
  496. package/src/telemetry/stall-reports.ts +218 -13
  497. package/src/test-support/outage.ts +8 -0
  498. package/src/test-support/preload.ts +9 -0
  499. package/src/tickers/company-name-listing.ts +59 -29
  500. package/src/tickers/company-name.ts +84 -1
  501. package/src/tickers/listing-fate.ts +14 -3
  502. package/src/tickers/saved-instrument.ts +17 -0
  503. package/src/tickers/search/home-listing.ts +104 -8
  504. package/src/tickers/search/index.ts +5 -2
  505. package/src/tickers/search/types.ts +6 -1
  506. package/src/time-series/bar-aggregation.ts +7 -1
  507. package/src/time-series/base-series.ts +3 -3
  508. package/src/time-series/chart-bounds.ts +1 -1
  509. package/src/time-series/chart-data.ts +5 -2
  510. package/src/time-series/chart-seed.ts +1 -1
  511. package/src/time-series/coin-session-close.ts +211 -0
  512. package/src/time-series/live-bars.ts +13 -2
  513. package/src/time-series/live-quotes.ts +1 -1
  514. package/src/time-series/market.ts +5 -3
  515. package/src/time-series/official-close.ts +159 -0
  516. package/src/time-series/price-comparison.ts +75 -8
  517. package/src/time-series/resolve.ts +32 -4
  518. package/src/time-series/series-presentation.ts +1 -1
  519. package/src/time-series/studies/formula.ts +205 -0
  520. package/src/time-series/studies/indicators.ts +299 -0
  521. package/src/time-series/studies/leverage.ts +75 -0
  522. package/src/time-series/studies/pair.ts +138 -0
  523. package/src/time-series/studies/resolve.ts +333 -0
  524. package/src/time-series/studies/shared.ts +141 -0
  525. package/src/time-series/studies/units.ts +243 -0
  526. package/src/time-series/studies/vwap.ts +274 -0
  527. package/src/time-series/types.ts +7 -0
  528. package/src/types/config.ts +18 -2
  529. package/src/types/data-provider.ts +23 -0
  530. package/src/types/financials.ts +40 -3
  531. package/src/types/headless.ts +42 -3
  532. package/src/types/plugin.ts +32 -3
  533. package/src/ui/host.tsx +17 -1
  534. package/src/ui/toast.tsx +26 -1
  535. package/src/utils/ascii-text.ts +140 -0
  536. package/src/utils/cli-output.ts +91 -18
  537. package/src/utils/concurrency.ts +24 -0
  538. package/src/utils/exchanges.ts +6 -0
  539. package/src/utils/external-url.ts +20 -7
  540. package/src/utils/financial-statements.ts +73 -16
  541. package/src/utils/fundamentals.ts +17 -5
  542. package/src/utils/futures-generic.ts +4 -4
  543. package/src/utils/http-proxy-upstream.ts +124 -0
  544. package/src/utils/lease-liabilities.ts +54 -0
  545. package/src/utils/main-thread-stall.ts +122 -5
  546. package/src/utils/perf-marks.ts +58 -12
  547. package/src/utils/price-history-integrity.ts +47 -1
  548. package/src/utils/private-files.ts +141 -0
  549. package/src/utils/public-address.ts +99 -0
  550. package/src/account/upgrade-personalization.ts +0 -57
  551. package/src/account/upgrade-tickers.ts +0 -124
  552. package/src/api-client/upgrade-experiment.ts +0 -91
  553. package/src/plugins/builtin/cloud/browser.tsx +0 -3
  554. package/src/time-series/studies.ts +0 -1628
@@ -0,0 +1,163 @@
1
+ import { useCallback, useMemo, useRef } from "react";
2
+ import { apiClient } from "../../../api-client";
3
+ import {
4
+ DataTableView,
5
+ PaneStatusBody,
6
+ usePaneFooter,
7
+ type DataTableCell,
8
+ type DataTableColumn,
9
+ } from "../../../components";
10
+ import { loadingErrorFooterInfo, usePaneRefreshKey } from "../../../components/data-table/table-pane";
11
+ import { useAsyncResource } from "../../../react/async-resource";
12
+ import { useAssetData, usePluginAppActions, usePluginPaneState } from "../../../public/react";
13
+ import { useAppSelector } from "../../../state/app/context";
14
+ import { resolveSavedInstrument } from "../../../tickers/saved-instrument";
15
+ import { colors } from "../../../theme/colors";
16
+ import type { HeadlessPaneColumn } from "../../../types/headless";
17
+ import { TextAttributes } from "../../../ui";
18
+ import { compareSortValues, nextHeaderSort, type SortComparableValue, type SortPreference } from "../../../utils/sort-values";
19
+ import { SignInWall } from "../cloud/auth-actions";
20
+ import { isCloudSessionRequired, useResearchCloudSession } from "../shared/research-cloud-session";
21
+ import { fetchDividendData } from "./client";
22
+ import { dividendRankingReads, type DividendReadContext } from "./headless";
23
+ import { projectDividendRanking, type DividendRankingEntry } from "./ranking";
24
+ import { readDividendRanking, type DividendRankingReads } from "./ranking-reads";
25
+
26
+ /** The pane setting a ranking of several tickers is opened with (`shot DVD KO JNJ`). */
27
+ export const RANK_SYMBOLS_SETTING = "rankSymbols";
28
+
29
+ /** The tickers a pane ranks, from its settings: two or more, or none for a pane of one ticker. */
30
+ export function rankSymbolsOf(settings: Readonly<Record<string, unknown>> | undefined): string[] {
31
+ const value = settings?.[RANK_SYMBOLS_SETTING];
32
+ const symbols = Array.isArray(value) ? value.filter((entry): entry is string => typeof entry === "string" && entry.trim() !== "") : [];
33
+ return symbols.length > 1 ? symbols : [];
34
+ }
35
+
36
+ /** How wide each of the ranking's columns starts, in characters; a column not named fits its header. */
37
+ const COLUMN_WIDTHS: Record<string, number> = { symbol: 10, lastExDate: 10, payDate: 10, note: 24, yieldBasis: 24 };
38
+ /** Growth figures read by their sign, as the single-ticker pane colors them. */
39
+ const SIGNED_COLUMNS = new Set(["growth1Y", "growth3Y"]);
40
+ const DATE_COLUMNS = new Set(["lastExDate", "payDate", "secYieldAsOf"]);
41
+
42
+ type RankingRow = Record<string, unknown> & { symbol: string };
43
+ type RankingColumn = DataTableColumn & { source: HeadlessPaneColumn };
44
+ /**
45
+ * The order `fn DVD` ranks in: trailing yield, highest first. A stable sort
46
+ * of the ranked rows keeps a ticker not read last among those with no yield.
47
+ */
48
+ const RANKED_ORDER: SortPreference<string> = { columnId: "trailingYield", direction: "desc" };
49
+
50
+ function sortRankingRows(rows: RankingRow[], sort: SortPreference<string>): RankingRow[] {
51
+ const { columnId, direction } = sort;
52
+ if (!columnId) return rows;
53
+ return [...rows].sort((left, right) => compareSortValues(left[columnId] as SortComparableValue, right[columnId] as SortComparableValue, direction));
54
+ }
55
+
56
+ /** Signed out, every read is refused for the account: the pane shows the wall the single-ticker DVD shows. */
57
+ function needsSignIn(entries: readonly DividendRankingEntry[] | null): boolean {
58
+ return !!entries?.length && entries.every((entry) => !("data" in entry))
59
+ && entries.some((entry) => "error" in entry && isCloudSessionRequired(entry.error));
60
+ }
61
+
62
+ function cellText(column: HeadlessPaneColumn, row: RankingRow): string {
63
+ const value = row[column.key];
64
+ const format = column.textFormat ?? column.format;
65
+ return format ? format(value, row) : value == null ? "" : String(value);
66
+ }
67
+
68
+ /**
69
+ * Several tickers ranked by trailing yield in one table, with the columns and
70
+ * figures `fn DVD A B C` prints; Enter opens a ticker's own dividend pane.
71
+ */
72
+ export function DividendRankingView({ symbols, focused, width, height, loadData }: {
73
+ symbols: readonly string[];
74
+ focused: boolean;
75
+ width: number;
76
+ height: number;
77
+ /** One listing's history alone, without the quote `fn DVD` prices it at; for tests. */
78
+ loadData?: typeof fetchDividendData;
79
+ }) {
80
+ const key = symbols.join(",");
81
+ const marketData = useAssetData();
82
+ const cloudSession = useResearchCloudSession();
83
+ // A key reads the listing `fn DVD` reads for it: its own venue, else the one its saved ticker is on.
84
+ // Through a ref, so a quote that updates a saved ticker does not read the ranking again.
85
+ const tickers = useAppSelector((state) => state.tickers);
86
+ const tickersRef = useRef(tickers);
87
+ tickersRef.current = tickers;
88
+ const reads = useMemo<DividendRankingReads>(() => {
89
+ const resolveInstrument = (typed: string) => resolveSavedInstrument(typed, (saved) => tickersRef.current.get(saved));
90
+ // `fn DVD`'s own reads: the listing's quote for the price, the stated forward rate, a fund's bond facts.
91
+ if (marketData && !loadData) return dividendRankingReads({ marketData, apiClient, resolveInstrument } satisfies DividendReadContext);
92
+ const history = loadData ?? fetchDividendData;
93
+ return { loadData: async (typed) => {
94
+ const listing = await resolveInstrument(typed);
95
+ return history(listing.symbol, null, listing.exchange ?? "");
96
+ } };
97
+ }, [loadData, marketData]);
98
+ // A sign-in or a verified email reads the ranking again.
99
+ const request = useCallback(() => readDividendRanking(key.split(","), reads), [key, reads, cloudSession.requestKey]);
100
+ const resource = useAsyncResource(request, { keepPreviousData: true });
101
+ const authWall = needsSignIn(resource.data);
102
+ const { createPaneFromTemplate } = usePluginAppActions();
103
+ const section = useMemo(() => resource.data ? projectDividendRanking(resource.data).sections[0] ?? null : null, [resource.data]);
104
+ const [sort, setSort] = usePluginPaneState<SortPreference<string>>("dividend-ranking:sort", RANKED_ORDER);
105
+ const rows = useMemo(() => sortRankingRows((section?.rows ?? []) as RankingRow[], sort), [section, sort]);
106
+ const columns = useMemo<RankingColumn[]>(() => (section?.columns ?? []).map((column) => ({
107
+ id: column.key,
108
+ label: column.header,
109
+ width: Math.max(column.header.length, COLUMN_WIDTHS[column.key] ?? 0),
110
+ align: column.align === "right" ? "right" : "left",
111
+ source: column,
112
+ })), [section]);
113
+ const [selected, setSelected] = usePluginPaneState<string | null>("dividend-ranking:selected", null);
114
+
115
+ usePaneFooter("dividend-ranking", () => ({
116
+ info: loadingErrorFooterInfo(resource.loading && !!resource.data, resource.data ? null : resource.error),
117
+ }), [resource.loading, resource.data, resource.error]);
118
+ usePaneRefreshKey(resource.reload, { focused, enabled: !authWall });
119
+
120
+ const renderCell = useCallback((row: RankingRow, column: RankingColumn): DataTableCell => {
121
+ const text = cellText(column.source, row);
122
+ const value = row[column.id];
123
+ if (column.id === "symbol") return { text, color: colors.textBright, attributes: TextAttributes.BOLD };
124
+ if (column.id === "note") return { text, color: colors.warning };
125
+ if (DATE_COLUMNS.has(column.id) || column.id === "yieldBasis") return { text, color: colors.textDim };
126
+ if (SIGNED_COLUMNS.has(column.id) && typeof value === "number") {
127
+ return { text, color: value > 0 ? colors.positive : value < 0 ? colors.negative : colors.text };
128
+ }
129
+ return { text };
130
+ }, []);
131
+
132
+ if (authWall) return <SignInWall placement="dividend-yield-signin" action="view dividend history" needsVerification={cloudSession.needsVerification} />;
133
+
134
+ return (
135
+ <PaneStatusBody
136
+ subject="dividends"
137
+ loading={!resource.data && resource.loading}
138
+ error={!resource.data ? resource.error : null}
139
+ empty={false}
140
+ >
141
+ <DataTableView<RankingRow, RankingColumn>
142
+ focused={focused}
143
+ columns={columns}
144
+ items={rows}
145
+ rootWidth={width}
146
+ rootHeight={height}
147
+ freezeFirstColumn
148
+ sortColumnId={sort.columnId}
149
+ sortDirection={sort.direction}
150
+ // A third click on a column returns to the ranking.
151
+ onHeaderClick={(id) => setSort((current) => nextHeaderSort(current, id, {
152
+ firstDirection: id === "symbol" || id === "note" || id === "yieldBasis" ? "asc" : "desc", resetTo: RANKED_ORDER }))}
153
+ getItemKey={(row) => row.symbol}
154
+ selection={{ kind: "id", selectedId: rows.some((row) => row.symbol === selected) ? selected! : rows[0]?.symbol ?? "",
155
+ getId: (row) => row.symbol, onChange: (id) => setSelected(id) }}
156
+ onActivate={(row) => createPaneFromTemplate("dividend-yield-pane", { symbol: row.symbol })}
157
+ renderCell={renderCell}
158
+ selectedTextOverridesCellColor
159
+ emptyStateTitle="No tickers"
160
+ />
161
+ </PaneStatusBody>
162
+ );
163
+ }
@@ -146,7 +146,7 @@ const RANKING_COLUMNS: HeadlessPaneColumn[] = [
146
146
  { key: "growth3Y", header: "3Y CAGR", align: "right", format: growth("growth3Y"),
147
147
  description: "Compound annual growth of trailing twelve-month cash per share over three years; n/a when either year holds fewer payments than the cadence pays." },
148
148
  { key: "lastExDate", header: "Last ex", format: day, shrink: false },
149
- { key: "payDate", header: "Pay date", format: day, shrink: false, stackPriority: 2,
149
+ { key: "payDate", header: "Next pay", format: day, shrink: false, stackPriority: 2,
150
150
  description: "Pay date of the next declared distribution; past distributions carry ex-dates only." },
151
151
  ];
152
152
 
@@ -1,4 +1,7 @@
1
1
  import type { DividendPayment, GrowthPayments } from "./types";
2
+ import { formatCurrency } from "../../../utils/format";
3
+ import { currencyMinorDigits } from "../../../market-data/market/format";
4
+ import { resolveCurrencyUnit } from "../../../utils/currency-units";
2
5
  import type { ProjectedChartPoint } from "../../../components/chart/core/data";
3
6
  import { calendarMonthsBefore } from "../../../utils/calendar-date";
4
7
  import { NOT_MEANINGFUL, formatPriceEarnings } from "../../../utils/price-earnings";
@@ -6,6 +9,24 @@ import { LATE_GRACE_DAYS, trailingCashAt, type TrailingPayments } from "../../..
6
9
 
7
10
  const DAY = 24 * 60 * 60 * 1000;
8
11
 
12
+ /**
13
+ * A cash amount the listing pays as a conversion of the currency its company
14
+ * declares in (`DividendData.convertedFrom`), to two decimals of the unit its
15
+ * source states it in: pence stay pence (BP's 6.4059p is `6.41p`, never
16
+ * `£0.06`), and a major unit keeps its currency's decimals (`ZAR 0.87`, not
17
+ * `ZAR 0.866087`), as the conversion gives it no more.
18
+ */
19
+ export function formatConvertedAmount(value: number | null | undefined, currency: string, subUnit?: string): string {
20
+ if (value == null || !Number.isFinite(value)) return "—";
21
+ const unit = subUnit ? resolveCurrencyUnit(subUnit) : null;
22
+ return unit && unit.divisor !== 1 && unit.currency === currency
23
+ ? formatCurrency(value * unit.divisor, subUnit!, 2)
24
+ : formatCurrency(value, currency, currencyMinorDigits(currency));
25
+ }
26
+
27
+ /** What a converted amount says beside it: `converted from USD`. */
28
+ export const convertedFromText = (currency: string) => `converted from ${currency}`;
29
+
9
30
  export function formatDividendYield(value: number | null): string {
10
31
  return value != null && Number.isFinite(value) ? `${(value * 100).toFixed(2)}%` : "—";
11
32
  }
@@ -6,7 +6,6 @@ import type { PaneTemplateContext, PaneTemplateCreateOptions } from "../../../ty
6
6
  import { getSharedRegistry } from "../../registry";
7
7
  import { buildRegistryHelpIndex } from "../help/function-index";
8
8
  import type { PluginModule } from "../plugin-module";
9
- import { DocsPane } from "./pane";
10
9
  import { docsTopicForArgument, docsTopicForPane } from "./targets";
11
10
 
12
11
  /** One reader: every request lands in the same pane, on top of its history. */
@@ -38,8 +37,8 @@ export const docsModule: PluginModule = {
38
37
  id: "docs",
39
38
  name: "Docs",
40
39
  icon: "?",
41
- component: DocsPane,
42
- reportFreshness: { status: "not-a-feed", basis: "documentation" },
40
+ get component(): typeof import("./pane").DocsPane { const { DocsPane } = require("./pane"); return DocsPane; },
41
+ reportFreshness: { status: "not-a-feed", basis: "documentation", local: true },
43
42
  // A page reads whole in a report, not cut at the first screen.
44
43
  shotFitsReader: true,
45
44
  defaultPosition: "right",
@@ -1,8 +1,8 @@
1
1
  import { apiClient } from "../../../api-client";
2
+ import { sourceDidNotAnswer } from "../../../api-client/errors";
2
3
  import type { DoeBoardPayload, DoeSeriesRow } from "../../../api-client/doe";
3
- import { ApiRequestError } from "../../../api-client/errors";
4
4
  import { createPluginCache } from "../../../data/plugin-cache";
5
- import { cachedCloudResource, loadCloudResource } from "../shared/cloud-resource";
5
+ import { cachedCloudResource, loadCloudResource, unavailableOnServer } from "../shared/cloud-resource";
6
6
 
7
7
  /** What the pane says while the server has no EIA weeks to serve: before the backend ships, or before its first read. */
8
8
  export const DOE_NOT_AVAILABLE = "Oil and gas inventories are not available yet.";
@@ -65,11 +65,12 @@ export async function fetchDoeBoard(client: Pick<typeof apiClient, "getCloudDoeB
65
65
  try {
66
66
  payload = await client.getCloudDoeBoard();
67
67
  } catch (error) {
68
- if (error instanceof ApiRequestError && (error.status === 404 || error.status === 503)) throw new Error(DOE_NOT_AVAILABLE);
69
- throw error;
68
+ throw unavailableOnServer(error, DOE_NOT_AVAILABLE, [404, 503]);
70
69
  }
71
70
  const board = validateDoeBoard(payload);
72
- if (board.status === "unavailable" || !board.series.some((row) => row.value != null)) throw new Error(DOE_NOT_AVAILABLE);
71
+ // The server's source did not answer this time: a saved copy answers, as it does a 5xx.
72
+ if (board.status === "unavailable") throw sourceDidNotAnswer(DOE_NOT_AVAILABLE);
73
+ if (!board.series.some((row) => row.value != null)) throw new Error(DOE_NOT_AVAILABLE);
73
74
  return board;
74
75
  }
75
76
 
@@ -49,6 +49,14 @@ function seasonalRows(row: DoeSeriesRow) {
49
49
  fiveYearLow: level(min), fiveYearAverage: level(average), fiveYearHigh: level(max) }));
50
50
  }
51
51
 
52
+ /** Words for natural gas storage, which `NGS` reads, rather than a series of the petroleum tables. */
53
+ const GAS_WORDS = new Set(["ngs", "ng", "gas", "natgas", "naturalgas", "natgasstorage", "naturalgasstorage", "gasstorage", "storage", "workinggas"]);
54
+
55
+ /** Whether `DOE` was given a word for gas storage (`NGS`, `natural gas`) rather than a series. */
56
+ export function doeGasWord(argument: string): boolean {
57
+ return GAS_WORDS.has(argument.toLowerCase().replace(/[^a-z]/g, ""));
58
+ }
59
+
52
60
  /** `DOE` reads every tab unless one is named; `NGS` starts on gas storage; a series argument adds its year. */
53
61
  export function doeHeadless(defaultTab: DoeTab | "all"): HeadlessPaneDefinition<"bundle"> {
54
62
  return {
@@ -77,10 +85,13 @@ export function doeHeadless(defaultTab: DoeTab | "all"): HeadlessPaneDefinition<
77
85
  nextExpectedAt: oldestReportTime(reports.map((report) => report.nextReleaseAt)) };
78
86
  };
79
87
  const argument = Array.isArray(args.argument) ? args.argument.join(" ") : args.argument;
80
- if (argument) {
88
+ // `DOE NGS` or `DOE natural gas` is the gas storage tables `NGS` opens on, and says so; `NGS gas` is them already.
89
+ const gasWord = argument ? doeGasWord(argument) : false;
90
+ const ngs = defaultTab === "gas";
91
+ if (argument && !gasWord) {
81
92
  const option = doeSeriesOption(argument);
82
93
  const row = option ? data.series.find((entry) => entry.id === option.value) : null;
83
- if (!row) throw new Error("Use a series such as cushing, gasoline, distillate or east");
94
+ if (!row) throw new Error(`Use a series such as cushing, gasoline, distillate or east${ngs ? "" : "; for gas storage, gloom fn NGS"}`);
84
95
  return {
85
96
  sections: [
86
97
  { title: doeHeaderLine(doeReport(data, row.tab)) ?? row.label, columns: SUMMARY_COLUMNS, rows: [reportRow(row)] },
@@ -95,8 +106,10 @@ export function doeHeadless(defaultTab: DoeTab | "all"): HeadlessPaneDefinition<
95
106
  metadata: { ...data, series: [row], complete: data.status === "available" },
96
107
  };
97
108
  }
98
- const tabs = args.options.tab === "all" || args.options.tab == null ? DOE_TABS.map(({ value }) => value) : [doeTab(args.options.tab)];
109
+ const tabs = gasWord ? ["gas" as const]
110
+ : args.options.tab === "all" || args.options.tab == null ? DOE_TABS.map(({ value }) => value) : [doeTab(args.options.tab)];
99
111
  return {
112
+ ...(gasWord && !ngs ? { warnings: [`Gas storage is its own function: gloom fn NGS (one region: gloom fn NGS east)`] } : {}),
100
113
  sections: tabs.map((tab) => ({
101
114
  title: `${DOE_TABS.find((entry) => entry.value === tab)!.label}${doeHeaderLine(doeReport(data, tab)) ? `: ${doeHeaderLine(doeReport(data, tab))}` : ""}`,
102
115
  columns: SUMMARY_COLUMNS,
@@ -4,15 +4,16 @@ import type { PluginModule } from "../plugin-module";
4
4
  import { DOE_CATALOG_SERIES } from "./series";
5
5
  import { doeBoardCache } from "./client";
6
6
  import { doePriceCache } from "./prices";
7
- import { doeHeadless } from "./headless";
7
+ import { doeGasWord, doeHeadless } from "./headless";
8
8
  import { DOE_SERIES_OPTIONS, doeSeriesOption } from "./model";
9
- import { DoePane } from "./pane";
10
9
 
11
10
  /** Opens on a tab, or on the series typed after the mnemonic and its tab. */
12
11
  function doeInstance(tab: DoeTab, options?: PaneTemplateCreateOptions): PaneTemplateInstanceConfig {
13
12
  const input = options?.arg?.trim();
13
+ // `DOE NGS` or `DOE natural gas` opens on gas storage, as `NGS` does.
14
+ if (input && doeGasWord(input)) return { placement: "floating", settings: { tab: "gas" } };
14
15
  const series = doeSeriesOption(input);
15
- if (input && !series) throw new Error("Use a series such as cushing, gasoline, distillate or east");
16
+ if (input && !series) throw new Error(`Use a series such as cushing, gasoline, distillate or east${tab === "gas" ? "" : "; for gas storage, gloom fn NGS"}`);
16
17
  return { placement: "floating", settings: { tab: series?.tab ?? tab }, ...(series ? { params: { series: series.value } } : {}) };
17
18
  }
18
19
 
@@ -23,7 +24,7 @@ const shortcut = (prefix: string, tab: DoeTab | null) => ({
23
24
 
24
25
  export const doeModule: PluginModule = {
25
26
  series: DOE_CATALOG_SERIES,
26
- panes: [{ id: "doe", name: "Oil and Gas Inventories", icon: "D", component: DoePane,
27
+ panes: [{ id: "doe", name: "Oil and Gas Inventories", icon: "D", get component(): typeof import("./pane").DoePane { const { DoePane } = require("./pane"); return DoePane; },
27
28
  defaultPosition: "right", defaultMode: "floating", defaultFloatingSize: { width: 96, height: 34 },
28
29
  tableExport: true, headless: doeHeadless("all") }],
29
30
  paneTemplates: [{
@@ -2,7 +2,7 @@ import { apiClient } from "../../../api-client";
2
2
  import type { DoePricesPayload } from "../../../api-client/doe";
3
3
  import { createPluginCache } from "../../../data/plugin-cache";
4
4
  import type { SeriesDefinition } from "../../../series/types";
5
- import { cachedSeriesWarning } from "../shared/series-warning";
5
+ import { cachedSeriesNote } from "../shared/series-warning";
6
6
  import { loadCloudResource, unavailableOnServer } from "../shared/cloud-resource";
7
7
 
8
8
  /**
@@ -73,10 +73,9 @@ export const DOE_PRICE_SERIES: readonly SeriesDefinition[] = PRICE_SERIES.map((s
73
73
  const resource = await loadCloudResource(doePriceCache, "prices", () => fetchDoePrices(), { validate: validatePrices });
74
74
  const row = resource.payload.series.find((entry) => entry.id === series.id);
75
75
  if (!row?.points.length) throw new Error(`${series.name} is not available yet.`);
76
- const warning = cachedSeriesWarning(resource);
77
76
  return {
78
77
  points: row.points.map(([date, value]) => ({ date, value })),
79
- ...(warning ? { warning } : {}),
78
+ ...cachedSeriesNote(resource),
80
79
  // Cited as the EIA series it is, not as the route that served it.
81
80
  attribution: { source: `EIA series ${series.key}`, publisher: "EIA" },
82
81
  };
@@ -1,6 +1,6 @@
1
1
  import type { DoeSeriesRow, DoeUnit } from "../../../api-client/doe";
2
2
  import type { SeriesDefinition, SeriesPoint } from "../../../series/types";
3
- import { cachedSeriesWarning } from "../shared/series-warning";
3
+ import { cachedSeriesNote } from "../shared/series-warning";
4
4
  import { loadDoeBoard } from "./client";
5
5
  import { DOE_SERIES_OPTIONS } from "./model";
6
6
  import { DOE_PRICE_SERIES } from "./prices";
@@ -50,14 +50,14 @@ const DOE_SERIES: readonly SeriesDefinition[] = DOE_SERIES_OPTIONS.map((option):
50
50
  unit: UNITS[unit].unit,
51
51
  frequency: "weekly",
52
52
  group: "Energy",
53
- tags: gas ? ["natural gas", "storage", "inventories"] : ["oil", "petroleum", "inventories", "US"],
53
+ // Jet fuel is kerosene, the word a reader may search it by.
54
+ tags: gas ? ["natural gas", "storage", "inventories"] : ["oil", "petroleum", "inventories", "US", ...option.value === "jet" ? ["kerosene"] : []],
54
55
  publisher: "EIA",
55
56
  load: async () => {
56
57
  const resource = await loadDoeBoard();
57
58
  const row = resource.payload.series.find((entry) => entry.id === option.value);
58
59
  if (!row) throw new Error(`${option.label} is not in this week's report.`);
59
- const warning = cachedSeriesWarning(resource);
60
- return { points: doeRowPoints(row), ...(warning ? { warning } : {}) };
60
+ return { points: doeRowPoints(row), ...cachedSeriesNote(resource) };
61
61
  },
62
62
  };
63
63
  });
@@ -10,6 +10,7 @@ import {
10
10
  type QueryBarFilter,
11
11
  type QueryBarView,
12
12
  } from "../../../components";
13
+ import { ageFooterSegment } from "../../../components/layout/pane/footer";
13
14
  import { handleRefreshKey, usePaneRefreshKey } from "../../../components/data-table/table-pane";
14
15
  import { openFormModal } from "../../../components/form-modal";
15
16
  import { FILL_REST } from "../../../components/ui/fill";
@@ -305,7 +306,7 @@ export function EarningsBoard({ focused, width, height, scopedSymbols, highlight
305
306
  loading: resource.loading,
306
307
  error: resource.error ?? data?.refreshError ?? null,
307
308
  stale: !!data?.stale,
308
- info: data && updatedAgo ? [{ id: "updated", parts: [{ text: updatedAgo, tone: "muted" as const }] }] : [],
309
+ info: data && updatedAgo ? [ageFooterSegment(updatedAgo)] : [],
309
310
  });
310
311
  const symbol = open?.symbol ?? selectedReport?.symbol ?? null;
311
312
  // The desktop and the web only: the terminal's footer stays as it was.
@@ -8,6 +8,7 @@ import {
8
8
  usePaneStatusFooter,
9
9
  type ChartTableChart,
10
10
  } from "../../../components";
11
+ import { ageFooterSegment } from "../../../components/layout/pane/footer";
11
12
  import { staticSeries } from "../../../components/chart/static/series";
12
13
  import { usePaneRefreshKey } from "../../../components/data-table/table-pane";
13
14
  import { useAsyncResource, useAutoRefresh, usePluginPaneState, useUpdatedAgo } from "../../../public/react";
@@ -107,7 +108,7 @@ export function EarningsHistoryView({ symbol, width, height, focused, registrati
107
108
  error: historyError,
108
109
  stale: !!resource.data?.stale,
109
110
  info: abroad ? [{ id: "us-listings", parts: [{ text: "past reports: US listings only", tone: "muted" as const }] }]
110
- : payload && updatedAgo ? [{ id: "updated", parts: [{ text: updatedAgo, tone: "muted" as const }] }] : [],
111
+ : payload && updatedAgo ? [ageFooterSegment(updatedAgo)] : [],
111
112
  });
112
113
 
113
114
  return (
@@ -19,6 +19,8 @@ import {
19
19
  type SectionedRow,
20
20
  } from "../../../components";
21
21
  import { handleRefreshKey } from "../../../components/data-table/table-pane";
22
+ import { savedCopyFooterInfo } from "../../../components/layout/pane/footer";
23
+ import { isPermanentClientError } from "../../../api-client/errors";
22
24
  import { TABLE_COLUMN_GAP, tableColumnWidth } from "../../../components/ui/table-layout";
23
25
  import { useAsyncResource } from "../../../react/async-resource";
24
26
  import { useAgeClock, useAutoRefresh } from "../../../react/auto-refresh";
@@ -162,6 +164,8 @@ export function UpcomingCallsView({ focused, width, height, view, renderCompany
162
164
  }, [range]);
163
165
  const resource = useAsyncResource(entitled ? loader : null, {
164
166
  keepPreviousData: true,
167
+ // A refusal asking again will not change takes the calendar off screen, for its wall or its error.
168
+ clearOnError: isPermanentClientError,
165
169
  initialData: () => cachedUpcomingCalls(range),
166
170
  });
167
171
  const data = resource.data;
@@ -204,12 +208,16 @@ export function UpcomingCallsView({ focused, width, height, view, renderCompany
204
208
  const proRequired = !signInRequired && !verificationRequired && (!access.hasProAccess || (!data && refused === 402));
205
209
 
206
210
  const hints = useMemo<PaneHint[]>(() => [{ id: "search", key: "/", label: "search", onPress: focusSearch }], [focusSearch]);
211
+ // A refresh that failed shows the calendar an earlier read saved, and says when that copy is from.
212
+ const savedAt = data?.savedAt ?? null;
213
+ const savedInfo = useMemo(() => savedCopyFooterInfo([savedAt]), [savedAt]);
207
214
  usePaneStatusFooter({
208
215
  registrationId: "earnings-calls:calendar",
209
216
  enabled: !detailOpen && entitled,
210
217
  loading: resource.loading && !!data,
211
- error: data ? data.refreshError ?? resource.error : null,
212
- stale: !!data && (data.stale || !!resource.error),
218
+ error: data ? (savedAt == null ? data.refreshError : null) ?? resource.error : null,
219
+ stale: !!data && savedAt == null && (data.stale || !!resource.error),
220
+ info: savedInfo,
213
221
  hints,
214
222
  });
215
223
  // A calendar cut at the server's limit says so, rather than end the week early.
@@ -5,7 +5,7 @@ import {
5
5
  type CloudEarningsCallPayload,
6
6
  type CloudEarningsTranscriptPayload,
7
7
  } from "../../../api-client";
8
- import { ApiRequestError } from "../../../api-client/errors";
8
+ import { ApiRequestError, isTransientFailure } from "../../../api-client/errors";
9
9
  import { listingAbroad, type IssuerListingParams, type ListingAbroad } from "../../../api-client/paths";
10
10
  import { servedFromSavedCopy } from "../../../data/saved-copies";
11
11
  import { areDifferentCompanies } from "../../../sources/sec-registrant";
@@ -45,6 +45,8 @@ export interface EarningsCallsResult {
45
45
  calls: CloudEarningsCallPayload[];
46
46
  fetchedAt: number;
47
47
  stale: boolean;
48
+ /** Its refresh failed and the copy saved at this time (ms) answered instead. */
49
+ savedAt?: number;
48
50
  refreshError?: string;
49
51
  /** HTTP status when the request failed, used to pick the right gate. */
50
52
  errorStatus?: number;
@@ -122,6 +124,8 @@ export interface UpcomingCallsResult {
122
124
  stale: boolean;
123
125
  /** The server answered its limit's worth: calls later on these days may be missing. */
124
126
  truncated?: boolean;
127
+ /** Its refresh failed and the copy saved at this time (ms) answered instead. */
128
+ savedAt?: number;
125
129
  refreshError?: string;
126
130
  errorStatus?: number;
127
131
  }
@@ -134,7 +138,7 @@ export interface UpcomingDays {
134
138
  let persistence: PluginPersistence | null = null;
135
139
  const activeUpcomingFetches = new Map<string, Promise<UpcomingCallsResult>>();
136
140
  const activeListFetches = new Map<string, Promise<EarningsCallsResult>>();
137
- const activeTranscriptFetches = new Map<string, Promise<CloudEarningsTranscriptPayload>>();
141
+ const activeTranscriptFetches = new Map<string, Promise<EarningsTranscriptResult>>();
138
142
 
139
143
  export function attachEarningsCallsPersistence(value: PluginPersistence): void {
140
144
  persistence = value;
@@ -253,20 +257,21 @@ export async function loadEarningsCallsWithClient(
253
257
  .catch((error: unknown) => {
254
258
  const refreshError = error instanceof Error ? error.message : String(error);
255
259
  const errorStatus = statusOf(error);
256
- // An auth failure must surface its gate rather than silently showing
257
- // a cached list the user is no longer entitled to refresh.
260
+ // A refusal (signed out, no Pro, a route the server stopped serving) or a reply that fails
261
+ // validation is said rather than covered by a cached list.
258
262
  const expired = store?.getResource<CloudEarningsCallListPayload>(LIST_KIND, key, {
259
263
  sourceKey: CACHE_SOURCE,
260
264
  schemaVersion: LIST_CACHE_SCHEMA_VERSION,
261
265
  allowExpired: true,
262
266
  });
263
- if (expired && errorStatus !== 401 && errorStatus !== 402 && errorStatus !== 403) {
267
+ if (expired && isTransientFailure(error)) {
264
268
  // A command that shows it ends with when the copy was saved, not with the failure.
265
269
  return servedFromSavedCopy({
266
270
  ...expired.value,
267
271
  ...sourceScope(expired.value.calls),
268
272
  fetchedAt: expired.fetchedAt,
269
273
  stale: true,
274
+ savedAt: expired.fetchedAt,
270
275
  refreshError,
271
276
  errorStatus,
272
277
  }, expired.fetchedAt);
@@ -367,9 +372,10 @@ export async function loadUpcomingCallsWithClient(
367
372
  const errorStatus = statusOf(error);
368
373
  // Losing Pro access re-gates the calendar, as it does the list.
369
374
  const expired = read(true);
370
- if (expired && errorStatus !== 401 && errorStatus !== 402 && errorStatus !== 403) {
375
+ if (expired && isTransientFailure(error)) {
371
376
  return servedFromSavedCopy({
372
377
  ...upcomingFromCache(expired.value, expired.fetchedAt, true),
378
+ savedAt: expired.fetchedAt,
373
379
  refreshError: error instanceof Error ? error.message : String(error),
374
380
  errorStatus,
375
381
  }, expired.fetchedAt);
@@ -409,11 +415,14 @@ export function isPendingTranscript(
409
415
  return record.status === "pending" || !Array.isArray(record.turns);
410
416
  }
411
417
 
418
+ /** A transcript, and when it was saved when its refresh failed and the saved copy answered. */
419
+ export type EarningsTranscriptResult = CloudEarningsTranscriptPayload & { savedAt?: number };
420
+
412
421
  export async function loadTranscriptWithClient(
413
422
  client: EarningsCallsApiClient,
414
423
  callId: string,
415
424
  options?: { force?: boolean },
416
- ): Promise<CloudEarningsTranscriptPayload> {
425
+ ): Promise<EarningsTranscriptResult> {
417
426
  const force = options?.force ?? false;
418
427
  const store = persistence;
419
428
 
@@ -444,7 +453,6 @@ export async function loadTranscriptWithClient(
444
453
  return transcript;
445
454
  })
446
455
  .catch((error: unknown) => {
447
- const status = statusOf(error);
448
456
  const expired = store?.getResource<CloudEarningsTranscriptPayload>(
449
457
  TRANSCRIPT_KIND,
450
458
  callId,
@@ -455,8 +463,8 @@ export async function loadTranscriptWithClient(
455
463
  },
456
464
  );
457
465
  // Losing Pro access must re-gate the transcript, not serve it from cache.
458
- if (expired && status !== 401 && status !== 402 && status !== 403) {
459
- return expired.value;
466
+ if (expired && isTransientFailure(error)) {
467
+ return servedFromSavedCopy({ ...expired.value, savedAt: expired.fetchedAt }, expired.fetchedAt);
460
468
  }
461
469
  throw error;
462
470
  })
@@ -473,7 +481,7 @@ export async function loadTranscriptWithClient(
473
481
  export function loadTranscript(
474
482
  callId: string,
475
483
  options?: { force?: boolean },
476
- ): Promise<CloudEarningsTranscriptPayload> {
484
+ ): Promise<EarningsTranscriptResult> {
477
485
  return loadTranscriptWithClient(apiClient, callId, options);
478
486
  }
479
487