gloomberb 0.18.2 → 0.18.4

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 (954) hide show
  1. package/README.md +8 -8
  2. package/README.zh-CN.md +11 -11
  3. package/bin/{gloomberb → gloom} +3 -1
  4. package/package.json +6 -2
  5. package/patches/electrobun@1.18.1.patch +106 -5
  6. package/src/account/checkout-handoff.tsx +225 -0
  7. package/src/account/device-signin-dialog.tsx +2 -2
  8. package/src/account/device-signin.ts +2 -2
  9. package/src/account/upgrade-dialog.tsx +1 -1
  10. package/src/api-client/askg.ts +2 -2
  11. package/src/api-client/auth.ts +2 -2
  12. package/src/api-client/central-bank-rates.ts +6 -0
  13. package/src/api-client/collections.ts +5 -1
  14. package/src/api-client/cot.ts +2 -0
  15. package/src/api-client/crypto-etf-flows.ts +37 -0
  16. package/src/api-client/crypto-markets.ts +60 -0
  17. package/src/api-client/data.ts +99 -5
  18. package/src/api-client/doe.ts +15 -0
  19. package/src/api-client/equity-screener.ts +28 -0
  20. package/src/api-client/exchange-announcements.ts +38 -0
  21. package/src/api-client/fund-facts.ts +39 -0
  22. package/src/api-client/futures-curve.ts +9 -1
  23. package/src/api-client/hiring.ts +301 -0
  24. package/src/api-client/index.ts +24 -5
  25. package/src/api-client/market-discovery.ts +14 -0
  26. package/src/api-client/members.ts +6 -0
  27. package/src/api-client/mna.ts +6 -0
  28. package/src/api-client/paths.ts +18 -1
  29. package/src/api-client/request.ts +26 -7
  30. package/src/api-client/short-side-publications.ts +37 -0
  31. package/src/api-client/socket.ts +64 -4
  32. package/src/api-client/swap-curve.ts +35 -0
  33. package/src/api-client/telemetry.ts +17 -0
  34. package/src/api-client/themes.ts +7 -1
  35. package/src/api-client/types.ts +90 -2
  36. package/src/app/global-shortcuts.ts +1 -16
  37. package/src/app/pane-runtime/plugin-bindings.ts +9 -2
  38. package/src/app/runtime/desktop-deeplink.ts +5 -5
  39. package/src/app/runtime/plugin-bindings.ts +2 -2
  40. package/src/app/runtime/startup.ts +13 -0
  41. package/src/app/runtime/update.ts +28 -8
  42. package/src/app/star-prompt/status-line.tsx +2 -2
  43. package/src/architecture/core-builtin-allowlist.json +5 -2
  44. package/src/architecture/pane-tabs-allowlist.json +65 -0
  45. package/src/architecture/ui-literals-allowlist.json +3 -3
  46. package/src/architecture/ui-tokens-allowlist.json +1 -1
  47. package/src/brokers/broker-portfolio-sync.ts +37 -0
  48. package/src/brokers/directory.ts +11 -0
  49. package/src/brokers/logos.ts +3 -1
  50. package/src/brokers/signed-in/adapter.ts +304 -26
  51. package/src/brokers/signed-in/client.ts +14 -2
  52. package/src/brokers/signed-in/connect.ts +10 -2
  53. package/src/brokers/sync-broker-instance.ts +9 -1
  54. package/src/capabilities/chart-series.ts +22 -6
  55. package/src/cli/bank-section.ts +1 -1
  56. package/src/cli/base-converter.ts +23 -14
  57. package/src/cli/central-bank-events.ts +1 -1
  58. package/src/cli/command-options.ts +59 -4
  59. package/src/cli/commands/api.ts +9 -6
  60. package/src/cli/commands/broker.ts +2 -2
  61. package/src/cli/commands/command-utils.ts +2 -2
  62. package/src/cli/commands/crypto-hints.ts +7 -20
  63. package/src/cli/commands/fred-series.ts +4 -2
  64. package/src/cli/commands/keybindings.ts +2 -2
  65. package/src/cli/commands/market.ts +1189 -296
  66. package/src/cli/commands/news-hints.ts +2 -2
  67. package/src/cli/commands/news-search.ts +59 -6
  68. package/src/cli/commands/options-leaps.ts +41 -26
  69. package/src/cli/commands/overview.ts +121 -48
  70. package/src/cli/commands/plugins.ts +9 -9
  71. package/src/cli/commands/remote.ts +13 -10
  72. package/src/cli/commands/search.ts +64 -44
  73. package/src/cli/commands/system.ts +27 -19
  74. package/src/cli/commands/themes.ts +1 -1
  75. package/src/cli/commands/ticker.ts +272 -70
  76. package/src/cli/context.ts +3 -2
  77. package/src/cli/desktop-pane-shot-view.ts +2 -2
  78. package/src/cli/desktop-pane-shot.ts +1 -1
  79. package/src/cli/earnings-calendar.ts +4 -4
  80. package/src/cli/entry.ts +18 -1
  81. package/src/cli/errors.ts +20 -2
  82. package/src/cli/exit-codes.ts +1 -1
  83. package/src/cli/export-source.ts +10 -4
  84. package/src/cli/freshness.ts +2 -8
  85. package/src/cli/fx-forward.ts +130 -0
  86. package/src/cli/fx-pair.ts +10 -9
  87. package/src/cli/glossary.ts +64 -20
  88. package/src/cli/help.ts +20 -14
  89. package/src/cli/history-export.ts +126 -0
  90. package/src/cli/history-rows.ts +196 -43
  91. package/src/cli/index-roots.ts +5 -45
  92. package/src/cli/index.ts +32 -18
  93. package/src/cli/issuer-membership.ts +103 -0
  94. package/src/cli/known-gaps.ts +118 -25
  95. package/src/cli/listing-arg.ts +315 -43
  96. package/src/cli/name-entries.ts +73 -22
  97. package/src/cli/native-smoke.ts +2 -0
  98. package/src/cli/not-a-ticker.ts +19 -9
  99. package/src/cli/opentui-libc.ts +5 -0
  100. package/src/cli/options.ts +12 -0
  101. package/src/cli/pane-functions/capabilities.ts +77 -20
  102. package/src/cli/pane-functions/catalog.ts +40 -23
  103. package/src/cli/pane-functions/discovery.ts +1 -1
  104. package/src/cli/pane-functions/dom.ts +12 -8
  105. package/src/cli/pane-functions/financial-ratio-shot.ts +2 -2
  106. package/src/cli/pane-functions/freshness.ts +174 -23
  107. package/src/cli/pane-functions/function-help.ts +66 -27
  108. package/src/cli/pane-functions/headless.ts +48 -16
  109. package/src/cli/pane-functions/index.ts +82 -21
  110. package/src/cli/pane-functions/options.ts +22 -2
  111. package/src/cli/pane-functions/report-notices.ts +12 -4
  112. package/src/cli/pane-functions/report.ts +5 -5
  113. package/src/cli/pane-functions/resolver.ts +65 -14
  114. package/src/cli/pane-functions/screenshot-assessment.ts +322 -0
  115. package/src/cli/pane-functions/screenshot-bridge.ts +152 -0
  116. package/src/cli/pane-functions/screenshot-chart-evidence.ts +396 -0
  117. package/src/cli/pane-functions/screenshot-data-evidence.ts +383 -0
  118. package/src/cli/pane-functions/screenshot-evidence.ts +1 -1
  119. package/src/cli/pane-functions/screenshot-payload.ts +453 -0
  120. package/src/cli/pane-functions/screenshot-render.ts +316 -0
  121. package/src/cli/pane-functions/screenshot-types.ts +162 -0
  122. package/src/cli/pane-functions/shot-options.ts +8 -3
  123. package/src/cli/peso-dollar-rates.ts +79 -0
  124. package/src/cli/quote-answer.ts +38 -3
  125. package/src/cli/report-tables.ts +190 -27
  126. package/src/cli/restore-plugins.ts +29 -14
  127. package/src/cli/result.ts +122 -25
  128. package/src/cli/series-search.ts +5 -5
  129. package/src/cli/statement-filings.ts +19 -0
  130. package/src/cli/suffixed-listing-rows.ts +49 -0
  131. package/src/cli/symbol-args.ts +50 -15
  132. package/src/cli/symbol-cells.ts +120 -0
  133. package/src/cli/symbol-list.ts +226 -60
  134. package/src/cli/symbol-rows.ts +131 -25
  135. package/src/cli/ticker-resolution.ts +108 -11
  136. package/src/cli/treasury-yields.ts +5 -5
  137. package/src/cli/types.ts +2 -0
  138. package/src/cli/uncovered-instrument.ts +3 -3
  139. package/src/cli/unknown-command.ts +6 -6
  140. package/src/components/chart/composite/chart-layout.ts +131 -0
  141. package/src/components/chart/composite/composite-chart.tsx +387 -21
  142. package/src/components/chart/composite/drawing-store.ts +26 -6
  143. package/src/components/chart/composite/interactions.ts +9 -3
  144. package/src/components/chart/composite/legend.tsx +75 -3
  145. package/src/components/chart/composite/levels.ts +5 -0
  146. package/src/components/chart/composite/panel-gestures.ts +41 -0
  147. package/src/components/chart/composite/panel-labels.ts +2 -1
  148. package/src/components/chart/composite/panel-surface.tsx +316 -11
  149. package/src/components/chart/composite/price-axis-labels.tsx +27 -3
  150. package/src/components/chart/composite/rasterizer.ts +44 -19
  151. package/src/components/chart/composite/scene.ts +16 -4
  152. package/src/components/chart/composite/text-renderer.ts +48 -21
  153. package/src/components/chart/composite/toolbar.tsx +52 -5
  154. package/src/components/chart/composite/types.ts +19 -0
  155. package/src/components/chart/composite/use-composite-chart-keyboard.ts +120 -13
  156. package/src/components/chart/composite/use-composite-value-scales.ts +44 -0
  157. package/src/components/chart/composite/use-composite-viewport.ts +12 -3
  158. package/src/components/chart/composite/value-scale.ts +149 -0
  159. package/src/components/chart/composite/volume-profile-paint.ts +6 -3
  160. package/src/components/chart/core/pointer.ts +2 -0
  161. package/src/components/chart-table/index.ts +2 -0
  162. package/src/components/chart-table/read.tsx +74 -0
  163. package/src/components/command-bar/commands/collection-list.ts +41 -19
  164. package/src/components/command-bar/commands/collection.ts +9 -3
  165. package/src/components/command-bar/commands/direct/index.ts +1 -1
  166. package/src/components/command-bar/commands/direct/runtime.ts +3 -2
  167. package/src/components/command-bar/pane-templates/items.ts +59 -6
  168. package/src/components/command-bar/pane-templates/workflow.ts +10 -1
  169. package/src/components/command-bar/routes/root/command-items.ts +32 -6
  170. package/src/components/command-bar/routes/root/plugin-install.ts +1 -1
  171. package/src/components/command-bar/routes/root/provider-search.ts +42 -11
  172. package/src/components/command-bar/routes/root/results.ts +18 -4
  173. package/src/components/command-bar/routes/root/runtime.ts +15 -3
  174. package/src/components/command-bar/routes/root/search-providers.ts +25 -2
  175. package/src/components/command-bar/routes/root/selection.ts +6 -1
  176. package/src/components/command-bar/routes/root/shortcut-feedback.ts +4 -1
  177. package/src/components/command-bar/routes/root/shortcuts.ts +30 -3
  178. package/src/components/command-bar/routes/ticker-search/results.ts +73 -4
  179. package/src/components/command-bar/selection-runtime.ts +50 -10
  180. package/src/components/command-bar/surface/index.tsx +24 -2
  181. package/src/components/command-bar/view-model.ts +7 -0
  182. package/src/components/command-bar/workflow/broker.ts +2 -2
  183. package/src/components/command-bar/workflow/collection-actions.ts +46 -7
  184. package/src/components/command-bar/workflow/collection-ticker.ts +32 -4
  185. package/src/components/command-bar/workflow/fields.ts +8 -2
  186. package/src/components/command-bar/workflow/ops.ts +18 -4
  187. package/src/components/command-bar/workflow/portfolio-position.ts +53 -4
  188. package/src/components/command-bar/workflow/submit.ts +3 -2
  189. package/src/components/command-bar/workflow/tickers.ts +5 -1
  190. package/src/components/company-logo.tsx +39 -0
  191. package/src/components/data-table/sections.ts +4 -2
  192. package/src/components/data-table/view.tsx +23 -3
  193. package/src/components/form-modal/confirm.tsx +2 -1
  194. package/src/components/form-modal/request.ts +5 -0
  195. package/src/components/form-modal/routes.ts +4 -1
  196. package/src/components/index.ts +13 -6
  197. package/src/components/layout/detached-pane-shell.tsx +1 -0
  198. package/src/components/layout/floating-pane.tsx +1 -0
  199. package/src/components/layout/header.tsx +234 -60
  200. package/src/components/layout/market-summary.ts +9 -6
  201. package/src/components/layout/pane/capture-status.ts +1 -1
  202. package/src/components/layout/pane/footer/model.ts +11 -5
  203. package/src/components/layout/pane/header.tsx +10 -3
  204. package/src/components/layout/pane/index.tsx +1 -0
  205. package/src/components/layout/pane/pane-tabs.tsx +9 -1
  206. package/src/components/layout/shell/chrome.ts +19 -6
  207. package/src/components/layout/shell/index.tsx +2 -1
  208. package/src/components/layout/shell/pane/layers.tsx +19 -1
  209. package/src/components/layout/status-bar.tsx +2 -2
  210. package/src/components/market-board/index.tsx +11 -3
  211. package/src/components/onboarding/onboarding-wizard.tsx +1 -0
  212. package/src/components/onboarding/portfolio-step/broker-setup-panel.tsx +2 -2
  213. package/src/components/onboarding/wizard-broker-sync.ts +4 -1
  214. package/src/components/pane-settings-dialog/desktop.tsx +20 -7
  215. package/src/components/pane-settings-dialog/tui.tsx +17 -9
  216. package/src/components/pane-settings-dialog/value.ts +8 -1
  217. package/src/components/pane-settings-dialog.tsx +44 -13
  218. package/src/components/sign-in-code-panel.tsx +27 -19
  219. package/src/components/ui/column-grid.tsx +48 -4
  220. package/src/components/ui/country-flag-art.tsx +180 -0
  221. package/src/components/ui/country-flag.tsx +118 -12
  222. package/src/components/ui/data-table/opentui/index.tsx +61 -2
  223. package/src/components/ui/data-table/opentui/model.ts +109 -2
  224. package/src/components/ui/data-table/types.ts +1 -1
  225. package/src/components/ui/display.tsx +5 -1
  226. package/src/components/ui/index.ts +3 -1
  227. package/src/components/ui/logo-mark.tsx +11 -2
  228. package/src/components/ui/query-bar.tsx +1 -1
  229. package/src/components/ui/row-notes.ts +79 -0
  230. package/src/components/ui/stat-grid.tsx +28 -3
  231. package/src/components/ui/status.tsx +3 -1
  232. package/src/core/app-services.ts +4 -0
  233. package/src/core/state/app/layout.ts +4 -4
  234. package/src/data/cached-query.ts +8 -1
  235. package/src/data/config/chart-settings.ts +3 -3
  236. package/src/data/config/home.ts +10 -2
  237. package/src/data/config/layout.ts +4 -1
  238. package/src/data/config/store/node.ts +4 -0
  239. package/src/data/config/store/normalize.ts +13 -3
  240. package/src/data/config/store/portfolio-as-of-column.ts +2 -2
  241. package/src/data/json-storage.ts +2 -0
  242. package/src/data/saved-copies.ts +84 -0
  243. package/src/docs/snapshot.json +65 -32
  244. package/src/i18n/es.ts +51 -12
  245. package/src/i18n/ja.ts +51 -12
  246. package/src/i18n/ko.ts +51 -12
  247. package/src/i18n/zh-cn.ts +51 -12
  248. package/src/i18n/zh-tw.ts +51 -12
  249. package/src/layout/desks.ts +1 -1
  250. package/src/layout/launch-layout.ts +1 -1
  251. package/src/layout/pane-manager/floating-actions.ts +5 -3
  252. package/src/layout/pane-manager/floating.ts +43 -1
  253. package/src/layout/pane-manager/index.ts +2 -0
  254. package/src/layout/selected-ticker.ts +74 -0
  255. package/src/market-data/depositary-pair.ts +251 -0
  256. package/src/market-data/futures-board.ts +1 -1
  257. package/src/market-data/futures-contract.ts +124 -40
  258. package/src/market-data/fx-closes.ts +17 -2
  259. package/src/market-data/hooks.tsx +19 -11
  260. package/src/market-data/market/change-basis.ts +49 -7
  261. package/src/market-data/market/freshness.ts +34 -5
  262. package/src/market-data/market/period-return.ts +163 -0
  263. package/src/market-data/market/trading-sessions.ts +13 -0
  264. package/src/market-data/market/year-to-date.ts +8 -13
  265. package/src/market-data/performance.ts +20 -14
  266. package/src/market-data/perpetual-premium.ts +32 -0
  267. package/src/market-data/published-apac-sessions.ts +2 -1
  268. package/src/market-data/published-futures-sessions.ts +69 -0
  269. package/src/market-data/quotes/contributions.ts +5 -1
  270. package/src/market-data/quotes/freshness.ts +42 -0
  271. package/src/market-data/quotes/intraday-bar.ts +37 -0
  272. package/src/market-data/quotes/regular-session-valuation.ts +13 -8
  273. package/src/market-data/quotes/resolution.ts +4 -2
  274. package/src/market-data/tax-equivalent.ts +1 -1
  275. package/src/market-data/ticker-price-returns.ts +3 -1
  276. package/src/news/aggregator.ts +5 -1
  277. package/src/news/news-model.ts +4 -0
  278. package/src/news/types.ts +11 -0
  279. package/src/notifications/app-notifier.ts +1 -1
  280. package/src/plugins/auto-update.ts +2 -2
  281. package/src/plugins/builtin/account-management/index.tsx +1 -0
  282. package/src/plugins/builtin/account-management/pane.tsx +9 -8
  283. package/src/plugins/builtin/alerts/alert-engine.ts +30 -18
  284. package/src/plugins/builtin/alerts/calendar-events.ts +378 -0
  285. package/src/plugins/builtin/alerts/cli.ts +485 -76
  286. package/src/plugins/builtin/alerts/command.ts +5 -10
  287. package/src/plugins/builtin/alerts/event-rows.ts +9 -5
  288. package/src/plugins/builtin/alerts/events-pane.tsx +71 -56
  289. package/src/plugins/builtin/alerts/events.ts +108 -5
  290. package/src/plugins/builtin/alerts/fired-log.ts +78 -0
  291. package/src/plugins/builtin/alerts/format.ts +2 -1
  292. package/src/plugins/builtin/alerts/headless.ts +100 -39
  293. package/src/plugins/builtin/alerts/history-pane.tsx +105 -62
  294. package/src/plugins/builtin/alerts/history-rows.ts +89 -0
  295. package/src/plugins/builtin/alerts/index.tsx +45 -18
  296. package/src/plugins/builtin/alerts/iv-level.ts +365 -0
  297. package/src/plugins/builtin/alerts/levels.ts +3 -3
  298. package/src/plugins/builtin/alerts/pane.tsx +97 -29
  299. package/src/plugins/builtin/alerts/price.ts +35 -0
  300. package/src/plugins/builtin/alerts/quotes.ts +24 -0
  301. package/src/plugins/builtin/alerts/research-builder.ts +84 -11
  302. package/src/plugins/builtin/alerts/research-rules.ts +77 -3
  303. package/src/plugins/builtin/alerts/scheduled-check.ts +206 -32
  304. package/src/plugins/builtin/alerts/scheduled-sources.ts +26 -3
  305. package/src/plugins/builtin/alerts/scheduled.ts +188 -29
  306. package/src/plugins/builtin/alerts/storage.ts +13 -10
  307. package/src/plugins/builtin/alerts/targets.ts +12 -3
  308. package/src/plugins/builtin/analytics/index.tsx +8 -2
  309. package/src/plugins/builtin/analytics/metrics.ts +1 -1
  310. package/src/plugins/builtin/analytics/risk-book.ts +215 -0
  311. package/src/plugins/builtin/analytics/risk-client.ts +13 -6
  312. package/src/plugins/builtin/analytics/risk-headless.ts +55 -21
  313. package/src/plugins/builtin/analytics/risk-math.ts +6 -3
  314. package/src/plugins/builtin/analytics/risk-model.ts +297 -104
  315. package/src/plugins/builtin/analytics/risk-options.ts +17 -2
  316. package/src/plugins/builtin/analytics/risk-pane.tsx +107 -43
  317. package/src/plugins/builtin/attention/pane.tsx +22 -16
  318. package/src/plugins/builtin/awards/headless.ts +7 -3
  319. package/src/plugins/builtin/awards/model.ts +18 -0
  320. package/src/plugins/builtin/awards/pane.tsx +12 -9
  321. package/src/plugins/builtin/backtest/export.ts +1 -1
  322. package/src/plugins/builtin/backtest/headless.ts +3 -3
  323. package/src/plugins/builtin/backtest/rules.ts +41 -5
  324. package/src/plugins/builtin/broker-manager/add-flow.tsx +12 -7
  325. package/src/plugins/builtin/broker-manager/detail.tsx +47 -19
  326. package/src/plugins/builtin/broker-manager/footer.ts +27 -11
  327. package/src/plugins/builtin/broker-manager/index.tsx +21 -12
  328. package/src/plugins/builtin/broker-manager/keyboard.ts +1 -1
  329. package/src/plugins/builtin/broker-manager/model.ts +12 -2
  330. package/src/plugins/builtin/broker-manager/pane-actions.tsx +183 -57
  331. package/src/plugins/builtin/broker-manager/table.ts +28 -12
  332. package/src/plugins/builtin/broker-trading/account-picker.tsx +2 -1
  333. package/src/plugins/builtin/broker-trading/chart-levels.ts +82 -13
  334. package/src/plugins/builtin/broker-trading/controller.ts +45 -13
  335. package/src/plugins/builtin/broker-trading/dock.tsx +24 -4
  336. package/src/plugins/builtin/broker-trading/headless.ts +2 -1
  337. package/src/plugins/builtin/broker-trading/index.tsx +7 -1
  338. package/src/plugins/builtin/broker-trading/linked-orders.ts +148 -0
  339. package/src/plugins/builtin/broker-trading/order-feed.ts +75 -7
  340. package/src/plugins/builtin/broker-trading/orders.tsx +109 -31
  341. package/src/plugins/builtin/broker-trading/overview-orders.tsx +9 -3
  342. package/src/plugins/builtin/broker-trading/pane-model.ts +30 -2
  343. package/src/plugins/builtin/broker-trading/pane.tsx +115 -26
  344. package/src/plugins/builtin/broker-trading/ticket-parts.tsx +36 -7
  345. package/src/plugins/builtin/broker-trading/ticket.tsx +122 -36
  346. package/src/plugins/builtin/buildout/cells.tsx +4 -0
  347. package/src/plugins/builtin/buildout/detail/index.tsx +5 -1
  348. package/src/plugins/builtin/buildout/detail/ui.tsx +7 -4
  349. package/src/plugins/builtin/buildout/pane/index.tsx +34 -18
  350. package/src/plugins/builtin/buildout/pane/status.tsx +0 -4
  351. package/src/plugins/builtin/builtin-plugin-meta.ts +1 -1
  352. package/src/plugins/builtin/catalysts/client.ts +2 -2
  353. package/src/plugins/builtin/catalysts/detail.tsx +3 -2
  354. package/src/plugins/builtin/catalysts/index.tsx +1 -1
  355. package/src/plugins/builtin/catalysts/model.ts +49 -4
  356. package/src/plugins/builtin/catalysts/pane.tsx +9 -11
  357. package/src/plugins/builtin/central-bank-rates/client.ts +5 -0
  358. package/src/plugins/builtin/central-bank-rates/headless.ts +36 -10
  359. package/src/plugins/builtin/central-bank-rates/model.ts +33 -8
  360. package/src/plugins/builtin/changelog/index.tsx +1 -0
  361. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +1 -0
  362. package/src/plugins/builtin/chart-composer/chart-spec-edit.ts +2 -1
  363. package/src/plugins/builtin/chart-composer/chart-spec.ts +7 -0
  364. package/src/plugins/builtin/chart-composer/cli-options.ts +2 -5
  365. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +2 -1
  366. package/src/plugins/builtin/chart-composer/editor/controller.ts +2 -0
  367. package/src/plugins/builtin/chart-composer/editor.tsx +4 -1
  368. package/src/plugins/builtin/chart-composer/headless-schema.ts +28 -26
  369. package/src/plugins/builtin/chart-composer/headless.ts +253 -35
  370. package/src/plugins/builtin/chart-composer/index.tsx +12 -2
  371. package/src/plugins/builtin/chart-composer/leverage-overlay.ts +142 -0
  372. package/src/plugins/builtin/chart-composer/pane.tsx +128 -28
  373. package/src/plugins/builtin/chart-composer/series-catalog.ts +1 -0
  374. package/src/plugins/builtin/chart-composer/series-expression.ts +65 -6
  375. package/src/plugins/builtin/chart-composer/session-history.ts +15 -21
  376. package/src/plugins/builtin/chart-composer/session-range.ts +4 -2
  377. package/src/plugins/builtin/chart-composer/settings.ts +31 -19
  378. package/src/plugins/builtin/chart-composer/use-series-catalog.ts +5 -6
  379. package/src/plugins/builtin/chart-composer/viewport-labels.ts +18 -8
  380. package/src/plugins/builtin/chat/attachments/model.ts +3 -1
  381. package/src/plugins/builtin/chat/content/index.tsx +1 -1
  382. package/src/plugins/builtin/chat/pane-links/open.ts +1 -1
  383. package/src/plugins/builtin/chat/people.ts +1 -1
  384. package/src/plugins/builtin/clinical-trials/client.ts +5 -2
  385. package/src/plugins/builtin/clinical-trials/model.ts +13 -4
  386. package/src/plugins/builtin/clinical-trials/pane.tsx +32 -12
  387. package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +17 -0
  388. package/src/plugins/builtin/cloud/askg/controller.ts +1 -1
  389. package/src/plugins/builtin/cloud/askg/lock-note.ts +1 -1
  390. package/src/plugins/builtin/cloud/askg/tool-display.ts +1 -1
  391. package/src/plugins/builtin/cloud/auth-commands.ts +3 -3
  392. package/src/plugins/builtin/cloud/team/pane-sections.tsx +105 -47
  393. package/src/plugins/builtin/cloud/team/pane-ui.tsx +42 -4
  394. package/src/plugins/builtin/cloud/team/pane.tsx +204 -64
  395. package/src/plugins/builtin/cloud/thesis/store.ts +45 -22
  396. package/src/plugins/builtin/cloud-tweets/registration.ts +1 -0
  397. package/src/plugins/builtin/commodities-energy/index.tsx +29 -2
  398. package/src/plugins/builtin/commodities-energy/view-state.ts +2 -0
  399. package/src/plugins/builtin/commodity-balances/pane.tsx +14 -8
  400. package/src/plugins/builtin/company-attention/app-rank.tsx +11 -10
  401. package/src/plugins/builtin/company-attention/building-model.ts +222 -0
  402. package/src/plugins/builtin/company-attention/building.tsx +172 -0
  403. package/src/plugins/builtin/company-attention/client.ts +117 -1
  404. package/src/plugins/builtin/company-attention/evidence.ts +16 -3
  405. package/src/plugins/builtin/company-attention/headless.ts +101 -5
  406. package/src/plugins/builtin/company-attention/index.tsx +6 -3
  407. package/src/plugins/builtin/company-attention/model.ts +33 -7
  408. package/src/plugins/builtin/company-attention/moves-model.ts +253 -0
  409. package/src/plugins/builtin/company-attention/moves.tsx +159 -0
  410. package/src/plugins/builtin/company-attention/pane.tsx +89 -31
  411. package/src/plugins/builtin/company-attention/signals-model.ts +229 -0
  412. package/src/plugins/builtin/company-attention/signals.tsx +160 -0
  413. package/src/plugins/builtin/company-kpis/headless.ts +3 -2
  414. package/src/plugins/builtin/company-kpis/model.ts +2 -2
  415. package/src/plugins/builtin/company-kpis/pane.tsx +5 -4
  416. package/src/plugins/builtin/composite-plugins.ts +2 -1
  417. package/src/plugins/builtin/congress-trades/headless.ts +1 -1
  418. package/src/plugins/builtin/connections/index.ts +1 -0
  419. package/src/plugins/builtin/correlation/headless-schema.ts +3 -2
  420. package/src/plugins/builtin/correlation/headless.ts +7 -4
  421. package/src/plugins/builtin/correlation/relationship/model.ts +1 -0
  422. package/src/plugins/builtin/correlation/settings.ts +1 -0
  423. package/src/plugins/builtin/cot/headless.ts +28 -6
  424. package/src/plugins/builtin/cot/model.ts +12 -0
  425. package/src/plugins/builtin/cot/pane.tsx +7 -5
  426. package/src/plugins/builtin/cpi/headless.ts +5 -1
  427. package/src/plugins/builtin/cpi/pane.tsx +11 -9
  428. package/src/plugins/builtin/credit-boards/model.ts +2 -0
  429. package/src/plugins/builtin/credit-documents/headless.ts +1 -1
  430. package/src/plugins/builtin/credit-documents/pane.tsx +8 -7
  431. package/src/plugins/builtin/crypto-board/client.ts +86 -0
  432. package/src/plugins/builtin/crypto-board/defi-view.tsx +218 -0
  433. package/src/plugins/builtin/crypto-board/defi.ts +239 -0
  434. package/src/plugins/builtin/crypto-board/headless.ts +117 -6
  435. package/src/plugins/builtin/crypto-board/index.tsx +8 -3
  436. package/src/plugins/builtin/crypto-board/model.ts +40 -8
  437. package/src/plugins/builtin/crypto-board/pane.tsx +55 -21
  438. package/src/plugins/builtin/crypto-board/series.ts +19 -1
  439. package/src/plugins/builtin/crypto-etf-flows/client.ts +60 -0
  440. package/src/plugins/builtin/crypto-etf-flows/headless.ts +132 -0
  441. package/src/plugins/builtin/crypto-etf-flows/index.tsx +44 -0
  442. package/src/plugins/builtin/crypto-etf-flows/model.ts +121 -0
  443. package/src/plugins/builtin/crypto-etf-flows/pane.tsx +193 -0
  444. package/src/plugins/builtin/debt-maturities/pane.tsx +4 -3
  445. package/src/plugins/builtin/debug/index.tsx +1 -0
  446. package/src/plugins/builtin/dividend-yield/index.tsx +1 -1
  447. package/src/plugins/builtin/docs/index.tsx +1 -1
  448. package/src/plugins/builtin/docs/pane.tsx +2 -2
  449. package/src/plugins/builtin/docs/targets.ts +1 -1
  450. package/src/plugins/builtin/doe/client.ts +5 -1
  451. package/src/plugins/builtin/doe/headless.ts +12 -6
  452. package/src/plugins/builtin/doe/model.ts +43 -6
  453. package/src/plugins/builtin/doe/pane.tsx +19 -12
  454. package/src/plugins/builtin/earnings/board-model.ts +13 -6
  455. package/src/plugins/builtin/earnings/board-table.ts +4 -1
  456. package/src/plugins/builtin/earnings/board.tsx +253 -50
  457. package/src/plugins/builtin/earnings/calendar-model.ts +188 -0
  458. package/src/plugins/builtin/earnings/calendar.tsx +338 -0
  459. package/src/plugins/builtin/earnings/client.ts +4 -3
  460. package/src/plugins/builtin/earnings/headless.ts +53 -15
  461. package/src/plugins/builtin/earnings/history-model.ts +53 -36
  462. package/src/plugins/builtin/earnings/index.tsx +15 -22
  463. package/src/plugins/builtin/earnings/model.ts +17 -12
  464. package/src/plugins/builtin/earnings/scope.ts +89 -0
  465. package/src/plugins/builtin/earnings-calls/calendar.tsx +310 -0
  466. package/src/plugins/builtin/earnings-calls/data.ts +146 -2
  467. package/src/plugins/builtin/earnings-calls/headless.ts +151 -9
  468. package/src/plugins/builtin/earnings-calls/index.tsx +3 -3
  469. package/src/plugins/builtin/earnings-calls/lock-note.ts +1 -1
  470. package/src/plugins/builtin/earnings-calls/pane.tsx +149 -31
  471. package/src/plugins/builtin/earnings-calls/upcoming.ts +195 -0
  472. package/src/plugins/builtin/earnings-ripple/pane.tsx +10 -5
  473. package/src/plugins/builtin/econ/calendar-model.ts +132 -20
  474. package/src/plugins/builtin/econ/detail-view.tsx +48 -10
  475. package/src/plugins/builtin/econ/fred-catalog.ts +23 -6
  476. package/src/plugins/builtin/econ/fred-series-map.ts +4 -0
  477. package/src/plugins/builtin/econ/index.tsx +112 -25
  478. package/src/plugins/builtin/econ/print-watch.ts +2 -1
  479. package/src/plugins/builtin/econ/types.ts +32 -1
  480. package/src/plugins/builtin/econ-statistics/client.ts +16 -3
  481. package/src/plugins/builtin/econ-statistics/defs.ts +4 -0
  482. package/src/plugins/builtin/econ-statistics/detail.tsx +2 -2
  483. package/src/plugins/builtin/econ-statistics/headless.ts +13 -2
  484. package/src/plugins/builtin/econ-statistics/index.tsx +3 -3
  485. package/src/plugins/builtin/econ-statistics/pane.tsx +2 -1
  486. package/src/plugins/builtin/econ-statistics/stats.ts +103 -0
  487. package/src/plugins/builtin/econ-statistics/view.ts +16 -1
  488. package/src/plugins/builtin/equity-screener/client.ts +21 -3
  489. package/src/plugins/builtin/equity-screener/headless.ts +77 -5
  490. package/src/plugins/builtin/equity-screener/live.ts +1 -1
  491. package/src/plugins/builtin/equity-screener/model.ts +34 -0
  492. package/src/plugins/builtin/equity-screener/pane.tsx +148 -106
  493. package/src/plugins/builtin/equity-screener/screen-check.ts +90 -0
  494. package/src/plugins/builtin/equity-screener/ticker-check.tsx +148 -0
  495. package/src/plugins/builtin/estimate-revisions/headless.ts +3 -1
  496. package/src/plugins/builtin/estimate-revisions/lock-note.ts +3 -3
  497. package/src/plugins/builtin/estimate-revisions/model.ts +8 -10
  498. package/src/plugins/builtin/exposure/headless.ts +51 -6
  499. package/src/plugins/builtin/exposure/index.tsx +28 -2
  500. package/src/plugins/builtin/exposure/model.ts +7 -0
  501. package/src/plugins/builtin/exposure/pane.tsx +16 -10
  502. package/src/plugins/builtin/fear-greed/index.tsx +1 -1
  503. package/src/plugins/builtin/filing-events/index.tsx +1 -1
  504. package/src/plugins/builtin/futures/model.ts +10 -9
  505. package/src/plugins/builtin/futures/returns.ts +8 -36
  506. package/src/plugins/builtin/futures/table.tsx +1 -1
  507. package/src/plugins/builtin/futures-curve/client.ts +40 -9
  508. package/src/plugins/builtin/futures-curve/headless.ts +39 -17
  509. package/src/plugins/builtin/futures-curve/model.ts +258 -69
  510. package/src/plugins/builtin/futures-curve/pane.tsx +46 -17
  511. package/src/plugins/builtin/fx-matrix/client.ts +15 -8
  512. package/src/plugins/builtin/fx-matrix/headless.ts +32 -14
  513. package/src/plugins/builtin/fx-matrix/index.tsx +13 -11
  514. package/src/plugins/builtin/fx-matrix/live-legs.ts +22 -0
  515. package/src/plugins/builtin/fx-matrix/settings.ts +2 -1
  516. package/src/plugins/builtin/gpu/board.tsx +4 -1
  517. package/src/plugins/builtin/gpu/changes.tsx +10 -10
  518. package/src/plugins/builtin/gpu/equities.tsx +4 -1
  519. package/src/plugins/builtin/gpu/headless.ts +9 -4
  520. package/src/plugins/builtin/gpu/history.tsx +4 -2
  521. package/src/plugins/builtin/gpu/pane.tsx +5 -3
  522. package/src/plugins/builtin/help/index.tsx +13 -3
  523. package/src/plugins/builtin/holders/beneficial-model.ts +8 -11
  524. package/src/plugins/builtin/holders/beneficial-report.ts +6 -5
  525. package/src/plugins/builtin/holders/beneficial-view.tsx +7 -3
  526. package/src/plugins/builtin/holders/client.ts +15 -2
  527. package/src/plugins/builtin/holders/format.ts +28 -17
  528. package/src/plugins/builtin/holders/headless.ts +48 -8
  529. package/src/plugins/builtin/holders/index-holders.ts +264 -0
  530. package/src/plugins/builtin/holders/pane.tsx +79 -15
  531. package/src/plugins/builtin/holders/report-rows.ts +35 -7
  532. package/src/plugins/builtin/holders/table-model.ts +2 -1
  533. package/src/plugins/builtin/holders/thirteenf-match.ts +3 -1
  534. package/src/plugins/builtin/holders/treemap.tsx +2 -2
  535. package/src/plugins/builtin/insider/base-values.ts +11 -7
  536. package/src/plugins/builtin/insider/client.ts +3 -0
  537. package/src/plugins/builtin/insider/headless.ts +20 -7
  538. package/src/plugins/builtin/insider/index.tsx +17 -8
  539. package/src/plugins/builtin/insider/issuer.ts +34 -4
  540. package/src/plugins/builtin/insider/signals-client.ts +3 -3
  541. package/src/plugins/builtin/insider/signals.tsx +1 -1
  542. package/src/plugins/builtin/insider/table-model.ts +35 -13
  543. package/src/plugins/builtin/insider/table.tsx +5 -2
  544. package/src/plugins/builtin/ipo-calendar/pane.tsx +7 -4
  545. package/src/plugins/builtin/iv-history/client.ts +20 -0
  546. package/src/plugins/builtin/iv-history/events.ts +145 -0
  547. package/src/plugins/builtin/iv-history/format.ts +3 -0
  548. package/src/plugins/builtin/iv-history/headless.ts +91 -7
  549. package/src/plugins/builtin/iv-history/index.tsx +1 -1
  550. package/src/plugins/builtin/iv-history/model.ts +13 -4
  551. package/src/plugins/builtin/iv-history/series.ts +3 -1
  552. package/src/plugins/builtin/jobs/lock-note.ts +1 -1
  553. package/src/plugins/builtin/jobs/model.ts +4 -15
  554. package/src/plugins/builtin/jobs/pane.tsx +7 -5
  555. package/src/plugins/builtin/jobs/share-bars.tsx +11 -5
  556. package/src/plugins/builtin/jodi/pane.tsx +14 -8
  557. package/src/plugins/builtin/layout-manager/index.tsx +1 -0
  558. package/src/plugins/builtin/macro-day/headless.ts +20 -7
  559. package/src/plugins/builtin/macro-day/index.tsx +3 -2
  560. package/src/plugins/builtin/macro-day/model.ts +29 -8
  561. package/src/plugins/builtin/macro-day/pane.tsx +46 -10
  562. package/src/plugins/builtin/macro-day/releases.ts +55 -13
  563. package/src/plugins/builtin/market-heatmap/data.ts +9 -2
  564. package/src/plugins/builtin/market-heatmap/evidence.ts +1 -1
  565. package/src/plugins/builtin/market-heatmap/headless.ts +9 -6
  566. package/src/plugins/builtin/market-heatmap/index.tsx +3 -3
  567. package/src/plugins/builtin/market-heatmap/model.ts +10 -7
  568. package/src/plugins/builtin/market-movers/client.ts +36 -21
  569. package/src/plugins/builtin/market-movers/headless.ts +8 -2
  570. package/src/plugins/builtin/market-movers/index.tsx +16 -10
  571. package/src/plugins/builtin/market-movers/screener.ts +33 -11
  572. package/src/plugins/builtin/market-movers/session-body.tsx +4 -2
  573. package/src/plugins/builtin/market-movers/session-table.ts +4 -2
  574. package/src/plugins/builtin/market-movers/table.ts +4 -1
  575. package/src/plugins/builtin/market-valuation/headless.ts +2 -0
  576. package/src/plugins/builtin/market-valuation/view.ts +5 -0
  577. package/src/plugins/builtin/members/headless.ts +6 -5
  578. package/src/plugins/builtin/members/pane.tsx +11 -4
  579. package/src/plugins/builtin/mna/client.ts +1 -0
  580. package/src/plugins/builtin/mna/detail.tsx +20 -32
  581. package/src/plugins/builtin/mna/distress/detail.tsx +16 -4
  582. package/src/plugins/builtin/mna/distress/model.ts +3 -2
  583. package/src/plugins/builtin/mna/distress/pane.tsx +7 -4
  584. package/src/plugins/builtin/mna/headless.ts +6 -13
  585. package/src/plugins/builtin/mna/model.ts +93 -15
  586. package/src/plugins/builtin/mna/pane.tsx +5 -4
  587. package/src/plugins/builtin/news/headless.ts +12 -6
  588. package/src/plugins/builtin/news/index.tsx +10 -1
  589. package/src/plugins/builtin/news/wire/index.tsx +3 -2
  590. package/src/plugins/builtin/news/wire/news/command-bar-search.ts +26 -10
  591. package/src/plugins/builtin/news/wire/news/query-presets.ts +6 -2
  592. package/src/plugins/builtin/news/wire/rss/cli.ts +3 -3
  593. package/src/plugins/builtin/notes/cli.ts +50 -15
  594. package/src/plugins/builtin/notes/export.ts +17 -6
  595. package/src/plugins/builtin/notes/files.ts +16 -5
  596. package/src/plugins/builtin/openfda/pane.tsx +1 -1
  597. package/src/plugins/builtin/options/carry.ts +2 -2
  598. package/src/plugins/builtin/options/enrichment-model.ts +10 -9
  599. package/src/plugins/builtin/options/index.tsx +1 -1
  600. package/src/plugins/builtin/options/model-spot.ts +70 -4
  601. package/src/plugins/builtin/options/view.tsx +51 -23
  602. package/src/plugins/builtin/options-calculator/headless.ts +46 -7
  603. package/src/plugins/builtin/options-calculator/pane.tsx +3 -2
  604. package/src/plugins/builtin/options-positioning/headless.ts +11 -3
  605. package/src/plugins/builtin/options-positioning/model.ts +24 -0
  606. package/src/plugins/builtin/options-positioning/pane.tsx +10 -12
  607. package/src/plugins/builtin/options-scenario/client.ts +142 -29
  608. package/src/plugins/builtin/options-scenario/collar.ts +8 -6
  609. package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +127 -0
  610. package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +165 -0
  611. package/src/plugins/builtin/options-scenario/csp-screen.ts +258 -0
  612. package/src/plugins/builtin/options-scenario/csp.ts +101 -21
  613. package/src/plugins/builtin/options-scenario/headless.ts +49 -21
  614. package/src/plugins/builtin/options-scenario/hedge.ts +20 -14
  615. package/src/plugins/builtin/options-scenario/index.tsx +28 -3
  616. package/src/plugins/builtin/options-scenario/model.ts +13 -0
  617. package/src/plugins/builtin/options-scenario/pane.tsx +22 -10
  618. package/src/plugins/builtin/perps/equity-row.tsx +11 -3
  619. package/src/plugins/builtin/perps/freshness.ts +8 -5
  620. package/src/plugins/builtin/perps/headless.ts +3 -1
  621. package/src/plugins/builtin/perps/list-perpetuals.ts +25 -4
  622. package/src/plugins/builtin/perps/live-rows.ts +161 -0
  623. package/src/plugins/builtin/perps/markets-section.tsx +154 -0
  624. package/src/plugins/builtin/perps/pane.tsx +36 -17
  625. package/src/plugins/builtin/perps/venue-markets.ts +96 -0
  626. package/src/plugins/builtin/plugin-marketplace/index.tsx +2 -1
  627. package/src/plugins/builtin/plugin-marketplace/pane.tsx +145 -60
  628. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +78 -27
  629. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +158 -141
  630. package/src/plugins/builtin/portfolio-list/cli/render.ts +49 -20
  631. package/src/plugins/builtin/portfolio-list/cli/shared.ts +3 -3
  632. package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +29 -24
  633. package/src/plugins/builtin/portfolio-list/headless.ts +6 -3
  634. package/src/plugins/builtin/portfolio-list/income.ts +13 -2
  635. package/src/plugins/builtin/portfolio-list/index.tsx +9 -2
  636. package/src/plugins/builtin/portfolio-list/pane/index.tsx +21 -8
  637. package/src/plugins/builtin/portfolio-list/pane/purchase-date-fx.ts +46 -0
  638. package/src/plugins/builtin/portfolio-list/summary/live-accounts.ts +16 -9
  639. package/src/plugins/builtin/portfolio-list/summary/totals.ts +2 -15
  640. package/src/plugins/builtin/power/client.ts +6 -3
  641. package/src/plugins/builtin/power/evidence.ts +22 -7
  642. package/src/plugins/builtin/power/gas.tsx +5 -1
  643. package/src/plugins/builtin/power/pane.tsx +44 -32
  644. package/src/plugins/builtin/power/prices.tsx +10 -3
  645. package/src/plugins/builtin/research/analyst-headless.ts +25 -8
  646. package/src/plugins/builtin/research/analyst-model.ts +68 -3
  647. package/src/plugins/builtin/research/corporate-actions-pane.tsx +63 -14
  648. package/src/plugins/builtin/research/event-model.ts +3 -1
  649. package/src/plugins/builtin/research/event-sources.ts +12 -4
  650. package/src/plugins/builtin/research/index.tsx +2 -1
  651. package/src/plugins/builtin/research/relative-valuation-basis.ts +58 -0
  652. package/src/plugins/builtin/research/relative-valuation-columns.ts +31 -2
  653. package/src/plugins/builtin/research/relative-valuation-currency.ts +12 -5
  654. package/src/plugins/builtin/research/relative-valuation-headless.ts +84 -26
  655. package/src/plugins/builtin/research/relative-valuation-model.ts +88 -19
  656. package/src/plugins/builtin/research/relative-valuation-pane.tsx +76 -9
  657. package/src/plugins/builtin/research/relative-valuation-peers.ts +1 -1
  658. package/src/plugins/builtin/reverse-dcf/client.ts +2 -1
  659. package/src/plugins/builtin/reverse-dcf/model.ts +10 -5
  660. package/src/plugins/builtin/risk-factors/index.tsx +2 -2
  661. package/src/plugins/builtin/scanner/lock-note.ts +1 -1
  662. package/src/plugins/builtin/seasonality/client.ts +2 -1
  663. package/src/plugins/builtin/sec/forms.ts +13 -0
  664. package/src/plugins/builtin/sec/fund-abroad.ts +32 -0
  665. package/src/plugins/builtin/sec/fund-facts.ts +68 -8
  666. package/src/plugins/builtin/sec/headless.ts +85 -8
  667. package/src/plugins/builtin/sec/index.tsx +58 -12
  668. package/src/plugins/builtin/sectors/client.ts +6 -4
  669. package/src/plugins/builtin/sectors/sector-model.ts +11 -15
  670. package/src/plugins/builtin/series-inventory.ts +2 -0
  671. package/src/plugins/builtin/shared/broker-reconnect.ts +107 -22
  672. package/src/plugins/builtin/shared/cloud-resource.ts +15 -2
  673. package/src/plugins/builtin/shared/cloud-upgrade.ts +34 -6
  674. package/src/plugins/builtin/shared/earnings-calendar.ts +375 -0
  675. package/src/plugins/builtin/{earnings/estimate-basis.ts → shared/earnings-estimate-basis.ts} +1 -1
  676. package/src/plugins/builtin/shared/earnings-report.ts +3 -3
  677. package/src/plugins/builtin/shared/fx-rate-freshness.ts +23 -1
  678. package/src/plugins/builtin/shared/job-posting.ts +129 -0
  679. package/src/plugins/builtin/shared/pro-lock-note.ts +3 -3
  680. package/src/plugins/builtin/shared/report-freshness.ts +3 -0
  681. package/src/plugins/builtin/shared/research-trading.tsx +23 -0
  682. package/src/plugins/builtin/shared/series-cache.ts +2 -0
  683. package/src/plugins/builtin/shared/series-list-detail.tsx +3 -1
  684. package/src/plugins/builtin/shared/ticker-surface.ts +2 -1
  685. package/src/plugins/builtin/shared/volatility/options.ts +2 -1
  686. package/src/plugins/builtin/short-interest/client.ts +2 -0
  687. package/src/plugins/builtin/short-interest/headless.ts +41 -12
  688. package/src/plugins/builtin/short-interest/index.tsx +23 -1
  689. package/src/plugins/builtin/short-interest/model.ts +70 -19
  690. package/src/plugins/builtin/short-interest/pane.tsx +19 -2
  691. package/src/plugins/builtin/short-interest/publications-client.ts +82 -0
  692. package/src/plugins/builtin/short-interest/publications-headless.ts +29 -0
  693. package/src/plugins/builtin/short-interest/publications-model.ts +50 -0
  694. package/src/plugins/builtin/short-interest/publications-pane.tsx +66 -0
  695. package/src/plugins/builtin/short-interest/schedule.ts +35 -2
  696. package/src/plugins/builtin/short-interest/surface.tsx +11 -4
  697. package/src/plugins/builtin/short-interest/types.ts +9 -0
  698. package/src/plugins/builtin/short-interest/watch-client.ts +14 -8
  699. package/src/plugins/builtin/short-interest/watch-model.ts +12 -25
  700. package/src/plugins/builtin/short-interest/watch-pane.tsx +5 -1
  701. package/src/plugins/builtin/social-mentions/client.ts +2 -1
  702. package/src/plugins/builtin/supply-chain/flow.tsx +33 -9
  703. package/src/plugins/builtin/supply-chain/model.ts +51 -3
  704. package/src/plugins/builtin/supply-chain/pane.tsx +78 -39
  705. package/src/plugins/builtin/supply-chain/row-evidence.tsx +1 -1
  706. package/src/plugins/builtin/themes/headless.ts +8 -5
  707. package/src/plugins/builtin/themes/model.ts +14 -4
  708. package/src/plugins/builtin/themes/pane.tsx +24 -5
  709. package/src/plugins/builtin/thirteenf/headless.ts +5 -5
  710. package/src/plugins/builtin/thirteenf/signals.ts +3 -1
  711. package/src/plugins/builtin/ticker-detail/adr-pair/headless.ts +123 -0
  712. package/src/plugins/builtin/ticker-detail/adr-pair/model.ts +132 -0
  713. package/src/plugins/builtin/ticker-detail/adr-pair/tab.tsx +87 -0
  714. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +40 -26
  715. package/src/plugins/builtin/ticker-detail/data-panes/history-bars.ts +8 -4
  716. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +67 -3
  717. package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +112 -5
  718. package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +17 -2
  719. package/src/plugins/builtin/ticker-detail/financials/aggregation.ts +21 -7
  720. package/src/plugins/builtin/ticker-detail/financials/model.ts +36 -1
  721. package/src/plugins/builtin/ticker-detail/financials/offerings.ts +56 -0
  722. package/src/plugins/builtin/ticker-detail/financials/ratios.ts +62 -13
  723. package/src/plugins/builtin/ticker-detail/financials/tab.tsx +30 -4
  724. package/src/plugins/builtin/ticker-detail/headless-schema.ts +14 -2
  725. package/src/plugins/builtin/ticker-detail/headless.ts +148 -35
  726. package/src/plugins/builtin/ticker-detail/index.tsx +37 -5
  727. package/src/plugins/builtin/ticker-detail/overview/model.ts +65 -10
  728. package/src/plugins/builtin/ticker-detail/overview/pane.tsx +9 -1
  729. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +68 -11
  730. package/src/plugins/builtin/ticker-detail/pane.tsx +11 -0
  731. package/src/plugins/builtin/ticker-detail/quote-footer.ts +8 -3
  732. package/src/plugins/builtin/ticker-detail/research-tabs.ts +11 -0
  733. package/src/plugins/builtin/vol-surface/client.ts +28 -7
  734. package/src/plugins/builtin/vol-surface/headless.ts +43 -15
  735. package/src/plugins/builtin/vol-surface/model.ts +34 -12
  736. package/src/plugins/builtin/vol-surface/pane.tsx +33 -13
  737. package/src/plugins/builtin/vol-surface/stored.ts +34 -3
  738. package/src/plugins/builtin/vol-surface/tables.ts +14 -0
  739. package/src/plugins/builtin/volatility/model.ts +5 -10
  740. package/src/plugins/builtin/world-venue-map/basemap-layer.tsx +4 -3
  741. package/src/plugins/builtin/world-venue-map/basemap.ts +3 -1
  742. package/src/plugins/builtin/world-venue-map/chokepoint-chart.ts +40 -0
  743. package/src/plugins/builtin/world-venue-map/evidence.ts +1 -1
  744. package/src/plugins/builtin/world-venue-map/geo-series.ts +90 -11
  745. package/src/plugins/builtin/world-venue-map/headless.ts +1 -1
  746. package/src/plugins/builtin/world-venue-map/index.ts +3 -26
  747. package/src/plugins/builtin/world-venue-map/layers.ts +1 -1
  748. package/src/plugins/builtin/world-venue-map/map.tsx +2 -1
  749. package/src/plugins/builtin/world-venue-map/pane.tsx +4 -2
  750. package/src/plugins/builtin/world-venue-map/venue-header.tsx +15 -4
  751. package/src/plugins/builtin/yield-curve/client.ts +25 -0
  752. package/src/plugins/builtin/yield-curve/curves.ts +7 -4
  753. package/src/plugins/builtin/yield-curve/headless.ts +38 -4
  754. package/src/plugins/builtin/yield-curve/history.ts +4 -3
  755. package/src/plugins/builtin/yield-curve/index.tsx +12 -6
  756. package/src/plugins/builtin/yield-curve/swaps-model.ts +93 -0
  757. package/src/plugins/builtin/yield-curve/swaps.tsx +99 -0
  758. package/src/plugins/bundle.ts +1 -1
  759. package/src/plugins/catalog-browser.ts +2 -1
  760. package/src/plugins/compat.ts +3 -3
  761. package/src/plugins/dependencies.ts +75 -14
  762. package/src/plugins/extracted-plugins.ts +132 -34
  763. package/src/plugins/host-link.ts +149 -34
  764. package/src/plugins/installer.ts +35 -10
  765. package/src/plugins/loader.ts +206 -38
  766. package/src/plugins/manager-node.ts +2 -2
  767. package/src/plugins/marketplace/deactivation.ts +22 -0
  768. package/src/plugins/marketplace/model.ts +2 -2
  769. package/src/plugins/marketplace/packs.ts +1 -1
  770. package/src/plugins/ownership.ts +1 -1
  771. package/src/plugins/peer-retry.ts +155 -0
  772. package/src/plugins/registry/contributions.ts +3 -1
  773. package/src/plugins/registry/index.ts +4 -3
  774. package/src/plugins/registry/plugin-state.ts +3 -0
  775. package/src/plugins/seed-notice.ts +20 -0
  776. package/src/plugins/shortcut-argument.ts +39 -0
  777. package/src/portfolio/allocation.ts +1 -1
  778. package/src/portfolio/collection-name.ts +31 -0
  779. package/src/portfolio/column-values.ts +10 -8
  780. package/src/portfolio/cost-fx.ts +296 -19
  781. package/src/portfolio/manual-lots.ts +75 -0
  782. package/src/portfolio/mutations.ts +60 -22
  783. package/src/portfolio/suffixed-listings.ts +209 -0
  784. package/src/portfolio/totals-currency.ts +24 -0
  785. package/src/public/__snapshots__/public-api.test.ts.snap +14 -0
  786. package/src/public/time-series.ts +11 -8
  787. package/src/remote/schema.ts +1 -1
  788. package/src/renderers/browser/config-host.ts +3 -0
  789. package/src/renderers/browser/main.tsx +23 -3
  790. package/src/renderers/dom/boot-frame-page.ts +187 -0
  791. package/src/renderers/dom/boot-frame.ts +237 -0
  792. package/src/renderers/dom/build-assets.ts +46 -8
  793. package/src/renderers/dom/desktop/controls.tsx +1 -1
  794. package/src/renderers/dom/desktop/query-bar.tsx +2 -0
  795. package/src/renderers/dom/dom-ui-host.tsx +4 -29
  796. package/src/renderers/dom/host/ascii-text.ts +16 -2
  797. package/src/renderers/dom/host/box.tsx +43 -2
  798. package/src/renderers/dom/host/chart-surface.tsx +2 -0
  799. package/src/renderers/dom/host/hls.chunk.ts +3 -0
  800. package/src/renderers/dom/host/image-surface.tsx +49 -0
  801. package/src/renderers/dom/host/media-surface.tsx +24 -18
  802. package/src/renderers/dom/host/mouse.ts +30 -0
  803. package/src/renderers/dom/host/size-observer.ts +1 -1
  804. package/src/renderers/dom/host/style.ts +1 -1
  805. package/src/renderers/dom/host/tabs.tsx +16 -5
  806. package/src/renderers/dom/host/top-surface.ts +127 -0
  807. package/src/renderers/dom/styles.css +115 -0
  808. package/src/renderers/dom/test-utils.tsx +2 -1
  809. package/src/renderers/electrobun/bun/application-menu/index.ts +3 -3
  810. package/src/renderers/electrobun/bun/core-capabilities.ts +9 -1
  811. package/src/renderers/electrobun/bun/desktop/backend-requests.ts +3 -0
  812. package/src/renderers/electrobun/bun/desktop/backend.ts +56 -3
  813. package/src/renderers/electrobun/bun/desktop/detached-windows.ts +1 -1
  814. package/src/renderers/electrobun/bun/desktop/host-requests.ts +9 -3
  815. package/src/renderers/electrobun/bun/desktop/initialization.ts +81 -18
  816. package/src/renderers/electrobun/bun/desktop/update.ts +210 -30
  817. package/src/renderers/electrobun/bun/external-plugins.ts +29 -7
  818. package/src/renderers/electrobun/bun/index.ts +11 -1
  819. package/src/renderers/electrobun/bun/window/boot-frame.ts +67 -0
  820. package/src/renderers/electrobun/bun/window/create.ts +4 -1
  821. package/src/renderers/electrobun/shared/boot-frame.ts +7 -0
  822. package/src/renderers/electrobun/shared/protocol.ts +6 -1
  823. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -1
  824. package/src/renderers/electrobun/view/config-host.ts +3 -0
  825. package/src/renderers/electrobun/view/fatal-screen.tsx +1 -1
  826. package/src/renderers/electrobun/view/main.tsx +30 -4
  827. package/src/renderers/electrobun/view/news-source.ts +9 -7
  828. package/src/renderers/electrobun/view/remote/asset-data-client.ts +9 -2
  829. package/src/renderers/electrobun/view/ui-host.tsx +1 -1
  830. package/src/renderers/opentui/browser-open.ts +88 -0
  831. package/src/renderers/opentui/host.tsx +5 -14
  832. package/src/renderers/opentui/image/loader.ts +3 -2
  833. package/src/renderers/opentui/native-smoke.ts +10 -1
  834. package/src/renderers/opentui/pane-image/index.ts +2 -1
  835. package/src/renderers/opentui/render-lib.ts +253 -0
  836. package/src/renderers/opentui/render-timing-smoke.ts +1 -1
  837. package/src/renderers/opentui/start.tsx +8 -1
  838. package/src/renderers/share/layout-view.tsx +2 -2
  839. package/src/renderers/share/main.tsx +4 -4
  840. package/src/renderers/share/view.tsx +1 -1
  841. package/src/series/resolve.ts +22 -6
  842. package/src/series/standalone.ts +30 -2
  843. package/src/series/types.ts +8 -1
  844. package/src/shares/api.ts +4 -1
  845. package/src/shares/pane.ts +1 -1
  846. package/src/sources/gloomberb-cloud/fred-series.ts +46 -1
  847. package/src/sources/gloomberb-cloud/index.ts +39 -16
  848. package/src/sources/gloomberb-cloud/news.ts +39 -0
  849. package/src/sources/gloomberb-cloud/normalizers.ts +14 -1
  850. package/src/sources/gloomberb-cloud/perpetuals.ts +49 -1
  851. package/src/sources/listing-symbols.ts +37 -0
  852. package/src/sources/provider-errors.ts +16 -2
  853. package/src/sources/provider-router/financial-routes.ts +21 -3
  854. package/src/sources/provider-router/financials.ts +15 -2
  855. package/src/sources/provider-router/history.ts +30 -8
  856. package/src/sources/provider-router/index.ts +16 -0
  857. package/src/sources/provider-router/primary.ts +1 -1
  858. package/src/state/app/bootstrap.ts +23 -0
  859. package/src/state/hooks/inline-tickers.ts +30 -5
  860. package/src/sync/controller.ts +1 -1
  861. package/src/sync/core-contributors.ts +46 -14
  862. package/src/team/collections-sync.ts +2 -1
  863. package/src/team/collections.ts +178 -17
  864. package/src/telemetry/crash-reports.ts +21 -4
  865. package/src/telemetry/update-failures-node.ts +59 -0
  866. package/src/telemetry/update-failures.ts +307 -0
  867. package/src/test-support/builtin-panes.ts +1 -1
  868. package/src/theme/colors.ts +3 -30
  869. package/src/theme/ui-tokens.ts +37 -1
  870. package/src/tickers/company-name-listing.ts +215 -36
  871. package/src/tickers/company-name.ts +43 -0
  872. package/src/tickers/index-roots.ts +47 -0
  873. package/src/tickers/instrument-label.ts +5 -1
  874. package/src/tickers/listing-fate.ts +30 -0
  875. package/src/tickers/metadata.ts +10 -0
  876. package/src/tickers/perpetual-listing.ts +5 -2
  877. package/src/tickers/search/home-listing.ts +75 -0
  878. package/src/tickers/search/index.ts +76 -27
  879. package/src/tickers/search/ranking.ts +54 -4
  880. package/src/tickers/search/upsert.ts +29 -0
  881. package/src/tickers/terminal-codes.ts +416 -40
  882. package/src/time-series/bar-aggregation.ts +243 -0
  883. package/src/time-series/base-series.ts +307 -0
  884. package/src/time-series/chart-bounds.ts +349 -0
  885. package/src/time-series/chart-seed.ts +142 -0
  886. package/src/time-series/economic.ts +5 -0
  887. package/src/time-series/fundamentals.ts +18 -3
  888. package/src/time-series/history-acquisition.ts +499 -0
  889. package/src/time-series/leverage.ts +100 -0
  890. package/src/time-series/market-resolution.ts +4 -2
  891. package/src/time-series/market.ts +2 -1
  892. package/src/time-series/price-comparison.ts +175 -30
  893. package/src/time-series/range.ts +9 -1
  894. package/src/time-series/resolution.ts +174 -16
  895. package/src/time-series/resolve-context.ts +157 -0
  896. package/src/time-series/resolve.ts +126 -1577
  897. package/src/time-series/series-name.ts +38 -0
  898. package/src/time-series/series-presentation.ts +208 -0
  899. package/src/time-series/spec.ts +11 -6
  900. package/src/time-series/studies.ts +109 -12
  901. package/src/time-series/tail-reconcile.ts +121 -0
  902. package/src/time-series/types.ts +29 -2
  903. package/src/time-series/use-chart-resolution.ts +4 -5
  904. package/src/types/broker.ts +10 -0
  905. package/src/types/config.ts +14 -0
  906. package/src/types/data-provider.ts +20 -0
  907. package/src/types/exchange-rate.ts +6 -0
  908. package/src/types/financials.ts +104 -7
  909. package/src/types/headless.ts +83 -6
  910. package/src/types/instrument.ts +3 -0
  911. package/src/types/issuer-listings.ts +28 -0
  912. package/src/types/plugin.ts +65 -8
  913. package/src/types/price-history.ts +2 -0
  914. package/src/types/ticker.ts +13 -0
  915. package/src/types/trading.ts +59 -1
  916. package/src/ui/ascii-font.ts +13 -1
  917. package/src/ui/host.tsx +6 -1
  918. package/src/updater/command-names.ts +175 -0
  919. package/src/updater/failure.ts +163 -0
  920. package/src/updater/index.ts +97 -28
  921. package/src/utils/calendar-date.ts +32 -15
  922. package/src/utils/cash-runway.ts +241 -0
  923. package/src/utils/central-banks.ts +42 -0
  924. package/src/utils/cli-output.ts +259 -80
  925. package/src/utils/contract-month.ts +16 -0
  926. package/src/utils/crypto-pair.ts +61 -3
  927. package/src/utils/currency-pair.ts +26 -0
  928. package/src/utils/currency-units.ts +29 -0
  929. package/src/utils/datetime-format.ts +10 -0
  930. package/src/utils/debug-log.ts +1 -1
  931. package/src/utils/decimal-comma.ts +39 -6
  932. package/src/utils/exchange-announcements.ts +71 -0
  933. package/src/utils/exchange-rate-snapshot.ts +10 -0
  934. package/src/utils/financial-statements.ts +83 -4
  935. package/src/utils/format.ts +34 -16
  936. package/src/utils/fundamentals.ts +150 -34
  937. package/src/utils/fx-market-hours.ts +10 -1
  938. package/src/utils/latest-financial-period.ts +8 -0
  939. package/src/utils/linux-libc.ts +54 -0
  940. package/src/utils/market-capitalization.ts +111 -15
  941. package/src/utils/max-drawdown.ts +64 -0
  942. package/src/utils/network-failure.ts +83 -0
  943. package/src/utils/option-expiry.ts +139 -7
  944. package/src/utils/price-earnings.ts +11 -5
  945. package/src/utils/price-history.ts +18 -8
  946. package/src/utils/price-window.ts +131 -0
  947. package/src/utils/release-date.ts +47 -0
  948. package/src/utils/sec.ts +71 -0
  949. package/src/utils/throttled-fetch.ts +11 -4
  950. package/src/utils/trailing-eps.ts +67 -0
  951. package/src/utils/typed-text.ts +21 -0
  952. package/src/utils/utc-time.ts +24 -1
  953. package/src/version.ts +6 -0
  954. package/src/cli/pane-functions/screenshot.ts +0 -2122
