@gloom-sh/terminal 0.18.5 → 0.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (570) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/package.json +1 -1
  4. package/patches/electrobun@1.18.1.patch +163 -8
  5. package/src/api-client/app-attention.ts +3 -0
  6. package/src/api-client/beneficial-owners.ts +3 -0
  7. package/src/api-client/company-kpis.ts +3 -0
  8. package/src/api-client/cot.ts +6 -2
  9. package/src/api-client/credit-documents.ts +6 -0
  10. package/src/api-client/data.ts +19 -7
  11. package/src/api-client/equity-screener.ts +24 -0
  12. package/src/api-client/fund-facts.ts +45 -4
  13. package/src/api-client/hiring.ts +3 -0
  14. package/src/api-client/index.ts +15 -0
  15. package/src/api-client/ipo.ts +24 -1
  16. package/src/api-client/market-discovery.ts +10 -0
  17. package/src/api-client/market-movers.ts +7 -0
  18. package/src/api-client/members.ts +1 -1
  19. package/src/api-client/mna.ts +3 -0
  20. package/src/api-client/paths.ts +9 -1
  21. package/src/api-client/perps.ts +43 -0
  22. package/src/api-client/research-activity.ts +3 -2
  23. package/src/api-client/roadmap.ts +85 -0
  24. package/src/api-client/types.ts +82 -2
  25. package/src/app/pane-runtime/plugin-bindings.ts +9 -5
  26. package/src/app/runtime/broker-import.ts +0 -2
  27. package/src/app/runtime/plugin-bindings.ts +73 -56
  28. package/src/architecture/core-builtin-allowlist.json +2 -2
  29. package/src/architecture/pane-tabs-allowlist.json +3 -0
  30. package/src/architecture/ttl-caches-allowlist.json +19 -0
  31. package/src/brokers/sync-broker-instance.ts +2 -8
  32. package/src/capabilities/chart-series.ts +1 -1
  33. package/src/cli/commands/crypto-hints.ts +40 -24
  34. package/src/cli/commands/market.ts +107 -38
  35. package/src/cli/commands/news-search.ts +22 -0
  36. package/src/cli/commands/options-chain.ts +48 -7
  37. package/src/cli/commands/options-leaps.ts +9 -3
  38. package/src/cli/commands/overview.ts +147 -32
  39. package/src/cli/commands/search.ts +21 -1
  40. package/src/cli/commands/themes.ts +11 -2
  41. package/src/cli/commands/ticker.ts +87 -36
  42. package/src/cli/company-name-hint.ts +41 -0
  43. package/src/cli/earnings-calendar.ts +1 -1
  44. package/src/cli/errors.ts +25 -2
  45. package/src/cli/glossary.ts +31 -2
  46. package/src/cli/history-rows.ts +57 -9
  47. package/src/cli/index-roots.ts +0 -7
  48. package/src/cli/index.ts +2 -0
  49. package/src/cli/listing-arg.ts +235 -60
  50. package/src/cli/output-file.ts +68 -0
  51. package/src/cli/pane-functions/capabilities.ts +4 -0
  52. package/src/cli/pane-functions/catalog.ts +80 -7
  53. package/src/cli/pane-functions/discovery.ts +21 -1
  54. package/src/cli/pane-functions/dom.ts +13 -3
  55. package/src/cli/pane-functions/freshness.ts +93 -4
  56. package/src/cli/pane-functions/function-help.ts +20 -10
  57. package/src/cli/pane-functions/headless.ts +17 -7
  58. package/src/cli/pane-functions/index.ts +54 -16
  59. package/src/cli/pane-functions/market-session.ts +66 -4
  60. package/src/cli/pane-functions/options.ts +19 -1
  61. package/src/cli/pane-functions/resolver.ts +19 -8
  62. package/src/cli/pane-functions/screenshot-assessment.ts +6 -4
  63. package/src/cli/pane-functions/screenshot-types.ts +5 -0
  64. package/src/cli/quote-answer.ts +22 -11
  65. package/src/cli/saved-copy-persistence.ts +88 -0
  66. package/src/cli/ticker-resolution.ts +3 -2
  67. package/src/components/chart/composite/composite-chart.tsx +4 -0
  68. package/src/components/chart/composite/interactions.ts +6 -0
  69. package/src/components/chart/composite/legend.tsx +45 -7
  70. package/src/components/chart/composite/panel-gestures.ts +33 -0
  71. package/src/components/chart/composite/panel-surface.tsx +52 -3
  72. package/src/components/chart/composite/rasterizer.ts +24 -13
  73. package/src/components/chart/composite/scene.ts +39 -4
  74. package/src/components/chart/composite/series-cues.ts +14 -0
  75. package/src/components/chart/composite/text-renderer.ts +9 -2
  76. package/src/components/chart/composite/tool-catalog.ts +36 -0
  77. package/src/components/chart/composite/tools.ts +203 -44
  78. package/src/components/chart/composite/use-composite-chart-keyboard.ts +11 -4
  79. package/src/components/command-bar/assist/inventory.ts +2 -1
  80. package/src/components/command-bar/commands/direct/index.ts +28 -13
  81. package/src/components/command-bar/list/model.ts +5 -0
  82. package/src/components/command-bar/pane-templates/items.ts +3 -2
  83. package/src/components/command-bar/routes/root/runtime.ts +6 -7
  84. package/src/components/command-bar/selection-runtime.ts +52 -3
  85. package/src/components/command-bar/surface/index.tsx +2 -0
  86. package/src/components/command-bar/workflow/collection-actions.ts +3 -3
  87. package/src/components/layout/pane/capture-stamp.ts +2 -2
  88. package/src/components/layout/pane/footer/index.tsx +22 -6
  89. package/src/components/layout/pane/footer/model.ts +83 -15
  90. package/src/components/layout/pane/status-footer.ts +35 -2
  91. package/src/components/layout/status-widget-space.ts +9 -0
  92. package/src/components/onboarding/onboarding-wizard.tsx +4 -1
  93. package/src/components/onboarding/portfolio-step/positions-panel.tsx +12 -7
  94. package/src/components/onboarding/wizard-broker-sync.ts +0 -1
  95. package/src/components/onboarding/wizard-positions.ts +3 -3
  96. package/src/components/ticker/list-table-view.tsx +8 -21
  97. package/src/components/ui/choice-dialog.tsx +23 -18
  98. package/src/components/ui/choice-groups.ts +18 -0
  99. package/src/components/ui/data-table/opentui/index.tsx +9 -7
  100. package/src/components/ui/data-table/opentui/model.ts +26 -1
  101. package/src/components/ui/data-table/types.ts +2 -0
  102. package/src/components/ui/display.tsx +18 -3
  103. package/src/components/ui/icon.tsx +4 -1
  104. package/src/components/ui/list-view.tsx +8 -4
  105. package/src/components/ui/query-bar-keys.tsx +2 -0
  106. package/src/components/ui/query-bar.tsx +1 -0
  107. package/src/components/ui/select-button.tsx +5 -1
  108. package/src/components/ui/select-field.tsx +1 -0
  109. package/src/components/ui/table-layout.ts +8 -5
  110. package/src/core/app-runtime.ts +2 -0
  111. package/src/core/app-services.ts +2 -2
  112. package/src/data/config/chart-settings.ts +1 -1
  113. package/src/data/json-ticker-repository.ts +17 -8
  114. package/src/data/plugin-cache.ts +5 -3
  115. package/src/data/saved-copies.ts +8 -0
  116. package/src/data/ticker-repository.ts +16 -9
  117. package/src/data/ticker-writes.ts +81 -0
  118. package/src/data/ttl-cache.ts +165 -0
  119. package/src/docs/snapshot.json +103 -142
  120. package/src/i18n/es.ts +20 -6
  121. package/src/i18n/ja.ts +20 -6
  122. package/src/i18n/ko.ts +20 -6
  123. package/src/i18n/zh-cn.ts +20 -6
  124. package/src/i18n/zh-tw.ts +20 -6
  125. package/src/market-data/depositary-pair.ts +27 -3
  126. package/src/market-data/fund-facts.ts +50 -0
  127. package/src/market-data/market/change-basis.ts +16 -3
  128. package/src/market-data/market/format.ts +25 -0
  129. package/src/market-data/market/freshness.ts +2 -1
  130. package/src/market-data/options-alternatives.ts +81 -4
  131. package/src/market-data/quotes/metadata.ts +7 -0
  132. package/src/news/aggregator.ts +119 -9
  133. package/src/news/cloud-session.ts +21 -0
  134. package/src/news/hooks.ts +1 -0
  135. package/src/news/news-model.ts +1 -0
  136. package/src/news/types.ts +17 -0
  137. package/src/plugins/builtin/alerts/alert-engine.ts +20 -1
  138. package/src/plugins/builtin/alerts/alert-log.ts +33 -0
  139. package/src/plugins/builtin/alerts/calendar-events.ts +10 -4
  140. package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
  141. package/src/plugins/builtin/alerts/cli.ts +362 -80
  142. package/src/plugins/builtin/alerts/command.ts +25 -1
  143. package/src/plugins/builtin/alerts/event-rows.ts +24 -3
  144. package/src/plugins/builtin/alerts/events-pane.tsx +34 -10
  145. package/src/plugins/builtin/alerts/fired-log.ts +73 -9
  146. package/src/plugins/builtin/alerts/format.ts +75 -5
  147. package/src/plugins/builtin/alerts/headless.ts +34 -12
  148. package/src/plugins/builtin/alerts/history-rows.ts +3 -2
  149. package/src/plugins/builtin/alerts/history.ts +1 -1
  150. package/src/plugins/builtin/alerts/index.tsx +79 -16
  151. package/src/plugins/builtin/alerts/metric-command.ts +77 -4
  152. package/src/plugins/builtin/alerts/metric-data.ts +54 -13
  153. package/src/plugins/builtin/alerts/metric-model.ts +163 -14
  154. package/src/plugins/builtin/alerts/metric-parity-fixture.json +1 -1
  155. package/src/plugins/builtin/alerts/pane.tsx +61 -23
  156. package/src/plugins/builtin/alerts/quotes.ts +6 -0
  157. package/src/plugins/builtin/alerts/storage.ts +12 -8
  158. package/src/plugins/builtin/alerts/types.ts +4 -0
  159. package/src/plugins/builtin/analytics/index.tsx +2 -1
  160. package/src/plugins/builtin/analytics/risk-client.ts +16 -5
  161. package/src/plugins/builtin/analytics/risk-fx.ts +70 -15
  162. package/src/plugins/builtin/analytics/risk-headless.ts +27 -0
  163. package/src/plugins/builtin/analytics/risk-model.ts +55 -9
  164. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
  165. package/src/plugins/builtin/attention/index.tsx +2 -1
  166. package/src/plugins/builtin/attention/listing.ts +14 -0
  167. package/src/plugins/builtin/broker-manager/index.tsx +7 -3
  168. package/src/plugins/builtin/buildout/cells.tsx +12 -4
  169. package/src/plugins/builtin/buildout/format.ts +38 -0
  170. package/src/plugins/builtin/buildout/model/normalizers.ts +36 -1
  171. package/src/plugins/builtin/buildout/pane/index.tsx +4 -1
  172. package/src/plugins/builtin/buildout/table-model.ts +23 -2
  173. package/src/plugins/builtin/builtin-plugin-meta.ts +6 -2
  174. package/src/plugins/builtin/cds/client.ts +142 -30
  175. package/src/plugins/builtin/cds/headless.ts +35 -8
  176. package/src/plugins/builtin/cds/model.ts +145 -18
  177. package/src/plugins/builtin/cds/pane.tsx +16 -5
  178. package/src/plugins/builtin/changelog/index.tsx +9 -6
  179. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +3 -0
  180. package/src/plugins/builtin/chart-composer/cli-options.ts +4 -0
  181. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +7 -0
  182. package/src/plugins/builtin/chart-composer/editor/controller.ts +2 -2
  183. package/src/plugins/builtin/chart-composer/follow-binding.ts +14 -2
  184. package/src/plugins/builtin/chart-composer/headless-schema.ts +7 -1
  185. package/src/plugins/builtin/chart-composer/headless.ts +6 -1
  186. package/src/plugins/builtin/chart-composer/index.tsx +5 -6
  187. package/src/plugins/builtin/chart-composer/pane.tsx +37 -8
  188. package/src/plugins/builtin/chart-composer/settings.ts +27 -0
  189. package/src/plugins/builtin/chat/content/command-suggestions.ts +1 -0
  190. package/src/plugins/builtin/chat/controller/index.ts +5 -1
  191. package/src/plugins/builtin/chat/controller/notifications.ts +33 -3
  192. package/src/plugins/builtin/chat/controller/state.ts +25 -2
  193. package/src/plugins/builtin/chat/controller/unread-people.ts +89 -23
  194. package/src/plugins/builtin/chat/controller/utils.ts +7 -2
  195. package/src/plugins/builtin/chat/pane-links/commands.ts +7 -1
  196. package/src/plugins/builtin/chat/status-preview.ts +79 -0
  197. package/src/plugins/builtin/chat/status-widget.tsx +147 -10
  198. package/src/plugins/builtin/chat/unread-badge.ts +51 -10
  199. package/src/plugins/builtin/clinical-trials/client.ts +49 -4
  200. package/src/plugins/builtin/clinical-trials/model.ts +86 -31
  201. package/src/plugins/builtin/clinical-trials/pane.tsx +61 -49
  202. package/src/plugins/builtin/clinical-trials/sponsor-listing.ts +90 -0
  203. package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +95 -15
  204. package/src/plugins/builtin/clinical-trials/trial-pages.ts +50 -0
  205. package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
  206. package/src/plugins/builtin/company-attention/building.tsx +5 -1
  207. package/src/plugins/builtin/company-attention/client.ts +8 -9
  208. package/src/plugins/builtin/company-attention/evidence.ts +4 -1
  209. package/src/plugins/builtin/company-attention/headless.ts +2 -1
  210. package/src/plugins/builtin/company-attention/moves.tsx +5 -1
  211. package/src/plugins/builtin/company-attention/pane.tsx +12 -2
  212. package/src/plugins/builtin/company-attention/signals.tsx +5 -1
  213. package/src/plugins/builtin/company-kpis/headless.ts +8 -3
  214. package/src/plugins/builtin/company-kpis/model.ts +73 -5
  215. package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
  216. package/src/plugins/builtin/composite-plugins.ts +2 -1
  217. package/src/plugins/builtin/correlation/headless.ts +13 -5
  218. package/src/plugins/builtin/correlation/history.ts +22 -0
  219. package/src/plugins/builtin/cot/client.ts +22 -18
  220. package/src/plugins/builtin/cot/headless.ts +15 -10
  221. package/src/plugins/builtin/cot/model.ts +14 -1
  222. package/src/plugins/builtin/cot/pane.tsx +22 -14
  223. package/src/plugins/builtin/credit-boards/client.ts +6 -10
  224. package/src/plugins/builtin/credit-conditions/headless.ts +8 -2
  225. package/src/plugins/builtin/credit-conditions/index.tsx +5 -1
  226. package/src/plugins/builtin/credit-conditions/model.ts +4 -0
  227. package/src/plugins/builtin/credit-conditions/series.ts +3 -1
  228. package/src/plugins/builtin/credit-documents/detail.tsx +2 -2
  229. package/src/plugins/builtin/credit-documents/export.ts +173 -0
  230. package/src/plugins/builtin/credit-documents/headless.ts +10 -10
  231. package/src/plugins/builtin/credit-documents/model.ts +104 -14
  232. package/src/plugins/builtin/credit-documents/pane.tsx +12 -5
  233. package/src/plugins/builtin/custom-view/sources.ts +2 -0
  234. package/src/plugins/builtin/dividend-yield/client.ts +131 -23
  235. package/src/plugins/builtin/dividend-yield/headless.ts +44 -16
  236. package/src/plugins/builtin/dividend-yield/model.ts +8 -5
  237. package/src/plugins/builtin/dividend-yield/pane.tsx +38 -13
  238. package/src/plugins/builtin/dividend-yield/ranking.ts +37 -14
  239. package/src/plugins/builtin/dividend-yield/types.ts +20 -2
  240. package/src/plugins/builtin/dividend-yield/view.ts +30 -2
  241. package/src/plugins/builtin/docs/targets.ts +5 -1
  242. package/src/plugins/builtin/doe/headless.ts +3 -2
  243. package/src/plugins/builtin/doe/pane.tsx +13 -3
  244. package/src/plugins/builtin/earnings/board-model.ts +16 -15
  245. package/src/plugins/builtin/earnings/board.tsx +62 -71
  246. package/src/plugins/builtin/earnings/headless.ts +40 -4
  247. package/src/plugins/builtin/earnings/scope.ts +31 -43
  248. package/src/plugins/builtin/earnings-calls/calendar.tsx +2 -2
  249. package/src/plugins/builtin/earnings-calls/data.ts +18 -6
  250. package/src/plugins/builtin/earnings-calls/format.ts +31 -2
  251. package/src/plugins/builtin/earnings-calls/headless.ts +20 -5
  252. package/src/plugins/builtin/earnings-calls/model.ts +6 -1
  253. package/src/plugins/builtin/earnings-calls/pane.tsx +12 -4
  254. package/src/plugins/builtin/econ/calendar-model.ts +48 -1
  255. package/src/plugins/builtin/econ/calendar-source.ts +4 -2
  256. package/src/plugins/builtin/econ/detail-view.tsx +2 -2
  257. package/src/plugins/builtin/econ/figures.ts +48 -0
  258. package/src/plugins/builtin/econ/index.tsx +14 -5
  259. package/src/plugins/builtin/econ/print-watch.ts +3 -1
  260. package/src/plugins/builtin/econ/types.ts +2 -0
  261. package/src/plugins/builtin/econ-statistics/client.ts +11 -0
  262. package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
  263. package/src/plugins/builtin/econ-statistics/headless.ts +16 -6
  264. package/src/plugins/builtin/econ-statistics/pane.tsx +8 -4
  265. package/src/plugins/builtin/econ-statistics/stats.ts +39 -9
  266. package/src/plugins/builtin/equity-screener/categories.ts +179 -0
  267. package/src/plugins/builtin/equity-screener/client.ts +4 -2
  268. package/src/plugins/builtin/equity-screener/headless.ts +144 -13
  269. package/src/plugins/builtin/equity-screener/ideas.ts +49 -0
  270. package/src/plugins/builtin/equity-screener/model.ts +99 -10
  271. package/src/plugins/builtin/equity-screener/pane.tsx +68 -13
  272. package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
  273. package/src/plugins/builtin/futures/use-board-terms.ts +42 -28
  274. package/src/plugins/builtin/futures/use-front-returns.ts +45 -28
  275. package/src/plugins/builtin/fx-matrix/client.ts +6 -3
  276. package/src/plugins/builtin/fx-matrix/headless.ts +1 -1
  277. package/src/plugins/builtin/fx-matrix/index.tsx +4 -2
  278. package/src/plugins/builtin/fx-matrix/live-legs.ts +42 -3
  279. package/src/plugins/builtin/gpu/headless.ts +8 -2
  280. package/src/plugins/builtin/help/function-index.ts +4 -1
  281. package/src/plugins/builtin/help/index.tsx +62 -59
  282. package/src/plugins/builtin/help/shortcut-model.ts +1 -1
  283. package/src/plugins/builtin/help/shortcut-table.tsx +5 -6
  284. package/src/plugins/builtin/help/table-cells.tsx +13 -2
  285. package/src/plugins/builtin/holders/beneficial-client.ts +5 -1
  286. package/src/plugins/builtin/holders/beneficial-view.tsx +3 -0
  287. package/src/plugins/builtin/holders/fund-holders.ts +51 -0
  288. package/src/plugins/builtin/holders/headless.ts +22 -3
  289. package/src/plugins/builtin/holders/pane.tsx +3 -0
  290. package/src/plugins/builtin/holders/report-rows.ts +4 -1
  291. package/src/plugins/builtin/insider/headless.ts +2 -2
  292. package/src/plugins/builtin/ipo-calendar/client.ts +18 -2
  293. package/src/plugins/builtin/ipo-calendar/headless.ts +75 -11
  294. package/src/plugins/builtin/ipo-calendar/listing-ipo.ts +160 -0
  295. package/src/plugins/builtin/ipo-calendar/model.ts +98 -2
  296. package/src/plugins/builtin/iv-history/client.ts +52 -11
  297. package/src/plugins/builtin/iv-history/format.ts +36 -23
  298. package/src/plugins/builtin/iv-history/headless.ts +33 -18
  299. package/src/plugins/builtin/iv-history/index.tsx +2 -2
  300. package/src/plugins/builtin/iv-history/model.ts +163 -27
  301. package/src/plugins/builtin/iv-history/pane.tsx +10 -6
  302. package/src/plugins/builtin/iv-history/screen-pane.tsx +22 -15
  303. package/src/plugins/builtin/iv-history/universe.ts +5 -1
  304. package/src/plugins/builtin/jobs/client.ts +25 -29
  305. package/src/plugins/builtin/kelly-sizer/model.ts +12 -8
  306. package/src/plugins/builtin/kelly-sizer/pane.tsx +9 -9
  307. package/src/plugins/builtin/kelly-sizer/sections.tsx +19 -10
  308. package/src/plugins/builtin/kelly-sizer/types.ts +10 -4
  309. package/src/plugins/builtin/macro-day/headless.ts +12 -5
  310. package/src/plugins/builtin/macro-day/index.tsx +1 -1
  311. package/src/plugins/builtin/macro-day/model.ts +39 -5
  312. package/src/plugins/builtin/macro-day/pane.tsx +27 -19
  313. package/src/plugins/builtin/macro-day/release-table.ts +2 -2
  314. package/src/plugins/builtin/market-heatmap/data.ts +8 -33
  315. package/src/plugins/builtin/market-movers/client.ts +27 -7
  316. package/src/plugins/builtin/market-movers/headless.ts +34 -12
  317. package/src/plugins/builtin/market-movers/index.tsx +26 -14
  318. package/src/plugins/builtin/market-movers/min-float.ts +74 -0
  319. package/src/plugins/builtin/market-movers/model.ts +4 -1
  320. package/src/plugins/builtin/market-movers/screener.ts +22 -4
  321. package/src/plugins/builtin/market-movers/session-body.tsx +13 -8
  322. package/src/plugins/builtin/market-movers/session-table.ts +4 -3
  323. package/src/plugins/builtin/market-movers/table.ts +17 -5
  324. package/src/plugins/builtin/members/breadth-pane.tsx +2 -1
  325. package/src/plugins/builtin/mna/headless.ts +2 -0
  326. package/src/plugins/builtin/mna/pane.tsx +3 -1
  327. package/src/plugins/builtin/news/headless.ts +3 -1
  328. package/src/plugins/builtin/news/index.tsx +4 -0
  329. package/src/plugins/builtin/news/wire/categories.ts +19 -3
  330. package/src/plugins/builtin/news/wire/industry-pane.tsx +1 -0
  331. package/src/plugins/builtin/news/wire/news/footer.ts +31 -5
  332. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +6 -1
  333. package/src/plugins/builtin/news/wire/news/table.tsx +2 -2
  334. package/src/plugins/builtin/openfda/client.ts +14 -2
  335. package/src/plugins/builtin/openfda/index.tsx +3 -2
  336. package/src/plugins/builtin/openfda/model.ts +9 -0
  337. package/src/plugins/builtin/openfda/pane.tsx +42 -6
  338. package/src/plugins/builtin/options/analytics.ts +29 -5
  339. package/src/plugins/builtin/options/enrichment.ts +17 -5
  340. package/src/plugins/builtin/options/table.ts +5 -2
  341. package/src/plugins/builtin/options/types.ts +2 -0
  342. package/src/plugins/builtin/options/view.tsx +17 -4
  343. package/src/plugins/builtin/options-positioning/client.ts +11 -0
  344. package/src/plugins/builtin/options-positioning/headless.ts +21 -8
  345. package/src/plugins/builtin/options-positioning/index.tsx +1 -1
  346. package/src/plugins/builtin/options-positioning/model.ts +78 -9
  347. package/src/plugins/builtin/options-positioning/pane.tsx +21 -6
  348. package/src/plugins/builtin/options-scenario/client.ts +116 -47
  349. package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +142 -10
  350. package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +206 -55
  351. package/src/plugins/builtin/options-scenario/csp-screen.ts +63 -23
  352. package/src/plugins/builtin/options-scenario/csp.ts +8 -2
  353. package/src/plugins/builtin/options-scenario/index.tsx +9 -7
  354. package/src/plugins/builtin/options-scenario/strategy-screen.ts +532 -0
  355. package/src/plugins/builtin/pe-band/model.ts +12 -49
  356. package/src/plugins/builtin/perps/cascade-tabs.tsx +161 -0
  357. package/src/plugins/builtin/perps/cascade.ts +236 -0
  358. package/src/plugins/builtin/perps/client.ts +56 -2
  359. package/src/plugins/builtin/perps/headless.ts +65 -9
  360. package/src/plugins/builtin/perps/index.tsx +7 -5
  361. package/src/plugins/builtin/perps/model.ts +18 -4
  362. package/src/plugins/builtin/perps/pane.tsx +19 -22
  363. package/src/plugins/builtin/perps/status-line.ts +25 -0
  364. package/src/plugins/builtin/plugin-module.ts +5 -1
  365. package/src/plugins/builtin/portfolio-list/cached-account.ts +3 -1
  366. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +2 -8
  367. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +4 -4
  368. package/src/plugins/builtin/portfolio-list/cli/render.ts +7 -5
  369. package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
  370. package/src/plugins/builtin/portfolio-list/pane/index.tsx +6 -7
  371. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +5 -2
  372. package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +3 -2
  373. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +0 -14
  374. package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +5 -2
  375. package/src/plugins/builtin/portfolio-list/table.tsx +15 -9
  376. package/src/plugins/builtin/primary-dealers/client.ts +13 -38
  377. package/src/plugins/builtin/realized-vol/client.ts +13 -5
  378. package/src/plugins/builtin/realized-vol/evidence.ts +29 -4
  379. package/src/plugins/builtin/realized-vol/headless.ts +8 -4
  380. package/src/plugins/builtin/realized-vol/index.tsx +2 -2
  381. package/src/plugins/builtin/realized-vol/model.ts +37 -6
  382. package/src/plugins/builtin/realized-vol/pane.tsx +11 -9
  383. package/src/plugins/builtin/relative-rotation/client.ts +18 -4
  384. package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
  385. package/src/plugins/builtin/relative-rotation/index.tsx +2 -1
  386. package/src/plugins/builtin/relative-rotation/model.ts +16 -40
  387. package/src/plugins/builtin/relative-rotation/pane.tsx +3 -2
  388. package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -2
  389. package/src/plugins/builtin/research/event-model.ts +40 -12
  390. package/src/plugins/builtin/research/headless.ts +1 -1
  391. package/src/plugins/builtin/research/index.tsx +7 -3
  392. package/src/plugins/builtin/research/relative-valuation-headless.ts +13 -26
  393. package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
  394. package/src/plugins/builtin/research/relative-valuation-pane.tsx +59 -20
  395. package/src/plugins/builtin/research/relative-valuation-peers.ts +217 -20
  396. package/src/plugins/builtin/research/relative-valuation-settings.ts +44 -0
  397. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -3
  398. package/src/plugins/builtin/reverse-dcf/model.ts +61 -6
  399. package/src/plugins/builtin/reverse-dcf/pane.tsx +7 -4
  400. package/src/plugins/builtin/risk-factors/data.ts +4 -2
  401. package/src/plugins/builtin/risk-factors/headless.ts +5 -2
  402. package/src/plugins/builtin/roadmap/index.tsx +28 -0
  403. package/src/plugins/builtin/roadmap/model.ts +67 -0
  404. package/src/plugins/builtin/roadmap/pane.tsx +244 -0
  405. package/src/plugins/builtin/scanner/flow-model.ts +50 -4
  406. package/src/plugins/builtin/scanner/flow-pane.tsx +367 -65
  407. package/src/plugins/builtin/scanner/flow-session.ts +146 -0
  408. package/src/plugins/builtin/scanner/headless.ts +204 -35
  409. package/src/plugins/builtin/scanner/index.ts +16 -5
  410. package/src/plugins/builtin/scanner/live-headless.ts +48 -5
  411. package/src/plugins/builtin/scanner/live-model.ts +11 -0
  412. package/src/plugins/builtin/scanner/live-pane.tsx +3 -3
  413. package/src/plugins/builtin/scanner/lock-note.ts +3 -2
  414. package/src/plugins/builtin/seasonality/headless.ts +10 -1
  415. package/src/plugins/builtin/sec/fund-facts-view.tsx +5 -2
  416. package/src/plugins/builtin/sec/fund-facts.ts +116 -34
  417. package/src/plugins/builtin/sec/headless.ts +4 -2
  418. package/src/plugins/builtin/sec/index.tsx +3 -0
  419. package/src/plugins/builtin/series-inventory.ts +2 -0
  420. package/src/plugins/builtin/shared/cloud-resource.ts +16 -7
  421. package/src/plugins/builtin/shared/cloud-upgrade.ts +4 -3
  422. package/src/plugins/builtin/shared/earnings-calendar.ts +6 -2
  423. package/src/plugins/builtin/shared/earnings-report.ts +2 -2
  424. package/src/plugins/builtin/shared/headless-market-data.ts +2 -2
  425. package/src/plugins/builtin/shared/job-posting.ts +1 -0
  426. package/src/plugins/builtin/shared/members-menu.tsx +3 -2
  427. package/src/plugins/builtin/shared/pro-lock-note.ts +12 -12
  428. package/src/plugins/builtin/shared/report-freshness.ts +11 -2
  429. package/src/plugins/builtin/shared/research-cells.tsx +1 -1
  430. package/src/plugins/builtin/shared/series-cache.ts +4 -4
  431. package/src/plugins/builtin/shared/ticker-choice.tsx +2 -1
  432. package/src/plugins/builtin/shared/ticker-company.ts +77 -0
  433. package/src/plugins/builtin/shared/use-quote-board.ts +2 -1
  434. package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
  435. package/src/plugins/builtin/shared/volatility/pricing.ts +19 -0
  436. package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
  437. package/src/plugins/builtin/themes/model.ts +16 -7
  438. package/src/plugins/builtin/themes/pane.tsx +5 -5
  439. package/src/plugins/builtin/thirteenf/api.ts +12 -1
  440. package/src/plugins/builtin/thirteenf/data.ts +7 -7
  441. package/src/plugins/builtin/thirteenf/headless.ts +31 -4
  442. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +43 -18
  443. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +3 -2
  444. package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +62 -0
  445. package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +83 -46
  446. package/src/plugins/builtin/ticker-detail/headless.ts +31 -16
  447. package/src/plugins/builtin/ticker-detail/overview/model.ts +11 -3
  448. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +2 -1
  449. package/src/plugins/builtin/ticker-detail/pane.tsx +14 -8
  450. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -1
  451. package/src/plugins/builtin/ticker-detail/settings.ts +1 -0
  452. package/src/plugins/builtin/time-sales/pane.tsx +31 -19
  453. package/src/plugins/builtin/treasury-auctions/headless.ts +21 -12
  454. package/src/plugins/builtin/treasury-auctions/pane.tsx +9 -3
  455. package/src/plugins/builtin/treasury-daily/client.ts +41 -5
  456. package/src/plugins/builtin/treasury-daily/headless.ts +2 -2
  457. package/src/plugins/builtin/treasury-daily/index.tsx +1 -1
  458. package/src/plugins/builtin/treasury-daily/model.ts +50 -3
  459. package/src/plugins/builtin/treasury-daily/pane.tsx +81 -2
  460. package/src/plugins/builtin/vol-surface/client.ts +3 -13
  461. package/src/plugins/builtin/vol-surface/headless.ts +3 -3
  462. package/src/plugins/builtin/vol-surface/index.tsx +1 -1
  463. package/src/plugins/builtin/vol-surface/pane.tsx +7 -5
  464. package/src/plugins/builtin/volatility/index.tsx +2 -2
  465. package/src/plugins/builtin/volatility/pane.tsx +7 -4
  466. package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
  467. package/src/plugins/builtin/yield-curve/client.ts +61 -1
  468. package/src/plugins/builtin/yield-curve/headless.ts +9 -6
  469. package/src/plugins/builtin/yield-curve/index.tsx +34 -10
  470. package/src/plugins/builtin/yield-curve/swaps.tsx +9 -5
  471. package/src/plugins/catalog-browser.ts +2 -1
  472. package/src/plugins/catalog-manifest.ts +2 -1
  473. package/src/plugins/host-link.ts +1 -1
  474. package/src/plugins/marketplace/model.ts +18 -5
  475. package/src/portfolio/column-values.ts +11 -7
  476. package/src/portfolio/futures-positions.ts +6 -3
  477. package/src/portfolio/position-quantity.ts +15 -9
  478. package/src/remote/resources.ts +2 -1
  479. package/src/renderers/dom/data-table/row.tsx +26 -0
  480. package/src/renderers/dom/desktop/icons.tsx +7 -0
  481. package/src/renderers/dom/desktop/list-view.tsx +13 -10
  482. package/src/renderers/dom/desktop/query-bar.tsx +4 -2
  483. package/src/renderers/dom/desktop/select-field.tsx +4 -2
  484. package/src/renderers/dom/host/chart-surface.tsx +21 -0
  485. package/src/renderers/electrobun/bun/desktop/app-name.ts +3 -3
  486. package/src/renderers/electrobun/bun/desktop/backend.ts +21 -10
  487. package/src/renderers/electrobun/bun/desktop/host-requests.ts +15 -0
  488. package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
  489. package/src/renderers/electrobun/bun/desktop/installer-folder.ts +731 -0
  490. package/src/renderers/electrobun/bun/desktop/relaunch.ts +102 -10
  491. package/src/renderers/electrobun/bun/desktop/update.ts +115 -14
  492. package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
  493. package/src/renderers/electrobun/bun/desktop/windows-script.ts +92 -0
  494. package/src/renderers/electrobun/bun/index.ts +26 -2
  495. package/src/renderers/electrobun/bun/window/create.ts +43 -4
  496. package/src/renderers/electrobun/bun/window/events.ts +20 -0
  497. package/src/renderers/electrobun/bun/window/windows-custom-chrome.ts +9 -2
  498. package/src/renderers/electrobun/bun/window/windows-native.ts +78 -0
  499. package/src/renderers/electrobun/bun/window/windows-quit.ts +57 -0
  500. package/src/renderers/electrobun/shared/app-name.ts +13 -0
  501. package/src/renderers/electrobun/shared/protocol.ts +12 -0
  502. package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
  503. package/src/renderers/electrobun/view/main.tsx +24 -1
  504. package/src/renderers/electrobun/view/news-source.ts +5 -1
  505. package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
  506. package/src/renderers/electrobun/view/ui-host.tsx +8 -0
  507. package/src/renderers/electrobun/view/view-ids.ts +85 -0
  508. package/src/renderers/opentui/start.tsx +5 -0
  509. package/src/series/registry.ts +1 -0
  510. package/src/series/types.ts +6 -0
  511. package/src/series/validate.ts +1 -0
  512. package/src/sources/gloomberb-cloud/fred-series.ts +3 -1
  513. package/src/sources/gloomberb-cloud/index.ts +43 -8
  514. package/src/sources/gloomberb-cloud/news.ts +47 -7
  515. package/src/sources/provider-router/financials.ts +15 -3
  516. package/src/sources/provider-router/index.ts +8 -5
  517. package/src/state/ticker-cleanup.ts +50 -0
  518. package/src/sync/controller.ts +4 -3
  519. package/src/sync/core-contributors.ts +90 -31
  520. package/src/telemetry/stall-reports-dom.ts +38 -0
  521. package/src/telemetry/stall-reports-node.ts +75 -0
  522. package/src/telemetry/stall-reports.ts +214 -0
  523. package/src/test-support/preload.ts +14 -0
  524. package/src/theme/themes.ts +10 -0
  525. package/src/tickers/company-name-listing.ts +53 -13
  526. package/src/tickers/company-name.ts +50 -4
  527. package/src/tickers/index-roots.ts +7 -0
  528. package/src/tickers/listing-fate.ts +2 -2
  529. package/src/tickers/listing-venues.ts +15 -3
  530. package/src/tickers/search/index.ts +6 -2
  531. package/src/tickers/search/ranking.ts +5 -1
  532. package/src/tickers/search/upsert.ts +13 -0
  533. package/src/tickers/terminal-codes.ts +19 -0
  534. package/src/tickers/ticker-changes.ts +8 -4
  535. package/src/time-series/alignment.ts +1 -1
  536. package/src/time-series/economic.ts +18 -4
  537. package/src/time-series/field-catalog.ts +1 -1
  538. package/src/time-series/filed-trailing-eps.ts +197 -0
  539. package/src/time-series/fundamentals.ts +44 -1
  540. package/src/time-series/ice-credit.ts +3 -0
  541. package/src/time-series/price-comparison.ts +30 -2
  542. package/src/time-series/resolve-context.ts +3 -1
  543. package/src/time-series/resolve.ts +56 -4
  544. package/src/time-series/session-bar.ts +11 -0
  545. package/src/time-series/spec.ts +3 -2
  546. package/src/time-series/studies.ts +8 -2
  547. package/src/time-series/total-return.ts +160 -0
  548. package/src/time-series/transforms.ts +2 -3
  549. package/src/time-series/types.ts +22 -1
  550. package/src/types/company-from.ts +40 -0
  551. package/src/types/data-provider.ts +7 -0
  552. package/src/types/financials.ts +59 -1
  553. package/src/types/headless.ts +27 -3
  554. package/src/types/news-source.ts +1 -0
  555. package/src/types/plugin.ts +9 -0
  556. package/src/ui/host.tsx +3 -0
  557. package/src/updater/failure.ts +4 -1
  558. package/src/utils/cli-output.ts +42 -1
  559. package/src/utils/crypto-pair.ts +165 -7
  560. package/src/utils/declared-dividends.ts +1 -1
  561. package/src/utils/forward-dividend.ts +2 -0
  562. package/src/utils/fundamentals.ts +60 -8
  563. package/src/utils/fx-market-hours.ts +26 -3
  564. package/src/utils/main-thread-stall.ts +35 -2
  565. package/src/utils/market-capitalization.ts +24 -1
  566. package/src/utils/price-history.ts +7 -1
  567. package/src/utils/shell-word.ts +8 -0
  568. package/src/utils/trailing-dividend.ts +53 -4
  569. package/src/version.ts +2 -2
  570. package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
