gloomberb 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
@@ -3,7 +3,7 @@ import {
3
3
  type CloudProxyStatementListPayload,
4
4
  type CloudProxyStatementPayload,
5
5
  } from "../../../api-client";
6
- import { ApiRequestError, isPermanentClientError } from "../../../api-client/errors";
6
+ import { ApiRequestError, isPermanentClientError, isTransientFailure } from "../../../api-client/errors";
7
7
  import type { IssuerListingParams } from "../../../api-client/paths";
8
8
  import type { PluginPersistence } from "../../../types/plugin";
9
9
  import { secListingKey } from "../../../utils/sec";
@@ -46,6 +46,8 @@ interface ProxyResult<T> {
46
46
  /** Null is an explicit 404, not a transient failure or access denial. */
47
47
  data: T | null;
48
48
  fetchedAt: number | null;
49
+ /** Its refresh failed and the copy saved at this time (ms) answered instead. */
50
+ savedAt?: number;
49
51
  refreshError?: string;
50
52
  }
51
53
  type ActiveRequest<T> = { store: PluginPersistence | null; promise: Promise<ProxyResult<T>> };
@@ -105,11 +107,14 @@ function loadCached<T>(
105
107
  }
106
108
  throw error;
107
109
  }
110
+ // A saved copy answers only for a failure asking again may get past, not for a reply that fails validation.
111
+ if (!isTransientFailure(error)) throw error;
108
112
  const fallback = store?.getResource<T>(kind, key, { ...options, allowExpired: true });
109
113
  if (!fallback || tierOf(fallback.value) !== tier) throw error;
110
114
  return {
111
115
  data: fallback.value,
112
116
  fetchedAt: fallback.fetchedAt,
117
+ savedAt: fallback.fetchedAt,
113
118
  refreshError: (error instanceof Error ? error.message : String(error)).trim() || "Request failed",
114
119
  };
115
120
  }).finally(() => { if (active.get(activeKey) === request) active.delete(activeKey); });
@@ -8,12 +8,12 @@ import {
8
8
  DataTableView,
9
9
  EmptyState, FigureList, PaneStatusBody, Prose, QueryBar, READING_WIDTH, SectionHeading,
10
10
  usePaneFooter,
11
- usePaneNoticeFooter,
12
11
  type DataTableCell,
13
12
  type DataTableColumn,
14
13
  type PaneFooterSegment,
15
14
  type PaneHint,
16
15
  } from "../../../components";
16
+ import { savedCopyFooterInfo } from "../../../components/layout/pane/footer";
17
17
  import { useAsyncResource } from "../../../react/async-resource";
18
18
  import { usePaneStateValue } from "../../../state/app/context";
19
19
  import { colors, getChartIndicatorColor } from "../../../theme/colors";