@@ -0,0 +1,161 @@
1
+ import { useCallback, useEffect, useMemo, useState } from "react";
2
+ import type { PerpBoardRow } from "../../../api-client/perps";
3
+ import { getServerClockOffsetMs } from "../../../market-data/quotes/clock";
4
+ import { buildQuoteKey, instrumentFromQuoteTarget } from "../../../market-data/selectors";
5
+ import { useLiveQuoteEntries } from "../../../state/hooks/quote-streaming";
6
+ import { perpetualListingOfMarket } from "../../../tickers/perpetual-listing";
7
+ import type { QuoteSubscriptionTarget } from "../../../types/data-provider";
8
+ import type { Quote } from "../../../types/financials";
9
+
10
+ /**
11
+ * Perpetual markets move with the app's quote stream like any listing: a
12
+ * market is quoted as the listing it is saved under (`BTC:HYPERLIQUID`), and
13
+ * its quote carries the market's whole row, live while Gloom Cloud follows the
14
+ * venue's own socket. A view that fetched rows (PERP's board, DES, a list's
15
+ * perpetual columns) lays each quote's newer row over its own, so the one
16
+ * subscription a watchlist row already holds feeds every view of the market.
17
+ */
18
+
19
+ type Surface = NonNullable<QuoteSubscriptionTarget["surface"]>;
20
+
21
+ /** The quote target of a market, as its saved listing is quoted; null for a venue the app has no listing for. */
22
+ function perpQuoteTarget(marketId: string, surface: Surface = "detail", visible = true): QuoteSubscriptionTarget | null {
23
+ const listing = perpetualListingOfMarket(marketId);
24
+ return listing ? { symbol: listing.key, exchange: listing.exchange, route: "auto", surface, ...(visible ? { visible: true } : {}) } : null;
25
+ }
26
+
27
+ /** A fetched row with the newer reading a quote of its market brought; the row itself when the quote has none newer. */
28
+ export function withLiveReading(row: PerpBoardRow, quote: Quote | null | undefined): PerpBoardRow {
29
+ const live = quote?.perpetual;
30
+ if (!live || live.marketId.toLowerCase() !== row.marketId.toLowerCase()) return row;
31
+ return Date.parse(live.observedAt) > Date.parse(row.observedAt) ? { ...row, ...live, marketId: row.marketId } : row;
32
+ }
33
+
34
+ /** As Gloom Cloud rules it: a market with no mark from its venue for a minute is no longer live. */
35
+ const LIVE_FOR_MS = 60_000;
36
+
37
+ /**
38
+ * Whether a quote of a market is fed by its venue's socket now, not the minute's read: streamed live, not stale, and
39
+ * marked within the last minute by the server's clock. The server says so too, but a socket that went quiet says
40
+ * nothing, so the app keeps its own count.
41
+ */
42
+ export function isLivePerpQuote(quote: Quote | null | undefined, now = Date.now() + (getServerClockOffsetMs() ?? 0)): boolean {
43
+ return quote?.dataSource === "live" && quote.stale !== true && Number.isFinite(quote.lastUpdated)
44
+ && now - quote.lastUpdated <= LIVE_FOR_MS;
45
+ }
46
+
47
+ /**
48
+ * How long until a view looks again at whether its live rows still are: when the first one's minute runs out, a beat
49
+ * after, never more than a minute away. A reading stamped ahead of the clock would otherwise ask for a timer past what
50
+ * `setTimeout` holds, which fires at once, again and again.
51
+ */
52
+ export function liveRecheckMs(expiresInMs: number): number {
53
+ return Math.min(LIVE_FOR_MS, Math.max(0, expiresInMs) + 250);
54
+ }
55
+
56
+ /** The most rows one view follows on the quote stream at once: as many as the app streams (the socket's budget). */
57
+ const LIVE_ROW_LIMIT = 60;
58
+
59
+ /** The markets of the rows a view follows on the quote stream: the first LIVE_ROW_LIMIT. */
60
+ export const followedPerpMarkets = (rows: readonly Pick<PerpBoardRow, "marketId">[]) =>
61
+ rows.slice(0, LIVE_ROW_LIMIT).map((row) => row.marketId);
62
+
63
+ export interface LivePerpRows {
64
+ rows: readonly PerpBoardRow[];
65
+ /** Every row followed is fed by its venue's socket now: the view may call itself live. */
66
+ live: boolean;
67
+ /** The markets fed by their venue's socket; the others are the minute's read. */
68
+ liveMarkets: ReadonlySet<string>;
69
+ }
70
+
71
+ const NONE: ReadonlySet<string> = new Set();
72
+
73
+ /**
74
+ * Fetched rows with the quote stream's newer readings laid over them. Only the
75
+ * first LIVE_ROW_LIMIT rows are subscribed: a list passes the rows in view.
76
+ */
77
+ export function useLivePerpRows(
78
+ rows: readonly PerpBoardRow[],
79
+ { surface = "detail", visible = true, enabled = true }: { surface?: Surface; visible?: boolean; enabled?: boolean } = {},
80
+ ): LivePerpRows {
81
+ const marketKey = enabled ? followedPerpMarkets(rows).join("\u001f") : "";
82
+ const targets = useMemo(() => marketKey ? marketKey.split("\u001f").flatMap((marketId) => {
83
+ const target = perpQuoteTarget(marketId, surface, visible);
84
+ return target ? [target] : [];
85
+ }) : [], [marketKey, surface, visible]);
86
+ const { entries } = useLiveQuoteEntries(targets);
87
+ // Ticks when a live row's minute runs out, so a feed that went quiet stops calling itself live.
88
+ const [expired, setExpired] = useState(0);
89
+ const result = useMemo(() => {
90
+ if (!targets.length) return { rows, live: false, liveMarkets: NONE, expiresAt: null };
91
+ const now = Date.now() + (getServerClockOffsetMs() ?? 0);
92
+ const followed = new Set(targets.map((target) => buildQuoteKey(instrumentFromQuoteTarget(target))));
93
+ const followedMarkets = new Set(marketKey.split("\u001f").filter((marketId) => perpQuoteTarget(marketId, surface, visible)));
94
+ const quoteOf = (marketId: string) => {
95
+ const target = perpQuoteTarget(marketId, surface, visible);
96
+ if (!target) return null;
97
+ const key = buildQuoteKey(instrumentFromQuoteTarget(target));
98
+ const entry = followed.has(key) ? entries.get(key) : undefined;
99
+ return entry?.data ?? entry?.lastGoodData ?? null;
100
+ };
101
+ const liveMarkets = new Set<string>();
102
+ let expiresAt: number | null = null;
103
+ const next = rows.map((row) => {
104
+ const quote = quoteOf(row.marketId);
105
+ if (quote && isLivePerpQuote(quote, now)) {
106
+ liveMarkets.add(row.marketId);
107
+ expiresAt = Math.min(expiresAt ?? Number.POSITIVE_INFINITY, quote.lastUpdated + LIVE_FOR_MS - now);
108
+ }
109
+ return withLiveReading(row, quote);
110
+ });
111
+ const live = [...followedMarkets].every((marketId) => liveMarkets.has(marketId));
112
+ return { rows: next, live, liveMarkets, expiresAt };
113
+ // `expired` is the clock that ends a quiet row's minute.
114
+ }, [entries, expired, marketKey, rows, surface, targets, visible]);
115
+ useEffect(() => {
116
+ if (result.expiresAt == null) return;
117
+ const timer = setTimeout(() => setExpired((count) => count + 1), liveRecheckMs(result.expiresAt));
118
+ return () => clearTimeout(timer);
119
+ }, [result]);
120
+ return result;
121
+ }
122
+
123
+ interface Range { start: number; end: number }
124
+ /** Rows a table starts with before it measures its viewport: a pane's height of them. */
125
+ const FIRST_VIEW: Range = { start: 0, end: 40 };
126
+
127
+ /**
128
+ * A table's rows with the quote stream laid over the ones in view: pass
129
+ * `onVisibleRangeChange` to the table, and its rows in view (and the row
130
+ * `pinned`, the one other tabs follow) are subscribed as it scrolls.
131
+ */
132
+ export function useLivePerpTableRows<T>(
133
+ items: readonly T[],
134
+ rowOf: (item: T) => PerpBoardRow | null,
135
+ withRow: (item: T, row: PerpBoardRow) => T,
136
+ { surface = "screener", pinned = null }: { surface?: Surface; pinned?: PerpBoardRow | null } = {},
137
+ ): { items: T[]; live: boolean; onVisibleRangeChange: (range: Range) => void } {
138
+ const [range, setRange] = useState<Range>(FIRST_VIEW);
139
+ const onVisibleRangeChange = useCallback((next: Range) => {
140
+ setRange((current) => current.start === next.start && current.end === next.end ? current : next);
141
+ }, []);
142
+ const inView = useMemo(() => {
143
+ const rows = new Map<string, PerpBoardRow>();
144
+ if (pinned) rows.set(pinned.marketId, pinned);
145
+ for (const item of items.slice(range.start, range.end)) {
146
+ const row = rowOf(item);
147
+ if (row) rows.set(row.marketId, row);
148
+ }
149
+ return [...rows.values()];
150
+ }, [items, pinned, range, rowOf]);
151
+ const live = useLivePerpRows(inView, { surface });
152
+ const next = useMemo(() => {
153
+ const byMarket = new Map(live.rows.map((row) => [row.marketId, row]));
154
+ return items.map((item) => {
155
+ const row = rowOf(item);
156
+ const fresh = row ? byMarket.get(row.marketId) : undefined;
157
+ return row && fresh && fresh !== row ? withRow(item, fresh) : item;
158
+ });
159
+ }, [items, live.rows, rowOf, withRow]);
160
+ return { items: next, live: live.live, onVisibleRangeChange };
161
+ }
@@ -0,0 +1,154 @@
1
+ import { useCallback, useMemo, useState } from "react";
2
+ import { isAccessDenied } from "../../../api-client/errors";
3
+ import { usePlanAccess } from "../../../api-client/plan-access";
4
+ import type { PerpBoardRow } from "../../../api-client/perps";
5
+ import { ActionRow, ColumnGrid, SectionHeading, type ColumnGridCell, type ColumnGridColumn } from "../../../components";
6
+ import { t, tf } from "../../../i18n";
7
+ import { useAsyncResource, useAutoRefresh } from "../../../public/react";
8
+ import { useThemeColors } from "../../../theme/theme-context";
9
+ import { perpetualListing } from "../../../tickers/perpetual-listing";
10
+ import { premiumToUnderlying } from "../../../market-data/perpetual-premium";
11
+ import type { Quote } from "../../../types/financials";
12
+ import { Box, Text } from "../../../ui";
13
+ import { isRoundTheClockCoin } from "../../../utils/crypto-pair";
14
+ import { displayWidth } from "../../../utils/format";
15
+ import { parsePublicTickerKey } from "../../../utils/exchanges";
16
+ import { useResearchCloudSession } from "../shared/research-cloud-session";
17
+ import { loadPerpsCompare, loadPerpsEquity } from "./client";
18
+ import { perpEquityIdentity } from "./equity-identity";
19
+ import { useLivePerpRows } from "./live-rows";
20
+ import { compact, perpCell, percent, price, readingTime, venueName } from "./model";
21
+ import { useOpenPerpMarket } from "./open-market";
22
+ import { contractText, foldSmallMarkets, mergeVenueMarkets, type PerpVenueMarket } from "./venue-markets";
23
+
24
+ const STOCK_TYPES = new Set(["STK", "EQUITY", "STOCK", "COMMON STOCK"]);
25
+ const COIN_PAIR = /^([A-Z0-9]{1,15})[-/](?:USD|USDT|USDC)$/;
26
+
27
+ /** What an asset's contracts are looked up by: a stock's listing, or a coin's base asset. Null for a contract itself. */
28
+ function perpAsset(symbol: string, exchange: string | undefined, instrumentType: string | undefined):
29
+ { kind: "stock"; symbol: string; exchange: string } | { kind: "coin"; base: string } | null {
30
+ if (perpetualListing(symbol, exchange)) return null;
31
+ if (isRoundTheClockCoin(symbol, exchange)) {
32
+ const bare = parsePublicTickerKey(symbol).symbol.toUpperCase();
33
+ const base = COIN_PAIR.exec(bare)?.[1] ?? (/^[A-Z0-9]{1,15}$/.test(bare) ? bare : null);
34
+ return base ? { kind: "coin", base } : null;
35
+ }
36
+ const identity = perpEquityIdentity(symbol, exchange);
37
+ return identity && STOCK_TYPES.has(instrumentType?.toUpperCase() ?? "") ? { kind: "stock", ...identity } : null;
38
+ }
39
+
40
+ const VENUE_MIN_WIDTH = 11;
41
+ /** Coinbase International, in full. */
42
+ const VENUE_MAX_WIDTH = 22;
43
+
44
+ interface Column { id: "venue" | "contract" | "mark" | "premium" | "funding" | "oi" | "volume"; label: string; width: number; align?: "right" }
45
+ // Reading order.
46
+ const COLUMNS: readonly Column[] = [
47
+ { id: "venue", label: "Venue", width: VENUE_MIN_WIDTH },
48
+ { id: "contract", label: "Contract", width: 11 },
49
+ { id: "mark", label: "Mark", width: 11, align: "right" },
50
+ { id: "premium", label: "Premium", width: 8, align: "right" },
51
+ { id: "funding", label: "Fund 8h", width: 9, align: "right" },
52
+ { id: "oi", label: "OI USD", width: 7, align: "right" },
53
+ { id: "volume", label: "Vol 24h", width: 7, align: "right" },
54
+ ];
55
+ /** What a narrow overview gives up first: the volume, then the contract, then the premium. */
56
+ const DROP_ORDER: readonly Column["id"][] = ["volume", "contract", "premium"];
57
+ const GAP = 2;
58
+ const span = (columns: readonly Column[]) => columns.reduce((sum, column) => sum + column.width, 0) + GAP * (columns.length - 1);
59
+
60
+ function fitColumns(width: number, venueWidth: number): Column[] {
61
+ let columns = COLUMNS.map((column) => column.id === "venue" ? { ...column, width: venueWidth } : column);
62
+ for (const id of DROP_ORDER) {
63
+ if (span(columns) <= width) break;
64
+ columns = columns.filter((column) => column.id !== id);
65
+ }
66
+ return columns;
67
+ }
68
+
69
+ /**
70
+ * DES's perpetual markets on a stock or a coin: one row per venue (its contracts merged, `venue-markets.ts`), the
71
+ * deepest first, markets with a small share of the open interest folded behind a row that shows them. A row opens
72
+ * PERP on its venue's deepest contract. The figures move with the quote stream while the venue streams; otherwise
73
+ * the section says when the venues were read.
74
+ */
75
+ export function PerpMarketsSection({ symbol, exchange, instrumentType, quote, width }: {
76
+ symbol: string;
77
+ exchange?: string;
78
+ instrumentType?: string;
79
+ /** The security's own quote: a stock's contracts are priced against it while it streams. */
80
+ quote?: Quote | null;
81
+ width: number;
82
+ }) {
83
+ const colors = useThemeColors();
84
+ const session = useResearchCloudSession();
85
+ const plan = usePlanAccess();
86
+ const openMarket = useOpenPerpMarket();
87
+ const [expanded, setExpanded] = useState(false);
88
+ const asset = perpAsset(symbol, exchange, instrumentType);
89
+ const assetKey = asset ? asset.kind === "coin" ? `coin:${asset.base}` : `stock:${asset.symbol}:${asset.exchange}` : null;
90
+ const accessKey = `${session.requestKey}:${plan.hasProAccess ? "pro" : "preview"}`;
91
+ const loader = useCallback((force: boolean) => {
92
+ const [kind, first, second] = assetKey!.split(":");
93
+ return kind === "coin" ? loadPerpsCompare(first!, accessKey, force) : loadPerpsEquity({ symbol: first!, exchange: second! }, accessKey, force);
94
+ }, [assetKey, accessKey]);
95
+ const resource = useAsyncResource(assetKey ? loader : null, { clearOnError: isAccessDenied });
96
+ useAutoRefresh(resource.updatedAt, resource.load, { intervalMs: 60_000 });
97
+ const markets = useMemo(() => mergeVenueMarkets(resource.data?.payload.rows ?? []), [resource.data]);
98
+ const { shown, folded } = useMemo(() => foldSmallMarkets(markets), [markets]);
99
+ const visible = expanded ? markets : shown;
100
+ // The deepest contract of each row on screen follows the quote stream.
101
+ const primaries = useMemo(() => visible.map((market) => market.primary), [visible]);
102
+ const live = useLivePerpRows(primaries, { surface: "detail" });
103
+ if (!assetKey || !markets.length) return null;
104
+ const rows = visible.map((market, index) => ({ market, row: live.rows[index] ?? market.primary }));
105
+ const premiumLabel = asset?.kind === "stock" ? "vs last" : "Premium";
106
+ // The venue column fits the longest venue name shown.
107
+ const venueWidth = Math.min(VENUE_MAX_WIDTH, Math.max(VENUE_MIN_WIDTH, ...visible.map((market) => displayWidth(venueName(market.venue)))));
108
+ const columns = fitColumns(width, venueWidth);
109
+ const fixed = span(columns);
110
+ // The last text column takes what the figures leave, on the desktop too, so a venue keeps its contract beside it.
111
+ const growing = columns.some((column) => column.id === "contract") ? "contract" : "venue";
112
+ const gridColumns: ColumnGridColumn[] = columns.map((column, index) => ({
113
+ width: column.id === growing ? Math.max(column.width, width - fixed + column.width) : column.width,
114
+ align: column.align,
115
+ gapBefore: index > 0 ? GAP : 0,
116
+ ...(column.id === growing ? { growFrom: column.width } : {}),
117
+ }));
118
+ const header: ColumnGridCell[] = columns.map((column) => ({ text: t(column.id === "premium" ? premiumLabel : column.label), color: colors.textDim }));
119
+ const cell = (column: Column, market: PerpVenueMarket, row: PerpBoardRow): ColumnGridCell => {
120
+ if (column.id === "venue") {
121
+ return { text: venueName(market.venue), color: row.stale ? colors.textDim : colors.textBright,
122
+ link: { label: tf("Open {label}", { label: `PERP ${row.baseAsset} · ${venueName(market.venue)}` }), onOpen: () => openMarket(row.marketId) } };
123
+ }
124
+ if (column.id === "contract") return { text: contractText(market), color: colors.textMuted };
125
+ if (column.id === "mark") return { text: price(row.markPrice ?? row.lastPrice), color: colors.text };
126
+ if (column.id === "premium") {
127
+ const value = asset?.kind === "stock" ? premiumToUnderlying(row, quote) : row.premium;
128
+ return { text: percent(value, 2), color: value == null || value === 0 ? colors.textMuted : value > 0 ? colors.positive : colors.negative };
129
+ }
130
+ if (column.id === "funding") {
131
+ const { text, color } = perpCell(row, "fundingRate8h", colors);
132
+ return { text, color };
133
+ }
134
+ // A venue's open interest and volume are its contracts' together.
135
+ return { text: compact(column.id === "oi" ? market.openInterestUsd : market.volume24hUsd), color: colors.text };
136
+ };
137
+ const body = rows.map(({ market, row }) => columns.map((column) => cell(column, market, row)));
138
+ // Streaming figures date themselves; the rows from the minute's read say when it was.
139
+ const read = rows.filter(({ row }) => !live.liveMarkets.has(row.marketId));
140
+ const readAt = read.length ? readingTime(read.reduce<string | null>((latest, { row }) => !latest || row.observedAt > latest ? row.observedAt : latest, null)) : null;
141
+ const stale = read.length > 0 && (!!resource.data?.stale || read.some(({ row }) => row.stale));
142
+ return (
143
+ <Box flexDirection="column" data-gloom-role="perp-markets">
144
+ <SectionHeading title={t("Perpetuals")} />
145
+ <ColumnGrid columns={gridColumns} rows={[header, ...body]} width={width} role="perp-markets-grid" />
146
+ {folded.length > 0 && (
147
+ <ActionRow expanded={expanded} width={width}
148
+ label={expanded ? t("Fewer markets") : folded.length === 1 ? t("1 smaller market") : tf("{count} smaller markets", { count: String(folded.length) })}
149
+ onPress={() => setExpanded((value) => !value)} />
150
+ )}
151
+ {readAt && <Text fg={stale ? colors.warning : colors.textDim}>{`${read.length < rows.length ? `${[...new Set(read.map(({ market }) => venueName(market.venue)))].join(", ")} ` : ""}${t("as of")} ${readAt}${stale ? ` · ${t("stale")}` : ""}`}</Text>}
152
+ </Box>
153
+ );
154
+ }
@@ -25,21 +25,27 @@ import { isCloudSessionRequired, useResearchCloudSession } from "../shared/resea
25
25
  import { boardSearchText, cachedPerpSelection, cachedPerpsBoard, cachedPerpsCompare, cachedPerpsRankings, loadPerpSelection, loadPerpsBoard, loadPerpsCompare,
26
26
  loadPerpsHistoryForRange, loadPerpsRankings } from "./client";