@@ -2,7 +2,7 @@ import type { PaneTemplateInstanceConfig } from "../../../types/plugin";
2
2
  import type { PluginModule } from "../plugin-module";
3
3
  import { commandRequestParams } from "../shared/command-tab";
4
4
  import { PERP_SERIES } from "./series";
5
- import { perpsCache, perpsHistoryCache, perpsListCache, perpsMarketCache, perpsRankingsCache } from "./client";
5
+ import { perpsCache, perpsDepthCache, perpsHistoryCache, perpsLiquidationsCache, perpsListCache, perpsMarketCache, perpsRankingsCache } from "./client";
6
6
  import { perpsHeadless } from "./headless";
7
7
  import { PerpsPane } from "./pane";
8
8
 
@@ -18,16 +18,18 @@ export function perpsInstance(arg: string | undefined): PaneTemplateInstanceConf
18
18
  settings: { market }, params: commandRequestParams({ market }) };
19
19
  }
20
20
 
21
+ const PERP_CACHES = [perpsCache, perpsHistoryCache, perpsListCache, perpsMarketCache, perpsRankingsCache, perpsLiquidationsCache, perpsDepthCache] as const;
22
+
21
23
  export const perpsModule: PluginModule = {
22
24
  series: PERP_SERIES,
23
25
  panes: [{ id: "perps", name: "Perpetuals", icon: "P", component: PerpsPane, defaultPosition: "right", defaultMode: "floating",
24
26
  defaultFloatingSize: { width: 138, height: 34 }, tableExport: true, headless: perpsHeadless }],
25
- paneTemplates: [{ id: "perps-pane", paneId: "perps", label: "Perpetuals", description: "Perpetual funding, open interest and premiums by market and venue, with rankings, a venue comparison and Pro history.",
26
- keywords: ["perp", "perpetual", "perpetuals", "funding", "funding rate", "open interest", "hip-3", "stock perps", "basis", "basis trade", "carry", "venue", "funding spread", "long short ratio", "long/short", "positioning"],
27
+ paneTemplates: [{ id: "perps-pane", paneId: "perps", label: "Perpetuals", description: "Perpetual funding, open interest and premiums by market and venue, with rankings, a venue comparison, liquidations, order book depth and Pro history.",
28
+ keywords: ["perp", "perpetual", "perpetuals", "funding", "funding rate", "open interest", "hip-3", "stock perps", "basis", "basis trade", "carry", "venue", "funding spread", "long short ratio", "long/short", "positioning", "liquidations", "liquidation", "order book depth", "market depth", "liquidity"],
27
29
  shortcut: { prefix: "PERP", argKind: "text", argOptional: true, argPlaceholder: "market" }, headless: perpsHeadless,
28
30
  createInstance: (_ctx, options) => perpsInstance(options?.arg) }],
29
31
  setup(ctx) {
30
- for (const cache of [perpsCache, perpsHistoryCache, perpsListCache, perpsMarketCache, perpsRankingsCache]) cache.attach(ctx.persistence);
32
+ for (const cache of PERP_CACHES) cache.attach(ctx.persistence);
31
33
  },
32
- dispose() { for (const cache of [perpsCache, perpsHistoryCache, perpsListCache, perpsMarketCache, perpsRankingsCache]) cache.reset(); },
34
+ dispose() { for (const cache of PERP_CACHES) cache.reset(); },
33
35
  };
@@ -6,14 +6,28 @@ import { formatCompact } from "../../../utils/format";
6
6
  import { isUsListingExchange, publicTickerKey } from "../../../utils/exchanges";
7
7
  import { isRecord } from "../../../utils/guards";
8
8
  import type { ThemeColors } from "../../../theme/colors";
9
- export const PERP_TABS = ["board", "rankings", "compare", "history", "evidence"] as const;
9
+ export const PERP_TABS = ["board", "rankings", "compare", "liquidations", "depth", "history", "evidence"] as const;
10
10
  export type PerpTab = typeof PERP_TABS[number];
11
11
  /** One market's tabs; the others list many markets. */
12
12
  export const isMarketTab = (tab: string) => tab === "history" || tab === "evidence";
13
- /** A named market opens its history; nothing named opens the board. */
14
- export const openingTab = (market: string | null | undefined): PerpTab => market?.trim() ? "history" : "board";
13
+ /** One base asset's tabs, across its contracts on every venue; they share the asset. */
14
+ export const isAssetTab = (tab: string) => tab === "compare" || tab === "liquidations" || tab === "depth";
15
+ /**
16
+ * A named market opens its history, or its latest values (Evidence) where history is Pro's, so a free account
17
+ * lands on the figures it has and not on a lock; nothing named opens the board.
18
+ */
19
+ export const openingTab = (market: string | null | undefined, pro = true): PerpTab => market?.trim() ? pro ? "history" : "evidence" : "board";
15
20
  export const ASSET_FILTERS = ["all", "stocks", "indices", "energy", "metals", "fx", "crypto"] as const;
16
- export const VENUES = ["hyperliquid", "binance", "bybit", "okx", "deribit", "coinbase", "kraken", "dydx"] as const;
21
+ /** The venues Gloom collects perpetuals from: what the venue filter and `--venue` take. */
22
+ export const VENUES = ["hyperliquid", "binance", "bybit", "okx"] as const;
23
+ /** Every venue a market identity can name (`kraken:PF_XBTUSD`), collected or not. */
24
+ export const IDENTITY_VENUES = [...VENUES, "deribit", "coinbase", "kraken", "dydx"] as const;
25
+ /** What `--venue` says of a venue Gloom does not collect yet; null for a covered venue and for any other word. */
26
+ export function uncoveredVenueNote(venue: string): string | null {
27
+ const id = venue.trim().toLowerCase();
28
+ return (IDENTITY_VENUES as readonly string[]).includes(id) && !(VENUES as readonly string[]).includes(id)
29
+ ? `Gloom doesn't cover ${venueName(id)} perps yet.` : null;
30
+ }
17
31
  export const BOARD_SORTS = ["oi", "funding", "oi-change", "premium", "long-short"] as const;
18
32
  export const sortLabel = (sort: string) => ({ oi: "OI", funding: "Funding", "oi-change": "OI change", premium: "Premium", "long-short": "Long %" })[sort] ?? sort;
19
33
  export const label = (s: string) => s === "fx" ? "FX" : s.charAt(0).toUpperCase() + s.slice(1);
@@ -5,13 +5,10 @@ import type { PerpBoardRow, PerpMarketPayload } from "../../../api-client/perps"
5
5
  import { buildSectionedRows, ChartTableHeader, DataTableView, EmptyState, KeyValueRow, SectionHeading, PaneStatusBody, QueryBar, StatGrid, useChartTableSelection,
6
6
  usePaneNoticeFooter, usePaneStatusFooter, usePaneTabs, useQueryBarSearch, type DataTableCell, type KeyValueRowProps, type PaneFooterSegment, type PaneHint, type SectionedRow,
7
7
  type StatItem } from "../../../components";
8
- import { deriveHeadlessFreshness, paneStatusLineVariants } from "../../../cli/pane-functions/freshness";
9
8
  import { openFormModal } from "../../../components/form-modal";
10
9
  import { tf } from "../../../i18n";
11
10
  import { usePaneRefreshKey } from "../../../components/data-table/table-pane";
12
11
  import { useAsyncResource, useAutoRefresh, usePaneSettingValue, usePaneTitle, usePluginAppActions, usePluginPaneState } from "../../../public/react";
13
- import { useAgeClock } from "../../../react/auto-refresh";
14
- import { useRemoteUiNode } from "../../../remote/semantic-tree";
15
12
  import { useThemeColors } from "../../../theme/theme-context";
16
13
  import type { PaneProps } from "../../../types/plugin";
17
14
  import { Box, ScrollBox, useRendererHost, useUiCapabilities } from "../../../ui";
@@ -25,9 +22,11 @@ import { isCloudSessionRequired, useResearchCloudSession } from "../shared/resea
25
22
  import { boardSearchText, cachedPerpSelection, cachedPerpsBoard, cachedPerpsCompare, cachedPerpsRankings, loadPerpSelection, loadPerpsBoard, loadPerpsCompare,
26
23
  loadPerpsHistoryForRange, loadPerpsRankings } from "./client";
27
24
  import { perpFreshness } from "./freshness";
25
+ import { Depth, Liquidations } from "./cascade-tabs";
26
+ import { useFreshnessStatusLine } from "./status-line";
28
27
  import { useLivePerpRows, useLivePerpTableRows } from "./live-rows";
29
28
  import { aprFormula, ASSET_FILTERS, BOARD_SORTS, boardColumns, collectedVenueCount, compact, compareColumns, dayChange, fundingInterval, fundingSpread, historyCaption, historyCell, historyChange, historyColumns,
30
- HISTORY_METRICS, historyRows, historySeries, historyValue, inPoints, isMarketTab, label, longShortReason, marketLabel, metricLabel, openingTab, perpCell, perpHistoryProText, PERP_TABS, percent,
29
+ HISTORY_METRICS, historyRows, historySeries, historyValue, inPoints, isAssetTab, isMarketTab, label, longShortReason, marketLabel, metricLabel, openingTab, perpCell, perpHistoryProText, PERP_TABS, percent,
31
30
  positioningFields, price, rankingColumns, rankingRows, RANKINGS, ratioText, share, sortLabel, time, underlyingTicker, venueLabel, venueName, venuesOf, type HistoryMetric,
32
31
  type PerpColumnId, type PerpTab, type RankingKey } from "./model";
33
32
 
@@ -45,17 +44,8 @@ const withRankedRow = (entry: SectionedRow<Ranked>, row: PerpBoardRow): Sectione
45
44
  * same way.
46
45
  */
47
46
  function usePerpStatusLine(rows: readonly PerpBoardRow[], funding: readonly PerpBoardRow[], since: number | null, live = false): PaneFooterSegment[] {
48
- const now = useAgeClock(since);
49
- const declared = useMemo(() => rows.length ? perpFreshness(rows, { funding, now, live }) : null, [rows, funding, now, live]);
50
- useRemoteUiNode(declared ? { role: "report-freshness", label: "Venue freshness", getMetadata: () => ({ ...declared }) } : null);
51
- return useMemo(() => {
52
- if (!declared) return [];
53
- const freshness = deriveHeadlessFreshness({ shape: "rows" }, { rows: [], freshness: declared }, now);
54
- const tone = freshness.status === "stale" ? "warning" as const : "muted" as const;
55
- // A footer too narrow beside its keys gives up the funding part before any venue's time.
56
- const [line, ...shorter] = paneStatusLineVariants(freshness).map((text) => [{ text, tone }]);
57
- return [{ id: "perp-freshness", asOf: true, parts: line!, variants: shorter }];
58
- }, [declared, rows, now]);
47
+ const declare = useCallback((now: number) => rows.length ? perpFreshness(rows, { funding, now, live }) : null, [rows, funding, live]);
48
+ return useFreshnessStatusLine(declare, since);
59
49
  }
60
50
 
61
51
  type EvidenceField = [label: string, value: string, detail?: string, link?: KeyValueRowProps["link"]];
@@ -378,11 +368,12 @@ export function PerpsPane({ width, height, focused, marketAction }: Pick<PanePro
378
368
  const openDescription = useOpenDescription();
379
369
  const upgrade = useCloudUpgradeAction("perp");
380
370
  const [openingMarket] = usePaneSettingValue("market", "");
381
- const [openingTabValue] = usePaneSettingValue("tab", openingTab(openingMarket));
371
+ const opening = openingTab(openingMarket, access.hasProAccess);
372
+ const [openingTabValue] = usePaneSettingValue("tab", opening);
382
373
  // A market asked for again (a stock's perpetual row, PERP) comes back to it on its opening tab, whatever this pane
383
374
  // shows by then.
384
375
  const [savedTab, setTab] = useCommandValue<string>("perps", "perps:tab", (params) => params.market ? openingTabValue : null, openingTabValue);
385
- const tab = PERP_TABS.includes(savedTab as PerpTab) ? savedTab as PerpTab : openingTab(openingMarket);
376
+ const tab = PERP_TABS.includes(savedTab as PerpTab) ? savedTab as PerpTab : opening;
386
377
  const marketTab = isMarketTab(tab);
387
378
  const [search, setSearch] = useCommandValue("perps", "perps:search", (params) => params.market || null, openingMarket);
388
379
  const [compareAsset, setCompareAsset] = usePluginPaneState<string>("perps:compare", compareOpening(openingMarket));
@@ -409,13 +400,17 @@ export function PerpsPane({ width, height, focused, marketAction }: Pick<PanePro
409
400
  // The market on screen goes with the user: History and Evidence open the row picked on a list, Compare its base asset.
410
401
  const current = marketTab ? active : listRow;
411
402
  const carried = marketTab ? active : listRow ?? picked;
403
+ // Liquidations and Depth list no board rows: a market tab after them opens their asset, and they keep Compare's.
404
+ const assetOnly = tab === "liquidations" || tab === "depth";
412
405
  const selectTab = useCallback((next: string) => {
413
- if (carried && isMarketTab(next) && !marketTab) setSearch(carried.marketId);
414
- if (carried && next === "compare") setCompareAsset(carried.baseAsset);
406
+ if (assetOnly && isMarketTab(next)) setSearch(compareAsset);
407
+ else if (carried && isMarketTab(next) && !marketTab) setSearch(carried.marketId);
408
+ if (carried && isAssetTab(next) && !assetOnly) setCompareAsset(carried.baseAsset);
415
409
  setListRow(null);
416
410
  setTab(next);
417
- }, [carried, marketTab, setSearch, setCompareAsset, setTab]);
418
- const openMarket = useCallback((row: PerpBoardRow, next: PerpTab = "history") => { setSearch(row.marketId); setTab(next); }, [setSearch, setTab]);
411
+ }, [assetOnly, carried, compareAsset, marketTab, setSearch, setCompareAsset, setTab]);
412
+ const openMarket = useCallback((row: PerpBoardRow, next: PerpTab = openingTab(row.marketId, access.hasProAccess)) => { setSearch(row.marketId); setTab(next); }, [setSearch, setTab, access.hasProAccess]);
413
+ const openMarketId = useCallback((marketId: string) => { setSearch(marketId); setTab(openingTab(marketId, access.hasProAccess)); }, [setSearch, setTab, access.hasProAccess]);
419
414
  const { strip, rows: tabRows } = usePaneTabs({ tabs: PERP_TABS.map((value) => ({ value, label: label(value) })), activeValue: tab, onSelect: selectTab, focused, dense: true });
420
415
  const refreshMarket = useCallback(() => { void resource.reload(); }, [resource.reload]);
421
416
  useAutoRefresh(resource.updatedAt, resource.load, { intervalMs: 60_000 });
@@ -450,7 +445,9 @@ export function PerpsPane({ width, height, focused, marketAction }: Pick<PanePro
450
445
  return <Box width={width} height={height} flexDirection="column">
451
446
  {strip}
452
447
  {tab === "board" ? <Board {...list} /> : tab === "rankings" ? <Rankings {...list} />
453
- : tab === "compare" ? <Compare {...list} asset={compareAsset} onAssetChange={setCompareAsset} /> : <>
448
+ : tab === "compare" ? <Compare {...list} asset={compareAsset} onAssetChange={setCompareAsset} />
449
+ : tab === "liquidations" ? <Liquidations accessKey={accessKey} width={width} height={listHeight} focused={focused} asset={compareAsset} onAssetChange={setCompareAsset} onOpenMarket={openMarketId} />
450
+ : tab === "depth" ? <Depth accessKey={accessKey} width={width} height={listHeight} focused={focused} asset={compareAsset} onAssetChange={setCompareAsset} onOpenMarket={openMarketId} /> : <>
454
451
  <QueryBar width={width} search={{ focused, value: search, onChange: setSearch, placeholder: "Market or canonical identity", debounceMs: 300, ...querySearch.searchProps }}
455
452
  filters={tab === "history" ? [
456
453
  { id: "metric", label: "Series", value: metric, options: HISTORY_METRICS.map((value) => ({ value, label: metricLabel(value) })), onChange: setMetric },
@@ -0,0 +1,25 @@
1
+ import { useMemo } from "react";
2
+ import { deriveHeadlessFreshness, paneStatusLineVariants } from "../../../cli/pane-functions/freshness";
3
+ import type { PaneFooterSegment } from "../../../components";
4
+ import { useAgeClock } from "../../../react/auto-refresh";
5
+ import { useRemoteUiNode } from "../../../remote/semantic-tree";
6
+ import type { HeadlessPaneFreshness } from "../../../types/headless";
7
+
8
+ /**
9
+ * The footer line a tab ends with, from the same freshness its text report declares (`declare`, null while there is
10
+ * nothing to date): the source, its time and how old it is, the line `fn PERP` and `shot PERP` end with. It keeps ageing
11
+ * while the pane is seen (`since` is when the data loaded), and is published for a capture to date itself the same way.
12
+ */
13
+ export function useFreshnessStatusLine(declare: ((now: number) => HeadlessPaneFreshness | null) | null, since: number | null): PaneFooterSegment[] {
14
+ const now = useAgeClock(since);
15
+ const declared = useMemo(() => declare?.(now) ?? null, [declare, now]);
16
+ useRemoteUiNode(declared ? { role: "report-freshness", label: "Venue freshness", getMetadata: () => ({ ...declared }) } : null);
17
+ return useMemo(() => {
18
+ if (!declared) return [];
19
+ const freshness = deriveHeadlessFreshness({ shape: "rows" }, { rows: [], freshness: declared }, now);
20
+ const tone = freshness.status === "stale" ? "warning" as const : "muted" as const;
21
+ // A footer too narrow beside its keys gives up the note before any venue's time.
22
+ const [line, ...shorter] = paneStatusLineVariants(freshness).map((text) => [{ text, tone }]);
23
+ return [{ id: "perp-freshness", asOf: true, parts: line!, variants: shorter }];
24
+ }, [declared, now]);
25
+ }
@@ -161,7 +161,11 @@ export function composeBuiltinPlugin(options: CompositePluginOptions): GloomPlug
161
161
  for (const module of modules) {
162
162
  startedModules.push(module);
163
163
  try {
164
- await module.setup?.(ctx);
164
+ // Only an async setup is waited for. Awaiting a synchronous one hands
165
+ // the tick to the panes first: Gloom Cloud restores the saved session
166
+ // in its second module, and the panes' first requests went out without it.
167
+ const pending = module.setup?.(ctx);
168
+ if (pending) await pending;
165
169
  } catch (error) {
166
170
  // Logged as the plugin, so the failure counts toward its health.
167
171
  ctx.log.error("Module setup failed", { error: error instanceof Error ? error.message : String(error) });
@@ -1,3 +1,4 @@
1
+ import { logFailure } from "../../../utils/log-failure";
1
2
  import { peekPersistedBrokerAccounts } from "../../../brokers/account-cache";
2
3
  import type { AppResourceStorePort } from "../../../core/app-service-ports";
3
4
  import type { AppConfig } from "../../../types/config";
@@ -20,8 +21,9 @@ export function peekCachedBrokerAccounts(
20
21
  try {
21
22
  const accounts = peekPersistedBrokerAccounts(resources, instance);
22
23
  if (accounts?.length) brokerAccounts[instance.id] = accounts;
23
- } catch {
24
+ } catch (error) {
24
25
  // An unreadable snapshot leaves the portfolio without account figures.
26
+ logFailure("portfolio-list.cached-account", "Read cached broker accounts failed")(error);
25
27
  }
26
28
  }
27
29
  return brokerAccounts;
@@ -8,6 +8,7 @@ import { splitSymbolArgs, splitSymbolWords, symbolInputKey } from "../../../../c
8
8
  import { notASymbolError } from "../../../../cli/symbol-cells";
9
9
  import { readSymbolList, SymbolListError } from "../../../../cli/symbol-list";
10
10
  import { cliStyles } from "../../../../utils/cli-output";
11
+ import { shellWord } from "../../../../utils/shell-word";
11
12
  import { dryRunNote } from "../../../../cli/helpers";
12
13
  import {
13
14
  addSymbol,
@@ -46,14 +47,7 @@ export const SYMBOL_LIST_HELP = {
46
47
  ],
47
48
  };
48
49
 
49
- /**
50
- * One word of a command line as a shell reads it back: as written when it
51
- * holds nothing a shell treats specially (`NVDA`, `SAN:EPA`, `BRK.B`), else
52
- * in single quotes (`'Altria Group'`, `'$nvda'`, `'AT&T'`).
53
- */
54
- export function shellWord(word: string): string {
55
- return /^[\p{L}\p{N}._:@%+,/][\p{L}\p{N}._:@%+,/=-]*$/u.test(word) ? word : `'${word.replaceAll("'", "'\\''")}'`;
56
- }
50
+ export { shellWord };
57
51
 
58
52
  /**
59
53
  * A row's line, and under it the listing a company's name was read as, the
@@ -50,7 +50,7 @@ import { importSource, notionalPositions, rankThirteenFLines, type ThirteenFImpo
50
50
  import { importedFromText } from "../imported-from";
51
51
  import { savedListingCoinNote } from "../../../../cli/commands/crypto-hints";
52
52
  import { findNamedCollection } from "../../../../portfolio/collection-name";
53
- import { wholeUnitQuantityProblem } from "../../../../portfolio/position-quantity";
53
+ import { ambiguousQuantityProblem } from "../../../../portfolio/position-quantity";
54
54
  import {
55
55
  CLI_COST_FX_WORDING, loadPurchaseDateCostFx, readCostFxRate, readPurchaseDate, resolvePositionCostFx, type CostFxHistoryClient,
56
56
  } from "../../../../portfolio/cost-fx";
@@ -363,8 +363,8 @@ async function setPositionCommand(
363
363
  const avgCost = parseAmountArg(avgCostValue, "Average cost");
364
364
  // The notes say which listing a bare symbol went to, and that a bare coin ticker is not the coin, as `portfolio add` does.
365
365
  const { ticker, notes } = await resolveCliTicker(symbol, store, dataProvider, exchange);
366
- // `1.250` on a listing held in whole lots asks which was meant, as `1,250` did above.
367
- const lots = wholeUnitQuantityProblem(sharesValue, ticker, { listing: savedListingName(ticker).key });
366
+ // `1.250` and `1.000` ask which was meant, as `1,250` did above: a dot before three digits is as often thousands.
367
+ const lots = ambiguousQuantityProblem(sharesValue, ticker, { listing: savedListingName(ticker).key });
368
368
  if (lots) throw new PortfolioCliError(lots);
369
369
  // A future is held in contracts of the size its terms give, as `quote` states it, and a cost typed as
370
370
  // corn is quoted counts cents.
@@ -942,7 +942,7 @@ const AMOUNTS_HELP = {
942
942
  lines: [
943
943
  "Numbers take either decimal mark: 1.234,5, 1234,5 and 1,234.5 are 1234.5, and 18,50 is 18.5.",
944
944
  "Cash and --notional are amounts of a currency (cash in the portfolio's unless named, --notional in USD), so 500,000 and 1.234 are thousands, a third decimal being below a cent. In a currency with three decimals (KWD, BHD, OMR) they could be either, and the command asks which.",
945
- "Shares, contracts and average cost read a lone dot as a decimal point (123.456) and ask which a lone comma before three digits meant (1,234). On a listing held in whole lots (shares listed outside the US, a future's contracts) a lone dot before three digits (1.250) asks too.",
945
+ "Average cost reads a lone dot as a decimal point (123.456). Shares and contracts ask which a lone dot or comma before three digits meant (1.000, 1,234), thousands or a decimal, and name both readings to type.",
946
946
  ],
947
947
  };
948
948
 
@@ -1,3 +1,4 @@
1
+ import { logFailure } from "../../../../utils/log-failure";
1
2
  import { createBaseConverter } from "../../../../cli/base-converter";
2
3
  import { savedListingName } from "../../../../cli/listing-arg";
3
4
  import { currentListingName } from "../../../../tickers/listing-name";
@@ -257,8 +258,9 @@ export async function loadCollectionQuotes(
257
258
  const quote = await dataProvider.getQuote(ticker.metadata.ticker, ticker.metadata.exchange,
258
259
  datesChangeBasis ? { ...context, datesChangeBasis } : context);
259
260
  quotes.set(ticker.metadata.ticker, quote);
260
- } catch {
261
- // Ignore partial quote failures so the rest of the table still renders.
261
+ } catch (error) {
262
+ // A partial quote failure leaves the rest of the table rendering.
263
+ logFailure("portfolio-list.cli.render", "Load collection quote failed")(error);
262
264
  }
263
265
  }),
264
266
  );
@@ -1269,7 +1271,7 @@ async function loadMarketCapFinancials(
1269
1271
  const targets = needed.map((ticker) => ({ symbol: ticker.metadata.ticker, exchange: ticker.metadata.exchange }));
1270
1272
  if (dataProvider.getTickerFinancialsBatch) {
1271
1273
  await Promise.all(chunked(targets, FINANCIALS_BATCH_SIZE).map(async (batch) => {
1272
- const results = await dataProvider.getTickerFinancialsBatch!(batch).catch(() => []);
1274
+ const results = await dataProvider.getTickerFinancialsBatch!(batch).catch(logFailure("portfolio-list.cli.render", "Load market cap financials batch failed", []));
1273
1275
  batch.forEach((target, index) => {
1274
1276
  const value = (results.find((result) => result.target === target) ?? results[index])?.financials;
1275
1277
  if (value) financials.set(target.symbol, value);
@@ -1278,7 +1280,7 @@ async function loadMarketCapFinancials(
1278
1280
  return financials;
1279
1281
  }
1280
1282
  await mapWithConcurrency(targets, FINANCIALS_CONCURRENCY, async (target) => {
1281
- const value = await dataProvider.getTickerFinancials(target.symbol, target.exchange).catch(() => null);
1283
+ const value = await dataProvider.getTickerFinancials(target.symbol, target.exchange).catch(logFailure("portfolio-list.cli.render", "Load market cap financials failed", null));
1282
1284
  if (value) financials.set(target.symbol, value);
1283
1285
  });
1284
1286
  return financials;
@@ -1298,7 +1300,7 @@ async function loadPerpetualFunding(tickers: readonly TickerRecord[]): Promise<M
1298
1300
  });
1299
1301
  const funding = new Map<string, number | null>();
1300
1302
  await mapWithConcurrency(markets, PERPETUAL_CONCURRENCY, async ({ symbol, listing }) => {
1301
- funding.set(symbol, await loadPerpetualMarket(listing).then((market) => market?.row.fundingRate8h ?? null, () => null));
1303
+ funding.set(symbol, await loadPerpetualMarket(listing).then((market) => market?.row.fundingRate8h ?? null, logFailure("portfolio-list.cli.render", "Load perpetual funding failed", null)));
1302
1304
  });
1303
1305
  return funding;
1304
1306
  }
@@ -0,0 +1,40 @@
1
+ import { useEffect, useMemo } from "react";
2
+ import { getSharedMarketDataCoordinator } from "../../../../market-data/coordinator";
3
+ import { formatMarketPrice, quoteFormatOptions, withCurrencyMinorDigits } from "../../../../market-data/market/format";
4
+ import { buildQuoteKey, instrumentFromQuoteTarget, resolveEntryData } from "../../../../market-data/selectors";
5
+ import type { PortfolioFuturesTerms } from "../../../../portfolio/futures-positions";
6
+ import { useLiveQuoteEntries } from "../../../../state/hooks/quote-streaming";
7
+ import type { QuoteSubscriptionTarget } from "../../../../types/data-provider";
8
+ import { logFailure } from "../../../../utils/log-failure";
9
+
10
+ /** Front references share one snapshot and stream per contract, independent of each row's own price. */
11
+ export function useFuturesFrontNotes(
12
+ terms: ReadonlyMap<string, PortfolioFuturesTerms>,
13
+ { enabled, liveStreaming }: { enabled: boolean; liveStreaming: boolean },
14
+ ): ReadonlyMap<string, string> {
15
+ const symbolsKey = useMemo(() => enabled
16
+ ? [...new Set([...terms.values()].flatMap((value) => value.front?.contractSymbol ?? []))].sort().join("\u001f") : "", [enabled, terms]);
17
+ const targets = useMemo<QuoteSubscriptionTarget[]>(() => symbolsKey ? symbolsKey.split("\u001f").map((symbol) => ({
18
+ symbol, exchange: "", route: "provider", surface: "watchlist", visible: true,
19
+ })) : [], [symbolsKey]);
20
+ const instruments = useMemo(() => targets.map(instrumentFromQuoteTarget), [targets]);
21
+ const coordinator = getSharedMarketDataCoordinator();
22
+ const { entries } = useLiveQuoteEntries(targets, { liveStreaming });
23
+ useEffect(() => {
24
+ // Polling primes its own batch immediately; a stream needs one snapshot while connecting.
25
+ if (!liveStreaming || !coordinator || !instruments.length) return;
26
+ void coordinator.loadQuotesBatch(instruments).catch(logFailure("portfolio-futures", "Read front contract quotes failed"));
27
+ }, [coordinator, instruments, liveStreaming]);
28
+ return useMemo(() => {
29
+ const notes = new Map<string, string>();
30
+ if (!enabled) return notes;
31
+ for (const [symbol, value] of terms) {
32
+ if (!value.front) continue;
33
+ const quote = resolveEntryData(entries.get(buildQuoteKey({ symbol: value.front.contractSymbol, exchange: "" })));
34
+ if (!quote || !Number.isFinite(quote.price)) continue;
35
+ const options = withCurrencyMinorDigits(quoteFormatOptions(quote, "FUTURE"), quote.currency);
36
+ notes.set(symbol, `front ${value.front.contract} ${formatMarketPrice(quote.price, options)}`);
37
+ }
38
+ return notes;
39
+ }, [enabled, entries, terms]);
40
+ }
@@ -79,7 +79,7 @@ import {
79
79
  removeTickerFromCollection,
80
80
  tickerRemovalSummary,
81
81
  } from "./remove-ticker";
82
- import { QuickAddTickerInput, type QuickAddCollectionKind } from "../quick-add";
82
+ import { QUICK_ADD_KEY, QuickAddTickerInput, type QuickAddCollectionKind } from "../quick-add";
83
83
  import { delistingLabel } from "../../../../market-data/quotes/delisting";
84
84
  import {
85
85
  buildTrackedCurrencies,
@@ -104,6 +104,7 @@ import { hasUnknownOptionMultiplier } from "../../../../portfolio/position-metri
104
104
  import { withFuturesContractSizes } from "../../../../portfolio/futures-positions";
105
105
  import { withPurchaseDateCostFx } from "../../../../portfolio/cost-fx";
106
106
  import { usePortfolioFuturesTerms } from "./futures-terms";
107
+ import { useFuturesFrontNotes } from "./futures-front";
107
108
  import { usePurchaseDateCostFx } from "./purchase-date-fx";
108
109
  import {
109
110
  allocationWeightBase,
@@ -142,8 +143,6 @@ export function PortfolioListPane({ focused, width, height }: PaneProps) {
142
143
 
143
144
  const [streamWindow, setStreamWindow] = useState({ start: 0, end: 24 });
144
145
  const [quickAddFocused, setQuickAddFocused] = useState(false);
145
- // Bumped by the empty table's "+ ticker" button to open the add row.
146
- const [quickAddRequest, setQuickAddRequest] = useState(0);
147
146
 
148
147
  const {
149
148
  cursorSymbol,
@@ -185,7 +184,8 @@ export function PortfolioListPane({ focused, width, height }: PaneProps) {
185
184
  [config.baseCurrency, currentPortfolio],
186
185
  );
187
186
  // A future counts contracts of the size its terms give, and at the close is valued at its settlement.
188
- const futuresTerms = usePortfolioFuturesTerms(collectionTickers, { enabled: isPortfolioTab, settlement: valuationBasis === "regular-close" });
187
+ const futuresTerms = usePortfolioFuturesTerms(collectionTickers, { enabled: true, settlement: isPortfolioTab && valuationBasis === "regular-close" });
188
+ const futuresFrontNotes = useFuturesFrontNotes(futuresTerms.terms, { enabled: appActive && viewMode === "table", liveStreaming });
189
189
  // A dated lot in another currency costs what its purchase date's FX close says in that currency, as portfolio show values it.
190
190
  const purchaseDateFx = usePurchaseDateCostFx(collectionTickers, { enabled: isPortfolioTab, portfolioId: activeCollectionId, into: totalsCurrency });
191
191
  const tickers = useMemo(
@@ -735,7 +735,6 @@ export function PortfolioListPane({ focused, width, height }: PaneProps) {
735
735
  return null;
736
736
  }, [activeCollectionId, config, currentPortfolio]);
737
737
  const showQuickAdd = !!(activeCollectionId && activeCollectionEntry && quickAddCollectionKind);
738
- const requestQuickAdd = useCallback(() => setQuickAddRequest((count) => count + 1), []);
739
738
  const quickAddHeight = showQuickAdd ? 1 : 0;
740
739
  const selectedFinancials = cursorSymbol ? financialsMap.get(cursorSymbol) : undefined;
741
740
  const selectedCap = liveMarketCapitalization(selectedFinancials?.quote, selectedFinancials?.fundamentals);
@@ -763,7 +762,6 @@ export function PortfolioListPane({ focused, width, height }: PaneProps) {
763
762
  width={width}
764
763
  onAdded={handleTickerAdded}
765
764
  onFocusChange={setQuickAddFocused}
766
- focusRequest={quickAddRequest}
767
765
  />
768
766
  ) : null;
769
767
 
@@ -796,6 +794,7 @@ export function PortfolioListPane({ focused, width, height }: PaneProps) {
796
794
  onCursorChange={handleCursorChange}
797
795
  financialsMap={financialsMap}
798
796
  columnContext={columnContext}
797
+ futuresFrontNotes={futuresFrontNotes}
799
798
  flashSymbols={flashSymbols}
800
799
  sinceLastSeen={sinceLastSeen}
801
800
  onRootKeyDown={handleTableKeyDown}
@@ -804,7 +803,7 @@ export function PortfolioListPane({ focused, width, height }: PaneProps) {
804
803
  visibleRangeBuffer={3}
805
804
  resetScrollKey={activeCollectionId}
806
805
  onRowActivate={handleRowActivate}
807
- onAddTicker={showQuickAdd ? requestQuickAdd : undefined}
806
+ addTickerShortcut={showQuickAdd ? QUICK_ADD_KEY : undefined}
808
807
  onOpenPerpetual={showsPerpetuals ? openPerpMarket : undefined}
809
808
  rootHeight={contentHeight}
810
809
  />
@@ -1,3 +1,4 @@
1
+ import { logFailure } from "../../../../utils/log-failure";
1
2
  import { useEffect, useMemo, useRef } from "react";
2
3
  import { getSharedMarketDataCoordinator } from "../../../../market-data/coordinator";
3
4
  import { instrumentFromTicker, quoteSubscriptionTargetFromTicker } from "../../../../market-data/request-types";
@@ -251,8 +252,9 @@ export function usePortfolioPaneStreaming({
251
252
  ? sharedCoordinator.loadSnapshotsBatch(normalSnapshotEntries.map((entry) => entry.instrument))
252
253
  : Promise.resolve(),
253
254
  ]);
254
- } catch {
255
+ } catch (error) {
255
256
  // Best-effort warmup for visible rows only.
257
+ logFailure("portfolio-list.pane.streaming", "Warm visible portfolio rows failed")(error);
256
258
  } finally {
257
259
  for (const entry of [...quoteEntries, ...forcedSnapshotEntries, ...normalSnapshotEntries]) {
258
260
  warmupInFlightRef.current.delete(entry.key);
@@ -304,8 +306,9 @@ export function usePortfolioPaneStreaming({
304
306
 
305
307
  try {
306
308
  await sharedCoordinator.loadQuotesBatch(quoteEntries.map((entry) => entry.instrument), { forceRefresh: true });
307
- } catch {
309
+ } catch (error) {
308
310
  // Best-effort watchdog for visible rows only.
311
+ logFailure("portfolio-list.pane.streaming", "Refresh visible portfolio quotes failed")(error);
309
312
  } finally {
310
313
  for (const entry of quoteEntries) {
311
314
  warmupInFlightRef.current.delete(entry.key);
@@ -1,3 +1,4 @@
1
+ import { logFailure } from "../../../../utils/log-failure";
1
2
  import { useEffect, useMemo, useRef, useState, type Dispatch, type SetStateAction } from "react";
2
3
  import type { ColumnConfig } from "../../../../types/config";
3
4
  import type { AnalystResearchData, CorporateActionsData, TickerFinancials } from "../../../../types/financials";
@@ -171,7 +172,7 @@ function useAnalystSupplemental(
171
172
  }
172
173
  })).then((entries) => {
173
174
  if (mountedRef.current) setState((current) => setAnalystValues(current, entries));
174
- }).catch(() => {});
175
+ }).catch(logFailure("portfolio-list.pane.supplemental", "Apply analyst research failed"));
175
176
  }, [enabled, mountedRef, provider, setState, state.analystResearch, targetsKey]);
176
177
  }
177
178
 
@@ -211,7 +212,7 @@ function useCorporateSupplemental(
211
212
  }
212
213
  })).then((entries) => {
213
214
  if (mountedRef.current) setState((current) => setCorporateValues(current, entries));
214
- }).catch(() => {});
215
+ }).catch(logFailure("portfolio-list.pane.supplemental", "Apply corporate actions failed"));
215
216
  }, [enabled, mountedRef, provider, setState, state.corporateActions, targetsKey]);
216
217
  }
217
218
 
@@ -112,7 +112,6 @@ export function QuickAddTickerInput({
112
112
  width,
113
113
  onAdded,
114
114
  onFocusChange,
115
- focusRequest = 0,
116
115
  }: {
117
116
  collectionId: string;
118
117
  collectionKind: QuickAddCollectionKind;
@@ -121,12 +120,6 @@ export function QuickAddTickerInput({
121
120
  width: number;
122
121
  onAdded: (symbol: string) => void;
123
122
  onFocusChange?: (focused: boolean) => void;
124
- /**
125
- * Bumped by a control outside the row, such as the empty table's
126
- * "+ ticker" button, to open it. A click there focuses the pane in the same
127
- * press, so the row opens once the pane is focused.
128
- */
129
- focusRequest?: number;
130
123
  }) {
131
124
  const dispatch = useAppDispatch();
132
125
  const { notify } = usePluginAppActions();
@@ -150,13 +143,6 @@ export function QuickAddTickerInput({
150
143
  setInputFocused(false);
151
144
  }, []);
152
145
 
153
- const handledFocusRequestRef = useRef(focusRequest);
154
- useEffect(() => {
155
- if (focusRequest === handledFocusRequestRef.current || !focused) return;
156
- handledFocusRequestRef.current = focusRequest;
157
- focusInput();
158
- }, [focusInput, focusRequest, focused]);
159
-
160
146
  // A symbol with no listing leaves the field, so the next one typed replaces
161
147
  // it instead of running on (OMV.VIREP.MC); the line under it still says what
162
148
  // failed until something is typed.
@@ -1,3 +1,4 @@
1
+ import { logFailure } from "../../../../utils/log-failure";
1
2
  import { getSharedRegistry } from "../../../registry";
2
3
  import {
3
4
  AmbiguousContractError,
@@ -21,7 +22,8 @@ import {
21
22
  } from "../../../../tickers/ticker-changes";
22
23
 
23
24
  const QUICK_ADD_MAX_QUERY_LENGTH = 32;
24
- const QUICK_ADD_SYMBOL_RE = /^[A-Z0-9][A-Z0-9.\-\s]*$/;
25
+ // A slash writes a pair as crypto exchanges and FX desks do: BTC/GBP, EUR/USD.
26
+ const QUICK_ADD_SYMBOL_RE = /^[A-Z0-9][A-Z0-9.\-/\s]*$/;
25
27
 
26
28
  export type QuickAddCollectionKind = "portfolio" | "watchlist";
27
29
 
@@ -178,7 +180,8 @@ export async function resolveQuickAddValidation({
178
180
  exchangeFromResolved(resolved),
179
181
  quoteContextFromResolved(resolved),
180
182
  );
181
- } catch {
183
+ } catch (error) {
184
+ logFailure("portfolio-list.quick-add.resolution", "Read quick-add quote failed")(error);
182
185
  quote = null;
183
186
  }
184
187
  }