@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
@@ -32,6 +32,7 @@ import {
32
32
  type QueryBarFilter,
33
33
  type DataTableVisibleRange,
34
34
  } from "../../../components";
35
+ import { statGridWithin } from "../../../components/ui/stat-grid";
35
36
  import { useShortcut } from "../../../react/input";
36
37
  import { useOptionalDialog, type AlertContext } from "../../../ui/dialog";
37
38
  import { useLiveQuoteEntries, useQuoteUpdates } from "../../../state/hooks/quote-streaming";
@@ -980,10 +981,10 @@ export function OptionsView({ width, height, focused, nestedInTabs = false, ivRa
980
981
  const expiryInline = Math.floor((statWidth - 2 * (cellColumns - 1)) / cellColumns) >= expiryCells;
981
982
  const statColumns = expiryInline ? cellColumns : figureColumns;
982
983
  const statItems = [expiryInline ? expiryItem : { ...expiryItem, wide: true }, ...figures];
983
- const summaryRowCount = Math.min(statGridRows(statItems, statWidth, statColumns), Math.max(0, height - 6));
984
- // A short band gives up figures from the end; the expiry stays.
985
- const shownStatItems = summaryRowCount === 0 ? []
986
- : [statItems[0]!, ...figures.slice(0, summaryRowCount * statColumns - (expiryInline ? 1 : statColumns))];
984
+ // A short band gives up figures from the end, by lines: a row that wraps
985
+ // takes two. The expiry leads, so it stays.
986
+ const summaryLines = Math.min(statGridRows(statItems, statWidth, statColumns), Math.max(0, height - 6));
987
+ const { items: shownStatItems, lines: summaryRowCount } = statGridWithin(statItems, statWidth, summaryLines, statColumns);
987
988
  // No term here follows the selection or the load, so an empty cold-expiry
988
989
  // response cannot resize the table: growing it during loading would turn a
989
990
  // clamped scroll into apparent user navigation.
@@ -997,7 +998,7 @@ export function OptionsView({ width, height, focused, nestedInTabs = false, ivRa
997
998
  <QueryBar width={Math.max(1, width - 2)} filters={queryFilters} />
998
999
 
999
1000
  {summaryRowCount > 0 && (
1000
- <StatGrid items={shownStatItems} width={statWidth} columns={statColumns} />
1001
+ <StatGrid items={shownStatItems} width={statWidth} columns={statColumns} maxLines={summaryLines} />
1001
1002
  )}
1002
1003
 
1003
1004
  {isOpt && parsed && (
@@ -3,7 +3,6 @@ import {
3
3
  OPTIONS_CALCULATOR_PANE_ID,
4
4
  OPTIONS_CALCULATOR_TEMPLATE_ID,
5
5
  } from "./model";
6
- import { OptionsCalculatorPane } from "./pane";
7
6
  import { optionsCalculatorHeadless } from "./headless";
8
7
 
9
8
  export const optionsCalculatorModule: PluginModule = {
@@ -12,7 +11,7 @@ export const optionsCalculatorModule: PluginModule = {
12
11
  id: OPTIONS_CALCULATOR_PANE_ID,
13
12
  name: "Options Calculator",
14
13
  icon: "V",
15
- component: OptionsCalculatorPane,
14
+ get component(): typeof import("./pane").OptionsCalculatorPane { const { OptionsCalculatorPane } = require("./pane"); return OptionsCalculatorPane; },
16
15
  defaultPosition: "right",
17
16
  defaultMode: "floating",
18
17
  defaultFloatingSize: { width: 96, height: 28 },
@@ -3,7 +3,6 @@ import type { PluginModule } from "../plugin-module";
3
3
  import { createTickerSurfacePaneTemplate, type TickerSurfacePaneTemplateOptions } from "../shared/ticker-surface";
4
4
  import { optionsPositioningHeadless } from "./headless";
5
5
  import { isPositioningTab, type PositioningTab } from "./model";
6
- import { OptionsPositioningPane } from "./pane";
7
6
 
8
7
  /** The underlying OPX opens on when no ticker is active. */
9
8
  const DEFAULT_UNDERLYING = "SPY";
@@ -45,7 +44,7 @@ export const optionsPositioningModule: PluginModule = {
45
44
  id: "options-positioning",
46
45
  name: "Options Positioning",
47
46
  icon: "O",
48
- component: OptionsPositioningPane,
47
+ get component(): typeof import("./pane").OptionsPositioningPane { const { OptionsPositioningPane } = require("./pane"); return OptionsPositioningPane; },
49
48
  defaultPosition: "right",
50
49
  tickerFollower: true,
51
50
  defaultMode: "floating",
@@ -1,8 +1,6 @@
1
1
  import type { PluginModule } from "../plugin-module";
2
2
  import { createTickerSurfacePaneTemplate } from "../shared/ticker-surface";
3
- import { OptionsScenarioPane } from "./pane";
4
3
  import { optionsScenarioHeadless } from "./headless";
5
- import { CspScreenPane } from "./csp-screen-pane";
6
4
  import { cspScreenHeadless } from "./csp-screen-headless";
7
5
 
8
6
  const scenarioSettingKeys = ["legs", "strategy", "strike", "delta", "putStrike", "callStrike", "shares", "expiration", "spot", "rate", "dividendYield", "currency", "asOf", "date", "volShift",
@@ -10,7 +8,7 @@ const scenarioSettingKeys = ["legs", "strategy", "strike", "delta", "putStrike",
10
8
 
11
9
  export const optionsScenarioModule: PluginModule = {
12
10
  panes: [{
13
- id: "options-scenario", name: "Options Scenario", icon: "V", component: OptionsScenarioPane,
11
+ id: "options-scenario", name: "Options Scenario", icon: "V", get component(): typeof import("./pane").OptionsScenarioPane { const { OptionsScenarioPane } = require("./pane"); return OptionsScenarioPane; },
14
12
  defaultPosition: "right", defaultMode: "floating", defaultFloatingSize: { width: 126, height: 36 },
15
13
  tableExport: true, headless: optionsScenarioHeadless,
16
14
  settings: {
@@ -26,7 +24,7 @@ export const optionsScenarioModule: PluginModule = {
26
24
  ],
27
25
  },
28
26
  }, {
29
- id: "csp-screen", name: "Options Strategy Screen", icon: "V", component: CspScreenPane,
27
+ id: "csp-screen", name: "Options Strategy Screen", icon: "V", get component(): typeof import("./csp-screen-pane").CspScreenPane { const { CspScreenPane } = require("./csp-screen-pane"); return CspScreenPane; },
30
28
  defaultPosition: "right", defaultMode: "floating", defaultFloatingSize: { width: 144, height: 24 },
31
29
  tableExport: true, headless: cspScreenHeadless,
32
30
  settings: {
@@ -4,7 +4,6 @@ import { commandRequestParams } from "../shared/command-tab";
4
4
  import { PERP_SERIES } from "./series";
5
5
  import { perpsCache, perpsDepthCache, perpsHistoryCache, perpsLiquidationsCache, perpsListCache, perpsMarketCache, perpsRankingsCache } from "./client";
6
6
  import { perpsHeadless } from "./headless";
7
- import { PerpsPane } from "./pane";
8
7
 
9
8
  /**
10
9
  * A named market has its own pane, and asking for it again (PERP, a stock's perpetual row in DES or QQ) brings that
@@ -22,7 +21,7 @@ const PERP_CACHES = [perpsCache, perpsHistoryCache, perpsListCache, perpsMarketC
22
21
 
23
22
  export const perpsModule: PluginModule = {
24
23
  series: PERP_SERIES,
25
- panes: [{ id: "perps", name: "Perpetuals", icon: "P", component: PerpsPane, defaultPosition: "right", defaultMode: "floating",
24
+ panes: [{ id: "perps", name: "Perpetuals", icon: "P", get component(): typeof import("./pane").PerpsPane { const { PerpsPane } = require("./pane"); return PerpsPane; }, defaultPosition: "right", defaultMode: "floating",
26
25
  defaultFloatingSize: { width: 138, height: 34 }, tableExport: true, headless: perpsHeadless }],
27
26
  paneTemplates: [{ id: "perps-pane", paneId: "perps", label: "Perpetuals", description: "Perpetual funding, open interest and premiums by market and venue, with rankings, a venue comparison, liquidations, order book depth and Pro history.",
28
27
  keywords: ["perp", "perpetual", "perpetuals", "funding", "funding rate", "open interest", "hip-3", "stock perps", "basis", "basis trade", "carry", "venue", "funding spread", "long short ratio", "long/short", "positioning", "liquidations", "liquidation", "order book depth", "market depth", "liquidity"],
@@ -1,7 +1,6 @@
1
1
  import { PLUGIN_AUTO_UPDATE_SETTING } from "../../auto-update";
2
2
  import type { PluginModule } from "../plugin-module";
3
3
  import { PLUGIN_MARKETPLACE_PANE_ID } from "../../marketplace/ids";
4
- import { PluginMarketplacePane } from "./pane";
5
4
 
6
5
  export const pluginMarketplaceModule: PluginModule = {
7
6
  panes: [
@@ -9,8 +8,8 @@ export const pluginMarketplaceModule: PluginModule = {
9
8
  id: PLUGIN_MARKETPLACE_PANE_ID,
10
9
  name: "Plugins",
11
10
  icon: "P",
12
- component: PluginMarketplacePane,
13
- reportFreshness: { status: "not-a-feed", basis: "plugin listings" },
11
+ get component(): typeof import("./pane").PluginMarketplacePane { const { PluginMarketplacePane } = require("./pane"); return PluginMarketplacePane; },
12
+ reportFreshness: { status: "not-a-feed", basis: "plugin listings", local: true },
14
13
  defaultPosition: "right",
15
14
  defaultMode: "floating",
16
15
  defaultFloatingSize: { width: 118, height: 34 },
@@ -21,6 +21,7 @@ import {
21
21
  type DataTableKeyEvent,
22
22
  type PaneHint,
23
23
  } from "../../../components";
24
+ import { ageFooterSegment } from "../../../components/layout/pane/footer";
24
25
  import { loadingErrorFooterInfo } from "../../../components/data-table/table-pane";
25
26
  import { useRowNotes } from "../../../components/ui/row-notes";
26
27
  import { colors } from "../../../theme/colors";
@@ -922,7 +923,7 @@ export function PluginMarketplacePane({ focused, width, height }: PaneProps) {
922
923
  info.push({ id: "restart", parts: [{ text: "restart to finish loading", tone: "warning" }] });
923
924
  }
924
925
  if (status === "ready" && fetchedAt && !stale && !busy) {
925
- info.push({ id: "updated", parts: [{ text: formatRelativeAge(fetchedAt), tone: "muted" }] });
926
+ info.push(ageFooterSegment(formatRelativeAge(fetchedAt)));
926
927
  }
927
928
 
928
929
  const hints: PaneHint[] = [];
@@ -2,6 +2,7 @@ import { Fragment, createElement, type ReactNode } from "react";
2
2
  import type {
3
3
  GloomPlugin,
4
4
  GloomPluginContext,
5
+ PaneDef,
5
6
  } from "../../types/plugin";
6
7
  import { pluginContextInNamespace } from "../registry/plugin-state";
7
8
  import { withPluginStateNamespace } from "../runtime/context";
@@ -63,6 +64,19 @@ function moduleEntry(input: PluginModuleInput): PluginModuleEntry {
63
64
  return "module" in input ? input : { module: input };
64
65
  }
65
66
 
67
+ /** Keep a pane's component lazy while moving its state into another plugin. */
68
+ function paneInNamespace(pane: PaneDef, stateId: string): PaneDef {
69
+ let component: PaneDef["component"] | undefined;
70
+ return Object.create(Object.getPrototypeOf(pane), {
71
+ ...Object.getOwnPropertyDescriptors(pane),
72
+ component: {
73
+ enumerable: true,
74
+ configurable: true,
75
+ get: () => component ??= withPluginStateNamespace(stateId, pane.component),
76
+ },
77
+ });
78
+ }
79
+
66
80
  /**
67
81
  * The module as it runs under a namespace other than its plugin's: every
68
82
  * surface it renders and the context its setup receives are moved there,
@@ -76,7 +90,7 @@ function moduleInNamespace(module: PluginModule, stateId: string): PluginModule
76
90
  return {
77
91
  ...module,
78
92
  ...(module.panes ? {
79
- panes: module.panes.map((pane) => ({ ...pane, component: withPluginStateNamespace(stateId, pane.component) })),
93
+ panes: module.panes.map((pane) => paneInNamespace(pane, stateId)),
80
94
  } : {}),
81
95
  ...(slots ? { slots } : {}),
82
96
  ...(module.setup ? {
@@ -84,7 +98,7 @@ function moduleInNamespace(module: PluginModule, stateId: string): PluginModule
84
98
  const scoped = pluginContextInNamespace(ctx, stateId);
85
99
  return module.setup!({
86
100
  ...scoped,
87
- registerPane: (pane) => ctx.registerPane({ ...pane, component: withPluginStateNamespace(stateId, pane.component) }),
101
+ registerPane: (pane) => ctx.registerPane(paneInNamespace(pane, stateId)),
88
102
  registerTickerResearchTab: (tab) => ctx.registerTickerResearchTab({
89
103
  ...tab,
90
104
  component: withPluginStateNamespace(stateId, tab.component),
@@ -1,5 +1,7 @@
1
1
  import { logFailure } from "../../../../utils/log-failure";
2
- import { createBaseConverter } from "../../../../cli/base-converter";
2
+ import { createBaseConverter, type BaseConversion } from "../../../../cli/base-converter";
3
+ import { quotedFxPair } from "../../../../utils/currency-pair";
4
+ import { formatFxRate } from "../../../../cli/fx-pair";
3
5
  import { savedListingName } from "../../../../cli/listing-arg";
4
6
  import { currentListingName } from "../../../../tickers/listing-name";
5
7
  import { withMarketData } from "../../../../cli/scoped-context";
@@ -575,6 +577,38 @@ export function weightBasisLine(allocation: Pick<PortfolioAllocation, "weightBas
575
577
  return weightBasisNote(allocation, (value) => formatAllocationMoney(value, currency, { whole: true }));
576
578
  }
577
579
 
580
+ /** `47h old`, or `3 days old` past two days: how far behind a stale rate is. */
581
+ function rateAge(asOf: number, now: number): string {
582
+ const hours = Math.max(0, Math.floor((now - asOf) / 3_600_000));
583
+ return hours < 48 ? `${hours}h old` : `${Math.floor(hours / 24)} days old`;
584
+ }
585
+
586
+ /** A rate the report converted at, as markets quote the pair and `fx` writes it, for `--json`. */
587
+ function fxRateRecord({ currency, baseCurrency, rate, asOf, stale }: BaseConversion) {
588
+ const quoted = quotedFxPair(currency, baseCurrency, rate);
589
+ return { currency, baseCurrency, pair: quoted.pair, rate: Number(formatFxRate(quoted.rate)), asOf: asOf == null ? null : new Date(asOf).toISOString(), stale };
590
+ }
591
+
592
+ /**
593
+ * `FX: USD/ZAR 16.7237, as of Fri 30 Oct 18:12 UTC`: each rate a portfolio's
594
+ * holdings in another currency were converted at, as markets quote the pair,
595
+ * dated; one past its fresh window says it is stale and how old. Null when
596
+ * nothing was converted.
597
+ */
598
+ function fxRatesLine(
599
+ conversions: readonly BaseConversion[],
600
+ { now = Date.now(), warning = (text: string) => text }: { now?: number; warning?: (text: string) => string } = {},
601
+ ): string | null {
602
+ const rates = conversions.filter((conversion) => conversion.currency !== conversion.baseCurrency && Number.isFinite(conversion.rate));
603
+ if (rates.length === 0) return null;
604
+ return `FX: ${rates.map((conversion) => {
605
+ const quoted = quotedFxPair(conversion.currency, conversion.baseCurrency, conversion.rate);
606
+ const dated = conversion.asOf == null ? "" : `, as of ${formatUtcDayTime({ time: conversion.asOf, dateOnly: false }, now)} UTC`;
607
+ const text = `${quoted.pair} ${formatFxRate(quoted.rate)}${dated}`;
608
+ return conversion.stale ? warning(`${text} (stale${conversion.asOf == null ? "" : `, ${rateAge(conversion.asOf, now)}`})`) : text;
609
+ }).join("; ")}`;
610
+ }
611
+
578
612
  /** What the TOTAL row weighs: all of a total; nothing on a gross base, where the weights net to the book's net over its gross. */
579
613
  function totalWeight(allocation: Pick<PortfolioAllocation, "weightBasis" | "total">): number | null {
580
614
  return allocation.weightBasis === "total" && allocation.total != null && allocation.total > 0 ? 100 : null;
@@ -818,7 +852,12 @@ async function showCollectionWithMarketData(
818
852
  })), dividends, toBase);
819
853
  const lotIncome = income.lots;
820
854
  const totalIncome = income.totalIncome;
821
- const portfolioYield = totalIncome != null && allocation.total != null && allocation.total > 0 ? (totalIncome / allocation.total) * 100 : null;
855
+ // The yield is on what is invested: cash pays no dividend, so it does not dilute it. A long-only book's is
856
+ // its priced holdings' net value. A book that holds a short is weighed on its gross exposure, and so is its
857
+ // yield: the net shrinks as the hedge grows (long 100k against short 80k nets 20k), which would inflate it.
858
+ const hedged = allocation.weightBasis === "gross";
859
+ const yieldBase = hedged ? allocation.weightBase : exposure.net;
860
+ const portfolioYield = totalIncome != null && yieldBase != null && yieldBase > 0 ? (totalIncome / yieldBase) * 100 : null;
822
861
  const incomeUnavailable = [...income.gaps].map(([symbol, gap]) => ({ symbol, ...gap }));
823
862
  const showTargets = allocation.targetSum != null;
824
863
  // Never dropped on a narrow terminal, so --explain reads the terms off them.
@@ -956,7 +995,9 @@ async function showCollectionWithMarketData(
956
995
  totalUnrealizedPnl: totalPnlValue,
957
996
  annualIncome: totalIncome, knownAnnualIncome: income.knownIncome, portfolioYield,
958
997
  incomeComplete: income.gaps.size === 0, incomeUnavailable,
959
- incomeBasis: "Trailing twelve-month dividends per share times shares held, in the portfolio's currency; a short owes the dividend, so its income is negative. Portfolio yield is the total over the total value; annualIncome is null when any holding's income is unknown, and knownAnnualIncome sums the holdings that have one.",
998
+ incomeBasis: "Trailing twelve-month dividends per share times shares held, in the portfolio's currency; a short owes the dividend, so its income is negative. Portfolio yield is the total over the invested value without cash: the priced holdings' net value (exposure.net), or for a book that holds a short its gross exposure (weightBase), as its weights are; annualIncome is null when any holding's income is unknown, and knownAnnualIncome sums the holdings that have one.",
999
+ // Each rate a holding in another currency was converted at, dated, and whether it is stale.
1000
+ fxRates: toBase.conversions().filter((conversion) => conversion.currency !== conversion.baseCurrency).map(fxRateRecord),
960
1001
  complete: unavailablePnl.size === 0 && unavailableCost.size === 0 && unavailableMarketValue.size === 0 && income.gaps.size === 0,
961
1002
  unavailableSymbols: [...new Set([...unavailablePnl, ...unavailableCost, ...unavailableMarketValue])],
962
1003
  unavailableCostSymbols: [...unavailableCost], brokerPnlSymbols: [...brokerPnlSymbols],
@@ -1098,7 +1139,16 @@ async function showCollectionWithMarketData(
1098
1139
  console.log("");
1099
1140
  if (basis === "regular-close") console.log(dayMoveLine(dayMove, currency, colorBySign, cliStyles.muted));
1100
1141
  if (valued.some((entry) => entry.position)) console.log(exposureLine(exposure, currency));
1142
+ // The rates the holdings in another currency are valued at, dated, so a stale one is seen beside the value it moves.
1143
+ const fxLine = fxRatesLine(toBase.conversions(), { warning: cliStyles.warning });
1144
+ if (fxLine) console.log(fxLine);
1101
1145
  const notes: string[] = [];
1146
+ // TOTAL's yield says what it divides: a hedged book's gross exposure, or, with cash in the book, the holdings alone.
1147
+ if (showIncome && portfolioYield != null && yieldBase != null && hedged) {
1148
+ notes.push(cliStyles.muted(`TOTAL yield is income over the gross exposure of ${wholeMoney(yieldBase)}, as the book holds a short; cash excluded.`));
1149
+ } else if (showIncome && portfolioYield != null && yieldBase != null && cashExport?.value) {
1150
+ notes.push(cliStyles.muted(`TOTAL yield is income over the invested ${wholeMoney(yieldBase)}, cash excluded.`));
1151
+ }
1102
1152
  const weightNote = weightBasisLine(allocation, currency);
1103
1153
  if (weightNote) notes.push(cliStyles.muted(weightNote));
1104
1154
  // The notes name the rows as the table does.
@@ -1,7 +1,6 @@
1
1
  import type { PaneTemplateContext, PaneTemplateInstanceConfig, ShortcutArgError } from "../../../types/plugin";
2
2
  import type { PluginModule } from "../plugin-module";
3
3
  import { unknownCollectionArgError } from "../../../portfolio/collection-name";
4
- import { PortfolioListPane } from "./pane";
5
4
  import {
6
5
  buildPortfolioPaneSettingsDef,
7
6
  getPortfolioPaneSettings,
@@ -54,7 +53,7 @@ export const portfolioListModule: PluginModule = {
54
53
  reportFreshness: { source: "Local portfolio and Gloom Cloud" },
55
54
  name: "Portfolio",
56
55
  icon: "P",
57
- component: PortfolioListPane,
56
+ get component(): typeof import("./pane").PortfolioListPane { const { PortfolioListPane } = require("./pane"); return PortfolioListPane; },
58
57
  defaultPosition: "left",
59
58
  defaultMode: "floating",
60
59
  defaultWidth: "40%",
@@ -34,6 +34,7 @@ import { convertCurrency } from "../../../../utils/format";
34
34
  import { nextHeaderSort } from "../../../../utils/sort-values";
35
35
  import { getSharedMarketDataCoordinator } from "../../../../market-data/coordinator";
36
36
  import type { TickerRecord } from "../../../../types/ticker";
37
+ import type { TickerFinancials } from "../../../../types/financials";
37
38
  import type { PaneProps } from "../../../../types/plugin";
38
39
  import type { InstrumentRef } from "../../../../market-data/request-types";
39
40
  import {
@@ -56,6 +57,7 @@ import {
56
57
  } from "../summary";
57
58
  import { usePortfolioAccountState } from "../summary/live-accounts";
58
59
  import { useOfflineBrokerFooter } from "../../shared/broker-reconnect";
60
+ import { useQuoteDelayFooter } from "../../shared/cloud-upgrade";
59
61
  import {
60
62
  getCollectionEntries,
61
63
  getPortfolioPaneSettings,
@@ -598,6 +600,8 @@ export function PortfolioListPane({ focused, width, height }: PaneProps) {
598
600
  summaryAccountState,
599
601
  ], FOOTER_TOTALS_THROTTLE_MS);
600
602
  const fxWarning = !!(fxStatus.stale || fxStatus.unknownTime || fxStatus.unavailable);
603
+ // Signed out or free, the rows' prices are the delayed ones; a list of coins is live and says nothing.
604
+ const quoteDelay = useQuoteDelayFooter(rowQuotes(sortedTickers, financialsMap), "portfolio-delay");
601
605
  const summaryNotices = isPortfolioTab
602
606
  ? buildPortfolioSummaryNotices({
603
607
  totals: portfolioSummaryTotals,
@@ -675,6 +679,7 @@ export function PortfolioListPane({ focused, width, height }: PaneProps) {
675
679
  ...offlineBrokerFooter.status,
676
680
  ...(fxStatusText && !fxWarning ? [{ id: "fx", parts: [{ text: `FX ${fxStatusText}`, tone: "muted" as const }] }] : []),
677
681
  ...(showsPerpetuals && perpetuals.stale ? [{ id: "perpetuals", parts: [{ text: "perpetuals stale", tone: "warning" as const }] }] : []),
682
+ ...(quoteDelay ? [quoteDelay] : []),
678
683
  ],
679
684
  hints: [
680
685
  ...offlineBrokerFooter.hints,
@@ -714,6 +719,7 @@ export function PortfolioListPane({ focused, width, height }: PaneProps) {
714
719
  isPortfolioTab,
715
720
  offlineBrokerFooter,
716
721
  perpetuals.stale,
722
+ quoteDelay,
717
723
  removalCollection,
718
724
  removeSelectedTicker,
719
725
  selectedTicker,
@@ -840,3 +846,8 @@ export function PortfolioListPane({ focused, width, height }: PaneProps) {
840
846
  </Box>
841
847
  );
842
848
  }
849
+
850
+ /** The quotes of the rows, in order, for the delayed clock, which stops at the first late one. */
851
+ function* rowQuotes(tickers: readonly TickerRecord[], financialsMap: ReadonlyMap<string, TickerFinancials>) {
852
+ for (const ticker of tickers) yield financialsMap.get(ticker.metadata.ticker)?.quote;
853
+ }
@@ -12,7 +12,7 @@ import { usePluginAppActions } from "../../../runtime";
12
12
  import { isDialogOpen } from "../../../../ui/dialog-stack";
13
13
  import { colors, priceColor } from "../../../../theme/colors";
14
14
  import { formatPercentRaw } from "../../../../utils/format";
15
- import { formatMarketPrice } from "../../../../market-data/market/format";
15
+ import { formatMarketPrice, quoteFormatOptions } from "../../../../market-data/market/format";
16
16
  import { listingChoiceQuery, upsertTickerFromSearchResult } from "../../../../tickers/search";
17
17
  import type { TickerRecord } from "../../../../types/ticker";
18
18
  import { addTickerToPortfolio, addTickerToWatchlist } from "../../../../portfolio/mutations";
@@ -79,7 +79,7 @@ function QuickAddPreview({
79
79
  const assetCategory = validation.ticker?.metadata.assetCategory
80
80
  ?? (validation.resolved.kind === "provider" ? validation.resolved.result.type : undefined);
81
81
  const priceText = quote?.price != null
82
- ? formatMarketPrice(quote.price, { assetCategory, maxWidth: 12 })
82
+ ? formatMarketPrice(quote.price, { ...quoteFormatOptions(quote, assetCategory), maxWidth: 12 })
83
83
  : "-";
84
84
  const changeValue = quote?.changePercent;
85
85
  const name = tickerNameFromValidation(validation);
@@ -1,4 +1,5 @@
1
1
  import { apiClient } from "../../../api-client";
2
+ import { sourceDidNotAnswer } from "../../../api-client/errors";
2
3
  import type { GasStorageBoard, GasStorageHistory, GasStorageRow, GasStorageSeasonal } from "../../../api-client/europe-energy";
3
4
  import { createPluginCache } from "../../../data/plugin-cache";
4
5
  import { cachedCloudResource, loadCloudResource, unavailableOnServer } from "../shared/cloud-resource";
@@ -77,7 +78,7 @@ export async function fetchGasStorage(client: Pick<GasApi, "getCloudEuropeGas">
77
78
  } catch (error) {
78
79
  throw unavailableOnServer(error, GAS_STORAGE_NOT_AVAILABLE, [404, 503]);
79
80
  }
80
- if (board.status === "unavailable") throw new Error(GAS_STORAGE_NOT_AVAILABLE);
81
+ if (board.status === "unavailable") throw sourceDidNotAnswer(GAS_STORAGE_NOT_AVAILABLE);
81
82
  return board;
82
83
  }
83
84
 
@@ -2,7 +2,6 @@ import type { PaneTemplateInstanceConfig } from "../../../types/plugin";
2
2
  import type { PluginModule } from "../plugin-module";
3
3
  import { powerBoardCache, powerDetailCache, powerHistoryCache } from "./client";
4
4
  import { powerHeadless } from "./headless";
5
- import { PowerPane } from "./pane";
6
5
  import { carbonHistoryCache, carbonPriceCache, powerPriceHistoryCache, powerPricesCache } from "./prices-client";
7
6
  import { powerPricesHeadless } from "./prices-headless";
8
7
  import { CARBON_SELECTION, CARBON_SERIES, DAY_AHEAD_SERIES, isCarbonQuery, POWER_PRICE_MARKETS, powerPriceMarket } from "./prices-model";
@@ -15,7 +14,7 @@ const CACHES = [powerBoardCache, powerHistoryCache, powerDetailCache, powerPrice
15
14
  export const powerModule: PluginModule = {
16
15
  series: [...DAY_AHEAD_SERIES, ...CARBON_SERIES, ...GAS_STORAGE_SERIES],
17
16
  // POWER sets the symbol param, EPWR the prices tab.
18
- panes: [{ id: "power", name: "Power & Grid", icon: "P", component: PowerPane, defaultPosition: "right", defaultMode: "floating", identitySettings: ["tab", "symbol"],
17
+ panes: [{ id: "power", name: "Power & Grid", icon: "P", get component(): typeof import("./pane").PowerPane { const { PowerPane } = require("./pane"); return PowerPane; }, defaultPosition: "right", defaultMode: "floating", identitySettings: ["tab", "symbol"],
19
18
  defaultFloatingSize: { width: 132, height: 34 }, tableExport: true, headless: powerHeadless }],
20
19
  paneTemplates: [{ id: "power-pane", paneId: "power", label: "Power & Grid",
21
20
  description: "Pro: interconnection queues, historical MW and outcomes, datacenter loads, utility exposure and generation capacity, with primary evidence.",
@@ -1,4 +1,5 @@
1
1
  import { apiClient } from "../../../api-client";
2
+ import { sourceDidNotAnswer } from "../../../api-client/errors";
2
3
  import type { CarbonBoard, CarbonHistory, EuropePowerBoard, EuropePowerHistory, PowerPriceRow } from "../../../api-client/europe-energy";
3
4
  import { createPluginCache } from "../../../data/plugin-cache";
4
5
  import { cachedCloudResource, loadCloudResource, unavailableOnServer } from "../shared/cloud-resource";
@@ -87,7 +88,7 @@ export async function fetchPowerPrices(client: Pick<PricesApi, "getCloudEuropePo
87
88
  } catch (error) {
88
89
  throw unavailableOnServer(error, POWER_PRICES_NOT_AVAILABLE, [404, 503]);
89
90
  }
90
- if (board.status === "unavailable") throw new Error(POWER_PRICES_NOT_AVAILABLE);
91
+ if (board.status === "unavailable") throw sourceDidNotAnswer(POWER_PRICES_NOT_AVAILABLE);
91
92
  return board;
92
93
  }
93
94
 
@@ -110,7 +111,7 @@ export async function fetchCarbonPrice(client: Pick<PricesApi, "getCloudEuropeCa
110
111
  } catch (error) {
111
112
  throw unavailableOnServer(error, CARBON_PRICE_NOT_AVAILABLE, [404, 503]);
112
113
  }
113
- if (board.status === "unavailable") throw new Error(CARBON_PRICE_NOT_AVAILABLE);
114
+ if (board.status === "unavailable") throw sourceDidNotAnswer(CARBON_PRICE_NOT_AVAILABLE);
114
115
  return board;
115
116
  }
116
117
 
@@ -1,10 +1,9 @@
1
1
  import type { PluginModule } from "../plugin-module";
2
2
  import { ratePathCache } from "./client";
3
3
  import { ratePathHeadless } from "./headless";
4
- import { RatePathPane } from "./pane";
5
4
 
6
5
  export const ratePathModule: PluginModule = {
7
- panes: [{ id: "rate-path", name: "US Rate Path", icon: "R", component: RatePathPane,
6
+ panes: [{ id: "rate-path", name: "US Rate Path", icon: "R", get component(): typeof import("./pane").RatePathPane { const { RatePathPane } = require("./pane"); return RatePathPane; },
8
7
  defaultPosition: "right", defaultMode: "floating", defaultFloatingSize: { width: 86, height: 30 },
9
8
  tableExport: true, headless: ratePathHeadless }],
10
9
  paneTemplates: [{
@@ -55,6 +55,8 @@ export function realizedVolHeadless(initialView: "graph" | "cone"): HeadlessPane
55
55
  const lastBar = model.asOf && Number.isFinite(model.asOf.getTime()) ? model.asOf.toISOString().slice(0, 10) : null;
56
56
  return {
57
57
  freshness: { asOf: lastBar },
58
+ // The session still trading is left out of every estimate, and said, as BT says it.
59
+ ...(model.formingNote ? { warnings: [`${model.formingNote}.`] } : {}),
58
60
  sections: [
59
61
  { title: "Volatility cone", columns: [
60
62
  { key: "window", header: "Bars" },
@@ -1,12 +1,11 @@
1
1
  import type { PluginModule } from "../plugin-module";
2
2
  import { createTickerSurfacePaneTemplate } from "../shared/ticker-surface";
3
- import { RealizedVolPane } from "./pane";
4
3
  import { realizedVolHeadless } from "./headless";
5
4
  import { ESTIMATOR_OPTIONS, WINDOW_OPTIONS } from "./settings";
6
5
 
7
6
  export const realizedVolModule: PluginModule = {
8
7
  panes: [{
9
- id: "realized-vol", name: "Realized Volatility", icon: "V", component: RealizedVolPane,
8
+ id: "realized-vol", name: "Realized Volatility", icon: "V", get component(): typeof import("./pane").RealizedVolPane { const { RealizedVolPane } = require("./pane"); return RealizedVolPane; },
10
9
  defaultPosition: "right", tickerFollower: true, defaultMode: "floating", defaultFloatingSize: { width: 112, height: 36 },
11
10
  tableExport: true,
12
11
  quickSettings: [{ type: "toggle", key: "showIv", icon: "zap", label: "Current ATM IV" }],
@@ -5,6 +5,7 @@ import {
5
5
  type RealizedVolatilityEstimator, type RollingRealizedVolatilityPoint, type VolatilityConeStatistics,
6
6
  } from "../shared/volatility";
7
7
  import type { SurfaceExpiry, SurfaceSnapshot } from "../vol-surface/model";
8
+ import { formingBarsState, isFormingBar } from "../shared/forming-bars";
8
9
 
9
10
  export interface RealizedVolatilityModel {
10
11
  symbol: string;
@@ -30,6 +31,12 @@ export interface RealizedVolatilityModel {
30
31
  * history covers the lookback and every window.
31
32
  */
32
33
  historyNote: string | null;
34
+ /**
35
+ * The newest session left out while it is not final, as BT leaves it out:
36
+ * "2026-10-30 is still forming (partial) and left out: the estimates run
37
+ * through 2026-10-29, the last complete session". Null when every bar is final.
38
+ */
39
+ formingNote: string | null;
33
40
  }
34
41
 
35
42
  /** "the 180-bar window", "the 180- and 260-bar windows", "the 90-, 180- and 260-bar windows". */
@@ -77,8 +84,15 @@ export function projectRealizedVolatility(
77
84
  }
78
85
  dates.set(date.getTime(), { ...point, date });
79
86
  }
80
- const history = [...dates.entries()].sort(([a], [b]) => a - b).map(([, point]) => point);
87
+ const served = [...dates.entries()].sort(([a], [b]) => a - b).map(([, point]) => point);
88
+ // A session still trading moves every estimate it is in until it ends: left out, as BT leaves it out, and said.
89
+ const forming = served.filter(isFormingBar);
90
+ const history = forming.length ? served.filter((point) => !isFormingBar(point)) : served;
81
91
  const asOf = history.at(-1)?.date ?? null;
92
+ const day = (date: Date) => date.toISOString().slice(0, 10);
93
+ const formingNote = forming.length === 0 ? null
94
+ : `${forming.map((point) => day(point.date)).join(", ")} ${formingBarsState(forming)} and left out`
95
+ + `${asOf ? `: the estimates run through ${day(asOf)}, the last complete session` : ""}`;
82
96
  const cutoff = asOf ? lookbackCutoff(asOf, lookbackYears) : 0;
83
97
  const cadenceIssue = realizedVolatilityCadenceIssue(history);
84
98
  if (cadenceIssue) warnings.push(cadenceIssue);
@@ -109,7 +123,7 @@ export function projectRealizedVolatility(
109
123
  : "the long windows' cone covers part of the lookback"}`;
110
124
  return { symbol: options.symbol.trim().toUpperCase(), estimator, lookbackYears, windows,
111
125
  history: visible, series: visibleSeries, cone, asOf, barsPerYear: barsPerYear(history.map((point) => point.date)),
112
- warnings: [...new Set(warnings)], shortWindows, historyNote };
126
+ warnings: [...new Set(warnings)], shortWindows, historyNote, formingNote };
113
127
  }
114
128
 
115
129
  interface CurrentAtmIvReference {
@@ -115,10 +115,10 @@ export function RealizedVolPane({ width, height, focused }: PaneProps) {
115
115
  symbol: history.data.symbol, estimator, windows, lookbackYears: Number(lookback) === 2 ? 2 : 1,
116
116
  }) : null, [history.data, estimator, windows, lookback]);
117
117
  const chartInput = useMemo(() => model ? { history: model.history, rolling: model.series, windows: model.windows,
118
- priceUnit: priceSeriesUnit(quote?.currency ?? "", quote?.instrumentType), iv: showIv ? iv.data?.reference ?? null : null } : null,
119
- [model, quote?.currency, quote?.instrumentType, showIv, iv.data]);
118
+ priceUnit: priceSeriesUnit(quote?.currency ?? "", quote?.instrumentType, quote?.priceBasis), iv: showIv ? iv.data?.reference ?? null : null } : null,
119
+ [model, quote?.currency, quote?.instrumentType, quote?.priceBasis, showIv, iv.data]);
120
120
  const notices = [identityError, history.error, history.data?.error, iv.error, ivRefresh.error, iv.data?.error,
121
- ...(model?.warnings ?? []), model?.historyNote, ...(showIv ? iv.data?.warnings ?? [] : []),
121
+ ...(model?.warnings ?? []), model?.historyNote, model?.formingNote, ...(showIv ? iv.data?.warnings ?? [] : []),
122
122
  ...(showIv && symbol && !spotAvailable ? ["Current ATM IV unavailable: underlying quote is missing or stale"] : []),
123
123
  ].filter((value): value is string => !!value);
124
124
  usePaneNoticeFooter({ registrationId: "realized-vol-notices", notices: [...new Set(notices)], focused });
@@ -2,7 +2,6 @@ import { isOnPortfolio } from "../../../shares/pane-image";
2
2
  import type { PluginModule } from "../plugin-module";
3
3
  import { rotationCache } from "./client";
4
4
  import { rotationHeadless } from "./headless";
5
- import { RelativeRotationPane } from "./pane";
6
5
  import { DEFAULT_ROTATION_BENCHMARK } from "./model";
7
6
 
8
7
  export const relativeRotationModule: PluginModule = {
@@ -11,7 +10,7 @@ export const relativeRotationModule: PluginModule = {
11
10
  id: "relative-rotation",
12
11
  name: "Relative Rotation",
13
12
  icon: "R",
14
- component: RelativeRotationPane,
13
+ get component(): typeof import("./pane").RelativeRotationPane { const { RelativeRotationPane } = require("./pane"); return RelativeRotationPane; },
15
14
  defaultPosition: "right",
16
15
  defaultMode: "floating",
17
16
  defaultFloatingSize: { width: 100, height: 36 },
@@ -20,6 +20,7 @@ import {
20
20
  recommendationMix,
21
21
  formatRatingLabel,
22
22
  formatRatingTarget,
23
+ ratingReason,
23
24
  recommendationTotal,
24
25
  sortRatingRows,
25
26
  type RatingColumnId,
@@ -43,6 +44,27 @@ const RATING_COLUMNS: HeadlessPaneColumn[] = [
43
44
  { key: "prior", header: "Prior" },
44
45
  ];
45
46
 
47
+ type RatingRow = AnalystResearchData["ratings"][number];
48
+
49
+ /**
50
+ * The reason coverage gave, on its own line under the action with its
51
+ * article; exports keep the line and the article's address as columns. Only
52
+ * a history with a reason somewhere has them.
53
+ */
54
+ const REASON_COLUMNS: HeadlessPaneColumn[] = [
55
+ {
56
+ key: "reason",
57
+ header: "Reason",
58
+ underRow: true,
59
+ format: (_value, row) => ratingReason(row as unknown as RatingRow)?.text ?? "",
60
+ textFormat: (_value, row) => {
61
+ const reason = ratingReason(row as unknown as RatingRow);
62
+ return reason ? `${reason.text} · ${reason.url}` : "";
63
+ },
64
+ },
65
+ { key: "reasonUrl", header: "Reason URL", underRow: true, textFormat: () => "" },
66
+ ];
67
+
46
68
  const SORT_COLUMNS: Record<string, RatingColumnId> = {
47
69
  date: "date",
48
70
  firm: "firm",
@@ -170,14 +192,19 @@ export function createAnalystResearchHeadless(
170
192
  direction: args.options.order === "asc" ? "asc" : "desc",
171
193
  })
172
194
  .slice(0, Number(args.options.limit))
173
- .map((rating) => ({
174
- ...rating,
175
- target: rating.currentPriceTarget ?? rating.priorPriceTarget ?? null,
176
- currency: currency ?? null,
177
- }));
195
+ .map((rating) => {
196
+ const reason = ratingReason(rating);
197
+ return {
198
+ ...rating,
199
+ target: rating.currentPriceTarget ?? rating.priorPriceTarget ?? null,
200
+ currency: currency ?? null,
201
+ ...(reason ? { reasonUrl: reason.url } : {}),
202
+ };
203
+ });
204
+ const columns = ratings.some((rating) => ratingReason(rating)) ? [...RATING_COLUMNS, ...REASON_COLUMNS] : RATING_COLUMNS;
178
205
  const sections: HeadlessBundleSection[] = [
179
206
  { title: "Summary", entries: overviewEntries(data, quote) },
180
- { title: "Recent analyst actions", columns: RATING_COLUMNS, rows: ratings },
207
+ { title: "Recent analyst actions", columns, rows: ratings },
181
208
  ];
182
209
  return {
183
210
  sections,