27
27
  import { perpFreshness, perpObservation } from "./freshness";
28
+ import { useLivePerpRows, useLivePerpTableRows } from "./live-rows";
28
29
  import { aprFormula, ASSET_FILTERS, BOARD_SORTS, boardColumns, collectedVenueCount, compact, compareColumns, dayChange, fundingInterval, fundingSpread, historyCaption, historyCell, historyChange, historyColumns,
29
30
  HISTORY_METRICS, historyRows, historySeries, historyValue, inPoints, isMarketTab, label, longShortReason, marketLabel, metricLabel, openingTab, perpCell, perpHistoryProText, PERP_TABS, percent,
30
31
  positioningFields, price, rankingColumns, rankingRows, RANKINGS, ratioText, share, sortLabel, time, underlyingTicker, venueLabel, venueName, venuesOf, type HistoryMetric,
31
32
  type PerpColumnId, type PerpTab, type RankingKey } from "./model";
32
33
 
33
34
  const NO_ROWS: readonly PerpBoardRow[] = [];
35
+ const rowItself = (row: PerpBoardRow) => row;
36
+ const replaced = (_: PerpBoardRow, row: PerpBoardRow) => row;
37
+ const rankedRow = (entry: SectionedRow<Ranked>) => entry.kind === "item" ? entry.item.row : null;
38
+ const withRankedRow = (entry: SectionedRow<Ranked>, row: PerpBoardRow): SectionedRow<Ranked> =>
39
+ entry.kind === "item" ? { ...entry, item: { ...entry.item, row } } : entry;
34
40
 