@@ -45,13 +45,6 @@ import {
45
45
 
46
46
  export const EXECUTIVES_PANE_ID = "executives";
47
47
 
48
- function retainedDataNotice(subject: string, error: string, fetchedAt: number | null): string {
49
- const retrieved = new Date(fetchedAt ?? Number.NaN);
50
- const age = Number.isFinite(retrieved.getTime())
51
- ? `retrieved ${retrieved.toISOString()}` : "with unavailable retrieval time";
52
- return `${subject} refresh failed: ${error}. Retained data ${age}.`;
53
- }
54
-
55
48
  const PAY_PARTS: Array<{ key: keyof CloudExecutiveRowPayload; label: string }> =
56
49
  [
57
50
  { key: "salary", label: "Salary" },
@@ -301,18 +294,10 @@ function ExecutiveResearch({ ticker, listing, tier, focused, width, nested, guar
301
294
  // The free preview: each executive's total pay; the breakdown and earlier years are Pro.
302
295
  const preview = statement?.access === "preview" || list.data?.data?.access === "preview";
303
296
  const loading = list.loading || detail.loading;
304
- const listError = list.error ?? list.data?.refreshError;
305
- const statementError = detail.error ?? detail.data?.refreshError;
306
- usePaneNoticeFooter({
307
- registrationId: `${EXECUTIVES_PANE_ID}:data-notices`,
308
- notices: [
309
- list.data?.data && listError
310
- ? retainedDataNotice("Proxy list", listError, list.data.fetchedAt) : null,
311
- detail.data?.data && statementError
312
- ? retainedDataNotice(`${year} proxy`, statementError, detail.data.fetchedAt) : null,
313
- ].filter((notice): notice is string => notice !== null),
314
- focused,
315
- });
297
+ // A copy saved earlier answering a failed refresh says when it was saved, in the footer every pane uses;
298
+ // a failed refresh with only what is on screen says why. Without anything shown, the body says it.
299
+ const listError = list.data?.data ? list.error ?? (list.data.savedAt == null ? list.data.refreshError : null) ?? null : null;
300
+ const statementError = detail.data?.data ? detail.error ?? (detail.data.savedAt == null ? detail.data.refreshError : null) ?? null : null;
316
301
  const refresh = useCallback(() => {
317
302
  void list.reload();
318
303
  if (year !== null) void detail.reload();
@@ -338,6 +323,9 @@ function ExecutiveResearch({ ticker, listing, tier, focused, width, nested, guar
338
323
  const info: PaneFooterSegment[] = [];
339
324
  if (loading)
340
325
  info.push({ id: "loading", parts: [{ text: "loading", tone: "muted" }] });
326
+ if (listError) info.push({ id: "list-error", parts: [{ text: `Proxy list: ${listError}`, tone: "warning" }] });
327
+ if (statementError) info.push({ id: "statement-error", parts: [{ text: `${year} proxy: ${statementError}`, tone: "warning" }] });
328
+ info.push(...savedCopyFooterInfo([list.data?.data ? list.data.savedAt : null, statement ? detail.data?.savedAt : null]));
341
329
  if (statement) {
342
330
  info.push({
343
331
  id: "filed",
@@ -351,7 +339,7 @@ function ExecutiveResearch({ ticker, listing, tier, focused, width, nested, guar
351
339
  if (yearHint) hints.push(yearHint);
352
340
  if (preview) hints.push({ id: "upgrade", key: CLOUD_PLAN_KEY, label: "upgrade", title: "Upgrade to Pro", onPress: openUpgrade });
353
341
  return { info, hints };
354
- }, [loading, statement, openFiling, yearHint, preview, openUpgrade]);
342
+ }, [loading, listError, statementError, year, list.data, detail.data, statement, openFiling, yearHint, preview, openUpgrade]);
355
343
 
356
344
  const figures = useMemo(
357
345
  () => (statement ? figuresOf(statement) : []),
@@ -1,5 +1,6 @@
1
1
  import type { ExposureHolding } from "../../../api-client/exposure";
2
2
  import type { DataProvider } from "../../../types/data-provider";
3
+ import type { Quote } from "../../../types/financials";
3
4
  import type { Portfolio, TickerRecord } from "../../../types/ticker";
4
5
  import { publicTickerKey } from "../../../utils/exchanges";
5
6
  import { resolveCurrencyUnit } from "../../../utils/currency-units";
@@ -7,8 +8,25 @@ import { convertCurrency } from "../../../utils/format";
7
8
  import { logFailure } from "../../../utils/log-failure";
8
9
  import { getPortfolioPositionMetrics, getPortfolioQuoteDisplay, resolvePortfolioMarketValue } from "../../../portfolio/position-metrics";
9
10
 
11
+ /**
12
+ * Every position's quote: one batch where the provider has one, else one
13
+ * request per position, all at once. A position without a quote is null.
14
+ */
15
+ async function positionQuotes(positions: TickerRecord[], market: Pick<DataProvider, "getQuote" | "getQuotesBatch">): Promise<Array<Quote | null>> {
16
+ const failed = logFailure("exposure.holdings", "Load portfolio holding quote failed", null);
17
+ if (market.getQuotesBatch) {
18
+ const targets = positions.map((ticker) => ({ symbol: ticker.metadata.ticker, exchange: ticker.metadata.exchange, surface: "portfolio" as const }));
19
+ const results = await market.getQuotesBatch(targets).catch((error: unknown) => {
20
+ failed(error);
21
+ return [];
22
+ });
23
+ return positions.map((_, index) => results[index]?.quote ?? null);
24
+ }
25
+ return Promise.all(positions.map((ticker) => market.getQuote(ticker.metadata.ticker, ticker.metadata.exchange).catch(failed)));
26
+ }
27
+
10
28
  /** Explicit NAV preserves cash, shorts and leverage; net holdings are never used as NAV. */
11
- export async function portfolioHoldings(portfolio: Portfolio, tickers: TickerRecord[], nav: number, market: Pick<DataProvider, "getQuote" | "getExchangeRate">): Promise<ExposureHolding[]> {
29
+ export async function portfolioHoldings(portfolio: Portfolio, tickers: TickerRecord[], nav: number, market: Pick<DataProvider, "getQuote" | "getQuotesBatch" | "getExchangeRate">): Promise<ExposureHolding[]> {
12
30
  if (!Number.isFinite(nav) || nav <= 0) throw new Error(`Enter positive NAV in ${portfolio.currency} for ${portfolio.name}.`);
13
31
  const positions = tickers.filter(t => t.metadata.positions.some(p => p.portfolio === portfolio.id && p.shares !== 0));
14
32
  if (!positions.length) throw new Error("The selected portfolio has no positions.");
@@ -21,25 +39,29 @@ export async function portfolioHoldings(portfolio: Portfolio, tickers: TickerRec
21
39
  t.metadata.positions.filter(p => p.portfolio === portfolio.id).forEach(p => { if (p.currency) currencies.add(p.currency); });
22
40
  }
23
41
  const rates = new Map<string, number>([["USD", 1]]);
24
- const majorRates = new Map<string, number>([["USD", 1]]);
42
+ // One request per major currency, however many of its units (GBP, GBp) ask at once.
43
+ const majorRates = new Map<string, Promise<number>>([["USD", Promise.resolve(1)]]);
25
44
  const ensureRate = async (rawCurrency: string) => {
26
45
  const unit = resolveCurrencyUnit(rawCurrency);
27
46
  if (!unit.currency) throw new Error("Portfolio currency is missing.");
28
- let rate = majorRates.get(unit.currency);
29
- if (rate === undefined) {
30
- rate = await market.getExchangeRate(unit.currency);
31
- if (!Number.isFinite(rate) || rate <= 0) throw new Error(`FX unavailable for ${unit.currency}; weights were not estimated.`);
32
- majorRates.set(unit.currency, rate);
47
+ const major = unit.currency;
48
+ let pending = majorRates.get(major);
49
+ if (!pending) {
50
+ pending = market.getExchangeRate(major).then((rate) => {
51
+ if (!Number.isFinite(rate) || rate <= 0) throw new Error(`FX unavailable for ${major}; weights were not estimated.`);
52
+ return rate;
53
+ });
54
+ majorRates.set(major, pending);
33
55
  }
34
- rates.set(rawCurrency, rate / unit.divisor);
56
+ rates.set(rawCurrency, (await pending) / unit.divisor);
35
57
  };
36
- for (const currency of currencies) await ensureRate(currency);
58
+ const [quotes] = await Promise.all([positionQuotes(positions, market), Promise.all([...currencies].map(ensureRate))]);
59
+ // A quote in a currency the positions did not name.
60
+ await Promise.all([...new Set(quotes.flatMap((quote) => quote?.currency && !rates.has(quote.currency) ? [quote.currency] : []))].map(ensureRate));
37
61
  const holdings: ExposureHolding[] = [];
38
- for (const ticker of positions) {
39
- const quote = await market.getQuote(ticker.metadata.ticker, ticker.metadata.exchange)
40
- .catch(logFailure("exposure.holdings", "Load portfolio holding quote failed", null));
62
+ for (const [index, ticker] of positions.entries()) {
63
+ const quote = quotes[index] ?? null;
41
64
  const currency = quote?.currency || ticker.metadata.currency;
42
- if (!rates.has(currency)) await ensureRate(currency);
43
65
  const metrics = getPortfolioPositionMetrics(ticker, portfolio.id, currency, {
44
66
  currency: portfolio.currency, convert: (value, from) => convertCurrency(value, from, portfolio.currency, rates),
45
67
  }, quote);
@@ -2,7 +2,6 @@ import type { PaneTemplateContext, ShortcutArgError } from "../../../types/plugi
2
2
  import { findNamedCollection, unknownCollectionArgError } from "../../../portfolio/collection-name";
3
3
  import type { PluginModule } from "../plugin-module";
4
4
  import { exposureHeadless } from "./headless";
5
- import { ExposurePane } from "./pane";
6
5
 
7
6
  const SOURCE = /^(PORT|WATCH):(.+)$/i;
8
7
 
@@ -29,7 +28,7 @@ function unknownSourceArg(arg: string, context: PaneTemplateContext): ShortcutAr
29
28
  }
30
29
 
31
30
  export const exposureModule: PluginModule = {
32
- panes: [{ id: "exposure", name: "Exposure", icon: "E", component: ExposurePane, defaultPosition: "right", defaultMode: "floating", defaultFloatingSize: { width: 138, height: 31 }, tableExport: true, headless: exposureHeadless,
31
+ panes: [{ id: "exposure", name: "Exposure", icon: "E", get component(): typeof import("./pane").ExposurePane { const { ExposurePane } = require("./pane"); return ExposurePane; }, defaultPosition: "right", defaultMode: "floating", defaultFloatingSize: { width: 138, height: 31 }, tableExport: true, headless: exposureHeadless,
33
32
  personalData: "holdings",
34
33
  portableShare: { private: { params: true, settings: true, state: true } } }],
35
34
  paneTemplates: [{ id: "exposure-pane", paneId: "exposure", label: "Exposure", description: "Pro scenario exposures across holdings, geographic revenue and supply chains, with source evidence and portfolio concentrations.",
@@ -35,6 +35,10 @@ export type FearGreedCacheEntry = {
35
35
  export interface FearGreedLoadResult extends FearGreedCacheEntry {
36
36
  /** Set when the network failed and cached data was served instead. */
37
37
  refreshError?: string;
38
+ /** `stale-fallback` when a saved copy answered a failed refresh. */
39
+ source?: "cache" | "network" | "stale-fallback";
40
+ /** The failure a `stale-fallback` copy answered. */
41
+ error?: unknown;
38
42
  }
39
43
 
40
44
  const cache = createPluginCache<FearGreedData, PersistedFearGreedData>({
@@ -0,0 +1,44 @@
1
+ import { CLI_COMMAND_GROUPS } from "../../../cli/help";
2
+ import { rowsFreshness } from "../../../cli/freshness";
3
+ import { humanizeCliKey } from "../../../cli/result";
4
+ import type { CliCommandDef } from "../../../types/plugin";
5
+ import { loadFearGreed } from "./cache";
6
+
7
+ const SCORE_KEYS = new Set(["score", "previousClose", "previousWeek", "previousMonth", "previousYear"]);
8
+ /** A score to a tenth of a point (17.9); a missing one leaves its cell empty, as before. */
9
+ const scoreTenths = (value: unknown) => typeof value === "number" && Number.isFinite(value) ? value.toFixed(1) : "";
10
+
11
+ // Without the pane cache attached, this fetches a fresh reading for the command.
12
+ export const fearGreedCliCommand: CliCommandDef = {
13
+ name: "fear-greed",
14
+ description: "Fetch CNN Fear & Greed gauge data",
15
+ help: { group: CLI_COMMAND_GROUPS.markets },
16
+ async execute(_args, ctx) {
17
+ const { data, stale } = await loadFearGreed(ctx.cliOptions.refresh);
18
+ const rows = [{
19
+ score: data.overall.score,
20
+ rating: data.overall.rating,
21
+ updatedAt: data.overall.updatedAt?.toISOString() ?? "",
22
+ previousClose: data.overall.previousClose,
23
+ previousWeek: data.overall.previousWeek,
24
+ previousMonth: data.overall.previousMonth,
25
+ previousYear: data.overall.previousYear,
26
+ }];
27
+ ctx.printResult({
28
+ data: rows,
29
+ // Exports cite CNN's reading as `fn FNG` does; the text stays the row alone.
30
+ exportFreshness: rowsFreshness(rows, { source: "CNN", status: stale ? "stale" : "not-a-feed", basis: "published index" }),
31
+ metadata: {
32
+ indicators: data.indicators.map((indicator) => ({
33
+ id: indicator.definition.id,
34
+ score: indicator.score,
35
+ rating: indicator.rating,
36
+ })),
37
+ },
38
+ }, {
39
+ // Scores read to a tenth of a point on the text table; exports keep every digit.
40
+ textColumns: Object.keys(rows[0]!).map((key) => ({ key, header: humanizeCliKey(key),
41
+ ...(SCORE_KEYS.has(key) ? { align: "right" as const, format: scoreTenths } : {}) })),
42
+ });
43
+ },
44
+ };
@@ -1,26 +1,15 @@
1
- import { CLI_COMMAND_GROUPS } from "../../../cli/help";
2
- import { rowsFreshness } from "../../../cli/freshness";
3
- import { humanizeCliKey } from "../../../cli/result";
1
+ import { fearGreedPluginMeta } from "../builtin-plugin-meta";
2
+ import { fearGreedCliCommand } from "./cli";
4
3
  import type { GloomPlugin } from "../../../types/plugin";
5
4
  import {
6
5
  attachFearGreedPersistence,
7
- loadFearGreed,
8
6
  resetFearGreedPersistence,
9
7
  } from "./cache";
10
- import { FearGreedPane } from "./pane";
11
8
  import { fearGreedHeadless } from "./headless";
12
9
  import { FEAR_GREED_SERIES } from "./series";
13
10
 
14
- const SCORE_KEYS = new Set(["score", "previousClose", "previousWeek", "previousMonth", "previousYear"]);
15
- /** A score to a tenth of a point (17.9); a missing one leaves its cell empty, as before. */
16
- const scoreTenths = (value: unknown) => typeof value === "number" && Number.isFinite(value) ? value.toFixed(1) : "";
17
-
18
11
  export const fearGreedPlugin: GloomPlugin = {
19
- id: "fear-greed",
20
- name: "Fear & Greed",
21
- version: "1.0.0",
22
- description: "CNN Fear & Greed index with the seven indicator charts",
23
- toggleable: true,
12
+ ...fearGreedPluginMeta,
24
13
 
25
14
  // One JSON endpoint over HTTPS, so every renderer. CNN sends no CORS
26
15
  // headers and wants a Referer, which is why the host is declared: the web
@@ -44,7 +33,7 @@ export const fearGreedPlugin: GloomPlugin = {
44
33
  id: "fear-greed",
45
34
  name: "Fear & Greed",
46
35
  icon: "G",
47
- component: FearGreedPane,
36
+ get component(): typeof import("./pane").FearGreedPane { const { FearGreedPane } = require("./pane"); return FearGreedPane; },
48
37
  defaultPosition: "right",
49
38
  defaultMode: "floating",
50
39
  defaultFloatingSize: { width: 110, height: 36 },
@@ -63,42 +52,5 @@ export const fearGreedPlugin: GloomPlugin = {
63
52
  },
64
53
  ],
65
54
 
66
- // `gloom fear-greed`, as before the plugin moved out of the host. Runs
67
- // without the pane cache attached when invoked from the CLI, so it always
68
- // fetches, which is what a CLI call wants anyway.
69
- cliCommands: [
70
- {
71
- name: "fear-greed",
72
- description: "Fetch CNN Fear & Greed gauge data",
73
- help: { group: CLI_COMMAND_GROUPS.markets },
74
- async execute(_args, ctx) {
75
- const { data, stale } = await loadFearGreed(ctx.cliOptions.refresh);
76
- const rows = [{
77
- score: data.overall.score,
78
- rating: data.overall.rating,
79
- updatedAt: data.overall.updatedAt?.toISOString() ?? "",
80
- previousClose: data.overall.previousClose,
81
- previousWeek: data.overall.previousWeek,
82
- previousMonth: data.overall.previousMonth,
83
- previousYear: data.overall.previousYear,
84
- }];
85
- ctx.printResult({
86
- data: rows,
87
- // Exports cite CNN's reading as `fn FNG` does; the text stays the row alone.
88
- exportFreshness: rowsFreshness(rows, { source: "CNN", status: stale ? "stale" : "not-a-feed", basis: "published index" }),
89
- metadata: {
90
- indicators: data.indicators.map((indicator) => ({
91
- id: indicator.definition.id,
92
- score: indicator.score,
93
- rating: indicator.rating,
94
- })),
95
- },
96
- }, {
97
- // Scores read to a tenth of a point on the text table; exports keep every digit.
98
- textColumns: Object.keys(rows[0]!).map((key) => ({ key, header: humanizeCliKey(key),
99
- ...(SCORE_KEYS.has(key) ? { align: "right" as const, format: scoreTenths } : {}) })),
100
- });
101
- },
102
- },
103
- ],
55
+ cliCommands: [fearGreedCliCommand],
104
56
  };
@@ -8,6 +8,7 @@ import {
8
8
  usePaneStatusFooter,
9
9
  type StatItem,
10
10
  } from "../../../components";
11
+ import { ageFooterSegment } from "../../../components/layout/pane/footer";
11
12
  import { usePaneRefreshKey } from "../../../components/data-table/table-pane";
12
13
  import { useAsyncResource } from "../../../react/async-resource";
13
14
  import type { PaneProps } from "../../../types/plugin";
@@ -61,7 +62,7 @@ export function FearGreedPane({ paneId, focused, width, height }: PaneProps) {
61
62
  ],
62
63
  }] : []),
63
64
  ...(stale ? [{ id: "stale", parts: [{ text: "stale", tone: "warning" as const }] }] : []),
64
- ...(updatedAgo ? [{ id: "age", parts: [{ text: `updated ${updatedAgo}`, tone: "value" as const }] }] : []),
65
+ ...(updatedAgo ? [ageFooterSegment(updatedAgo, { id: "age", verb: "updated", tone: "value" })] : []),
65
66
  ], [data, stale, updatedAgo]);
66
67
  usePaneStatusFooter({ registrationId: paneId, loading, error, info: footerInfo });
67
68
 
@@ -1,5 +1,6 @@
1
1
  import type { SeriesDefinition, SeriesLoadResult, SeriesPoint } from "../../../series/types";
2
- import { cachedSeriesWarning } from "../shared/series-warning";
2
+ import { savedCopyForTransientFailure } from "../../../data/plugin-cache";
3
+ import { cachedSeriesNote } from "../shared/series-warning";
3
4
  import { loadFearGreed } from "./cache";
4
5
  import type { FearGreedData } from "./data";
5
6
 
@@ -30,9 +31,10 @@ function series(id: string, name: string, unit: string, read: Reading, tags: rea
30
31
  tags: ["fear and greed", ...tags],
31
32
  refreshMs: REFRESH_MS,
32
33
  load: async (): Promise<SeriesLoadResult> => {
33
- const result = await loadFearGreed();
34
- const warning = cachedSeriesWarning(result);
35
- return { points: read(result.data), ...(warning ? { warning } : {}) };
34
+ // A saved copy answers only a failure asking again may get past; a refusal is the chart's error.
35
+ const result = savedCopyForTransientFailure(await loadFearGreed());
36
+ return { points: read(result.data),
37
+ ...cachedSeriesNote({ ...result, ...(result.source === "stale-fallback" ? { savedAt: result.fetchedAt } : {}) }) };
36
38
  },
37
39
  };
38
40
  }
@@ -1,14 +1,13 @@
1
1
  import type { PluginModule } from "../plugin-module";
2
2
  import { treasuryAuctionsSettings } from "../treasury-auctions";
3
3
  import { FIXED_INCOME_PANE_ID } from "./model";
4
- import { FixedIncomePane } from "./pane";
5
4
 
6
5
  export const fixedIncomeModule: PluginModule = {
7
6
  panes: [{
8
7
  id: FIXED_INCOME_PANE_ID,
9
8
  name: "Fixed Income",
10
9
  icon: "B",
11
- component: FixedIncomePane,
10
+ get component(): typeof import("./pane").FixedIncomePane { const { FixedIncomePane } = require("./pane"); return FixedIncomePane; },
12
11
  defaultPosition: "right",
13
12
  defaultMode: "floating",
14
13
  defaultFloatingSize: { width: 116, height: 32 },
@@ -0,0 +1,25 @@
1
+ import { createConcurrencyLimiter } from "../../../utils/concurrency";
2
+
3
+ /**
4
+ * The board's contract histories (use-front-returns) and contract terms
5
+ * (use-board-terms) share one limit of twelve reads at a time, across both and
6
+ * every board open: seventy-odd rows load in about a second without the two
7
+ * crowding out each other or the quotes.
8
+ */
9
+ const BOARD_READ_LIMIT = 12;
10
+
11
+ export const boardRead = createConcurrencyLimiter(BOARD_READ_LIMIT);
12
+
13
+ /**
14
+ * Reads that reach a source sending reads asked together as batch requests
15
+ * skip that limit: asked all at once, the API client sends a board's rows
16
+ * sixteen a request. This bound only keeps a runaway caller in check.
17
+ */
18
+ const BOARD_BATCHED_READ_LIMIT = 200;
19
+
20
+ const boardBatchedRead = createConcurrencyLimiter(BOARD_BATCHED_READ_LIMIT);
21
+
22
+ /** The gate for the board's reads: twelve at a time, unless they go out in batch requests. */
23
+ export function boardReadGate(batched: boolean): typeof boardRead {
24
+ return batched ? boardBatchedRead : boardRead;
25
+ }
@@ -0,0 +1,44 @@
1
+ /** How long reads landing one after another are gathered into one redraw. */
2
+ const LANDING_GATHER_MS = 100;
3
+
4
+ /**
5
+ * A redraw for reads that land one by one (a board's seventy contract
6
+ * histories): the first landing redraws at once, and those in the next
7
+ * `LANDING_GATHER_MS` are drawn together when it ends, so the board redraws a
8
+ * few times rather than once per read. `flush` draws what is waiting at once
9
+ * (the last read landed) and `cancel` drops it (the board moved on).
10
+ */
11
+ export function coalescedBump(redraw: () => void, gatherMs = LANDING_GATHER_MS) {
12
+ let timer: ReturnType<typeof setTimeout> | undefined;
13
+ let waiting = false;
14
+ const gather = () => {
15
+ timer = setTimeout(() => {
16
+ timer = undefined;
17
+ if (!waiting) return;
18
+ waiting = false;
19
+ redraw();
20
+ gather();
21
+ }, gatherMs);
22
+ };
23
+ const cancel = () => {
24
+ clearTimeout(timer);
25
+ timer = undefined;
26
+ waiting = false;
27
+ };
28
+ return {
29
+ bump() {
30
+ if (timer !== undefined) {
31
+ waiting = true;
32
+ return;
33
+ }
34
+ redraw();
35
+ gather();
36
+ },
37
+ flush() {
38
+ const draw = waiting;
39
+ cancel();
40
+ if (draw) redraw();
41
+ },
42
+ cancel,
43
+ };
44
+ }
@@ -25,9 +25,11 @@ import type { PluginModule } from "../plugin-module";
25
25
  import { FUTURES_SERIES } from "./series";
26
26
  import { useLiveStreamingSetting } from "../../../state/hooks/live-streaming";
27
27
  import { quoteObservation } from "../shared/report-freshness";
28
+ import { useQuoteDelayFooter } from "../shared/cloud-upgrade";
28
29
  import {
29
30
  boardErrorMessage,
30
31
  boardHeadlineQuote,
32
+ boardQuotes,
31
33
  quoteBoardFooterInfo,
32
34
  quoteBoardStatus,
33
35
  useQuoteBoard,
@@ -46,6 +48,7 @@ import {
46
48
  DEFAULT_FUTURES_SORT,
47
49
  effectiveCollapsedSectors,
48
50
  FUTURES_RETURN_COLUMNS,
51
+ futuresNoMatchFailure,
49
52
  futuresRowId,
50
53
  type FuturesColumnId,
51
54
  type FuturesSortPreference,
@@ -255,15 +258,26 @@ export function FuturesPane({ focused, width, height }: PaneProps) {
255
258
  },
256
259
  } : null);
257
260
 
261
+ // `fn FUT natgas` with no contract to list fails with what to type instead, not the board's empty labels.
262
+ const noMatch = !!dataProvider && !!searchQuery.trim() && !rows.some((row) => row.type === "row");
263
+ useRemoteUiNode(noMatch ? {
264
+ role: "report-failure",
265
+ label: "No matching contracts",
266
+ getMetadata: () => futuresNoMatchFailure(searchQuery),
267
+ } : null);
268
+
258
269
  const status = quoteBoardStatus(listedQuotes);
259
270
  const errorMessage = boardErrorMessage(listedQuotes);
260
271
  // Every load came back empty: a board of dashes reads as broken, so the
261
272
  // body says so once and offers a retry. One quote is enough to keep rows.
262
273
  const noQuotes = !!dataProvider && listedQuotes.size > 0 && status.unavailable === listedQuotes.size;
274
+ // Signed out or free, the contracts' quotes are delayed ones.
275
+ const quoteDelay = useQuoteDelayFooter(boardQuotes(listedQuotes), "futures-delay");
263
276
  usePaneFooter(FUTURES_PANE_ID, () => {
264
277
  if (noQuotes) return { info: [], hints: [] };
265
278
  const info: PaneFooterSegment[] = quoteBoardFooterInfo(status);
266
279
  if (errorMessage) info.push({ id: "reason", parts: [{ text: errorMessage, tone: "warning" }] });
280
+ if (quoteDelay) info.push(quoteDelay);
267
281
  return {
268
282
  info,
269
283
  hints: [{ id: "search", key: "/", label: "search", onPress: focusSearch }],
@@ -272,6 +286,7 @@ export function FuturesPane({ focused, width, height }: PaneProps) {
272
286
  errorMessage,
273
287
  focusSearch,
274
288
  noQuotes,
289
+ quoteDelay,
275
290
  status.latestTs,
276
291
  status.loading,
277
292
  status.savedAt,
@@ -149,9 +149,23 @@ function matchesFuturesSearch(contract: FuturesContract, query: string, quote?:
149
149
  || contract.name.toLowerCase().includes(normalized)
150
150
  || futuresContractName(contract, quote).toLowerCase().includes(normalized)
151
151
  || contract.symbol.toLowerCase().includes(normalized)
152
+ // Everyday names the board's own does not hold: natgas is Natural Gas.
153
+ || (contract.aliases ?? []).some((alias) => alias.includes(normalized))
152
154
  );
153
155
  }
154
156
 
157
+ /**
158
+ * Why `fn FUT urea` lists nothing, for a search no contract matches: what
159
+ * to type instead, never the board's own buttons ("Clear search.").
160
+ */
161
+ export function futuresNoMatchFailure(query: string): { message: string; details: string } {
162
+ return {
163
+ message: `No futures contract matches "${query.trim()}".`,
164
+ details: "Try a code such as NG, CL or GC, or a name such as natural gas or gold: gloom fn FUT NG."
165
+ + " Other boards: gloom fn FUT --tab inventories, positioning or crops.",
166
+ };
167
+ }
168
+
155
169
  export const FUTURES_RETURN_COLUMNS: Readonly<Partial<Record<FuturesColumnId, FuturesReturnHorizon>>> = {
156
170
  return1w: "1W",
157
171
  return1m: "1M",
@@ -5,6 +5,9 @@ import type { FuturesContract } from "../../../market-data/futures-board";
5
5
  import { createTtlCache } from "../../../data/ttl-cache";
6
6
  import { loadFuturesContractTerms } from "../../../market-data/futures-contract";
7
7
  import { logFailure } from "../../../utils/log-failure";
8
+ import { coalescedBump } from "./coalesced-bump";
9
+ import { apiClient } from "../../../api-client";
10
+ import { boardReadGate } from "./board-reads";
8
11
 
9
12
  interface FuturesTermsState {
10
13
  /** The row's terms are still being asked for. */
@@ -23,8 +26,6 @@ const TERMS_TTL_MS = 30 * 60_000;
23
26
  * once a minute, not once a tick.
24
27
  */
25
28
  const TERMS_RETRY_MS = 60_000;
26
- /** Forty-odd rows open at once; a few requests at a time keep the board's quotes first. */
27
- const TERMS_CONCURRENCY = 4;
28
29
 
29
30
  /**
30
31
  * Null when the server has no terms for the symbol, which is an answer and is
@@ -33,18 +34,18 @@ const TERMS_CONCURRENCY = 4;
33
34
  */
34
35
  const termsCache = createTtlCache<FuturesContractTermsPayload | null>({ ttlMs: TERMS_TTL_MS, rejectionMs: TERMS_RETRY_MS });
35
36
 
36
- async function readTerms(symbol: string): Promise<FuturesContractTermsPayload | null> {
37
+ async function readTerms(symbol: string, batch: boolean): Promise<FuturesContractTermsPayload | null> {
37
38
  try {
38
- return await loadFuturesContractTerms(symbol);
39
+ return await loadFuturesContractTerms(symbol, apiClient, { batch });
39
40
  } catch (error) {
40
41
  if (error instanceof ApiRequestError && error.status === 404) return null;
41
42
  throw error;
42
43
  }
43
44
  }
44
45
 
45
- /** Whether the row has an answer; false when the read failed. */
46
- function loadTerms(symbol: string): Promise<boolean> {
47
- return termsCache.load(symbol, () => readTerms(symbol))
46
+ /** Whether the row has an answer; false when the read failed. `batch`: asked with every other row's, in batch requests. */
47
+ function loadTerms(symbol: string, batch: boolean): Promise<boolean> {
48
+ return termsCache.load(symbol, () => readTerms(symbol, batch))
48
49
  .then(() => true, logFailure("futures", "Read a futures row's contract terms failed", false));
49
50
  }
50
51
 
@@ -67,18 +68,24 @@ export function useFuturesBoardTerms(contracts: readonly FuturesContract[], enab
67
68
  let cancelled = false;
68
69
  let failed = false;
69
70
  let retryTimer: ReturnType<typeof setTimeout> | undefined;
70
- const worker = async () => {
71
- for (let symbol = queue.shift(); symbol; symbol = queue.shift()) {
72
- if (!await loadTerms(symbol)) failed = true;
73
- if (!cancelled) setVersion((current) => current + 1);
74
- }
75
- };
76
- void Promise.all(Array.from({ length: Math.min(TERMS_CONCURRENCY, queue.length) }, worker)).then(() => {
71
+ const landed = coalescedBump(() => {
72
+ if (!cancelled) setVersion((current) => current + 1);
73
+ });
74
+ // Twelve reads at a time, shared with the rows' histories (`boardRead`), or all at once while the client
75
+ // sends terms in batch requests; a board that moved on skips its turn.
76
+ const batch = apiClient.batchesReads("futures-terms");
77
+ const read = boardReadGate(batch);
78
+ void Promise.all(queue.map((symbol) => read(async () => {
79
+ if (cancelled) return;
80
+ if (!await loadTerms(symbol, batch)) failed = true;
81
+ landed.bump();
82
+ }))).then(() => {
83
+ landed.flush();
77
84
  if (failed && !cancelled) retryTimer = setTimeout(() => setRetry((current) => current + 1), TERMS_RETRY_MS);
78
85
  });
79
86
  return () => {
80
87
  cancelled = true;
81
- queue.length = 0;
88
+ landed.cancel();
82
89
  clearTimeout(retryTimer);
83
90
  };
84
91
  }, [enabled, symbolKey, retry]);