35
41
  /**
36
42
  * The footer line for the rows on screen: each venue's own observation time, how old the oldest is, the delay state
37
43
  * and what the funding figures are, the line `fn PERP` and `shot PERP` end with. It keeps ageing while the pane is
38
44
  * seen (`since` is when the rows loaded), and is published for a capture to date itself the same way.
39
45
  */
40
- function usePerpStatusLine(rows: readonly PerpBoardRow[], funding: readonly PerpBoardRow[], since: number | null): PaneFooterSegment[] {
46
+ function usePerpStatusLine(rows: readonly PerpBoardRow[], funding: readonly PerpBoardRow[], since: number | null, live = false): PaneFooterSegment[] {
41
47
  const now = useAgeClock(since);
42
- const declared = useMemo(() => rows.length ? perpFreshness(rows, { funding, now }) : null, [rows, funding, now]);
48
+ const declared = useMemo(() => rows.length ? perpFreshness(rows, { funding, now, live }) : null, [rows, funding, now, live]);
43
49
  useRemoteUiNode(declared ? { role: "report-freshness", label: "Venue freshness", getMetadata: () => ({ ...declared, observations: rows.map(perpObservation) }) } : null);
44
50
  return useMemo(() => {
45
51
  if (!declared) return [];
@@ -104,8 +110,8 @@ function MarketEvidence({ row, data, width, height }: { row: PerpBoardRow; data:
104
110
  </ScrollBox>;
105
111
  }
106
112
 
107
- function History({ market, venues, accessKey, width, height, focused, range, metric, refreshMarket }: {
108
- market: PerpBoardRow; venues: number | null; accessKey: string; width: number; height: number; focused: boolean; range: string; metric: HistoryMetric; refreshMarket: () => void;
113
+ function History({ market, live = false, venues, accessKey, width, height, focused, range, metric, refreshMarket }: {
114
+ market: PerpBoardRow; live?: boolean; venues: number | null; accessKey: string; width: number; height: number; focused: boolean; range: string; metric: HistoryMetric; refreshMarket: () => void;
109
115
  }) {
110
116
  const colors = useThemeColors();
111
117
  const loader = useCallback((force: boolean) => loadPerpsHistoryForRange(market.marketId, Number(range), accessKey, force), [market.marketId, range, accessKey]);
@@ -125,7 +131,7 @@ function History({ market, venues, accessKey, width, height, focused, range, met
125
131
  const lastTime = rows.at(-1)?.time;
126
132
  // The market row dates the figures beside the series. Its funding is the current estimate, unless the series is paid funding.
127
133
  const marketRows = useMemo(() => [market], [market]);
128
- const line = usePerpStatusLine(marketRows, metric === "funding" && !data?.funding.length ? marketRows : NO_ROWS, resource.updatedAt);
134
+ const line = usePerpStatusLine(marketRows, metric === "funding" && !data?.funding.length ? marketRows : NO_ROWS, resource.updatedAt, live);
129
135
  usePaneStatusFooter({ registrationId: "perps:history", loading: resource.loading, error: resource.error, stale: resource.data?.stale,
130
136
  info: [...line, ...(lastTime ? [{ id: "history-asof", parts: [{ text: `${historyCaption(data, metric)} · ${time(lastTime)}`, tone: "muted" as const }] }] : [])] });
131
137
  usePaneNoticeFooter({ registrationId: "perps:history-notice", focused, notices: [resource.data?.refreshError,
@@ -221,14 +227,18 @@ function Board({ accessKey, width, height, focused, onPick, onOpen }: ListTabPro
221
227
  const resource = useAsyncResource(loader, { initialData: () => cachedPerpsBoard(query, accessKey), clearOnError: isAccessDenied, keepPreviousData: true });
222
228
  const data = resource.data?.payload;
223
229
  const venues = useMemo(() => venuesOf(data?.rows ?? []), [data]);
224
- const rows = useMemo(() => venue === "all" ? data?.rows ?? [] : (data?.rows ?? []).filter((row) => row.venue === venue), [data, venue]);
230
+ const fetchedRows = useMemo(() => venue === "all" ? data?.rows ?? [] : (data?.rows ?? []).filter((row) => row.venue === venue), [data, venue]);
231
+ // The rows in view move with the quote stream.
232
+ const pinned = fetchedRows.find((row) => row.marketId === selectedId) ?? null;
233
+ const stream = useLivePerpTableRows(fetchedRows, rowItself, replaced, { pinned });
234
+ const rows = stream.items;
225
235
  const selected = rows.find((row) => row.marketId === selectedId) ?? rows[0] ?? null;
226
236
  useEffect(() => { onPick(selected); }, [selected, onPick]);
227
237
  const columns = useMemo(() => boardColumns(width), [width]);
228
238
  const locked = typeof data?.locked === "number" ? data.locked : 0;
229
239
  useAutoRefresh(resource.updatedAt, resource.load, { intervalMs: 60_000 });
230
240
  usePaneRefreshKey(() => { void resource.reload(); }, { focused: focused && !querySearch.active });
231
- const line = usePerpStatusLine(rows, rows, resource.updatedAt);
241
+ const line = usePerpStatusLine(rows, rows, resource.updatedAt, stream.live);
232
242
  usePaneStatusFooter({ registrationId: "perps:board", loading: resource.loading, error: data ? resource.error : null, stale: resource.data?.stale, info: line });
233
243
  usePaneNoticeFooter({ registrationId: "perps:board-notice", focused, notices: resource.data?.refreshError ? [resource.data.refreshError] : [] });
234
244
  if (!data && isCloudSessionRequired(resource.error)) return <SignInWall placement="perps-signin" action="view perpetual markets" needsVerification={session.needsVerification} />;
@@ -244,7 +254,7 @@ function Board({ accessKey, width, height, focused, onPick, onOpen }: ListTabPro
244
254
  ]} />;
245
255
  return <PaneStatusBody loading={!data && resource.loading} error={!data ? resource.error : null} subject="perpetual markets">
246
256
  <DataTableView<PerpBoardRow> columns={columns} items={rows} focused={focused && !querySearch.active} rootWidth={width} rootHeight={height} rootBefore={queryBar}
247
- getItemKey={(row) => row.marketId} sortColumnId={null} sortDirection="desc" resetScrollKey={`${JSON.stringify(query)}:${venue}`}
257
+ getItemKey={(row) => row.marketId} sortColumnId={null} sortDirection="desc" resetScrollKey={`${JSON.stringify(query)}:${venue}`} onVisibleRangeChange={stream.onVisibleRangeChange}
248
258
  selection={{ kind: "id", selectedId: selected?.marketId ?? null, getId: (row) => row.marketId, onChange: setSelected }} onActivate={(row) => onOpen(row)}
249
259
  renderCell={(row, column) => perpCell(row, column.id as PerpColumnId, colors)} selectedTextOverridesCellColor
250
260
  emptyStateTitle="No markets match these filters." emptyContent={lock && <Box paddingX={1} paddingY={1}><EmptyState title="No preview markets match." actions={<UpgradeLabel text={moreText(locked, "markets")} onPress={upgrade} role="perps-board-upgrade" />} /></Box>}
@@ -264,8 +274,11 @@ function Rankings({ accessKey, width, height, focused, onPick, onOpen }: ListTab
264
274
  const loader = useCallback((force: boolean) => loadPerpsRankings(accessKey, force), [accessKey]);
265
275
  const resource = useAsyncResource(loader, { initialData: () => cachedPerpsRankings(accessKey), clearOnError: isAccessDenied });
266
276
  const data = resource.data?.payload;
267
- const items = useMemo(() => buildSectionedRows(RANKINGS.map((ranking) => ({ label: ranking.label, emptyLabel: "None",
277
+ const fetchedItems = useMemo(() => buildSectionedRows(RANKINGS.map((ranking) => ({ label: ranking.label, emptyLabel: "None",
268
278
  items: rankingRows(data, ranking.key).map((row): Ranked => ({ list: ranking.key, row })) })), rankedKey), [data]);
279
+ // The ranked rows in view move with the quote stream; the lists keep the order they were ranked in.
280
+ const stream = useLivePerpTableRows(fetchedItems, rankedRow, withRankedRow);
281
+ const items = stream.items;
269
282
  const ranked = items.flatMap((item) => item.kind === "item" ? [item.item] : []);
270
283
  const selected = ranked.find((item) => rankedKey(item) === selectedId) ?? ranked[0] ?? null;
271
284
  useEffect(() => { onPick(selected?.row ?? null); }, [selected, onPick]);
@@ -275,7 +288,7 @@ function Rankings({ accessKey, width, height, focused, onPick, onOpen }: ListTab
275
288
  // Every ranked row is dated; only the funding lists show funding.
276
289
  const rankedRows = useMemo(() => RANKINGS.flatMap((ranking) => rankingRows(data, ranking.key)), [data]);
277
290
  const fundingRows = useMemo(() => RANKINGS.filter((ranking) => ranking.field === "fundingRate8h").flatMap((ranking) => rankingRows(data, ranking.key)), [data]);
278
- const line = usePerpStatusLine(rankedRows, fundingRows, resource.updatedAt);
291
+ const line = usePerpStatusLine(rankedRows, fundingRows, resource.updatedAt, stream.live);
279
292
  usePaneStatusFooter({ registrationId: "perps:rankings", loading: resource.loading, error: data ? resource.error : null, stale: resource.data?.stale, info: line });
280
293
  usePaneNoticeFooter({ registrationId: "perps:rankings-notice", focused, notices: resource.data?.refreshError ? [resource.data.refreshError] : [] });
281
294
  if (!data && isCloudSessionRequired(resource.error)) return <SignInWall placement="perps-signin" action="view perpetual rankings" needsVerification={session.needsVerification} />;
@@ -290,7 +303,7 @@ function Rankings({ accessKey, width, height, focused, onPick, onOpen }: ListTab
290
303
  };
291
304
  return <PaneStatusBody loading={!data && resource.loading} error={!data ? resource.error : null} subject="perpetual rankings">
292
305
  <DataTableView<SectionedRow<Ranked>> columns={columns} items={items} focused={focused} rootWidth={width} rootHeight={height}
293
- getItemKey={(entry) => entry.key} sortColumnId={null} sortDirection="desc" isNavigable={(entry) => entry.kind === "item"}
306
+ getItemKey={(entry) => entry.key} sortColumnId={null} sortDirection="desc" isNavigable={(entry) => entry.kind === "item"} onVisibleRangeChange={stream.onVisibleRangeChange}
294
307
  renderSectionHeader={(entry) => entry.kind === "section" ? { text: entry.label } : null}
295
308
  selection={{ kind: "id", selectedId: selected ? rankedKey(selected) : null, getId: (entry) => entry.key, onChange: (id) => { if (ranked.some((item) => rankedKey(item) === id)) setSelected(id); } }}
296
309
  onActivate={(entry) => { if (entry.kind === "item") onOpen(entry.item.row); }}
@@ -310,11 +323,13 @@ function Compare({ asset, onAssetChange, accessKey, width, height, focused, onPi
310
323
  const loader = useCallback((force: boolean) => loadPerpsCompare(base, accessKey, force), [base, accessKey]);
311
324
  const resource = useAsyncResource(loader, { initialData: () => cachedPerpsCompare(base, accessKey), clearOnError: isAccessDenied, keepPreviousData: true });
312
325
  const data = resource.data?.payload;
313
- const rows = useMemo(() => data?.rows ?? [], [data]);
326
+ const fetchedRows = useMemo(() => data?.rows ?? [], [data]);
327
+ const stream = useLivePerpTableRows(fetchedRows, rowItself, replaced, { pinned: fetchedRows.find((row) => row.marketId === selectedId) ?? null });
328
+ const rows = stream.items;
314
329
  const selected = rows.find((row) => row.marketId === selectedId) ?? rows[0] ?? null;
315
330
  useEffect(() => { onPick(selected); }, [selected, onPick]);
316
331
  const columns = useMemo(() => compareColumns(width), [width]);
317
- const line = usePerpStatusLine(rows, rows, resource.updatedAt);
332
+ const line = usePerpStatusLine(rows, rows, resource.updatedAt, stream.live);
318
333
  const spread = useMemo(() => fundingSpread(rows), [rows]);
319
334
  const locked = typeof data?.locked === "number" ? data.locked : 0;
320
335
  useAutoRefresh(resource.updatedAt, resource.load, { intervalMs: 60_000 });
@@ -330,7 +345,7 @@ function Compare({ asset, onAssetChange, accessKey, width, height, focused, onPi
330
345
  </>;
331
346
  return <PaneStatusBody loading={!data && resource.loading} error={!data ? resource.error : null} subject="perpetual contracts">
332
347
  <DataTableView<PerpBoardRow> columns={columns} items={rows} focused={focused && !querySearch.active} rootWidth={width} rootHeight={height} rootBefore={before}
333
- getItemKey={(row) => row.marketId} sortColumnId={null} sortDirection="desc" resetScrollKey={base}
348
+ getItemKey={(row) => row.marketId} sortColumnId={null} sortDirection="desc" resetScrollKey={base} onVisibleRangeChange={stream.onVisibleRangeChange}
334
349
  selection={{ kind: "id", selectedId: selected?.marketId ?? null, getId: (row) => row.marketId, onChange: setSelected }} onActivate={(row) => onOpen(row)}
335
350
  renderCell={(row, column) => perpCell(row, column.id as PerpColumnId, colors)} selectedTextOverridesCellColor
336
351
  emptyStateTitle={`No perpetual contracts for ${base}.`} emptyContent={lock && <Box paddingX={1} paddingY={1}><EmptyState title={`No preview contracts for ${base}.`} actions={<UpgradeLabel text={moreText(locked, locked === 1 ? "contract" : "contracts")} onPress={upgrade} role="perps-compare-upgrade" />} /></Box>}
@@ -380,7 +395,11 @@ export function PerpsPane({ width, height, focused, marketAction }: Pick<PanePro
380
395
  const loader = useCallback((force: boolean) => loadPerpSelection(search, accessKey, force), [search, accessKey]);
381
396
  const resource = useAsyncResource(marketTab ? loader : null, { initialData: () => cachedPerpSelection(search, accessKey), clearOnError: isAccessDenied });
382
397
  const data = marketTab ? resource.data?.payload : undefined;
383
- const active = data?.rows[0] ?? null;
398
+ const fetchedActive = data?.rows[0] ?? null;
399
+ // The market on screen moves with the quote stream, as its listing's quote does everywhere else.
400
+ const activeRows = useMemo(() => fetchedActive ? [fetchedActive] : NO_ROWS, [fetchedActive]);
401
+ const activeStream = useLivePerpRows(activeRows, { surface: "detail", enabled: marketTab });
402
+ const active = activeStream.rows[0] ?? null;
384
403
  usePaneTitle(active ? `PERP ${marketLabel(active)} · ${venueLabel(active)}` : "PERP");
385
404
  const querySearch = useQueryBarSearch();
386
405
  // The market on screen goes with the user: History and Evidence open the row picked on a list, Compare its base asset.
@@ -416,7 +435,7 @@ export function PerpsPane({ width, height, focused, marketAction }: Pick<PanePro
416
435
  ];
417
436
  // History dates its own market beside its series; Evidence is dated here.
418
437
  const evidenceRows = useMemo(() => tab === "evidence" && active ? [active] : NO_ROWS, [tab, active]);
419
- const evidenceLine = usePerpStatusLine(evidenceRows, evidenceRows, resource.updatedAt);
438
+ const evidenceLine = usePerpStatusLine(evidenceRows, evidenceRows, resource.updatedAt, activeStream.live);
420
439
  usePaneStatusFooter({ registrationId: "perps", loading: marketTab && resource.loading, error: data ? resource.error : null, stale: marketTab && resource.data?.stale,
421
440
  info: evidenceLine, hints });
422
441
  usePaneNoticeFooter({ registrationId: "perps:notices", focused, notices: marketTab ? [resource.data?.refreshError,
@@ -434,7 +453,7 @@ export function PerpsPane({ width, height, focused, marketAction }: Pick<PanePro
434
453
  { id: "range", label: "Range", value: range, options: ["1", "7", "30", "90", "365"].map((value) => ({ value, label: `${value}D` })), onChange: setRange },
435
454
  ] : []} />
436
455
  <PaneStatusBody loading={!data && resource.loading} error={!data ? resource.error : null} subject="perpetual market">
437
- {active ? tab === "history" ? <History key={active.marketId} market={active} venues={collectedVenueCount(data?.sources)} accessKey={accessKey} width={width} height={listHeight - 1} focused={focused && !querySearch.active} range={range} metric={metric} refreshMarket={refreshMarket} />
456
+ {active ? tab === "history" ? <History key={active.marketId} market={active} live={activeStream.live} venues={collectedVenueCount(data?.sources)} accessKey={accessKey} width={width} height={listHeight - 1} focused={focused && !querySearch.active} range={range} metric={metric} refreshMarket={refreshMarket} />
438
457
  : <MarketEvidence row={active} data={data!} width={width} height={listHeight - 1} />
439
458
  : <EmptyState title="No market matches." message={data?.suggestions?.length ? `Try ${data.suggestions.join(", ")}.` : undefined} />}
440
459
  </PaneStatusBody>
@@ -0,0 +1,96 @@
1
+ import type { PerpBoardRow } from "../../../api-client/perps";
2
+ import { venueName } from "./model";
3
+
4
+ /**
5
+ * An asset's perpetual markets as DES lists them: one row per venue.
6
+ *
7
+ * A venue often lists one asset several times: Bybit's BTC settles in USDT,
8
+ * in USDC and inversely in BTC, and on Hyperliquid each builder dex (xyz, flx,
9
+ * km...) deploys its own NVDA. Listed contract by contract they read as the
10
+ * same line over and over, so each venue's contracts are one market here: the
11
+ * deepest contract gives the row its mark, funding and premium, and the open
12
+ * interest and volume are the venue's whole. A row reached twice (the same
13
+ * market id in two cases, or answered by two reads) is one contract.
14
+ */
15
+ export interface PerpVenueMarket {
16
+ /** `hyperliquid:BTC`: the venue and the asset, whatever the contract. */
17
+ key: string;
18
+ venue: string;
19
+ /** The deepest contract, which a click opens. */
20
+ primary: PerpBoardRow;
21
+ /** Every contract on the venue, deepest first; the primary is the first. */
22
+ contracts: PerpBoardRow[];
23
+ /** Open interest across the contracts, in USD; null when no contract states it. */
24
+ openInterestUsd: number | null;
25
+ volume24hUsd: number | null;
26
+ }
27
+
28
+ const sumOrNull = (values: Array<number | null | undefined>) => {
29
+ const known = values.filter((value): value is number => typeof value === "number" && Number.isFinite(value));
30
+ return known.length ? known.reduce((sum, value) => sum + value, 0) : null;
31
+ };
32
+ const depth = (value: number | null | undefined) => typeof value === "number" && Number.isFinite(value) ? value : -1;
33
+ const deeper = (a: { openInterestUsd: number | null; volume24hUsd: number | null }, b: typeof a) =>
34
+ depth(b.openInterestUsd) - depth(a.openInterestUsd) || depth(b.volume24hUsd) - depth(a.volume24hUsd);
35
+
36
+ /** Rows merged into one row per venue and asset, the deepest first (open interest, then volume). Delisted contracts are left out. */
37
+ export function mergeVenueMarkets(rows: readonly PerpBoardRow[]): PerpVenueMarket[] {
38
+ const contracts = new Map<string, PerpBoardRow>();
39
+ for (const row of rows) {
40
+ if (row.delisted) continue;
41
+ const id = row.marketId.toLowerCase();
42
+ const seen = contracts.get(id);
43
+ if (!seen || Date.parse(row.observedAt) > Date.parse(seen.observedAt)) contracts.set(id, row);
44
+ }
45
+ const venues = new Map<string, PerpBoardRow[]>();
46
+ for (const row of contracts.values()) {
47
+ const key = `${row.venue}:${row.baseAsset.toUpperCase()}`;
48
+ venues.set(key, [...venues.get(key) ?? [], row]);
49
+ }
50
+ return [...venues].map(([key, group]): PerpVenueMarket => {
51
+ const sorted = [...group].sort((a, b) => deeper(a, b) || a.marketId.localeCompare(b.marketId));
52
+ return {
53
+ key,
54
+ venue: sorted[0]!.venue,
55
+ primary: sorted[0]!,
56
+ contracts: sorted,
57
+ openInterestUsd: sumOrNull(sorted.map((row) => row.openInterestUsd)),
58
+ volume24hUsd: sumOrNull(sorted.map((row) => row.volume24hUsd)),
59
+ };
60
+ }).sort((a, b) => deeper(a, b) || venueName(a.venue).localeCompare(venueName(b.venue)) || a.key.localeCompare(b.key));
61
+ }
62
+
63
+ /** A market under this share of the asset's open interest is folded behind "more". */
64
+ const SMALL_MARKET_SHARE = 0.02;
65
+
66
+ /**
67
+ * The markets shown and those folded behind "more": a market holding less than
68
+ * `minShare` of the asset's open interest across venues, or past `maxShown`,
69
+ * is folded. The deepest market always shows, and one with no open interest
70
+ * stated shows only while no market states any.
71
+ */
72
+ export function foldSmallMarkets(markets: readonly PerpVenueMarket[], { minShare = SMALL_MARKET_SHARE, maxShown = 4 }: { minShare?: number; maxShown?: number } = {}): {
73
+ shown: PerpVenueMarket[]; folded: PerpVenueMarket[];
74
+ } {
75
+ const total = sumOrNull(markets.map((market) => market.openInterestUsd));
76
+ const shown: PerpVenueMarket[] = [];
77
+ const folded: PerpVenueMarket[] = [];
78
+ for (const market of markets) {
79
+ const share = total && market.openInterestUsd != null ? market.openInterestUsd / total : total ? 0 : 1;
80
+ if (shown.length === 0 || (shown.length < maxShown && share >= minShare)) shown.push(market);
81
+ else folded.push(market);
82
+ }
83
+ return { shown, folded };
84
+ }
85
+
86
+ /**
87
+ * The contract a venue's row reads from, as the venue settles it (`USDT`, `USD inverse`, a Hyperliquid builder dex
88
+ * such as `xyz`), and how many more it stands for: `USDT +2`.
89
+ */
90
+ export function contractText(market: PerpVenueMarket): string {
91
+ const row = market.primary;
92
+ const own = row.venue === "hyperliquid" && row.dex && row.dex !== "default" ? row.dex
93
+ : row.contractType === "inverse" ? `${row.quoteCurrency} inverse` : row.quoteCurrency;
94
+ const others = market.contracts.length - 1;
95
+ return others > 0 ? `${own} +${others}` : own;
96
+ }
@@ -10,6 +10,7 @@ export const pluginMarketplaceModule: PluginModule = {
10
10
  name: "Plugins",
11
11
  icon: "P",
12
12
  component: PluginMarketplacePane,
13
+ reportFreshness: { status: "not-a-feed", basis: "plugin listings" },
13
14
  defaultPosition: "right",
14
15
  defaultMode: "floating",
15
16
  defaultFloatingSize: { width: 118, height: 34 },
@@ -34,7 +35,7 @@ export const pluginMarketplaceModule: PluginModule = {
34
35
  id: "plugin-marketplace-pane",
35
36
  paneId: PLUGIN_MARKETPLACE_PANE_ID,
36
37
  label: "Plugins",
37
- description: "Browse and install Gloomberb plugins, and enable or disable the ones you have.",
38
+ description: "Browse and install Gloom plugins, and enable or disable the ones you have.",
38
39
  keywords: ["plugin", "plugins", "marketplace", "install", "extend", "addon", "extension"],
39
40
  shortcut: { prefix: "PL" },
40
41
  createInstance: () => ({ placement: "floating" }),