@gloom-sh/terminal 0.19.2 → 0.19.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (472) hide show
  1. package/README.md +7 -1
  2. package/package.json +1 -1
  3. package/patches/electrobun@1.18.1.patch +108 -1
  4. package/src/account/upgrade-dialog.tsx +4 -23
  5. package/src/api-client/coalesce.ts +118 -0
  6. package/src/api-client/credit-boards.ts +13 -0
  7. package/src/api-client/crypto-etf-flows.ts +6 -0
  8. package/src/api-client/crypto-markets.ts +40 -0
  9. package/src/api-client/data.ts +201 -14
  10. package/src/api-client/equity-screener.ts +10 -0
  11. package/src/api-client/errors.ts +20 -0
  12. package/src/api-client/gpu.ts +29 -0
  13. package/src/api-client/index.ts +5 -2
  14. package/src/api-client/plan-access.ts +10 -1
  15. package/src/api-client/plan-rules.ts +35 -0
  16. package/src/api-client/research-activity.ts +1 -32
  17. package/src/api-client/types.ts +21 -1
  18. package/src/app/attention-notice.tsx +94 -0
  19. package/src/app.tsx +23 -1
  20. package/src/architecture/core-builtin-allowlist.json +2 -1
  21. package/src/capabilities/factories.ts +1 -0
  22. package/src/cli/ascii-output.ts +42 -0
  23. package/src/cli/base-converter.ts +10 -2
  24. package/src/cli/command-options.ts +16 -1
  25. package/src/cli/commands/crypto-hints.ts +23 -4
  26. package/src/cli/commands/market.ts +92 -23
  27. package/src/cli/commands/news-search.ts +24 -5
  28. package/src/cli/commands/overview.ts +9 -4
  29. package/src/cli/commands/search.ts +71 -12
  30. package/src/cli/commands/system.ts +22 -30
  31. package/src/cli/commands/ticker.ts +49 -9
  32. package/src/cli/commands/version.ts +33 -0
  33. package/src/cli/context.ts +5 -9
  34. package/src/cli/data-dir.ts +7 -0
  35. package/src/cli/desktop-pane-shot-api-policy.ts +85 -0
  36. package/src/cli/desktop-pane-shot-browser.ts +320 -0
  37. package/src/cli/desktop-pane-shot-routes.ts +26 -1
  38. package/src/cli/desktop-pane-shot.ts +196 -256
  39. package/src/cli/early-command.ts +42 -0
  40. package/src/cli/earnings-calendar.ts +9 -0
  41. package/src/cli/entry.ts +19 -13
  42. package/src/cli/errors.ts +12 -2
  43. package/src/cli/help.ts +1 -1
  44. package/src/cli/history-export.ts +22 -2
  45. package/src/cli/history-rows.ts +154 -4
  46. package/src/cli/index.ts +18 -1
  47. package/src/cli/known-gaps.ts +40 -5
  48. package/src/cli/listing-arg.ts +46 -9
  49. package/src/cli/options.ts +1 -1
  50. package/src/cli/output-file.ts +60 -13
  51. package/src/cli/pane-functions/catalog.ts +14 -3
  52. package/src/cli/pane-functions/data.ts +1 -1
  53. package/src/cli/pane-functions/dom.ts +45 -0
  54. package/src/cli/pane-functions/freshness.ts +46 -9
  55. package/src/cli/pane-functions/function-help.ts +17 -9
  56. package/src/cli/pane-functions/headless.ts +22 -14
  57. package/src/cli/pane-functions/index.ts +6 -2
  58. package/src/cli/pane-functions/options.ts +6 -2
  59. package/src/cli/pane-functions/resolver.ts +27 -5
  60. package/src/cli/pane-functions/screenshot-assessment.ts +33 -0
  61. package/src/cli/pane-functions/screenshot-bridge.ts +94 -13
  62. package/src/cli/pane-functions/screenshot-http.ts +70 -0
  63. package/src/cli/pane-functions/screenshot-render.ts +19 -5
  64. package/src/cli/quote-answer.ts +20 -0
  65. package/src/cli/registry.ts +41 -13
  66. package/src/cli/result.ts +12 -6
  67. package/src/cli/ticker-resolution.ts +2 -1
  68. package/src/cli/types.ts +2 -0
  69. package/src/components/chart/composite/format.ts +3 -0
  70. package/src/components/chart-table/header.tsx +1 -1
  71. package/src/components/chart-table/layout.ts +28 -11
  72. package/src/components/command-bar/action-runtime.ts +4 -0
  73. package/src/components/command-bar/commands/direct/index.ts +18 -33
  74. package/src/components/command-bar/commands/direct/runtime.ts +5 -0
  75. package/src/components/command-bar/commands/registry.ts +10 -1
  76. package/src/components/command-bar/routes/root/command-items.ts +9 -4
  77. package/src/components/command-bar/routes/root/runtime.ts +24 -1
  78. package/src/components/command-bar/surface/index.tsx +9 -3
  79. package/src/components/company-logo.tsx +2 -1
  80. package/src/components/layout/header.tsx +8 -3
  81. package/src/components/layout/market-summary.ts +25 -7
  82. package/src/components/layout/pane/footer/index.tsx +7 -2
  83. package/src/components/layout/pane/footer/model.ts +40 -5
  84. package/src/components/market-board/index.tsx +3 -2
  85. package/src/components/onboarding/desks-step.tsx +12 -4
  86. package/src/components/onboarding/first-run-workspace.ts +81 -4
  87. package/src/components/onboarding/onboarding-frame.tsx +2 -1
  88. package/src/components/onboarding/onboarding-keyboard.ts +8 -5
  89. package/src/components/onboarding/onboarding-wizard.tsx +54 -23
  90. package/src/components/onboarding/portfolio-step/positions-panel.tsx +7 -2
  91. package/src/components/onboarding/run-setup-again.ts +24 -0
  92. package/src/components/onboarding/use-onboarding-progress.ts +33 -15
  93. package/src/components/onboarding/use-onboarding-upgrade.ts +2 -6
  94. package/src/components/onboarding/wizard-model.ts +57 -3
  95. package/src/components/onboarding/wizard-positions.ts +1 -1
  96. package/src/components/ui/delayed-mark.tsx +56 -0
  97. package/src/components/ui/external-link.tsx +3 -6
  98. package/src/components/ui/icon.tsx +8 -1
  99. package/src/components/ui/logo-mark.tsx +8 -5
  100. package/src/components/ui/stat-grid.tsx +263 -19
  101. package/src/data/config/store/node.ts +11 -5
  102. package/src/data/config/store/normalize.ts +16 -3
  103. package/src/data/profile-permissions.ts +47 -0
  104. package/src/data/resource-store.ts +34 -31
  105. package/src/data/sqlite/access.ts +19 -8
  106. package/src/data/sqlite/checkpointer.ts +95 -8
  107. package/src/data/sqlite/database.ts +40 -7
  108. package/src/docs/snapshot.json +191 -78
  109. package/src/i18n/es.ts +18 -2
  110. package/src/i18n/ja.ts +18 -2
  111. package/src/i18n/ko.ts +18 -2
  112. package/src/i18n/zh-cn.ts +18 -2
  113. package/src/i18n/zh-tw.ts +18 -2
  114. package/src/layout/desk-keys.ts +4 -1
  115. package/src/layout/desks.ts +38 -5
  116. package/src/layout-marketplace/gallery-desktop.tsx +45 -16
  117. package/src/market-data/batched-reads.ts +16 -0
  118. package/src/market-data/coordinator/auxiliary.ts +4 -2
  119. package/src/market-data/coordinator/index.ts +11 -2
  120. package/src/market-data/futures-board.ts +1 -1
  121. package/src/market-data/futures-contract.ts +3 -1
  122. package/src/market-data/market/index-level.ts +6 -4
  123. package/src/market-data/published-apac-sessions.ts +6 -0
  124. package/src/news/aggregator.ts +7 -4
  125. package/src/news/news-model.ts +11 -0
  126. package/src/news/story-id.ts +22 -0
  127. package/src/news/ticker-symbols.ts +17 -1
  128. package/src/notifications/app-notifier.ts +35 -14
  129. package/src/plugins/builtin/account-management/form-components.tsx +1 -1
  130. package/src/plugins/builtin/account-management/index.tsx +2 -3
  131. package/src/plugins/builtin/account-management/model.ts +1 -1
  132. package/src/plugins/builtin/alerts/cli-forms.ts +15 -1
  133. package/src/plugins/builtin/alerts/events-pane.tsx +7 -5
  134. package/src/plugins/builtin/alerts/events.ts +10 -2
  135. package/src/plugins/builtin/alerts/index.tsx +5 -5
  136. package/src/plugins/builtin/alerts/research-builder.ts +5 -1
  137. package/src/plugins/builtin/alerts/research-rules.ts +18 -0
  138. package/src/plugins/builtin/alerts/targets.ts +4 -1
  139. package/src/plugins/builtin/attention/headless.ts +1 -1
  140. package/src/plugins/builtin/attention/index.tsx +2 -4
  141. package/src/plugins/builtin/attention/listing.ts +1 -1
  142. package/src/plugins/builtin/attention/series.ts +1 -1
  143. package/src/plugins/builtin/awards/index.tsx +1 -2
  144. package/src/plugins/builtin/backtest/client.ts +1 -0
  145. package/src/plugins/builtin/backtest/index.tsx +1 -2
  146. package/src/plugins/builtin/backtest/rules.ts +3 -1
  147. package/src/plugins/builtin/backtest/window.ts +8 -7
  148. package/src/plugins/builtin/broker-manager/detail.tsx +16 -3
  149. package/src/plugins/builtin/broker-manager/index.tsx +1 -1
  150. package/src/plugins/builtin/buildout/cells.tsx +5 -0
  151. package/src/plugins/builtin/buildout/company/flow.tsx +367 -0
  152. package/src/plugins/builtin/buildout/company/id.ts +2 -0
  153. package/src/plugins/builtin/buildout/company/model.ts +247 -0
  154. package/src/plugins/builtin/buildout/company/pane.tsx +317 -0
  155. package/src/plugins/builtin/buildout/data-runtime.ts +31 -10
  156. package/src/plugins/builtin/buildout/facilities.ts +309 -0
  157. package/src/plugins/builtin/buildout/format.ts +9 -0
  158. package/src/plugins/builtin/buildout/index.tsx +51 -5
  159. package/src/plugins/builtin/buildout/model/index.ts +62 -10
  160. package/src/plugins/builtin/buildout/model/normalizers.ts +5 -0
  161. package/src/plugins/builtin/buildout/model/types.ts +11 -2
  162. package/src/plugins/builtin/buildout/pane/index.tsx +91 -20
  163. package/src/plugins/builtin/buildout/pane/status.tsx +1 -1
  164. package/src/plugins/builtin/buildout/pane/themes.tsx +110 -0
  165. package/src/plugins/builtin/buildout/table-model.ts +26 -2
  166. package/src/plugins/builtin/builtin-plugin-meta.ts +16 -0
  167. package/src/plugins/builtin/cds/headless.ts +10 -2
  168. package/src/plugins/builtin/cds/model.ts +7 -3
  169. package/src/plugins/builtin/central-bank-rates/index.tsx +1 -2
  170. package/src/plugins/builtin/central-bank-rates/pane.tsx +2 -1
  171. package/src/plugins/builtin/changelog/index.tsx +3 -2
  172. package/src/plugins/builtin/chart-composer/headless-schema.ts +7 -2
  173. package/src/plugins/builtin/chart-composer/headless.ts +88 -21
  174. package/src/plugins/builtin/chart-composer/index.tsx +2 -3
  175. package/src/plugins/builtin/chart-composer/pane.tsx +26 -2
  176. package/src/plugins/builtin/chart-composer/series-expression.ts +3 -2
  177. package/src/plugins/builtin/chart-composer/studies.ts +1 -1
  178. package/src/plugins/builtin/chat/controller/index.ts +1 -1
  179. package/src/plugins/builtin/chat/controller/messages.ts +15 -4
  180. package/src/plugins/builtin/chat/ghost-user.ts +8 -0
  181. package/src/plugins/builtin/chat/index.tsx +5 -5
  182. package/src/plugins/builtin/clinical-trials/index.tsx +1 -2
  183. package/src/plugins/builtin/cloud/company-picker.tsx +1 -1
  184. package/src/plugins/builtin/cloud/index.tsx +1 -2
  185. package/src/plugins/builtin/cloud/plugin.tsx +6 -4
  186. package/src/plugins/builtin/cloud/team/module.tsx +1 -1
  187. package/src/plugins/builtin/cloud/thesis/module.tsx +1 -1
  188. package/src/plugins/builtin/cloud/trial-offer-status-widget.tsx +24 -1
  189. package/src/plugins/builtin/cloud/upgrade-status-widget.tsx +39 -15
  190. package/src/plugins/builtin/comment-letters/index.tsx +1 -2
  191. package/src/plugins/builtin/commodity-balances/index.tsx +3 -3
  192. package/src/plugins/builtin/commodity-balances/pane.tsx +2 -2
  193. package/src/plugins/builtin/congress-trades/aggregates.ts +13 -2
  194. package/src/plugins/builtin/congress-trades/detail.tsx +20 -4
  195. package/src/plugins/builtin/congress-trades/headless.ts +2 -0
  196. package/src/plugins/builtin/congress-trades/model.ts +92 -64
  197. package/src/plugins/builtin/congress-trades/table.tsx +14 -2
  198. package/src/plugins/builtin/connections/index.ts +2 -3
  199. package/src/plugins/builtin/cot/index.tsx +1 -2
  200. package/src/plugins/builtin/cpi/index.tsx +1 -2
  201. package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
  202. package/src/plugins/builtin/credit-boards/client.ts +7 -2
  203. package/src/plugins/builtin/credit-boards/headless.ts +25 -12
  204. package/src/plugins/builtin/credit-boards/model.ts +29 -0
  205. package/src/plugins/builtin/credit-boards/sovr-pane.tsx +9 -4
  206. package/src/plugins/builtin/crypto-board/bitcoin-network.ts +143 -0
  207. package/src/plugins/builtin/crypto-board/headless.ts +4 -3
  208. package/src/plugins/builtin/crypto-board/index.tsx +4 -2
  209. package/src/plugins/builtin/crypto-board/series.ts +2 -0
  210. package/src/plugins/builtin/crypto-etf-flows/client.ts +9 -2
  211. package/src/plugins/builtin/crypto-etf-flows/headless.ts +61 -6
  212. package/src/plugins/builtin/crypto-etf-flows/index.tsx +1 -2
  213. package/src/plugins/builtin/crypto-etf-flows/model.ts +14 -1
  214. package/src/plugins/builtin/crypto-etf-flows/pane.tsx +8 -5
  215. package/src/plugins/builtin/debt-maturities/headless.ts +4 -3
  216. package/src/plugins/builtin/debt-maturities/model.ts +17 -1
  217. package/src/plugins/builtin/debt-maturities/pane.tsx +18 -4
  218. package/src/plugins/builtin/debug/index.tsx +1 -1
  219. package/src/plugins/builtin/dividend-yield/client.ts +32 -2
  220. package/src/plugins/builtin/dividend-yield/headless.ts +58 -33
  221. package/src/plugins/builtin/dividend-yield/index.tsx +37 -2
  222. package/src/plugins/builtin/dividend-yield/pane.tsx +32 -13
  223. package/src/plugins/builtin/dividend-yield/ranking-reads.ts +73 -0
  224. package/src/plugins/builtin/dividend-yield/ranking-view.tsx +163 -0
  225. package/src/plugins/builtin/dividend-yield/ranking.ts +1 -1
  226. package/src/plugins/builtin/dividend-yield/view.ts +21 -0
  227. package/src/plugins/builtin/docs/index.tsx +2 -3
  228. package/src/plugins/builtin/doe/headless.ts +16 -3
  229. package/src/plugins/builtin/doe/index.tsx +5 -4
  230. package/src/plugins/builtin/earnings/board.tsx +2 -1
  231. package/src/plugins/builtin/earnings/history.tsx +2 -1
  232. package/src/plugins/builtin/earnings-ripple/client.ts +32 -7
  233. package/src/plugins/builtin/earnings-ripple/index.tsx +1 -2
  234. package/src/plugins/builtin/econ/calendar-model.ts +29 -6
  235. package/src/plugins/builtin/econ-statistics/index.tsx +1 -2
  236. package/src/plugins/builtin/equity-screener/client.ts +1 -1
  237. package/src/plugins/builtin/equity-screener/headless.ts +1 -0
  238. package/src/plugins/builtin/equity-screener/ideas.ts +30 -1
  239. package/src/plugins/builtin/equity-screener/index.tsx +1 -2
  240. package/src/plugins/builtin/equity-screener/model.ts +28 -4
  241. package/src/plugins/builtin/equity-screener/pane.tsx +5 -0
  242. package/src/plugins/builtin/equity-screener/screen-check.ts +23 -1
  243. package/src/plugins/builtin/estimate-revisions/index.tsx +1 -2
  244. package/src/plugins/builtin/exposure/holdings.ts +35 -13
  245. package/src/plugins/builtin/exposure/index.tsx +1 -2
  246. package/src/plugins/builtin/fear-greed/cli.ts +44 -0
  247. package/src/plugins/builtin/fear-greed/index.tsx +5 -53
  248. package/src/plugins/builtin/fear-greed/pane.tsx +2 -1
  249. package/src/plugins/builtin/fixed-income/index.tsx +1 -2
  250. package/src/plugins/builtin/futures/board-reads.ts +25 -0
  251. package/src/plugins/builtin/futures/coalesced-bump.ts +44 -0
  252. package/src/plugins/builtin/futures/index.tsx +15 -0
  253. package/src/plugins/builtin/futures/model.ts +14 -0
  254. package/src/plugins/builtin/futures/use-board-terms.ts +22 -15
  255. package/src/plugins/builtin/futures/use-front-returns.ts +20 -14
  256. package/src/plugins/builtin/futures-curve/headless.ts +6 -1
  257. package/src/plugins/builtin/futures-curve/index.tsx +1 -2
  258. package/src/plugins/builtin/fx-matrix/index.tsx +2 -2
  259. package/src/plugins/builtin/gpu/board.tsx +51 -12
  260. package/src/plugins/builtin/gpu/client.ts +15 -2
  261. package/src/plugins/builtin/gpu/equities.tsx +5 -2
  262. package/src/plugins/builtin/gpu/headless.ts +14 -2
  263. package/src/plugins/builtin/gpu/index.tsx +1 -2
  264. package/src/plugins/builtin/gpu/model.ts +20 -2
  265. package/src/plugins/builtin/gpu/pane.tsx +1 -1
  266. package/src/plugins/builtin/help/index.tsx +1 -1
  267. package/src/plugins/builtin/holders/index-holders.ts +24 -12
  268. package/src/plugins/builtin/holders/index.tsx +2 -3
  269. package/src/plugins/builtin/ipo-calendar/index.tsx +1 -2
  270. package/src/plugins/builtin/iv-history/index.tsx +1 -2
  271. package/src/plugins/builtin/jodi/index.tsx +1 -2
  272. package/src/plugins/builtin/kelly-sizer/index.tsx +1 -2
  273. package/src/plugins/builtin/layout-manager/index.tsx +9 -8
  274. package/src/plugins/builtin/macro-day/pane.tsx +4 -2
  275. package/src/plugins/builtin/market-halts/index.tsx +1 -2
  276. package/src/plugins/builtin/market-heatmap/index.tsx +11 -2
  277. package/src/plugins/builtin/market-movers/index.tsx +7 -1
  278. package/src/plugins/builtin/market-movers/session-body.tsx +4 -1
  279. package/src/plugins/builtin/market-valuation/index.tsx +1 -2
  280. package/src/plugins/builtin/members/index.tsx +1 -2
  281. package/src/plugins/builtin/money-markets/index.tsx +1 -2
  282. package/src/plugins/builtin/money-markets/pane.tsx +2 -1
  283. package/src/plugins/builtin/news/headless.ts +7 -0
  284. package/src/plugins/builtin/news/wire/index.tsx +1 -1
  285. package/src/plugins/builtin/news/wire/news/pop-out.tsx +9 -3
  286. package/src/plugins/builtin/notes/index.tsx +3 -6
  287. package/src/plugins/builtin/openfda/index.tsx +1 -2
  288. package/src/plugins/builtin/options/view.tsx +6 -5
  289. package/src/plugins/builtin/options-calculator/index.tsx +1 -2
  290. package/src/plugins/builtin/options-positioning/index.tsx +1 -2
  291. package/src/plugins/builtin/options-scenario/index.tsx +2 -4
  292. package/src/plugins/builtin/perps/index.tsx +1 -2
  293. package/src/plugins/builtin/plugin-marketplace/index.tsx +2 -3
  294. package/src/plugins/builtin/plugin-marketplace/pane.tsx +2 -1
  295. package/src/plugins/builtin/plugin-module.ts +16 -2
  296. package/src/plugins/builtin/portfolio-list/cli/render.ts +53 -3
  297. package/src/plugins/builtin/portfolio-list/index.tsx +1 -2
  298. package/src/plugins/builtin/portfolio-list/pane/index.tsx +11 -0
  299. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -2
  300. package/src/plugins/builtin/power/index.tsx +1 -2
  301. package/src/plugins/builtin/rate-path/index.tsx +1 -2
  302. package/src/plugins/builtin/realized-vol/headless.ts +2 -0
  303. package/src/plugins/builtin/realized-vol/index.tsx +1 -2
  304. package/src/plugins/builtin/realized-vol/model.ts +16 -2
  305. package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
  306. package/src/plugins/builtin/relative-rotation/index.tsx +1 -2
  307. package/src/plugins/builtin/research/analyst-headless.ts +33 -6
  308. package/src/plugins/builtin/research/analyst-model.ts +37 -2
  309. package/src/plugins/builtin/research/analyst-pane.tsx +25 -5
  310. package/src/plugins/builtin/research/index.tsx +5 -8
  311. package/src/plugins/builtin/research-search/index.tsx +1 -2
  312. package/src/plugins/builtin/revenue-breakdown/headless.ts +22 -8
  313. package/src/plugins/builtin/revenue-breakdown/model.ts +23 -0
  314. package/src/plugins/builtin/reverse-dcf/pane.tsx +4 -2
  315. package/src/plugins/builtin/roadmap/index.tsx +2 -3
  316. package/src/plugins/builtin/roadmap/pane.tsx +49 -2
  317. package/src/plugins/builtin/scanner/hilo-model.ts +18 -1
  318. package/src/plugins/builtin/scanner/hilo-pane.tsx +12 -2
  319. package/src/plugins/builtin/scanner/live-pane.tsx +7 -4
  320. package/src/plugins/builtin/sec/filing-content.ts +38 -6
  321. package/src/plugins/builtin/sec/headless.ts +5 -1
  322. package/src/plugins/builtin/sec/index.tsx +4 -2
  323. package/src/plugins/builtin/sec/model.ts +2 -0
  324. package/src/plugins/builtin/sectors/index.tsx +11 -5
  325. package/src/plugins/builtin/series-inventory.ts +2 -0
  326. package/src/plugins/builtin/shared/cloud-resource.ts +60 -0
  327. package/src/plugins/builtin/shared/cloud-upgrade.ts +24 -1
  328. package/src/plugins/builtin/shared/forming-bars.ts +29 -0
  329. package/src/plugins/builtin/shared/fx-rate-freshness.ts +17 -0
  330. package/src/plugins/builtin/shared/series-detail-chart.tsx +4 -2
  331. package/src/plugins/builtin/shared/use-quote-board.ts +5 -0
  332. package/src/plugins/builtin/short-interest/publications-pane.tsx +2 -1
  333. package/src/plugins/builtin/short-volume/pane.tsx +2 -1
  334. package/src/plugins/builtin/social-mentions/index.tsx +1 -2
  335. package/src/plugins/builtin/social-mentions/pane.tsx +2 -1
  336. package/src/plugins/builtin/supply-chain/client.ts +22 -1
  337. package/src/plugins/builtin/supply-chain/graph-client.ts +22 -1
  338. package/src/plugins/builtin/supply-chain/index.tsx +1 -2
  339. package/src/plugins/builtin/thirteenf/api.ts +116 -13
  340. package/src/plugins/builtin/thirteenf/data.ts +132 -30
  341. package/src/plugins/builtin/thirteenf/index.tsx +2 -4
  342. package/src/plugins/builtin/thirteenf/model.ts +2 -0
  343. package/src/plugins/builtin/thirteenf/pane.tsx +20 -11
  344. package/src/plugins/builtin/thirteenf/types.ts +12 -0
  345. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +17 -4
  346. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +81 -11
  347. package/src/plugins/builtin/ticker-detail/headless.ts +11 -3
  348. package/src/plugins/builtin/ticker-detail/index.tsx +5 -10
  349. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +16 -6
  350. package/src/plugins/builtin/time-sales/index.tsx +1 -2
  351. package/src/plugins/builtin/time-sales/pane.tsx +3 -1
  352. package/src/plugins/builtin/trace-bonds/headless.ts +11 -2
  353. package/src/plugins/builtin/trace-bonds/model.ts +44 -10
  354. package/src/plugins/builtin/trace-bonds/pane.tsx +12 -7
  355. package/src/plugins/builtin/treasury-auctions/pane.tsx +2 -2
  356. package/src/plugins/builtin/vol-surface/client.ts +18 -3
  357. package/src/plugins/builtin/vol-surface/index.tsx +1 -2
  358. package/src/plugins/builtin/volatility/index.tsx +1 -2
  359. package/src/plugins/builtin/world-indices/footer.ts +6 -1
  360. package/src/plugins/builtin/world-venue-map/client.ts +10 -1
  361. package/src/plugins/builtin/world-venue-map/index.ts +6 -2
  362. package/src/plugins/builtin/world-venue-map/layered-pane.tsx +48 -7
  363. package/src/plugins/builtin/world-venue-map/layers.ts +17 -0
  364. package/src/plugins/builtin/world-venue-map/local-layers.ts +141 -0
  365. package/src/plugins/builtin/world-venue-map/map-symbols.ts +18 -0
  366. package/src/plugins/builtin/world-venue-map/map.tsx +4 -2
  367. package/src/plugins/builtin/world-venue-map/scope.ts +63 -0
  368. package/src/plugins/builtin/yield-curve/curves.ts +16 -2
  369. package/src/plugins/builtin/yield-curve/headless.ts +3 -3
  370. package/src/plugins/bundle.ts +52 -2
  371. package/src/plugins/catalog-browser.ts +2 -2
  372. package/src/plugins/catalog-cli.ts +20 -0
  373. package/src/plugins/host-link.ts +13 -1
  374. package/src/plugins/installer.ts +15 -4
  375. package/src/plugins/loader.ts +43 -11
  376. package/src/plugins/peer-imports.ts +201 -0
  377. package/src/plugins/peer-retry.ts +26 -29
  378. package/src/plugins/plugin-process.ts +128 -0
  379. package/src/plugins/redirection-guard.ts +45 -0
  380. package/src/remote/endpoint.ts +16 -3
  381. package/src/renderers/browser/main.tsx +3 -2
  382. package/src/renderers/cloudflare/http-proxy.ts +7 -123
  383. package/src/renderers/dom/desktop/icons.tsx +2 -0
  384. package/src/renderers/dom/host/image-surface.tsx +46 -21
  385. package/src/renderers/dom/styles.css +7 -0
  386. package/src/renderers/dom/toast-host.tsx +2 -0
  387. package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
  388. package/src/renderers/electrobun/bun/desktop/initialization.ts +3 -2
  389. package/src/renderers/electrobun/bun/desktop/rpc-timing.ts +88 -0
  390. package/src/renderers/electrobun/bun/desktop/update.ts +20 -4
  391. package/src/renderers/electrobun/bun/desktop/view-ids.ts +8 -4
  392. package/src/renderers/electrobun/bun/external-plugins.ts +4 -0
  393. package/src/renderers/electrobun/bun/window/create.ts +4 -2
  394. package/src/renderers/electrobun/bun/window/native-timing.ts +48 -0
  395. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +82 -4
  396. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +13 -5
  397. package/src/renderers/electrobun/view/desktop/window/snapshot.ts +3 -0
  398. package/src/renderers/electrobun/view/main.tsx +7 -2
  399. package/src/renderers/electrobun/view/remote/asset-data-client.ts +1 -0
  400. package/src/renderers/opentui/browser-open.ts +10 -4
  401. package/src/renderers/opentui/image/surface.tsx +1 -1
  402. package/src/renderers/opentui/start.tsx +12 -6
  403. package/src/renderers/opentui/toast-host.tsx +2 -1
  404. package/src/series/registry.ts +18 -0
  405. package/src/series/standalone.ts +10 -1
  406. package/src/series/types.ts +7 -0
  407. package/src/series/validate.ts +11 -2
  408. package/src/sources/gloomberb-cloud/index.ts +28 -5
  409. package/src/sources/gloomberb-cloud/news.ts +24 -3
  410. package/src/sources/gloomberb-cloud/normalizers.ts +17 -2
  411. package/src/sources/provider-errors.ts +26 -3
  412. package/src/sources/provider-router/history.ts +5 -0
  413. package/src/sources/provider-router/index.ts +13 -0
  414. package/src/sync/core-contributors.ts +32 -0
  415. package/src/telemetry/attention-counts.ts +64 -6
  416. package/src/telemetry/attention-setting.ts +33 -0
  417. package/src/telemetry/crash-reports-node.ts +3 -1
  418. package/src/telemetry/stall-clocks.ts +93 -0
  419. package/src/telemetry/stall-reports-node.ts +12 -7
  420. package/src/telemetry/stall-reports.ts +34 -5
  421. package/src/test-support/preload.ts +9 -0
  422. package/src/tickers/company-name.ts +19 -0
  423. package/src/tickers/listing-fate.ts +14 -3
  424. package/src/tickers/saved-instrument.ts +17 -0
  425. package/src/tickers/search/home-listing.ts +13 -2
  426. package/src/tickers/search/index.ts +4 -1
  427. package/src/tickers/search/types.ts +6 -1
  428. package/src/time-series/bar-aggregation.ts +7 -1
  429. package/src/time-series/base-series.ts +3 -3
  430. package/src/time-series/chart-bounds.ts +1 -1
  431. package/src/time-series/chart-data.ts +5 -2
  432. package/src/time-series/chart-seed.ts +1 -1
  433. package/src/time-series/coin-session-close.ts +211 -0
  434. package/src/time-series/live-bars.ts +13 -2
  435. package/src/time-series/live-quotes.ts +1 -1
  436. package/src/time-series/official-close.ts +159 -0
  437. package/src/time-series/price-comparison.ts +55 -6
  438. package/src/time-series/resolve.ts +12 -3
  439. package/src/time-series/series-presentation.ts +1 -1
  440. package/src/time-series/studies/formula.ts +205 -0
  441. package/src/time-series/studies/indicators.ts +299 -0
  442. package/src/time-series/studies/leverage.ts +75 -0
  443. package/src/time-series/studies/pair.ts +138 -0
  444. package/src/time-series/studies/resolve.ts +333 -0
  445. package/src/time-series/studies/shared.ts +141 -0
  446. package/src/time-series/studies/units.ts +243 -0
  447. package/src/time-series/studies/vwap.ts +274 -0
  448. package/src/types/config.ts +18 -2
  449. package/src/types/data-provider.ts +23 -0
  450. package/src/types/financials.ts +22 -0
  451. package/src/types/headless.ts +42 -3
  452. package/src/types/plugin.ts +32 -3
  453. package/src/ui/host.tsx +11 -0
  454. package/src/ui/toast.tsx +26 -1
  455. package/src/utils/ascii-text.ts +140 -0
  456. package/src/utils/cli-output.ts +91 -18
  457. package/src/utils/concurrency.ts +24 -0
  458. package/src/utils/exchanges.ts +6 -0
  459. package/src/utils/external-url.ts +20 -7
  460. package/src/utils/futures-generic.ts +4 -4
  461. package/src/utils/http-proxy-upstream.ts +124 -0
  462. package/src/utils/main-thread-stall.ts +75 -3
  463. package/src/utils/perf-marks.ts +36 -2
  464. package/src/utils/private-files.ts +141 -0
  465. package/src/utils/public-address.ts +99 -0
  466. package/src/account/upgrade-personalization.ts +0 -57
  467. package/src/account/upgrade-tickers.ts +0 -124
  468. package/src/api-client/upgrade-experiment.ts +0 -91
  469. package/src/plugins/builtin/cloud/browser.tsx +0 -3
  470. package/src/renderers/electrobun/launcher/preload.ts +0 -13
  471. package/src/renderers/electrobun/launcher/windows-emulation.ts +0 -50
  472. package/src/time-series/studies.ts +0 -1628
@@ -31,9 +31,12 @@ import type { TickerFinancials, PricePoint, Quote } from "../../../types/financi
31
31
  import { chartQuoteOverrideKeyForTarget, getLiveChartQuoteTargets } from "../../../time-series/live-quotes";
32
32
  import { createChartSeriesResolver } from "../../../capabilities";
33
33
  import { loadFuturesContractTerms, loadFuturesPriceUnit } from "../../../market-data/futures-contract";
34
- import { isUsListingExchange, parsePublicTickerKey, publicTickerKey } from "../../../utils/exchanges";
34
+ import { canonicalExchange, isKnownExchangeCode, isUsListingExchange, parsePublicTickerKey, publicTickerKey } from "../../../utils/exchanges";
35
35
  import { zonedDateTimeParts } from "../../../utils/zoned-date-time";
36
36
  import { futuresSessionOpenMinute } from "../../../market-data/market/trading-sessions";
37
+ import { isAlwaysOpenExchange, isRegularSessionTime } from "../../../market-data/market/freshness";
38
+ import { isRoundTheClockCoin } from "../../../utils/crypto-pair";
39
+ import { isCurrencyPairListing } from "../../../utils/fx-market-hours";
37
40
  import { cleanFloat32Price } from "../../../cli/history-rows";
38
41
  import { parseChartSpec } from "./chart-spec";
39
42
  import { paneSchemas } from "./headless-schema";
@@ -46,7 +49,8 @@ import { loadFundFacts } from "../sec/fund-facts";
46
49
  import { comparedFundTickers, loadDailyObjectives, mayDrawDailyObjective, withLeverageOverlay } from "./leverage-overlay";
47
50
  import { classifyInstrumentType } from "../../../tickers/instrument-kind";
48
51
  import { priceComparisonEndsIntraday, type PriceComparison } from "../../../time-series/price-comparison";
49
- import { LATEST_INTRADAY_TEXT } from "../../../market-data/market/period-return";
52
+ import { LATEST_INTRADAY_TEXT, periodDayText } from "../../../market-data/market/period-return";
53
+ import { CLOSING_PRINT_PENDING_TEXT, newestClosedSession, newestSessionClose, withOfficialCloses } from "../../../time-series/official-close";
50
54
 
51
55
  export interface ChartPaneModel extends HeadlessSeriesResult {
52
56
  /** The renderer consumes the full model; generic reports project series and metadata. */
@@ -368,10 +372,19 @@ function chartFreshness(
368
372
  const coarsest = resolutions.reduce((worst, resolution) => (
369
373
  order.indexOf(resolution!) > order.indexOf(worst!) ? resolution : worst
370
374
  ), resolutions[0]);
375
+ const closedDay = closedPriceBarDay(series);
376
+ // A window that ended before today (`--to 2026-10-23`) is the history asked for, which no later close makes stale:
377
+ // `daily closes through Fri 23 Oct`.
378
+ const windowEnd = spec.viewport.dateWindow?.end;
379
+ if (windowEnd && /^\d{4}-\d{2}-\d{2}$/.test(windowEnd) && windowEnd < new Date().toISOString().slice(0, 10)) {
380
+ const through = closedDay ?? series.flatMap((entry) => entry.points.at(-1)?.date ?? []).map((date) => new Date(date).toISOString().slice(0, 10)).sort().at(-1);
381
+ const basis = coarsest === "1d" ? "daily closes" : coarsest === "1wk" ? "weekly closes" : coarsest === "1mo" ? "monthly closes" : "intraday bars";
382
+ return { status: "not-a-feed", basis: through ? `${basis} through ${periodDayText(through, new Date().getUTCFullYear())}` : basis,
383
+ ...(through && closedDay ? { asOf: through } : {}), oldest: null };
384
+ }
371
385
  const forming = formingPriceBar(series);
372
386
  // A history: its first bars are not stale data, so the line dates it by the bar still forming alone.
373
387
  if (forming) return { ...barHistoryFreshness(coarsest), periodToDate: forming, oldest: null };
374
- const closedDay = closedPriceBarDay(series);
375
388
  // A comparison whose newest point is a session still trading is not a day's closes.
376
389
  const basis = coarsest === "1d" && priceComparisonEndsIntraday(comparison) ? { basis: LATEST_INTRADAY_TEXT } : {};
377
390
  // Daily bars are dated by the time of the newest point: a closed session's day, or when a session still trading was last quoted.
@@ -600,30 +613,34 @@ export function argumentWithListing(argument: string, symbol: string, key: strin
600
613
  }
601
614
 
602
615
  /**
603
- * A FRED series id typed where a ticker goes (`fn G DFII10`), as the chart of
604
- * FRED:DFII10: the catalog lists it, no listing answers to it, and the quote
605
- * source says it is not a ticker. A real ticker of the same letters, read as
606
- * a listing or quoted, still charts as the ticker; null then, and for any
607
- * other series.
616
+ * A catalog series typed where a ticker goes, as its chart: a FRED series id
617
+ * (`fn G DFII10` is FRED:DFII10), or a word a series lists as an alias
618
+ * (`fn G hashrate` is CAP:bitcoin:hashrate), when the catalog lists it, no
619
+ * listing answers to it, and the quote source says it is not a ticker. A
620
+ * real ticker of the same letters, read as a listing or quoted, still charts
621
+ * as the ticker; null then, and for any other symbol.
608
622
  */
609
- export async function bareFredSeries(
623
+ export async function bareCatalogSeries(
610
624
  series: ChartSeriesSpec,
611
625
  readListing: () => Promise<unknown>,
612
- context: Pick<HeadlessPaneContext, "marketData" | "catalogSeriesName">,
626
+ context: Pick<HeadlessPaneContext, "marketData" | "catalogSeriesName" | "catalogSeriesNamed">,
613
627
  ): Promise<ChartSeriesSpec | null> {
614
628
  const { source } = series;
615
629
  if (source.kind !== "security" || source.instrument.exchange || source.instrument.instrument || source.fieldId !== CHART_FIELD_IDS.price) return null;
616
630
  const id = source.instrument.symbol.trim().toUpperCase();
617
631
  const expression = `FRED:${id}`;
618
- const label = /^[A-Z0-9]+$/.test(id)
632
+ const fredLabel = /^[A-Z0-9]+$/.test(id)
619
633
  ? context.catalogSeriesName?.(expression) ?? getSharedRegistry()?.series?.findByExpression(expression)?.definition.name
620
634
  : undefined;
635
+ const named = fredLabel ? undefined : context.catalogSeriesNamed?.(id);
636
+ const parsed = named ? parseSeriesExpression(named.expression) : null;
637
+ const label = fredLabel ?? (parsed ? named?.name : undefined);
621
638
  if (!label || await readListing()) return null;
622
639
  const failure = await Promise.resolve().then(() => context.marketData.getQuote(id, ""))
623
640
  .then((quote) => quote ? null : `Not a ticker: ${id}.`, (error: unknown) => error instanceof Error ? error.message : String(error));
624
641
  if (!failure || !notATickerSymbol(failure)) return null;
625
- const fred = buildSeriesSpec({ kind: "economic", provider: "fred", seriesId: id, label }, 0);
626
- return { ...fred, id: series.id, transform: series.transform, axis: series.axis, panelId: series.panelId };
642
+ const spec = parsed ? buildSeriesSpec({ ...parsed, label }, 0) : buildSeriesSpec({ kind: "economic", provider: "fred", seriesId: id, label }, 0);
643
+ return { ...spec, id: series.id, transform: series.transform, axis: series.axis, panelId: series.panelId };
627
644
  }
628
645
 
629
646
  /**
@@ -646,14 +663,28 @@ async function possibleFundTickers(spec: ChartSpec, context: HeadlessPaneContext
646
663
  });
647
664
  }
648
665
 
666
+ /**
667
+ * `--exchange TSX`: every ticker series that names no venue, on that one, as
668
+ * `BDI:TSX` would be. A code the app does not know fails before any request.
669
+ */
670
+ function onExchange(spec: ChartSpec, exchange: string): ChartSpec {
671
+ const code = exchange.trim().toUpperCase();
672
+ if (!isKnownExchangeCode(code)) throw new Error(`Unknown exchange "${exchange.trim()}". Write a venue code such as TSX, LSE or XETRA, or name it in the series: BDI:TSX.`);
673
+ return { ...spec, series: spec.series.map((series) => series.source.kind !== "security" || series.source.instrument.exchange || series.source.instrument.instrument
674
+ ? series
675
+ : { ...series, source: { ...series.source, instrument: { ...series.source.instrument, exchange: canonicalExchange(code) } } }) };
676
+ }
677
+
649
678
  export function chartHeadless(template: keyof typeof paneSchemas): HeadlessPaneDefinition<"series"> {
650
679
  return {
651
680
  ...paneSchemas[template],
652
681
  shape: "series",
653
682
  async load(args, context) {
654
683
  const intradayHistories: ChartPaneModel["snapshot"]["intradayHistories"] = [];
655
- const saved = parseChartSpec(context.settings?.chartSpec);
656
- if (!saved) throw new Error("The chart specification is invalid.");
684
+ const closingPrintsPending: string[] = [];
685
+ const stored = parseChartSpec(context.settings?.chartSpec);
686
+ if (!stored) throw new Error("The chart specification is invalid.");
687
+ const saved = typeof args.options.exchange === "string" ? onExchange(stored, args.options.exchange) : stored;
657
688
  // A leveraged or inverse fund compared with anything draws its daily objective beside it.
658
689
  const parsed = template === "comparison-chart-pane" && mayDrawDailyObjective(saved)
659
690
  ? withLeverageOverlay(saved, await loadDailyObjectives(
@@ -680,9 +711,9 @@ export function chartHeadless(template: keyof typeof paneSchemas): HeadlessPaneD
680
711
  series: await Promise.all(parsed.series.map(async (series) => {
681
712
  if (series.source.kind !== "security" || series.source.instrument.exchange) return series;
682
713
  const { symbol } = series.source.instrument;
683
- // A FRED id no ticker answers to (DFII10) is its FRED series; an intraday chart has none.
684
- const fred = template === "graph-intraday-price-pane" ? null : await bareFredSeries(series, () => readListing(symbol), context);
685
- if (fred) return fred;
714
+ // A FRED id (DFII10) or a series alias (hashrate) no ticker answers to is that series; an intraday chart has none.
715
+ const catalog = template === "graph-intraday-price-pane" ? null : await bareCatalogSeries(series, () => readListing(symbol), context);
716
+ if (catalog) return catalog;
686
717
  if (!reads.has(symbol)) reads.set(symbol, readSeriesListing(symbol));
687
718
  const instrument = await reads.get(symbol)!;
688
719
  return instrument ? { ...series, source: { ...series.source, instrument: { ...series.source.instrument, ...instrument } } } : series;
@@ -709,6 +740,37 @@ export function chartHeadless(template: keyof typeof paneSchemas): HeadlessPaneD
709
740
  ...await loadIntradayWindow({ provider: context.marketData, symbol: target.symbol, exchange: target.exchange ?? "", request, context: { brokerId: target.brokerId, brokerInstanceId: target.brokerInstanceId, instrument: target.instrument } }),
710
741
  })));
711
742
  context.signal.throwIfAborted();
743
+ // Once a session has closed, its last bar ends on the official close (the closing auction's print), as
744
+ // `history` and CMP give it, not on the last trade before it; until the quote has that close, the footer says so.
745
+ await Promise.all(histories.map(async (history) => {
746
+ // A future settles rather than closing on an auction print, and a coin or a currency pair has no session
747
+ // to close: their bars stay as traded, and no quote is read for them.
748
+ if (!history.points.length || futuresSessionOpenMinute(history.symbol, history.exchange) != null
749
+ || isRoundTheClockCoin(history.symbol, history.exchange) || isCurrencyPairListing(history.symbol, history.exchange)) return;
750
+ const now = Date.now();
751
+ const known = history.exchange || history.quote?.listingExchangeName || history.quote?.exchangeName || undefined;
752
+ if (known && isAlwaysOpenExchange(known)) return;
753
+ // While the session trades, no session of the chart has closed since its bars were read.
754
+ if (history.quote?.marketState === "REGULAR" || (known && isRegularSessionTime(known, now) === true)) return;
755
+ if (known && !newestClosedSession(history.points, { venue: known, now })) return;
756
+ // A captured shot's source can have no quotes: its bars are drawn as captured.
757
+ const quote = history.quote ?? (typeof context.marketData.getQuote === "function"
758
+ ? await Promise.resolve(context.marketData.getQuote(history.symbol, history.exchange,
759
+ { brokerId: history.target.brokerId, brokerInstanceId: history.target.brokerInstanceId, instrument: history.target.instrument }))
760
+ .catch(logFailure("chart-composer.headless", "Read the official close of an intraday chart failed", null))
761
+ : null);
762
+ const venue = known || quote?.listingExchangeName || quote?.exchangeName || undefined;
763
+ const newest = newestClosedSession(history.points, { venue, now });
764
+ const decided = newest ? newestSessionClose(quote, { ...newest, venue }) : null;
765
+ if (!newest || !decided) return;
766
+ if ("pending" in decided) {
767
+ closingPrintsPending.push(history.symbol);
768
+ return;
769
+ }
770
+ const closes = new Map([[newest.date, decided.close]]);
771
+ history.points = withOfficialCloses(history.points, closes, { venue, now });
772
+ history.bufferedPoints = withOfficialCloses(history.bufferedPoints, closes, { venue, now });
773
+ }));
712
774
  intradayHistories.push(...histories);
713
775
  const starts = histories.flatMap(({ start }) => start ? [start.getTime()] : []);
714
776
  const ends = histories.flatMap(({ end }) => end ? [end.getTime()] : []);
@@ -725,9 +787,11 @@ export function chartHeadless(template: keyof typeof paneSchemas): HeadlessPaneD
725
787
  }, context.marketData) };
726
788
  }
727
789
  const model = await loadChartPaneModel(spec, context);
728
- // A bare coin ticker charted as another listing (ETH, the Grayscale fund) says where the coin is, as `quote` does.
729
- const coinNotes = (await Promise.all(listings.values())).flatMap((read) => read?.coinNote ?? []);
730
- if (coinNotes.length) model.warnings = [...model.warnings ?? [], ...coinNotes];
790
+ // What became of the company that had a bare symbol (GOGL), and a bare coin ticker charted as another listing
791
+ // (ETH, the Grayscale fund), are said as `quote` says them.
792
+ const readListings = await Promise.all(listings.values());
793
+ const listingNotes = [...readListings.flatMap((read) => read?.successorNote ?? []), ...readListings.flatMap((read) => read?.coinNote ?? [])];
794
+ if (listingNotes.length) model.warnings = [...model.warnings ?? [], ...listingNotes];
731
795
  if (template === "graph-intraday-price-pane" && !intradayHistories.length && model.chart.viewport) {
732
796
  const { start, end } = model.chart.viewport;
733
797
  for (const { target, data } of [
@@ -782,6 +846,9 @@ export function chartHeadless(template: keyof typeof paneSchemas): HeadlessPaneD
782
846
  model.metadata = { ...model.metadata, resolution: dropped.drop };
783
847
  }
784
848
  model.freshness = chartFreshness(model.series, model.spec, model.chart.priceComparison, chartQuoteTimes(model));
849
+ if (closingPrintsPending.length) {
850
+ model.freshness = { ...model.freshness, note: [model.freshness.note, CLOSING_PRINT_PENDING_TEXT].filter(Boolean).join(", ") };
851
+ }
785
852
  const priceDomainFailures = intradayHistories.flatMap(({ symbol, exchange, priceDomainFailure }) =>
786
853
  priceDomainFailure ? [{ symbol, exchange, ...priceDomainFailure }] : []);
787
854
  if (priceDomainFailures.length) {
@@ -11,7 +11,6 @@ import { canonicalExchange, parsePublicTickerKey, publicTickerKey } from "../../
11
11
  import { tickerInstrumentLabel } from "../../../tickers/instrument-label";
12
12
  import type { ChartSpec } from "../../../time-series/types";
13
13
  import { ChartComposerPane, ChartComposerResearchTab } from "./pane";
14
- import { DataCatalogPane } from "./data-catalog-pane";
15
14
  import { listFieldSeries } from "./field-series";
16
15
  import { registeredSeriesName } from "./series-expression";
17
16
  import {
@@ -404,7 +403,7 @@ const chartComposerTemplates: PaneTemplateDef[] = [
404
403
  "positioning",
405
404
  ],
406
405
  shortcut: { prefix: "CAT", argPlaceholder: "query", argKind: "text", argOptional: true },
407
- reportFreshness: { status: "not-a-feed", basis: "series catalog" },
406
+ reportFreshness: { status: "not-a-feed", basis: "series catalog", local: true },
408
407
  canCreate: () => true,
409
408
  createInstance: (_context, options) => {
410
409
  const query = options?.arg?.trim() ?? options?.values?.query?.trim() ?? "";
@@ -525,7 +524,7 @@ export const chartComposerModule: PluginModule = {
525
524
  id: DATA_CATALOG_PANE_ID,
526
525
  name: "Data Catalog",
527
526
  icon: "C",
528
- component: DataCatalogPane,
527
+ get component(): typeof import("./data-catalog-pane").DataCatalogPane { const { DataCatalogPane } = require("./data-catalog-pane"); return DataCatalogPane; },
529
528
  defaultPosition: "right",
530
529
  defaultMode: "floating",
531
530
  defaultFloatingSize: { width: 110, height: 32 },
@@ -141,7 +141,10 @@ import { alertInstrument, syncAlertQuoteStream } from "../alerts/live";
141
141
  import { getSharedMarketDataCoordinator } from "../../../market-data/coordinator";
142
142
  import { useOptionChartRange } from "./option-range";
143
143
  import { getActiveQuoteDisplay } from "../../../market-data/market/status";
144
- import { resolveEntryData } from "../../../market-data/selectors";
144
+ import { buildQuoteKey, resolveEntryData } from "../../../market-data/selectors";
145
+ import { useQuoteEntries } from "../../../market-data/hooks";
146
+ import { usePlanAccess } from "../../../api-client/plan-access";
147
+ import { useQuoteDelayFooter } from "../shared/cloud-upgrade";
145
148
  import { editPriceLevels, parsePriceLevels, PRICE_LEVELS_KEY, priceLevelTickerKey } from "./price-levels";
146
149
  import { canonicalExchange } from "../../../utils/exchanges";
147
150
  import { resolveInstrumentForPane } from "../../../core/state/app/instrument";
@@ -218,6 +221,8 @@ interface ChartComposerSurfaceProps {
218
221
  onCapture?: (capturing: boolean) => void;
219
222
  /** The legend names each series' company; a ticker's own research tab already does. */
220
223
  nameSeries?: boolean;
224
+ /** The footer's delayed clock; a ticker's research tab says it in its quote footer instead. */
225
+ delayFooter?: boolean;
221
226
  }
222
227
 
223
228
  const QUICK_ADD_CAPTURE = "quick-add";
@@ -307,6 +312,7 @@ function ChartComposerSurface({
307
312
  footerId,
308
313
  onCapture,
309
314
  nameSeries = true,
315
+ delayFooter = true,
310
316
  }: ChartComposerSurfaceProps) {
311
317
  const trade = useResearchTrading();
312
318
  const dialog = useDialog();
@@ -1119,6 +1125,22 @@ function ChartComposerSurface({
1119
1125
  }, [activeRuntimeViewport, plottedSeries, spec, viewport]);
1120
1126
  // A series a failed refresh left on a saved copy says when that copy was saved, as every pane does.
1121
1127
  const savedAt = resolution.savedAt;
1128
+ // Signed out or free, a market series' latest bars are built from delayed quotes.
1129
+ const marketInstruments = useMemo(() => (delayFooter ? spec.series.flatMap((entry) => (
1130
+ entry.source.kind === "security" && isMarketFieldId(entry.source.fieldId) ? [entry.source.instrument] : []
1131
+ )) : []), [delayFooter, spec.series]);
1132
+ const marketQuotes = useQuoteEntries(marketInstruments);
1133
+ // Signed out no stream feeds a chart's quotes: without Pro the chart reads them once to know which are late.
1134
+ const { hasProAccess } = usePlanAccess();
1135
+ const marketQuoteKey = marketInstruments.map(buildQuoteKey).join("\u001f");
1136
+ useEffect(() => {
1137
+ const coordinator = getSharedMarketDataCoordinator();
1138
+ if (hasProAccess || !coordinator) return;
1139
+ for (const instrument of marketInstruments) {
1140
+ void coordinator.loadQuote(instrument).catch(logFailure("chart-composer.pane", "Loading a chart quote failed"));
1141
+ }
1142
+ }, [hasProAccess, marketQuoteKey]);
1143
+ const quoteDelay = useQuoteDelayFooter(Array.from(marketQuotes.values(), resolveEntryData), "chart-delay");
1122
1144
  usePaneFooter(footerId, () => ({
1123
1145
  info: [
1124
1146
  ...(delistingKey ? [{ id: "delisted", parts: [{ text: delistingKey, tone: "warning" as const }] }] : []),
@@ -1130,6 +1152,7 @@ function ChartComposerSurface({
1130
1152
  : newestBeyondView
1131
1153
  ? [{ id: "newest", asOf: true, parts: [{ text: `newest ${newestBeyondView}`, tone: "muted" as const }] }]
1132
1154
  : []),
1155
+ ...(quoteDelay ? [quoteDelay] : []),
1133
1156
  ],
1134
1157
  hints: [
1135
1158
  // One key opens the ticket, which chooses Buy or Sell; [s]eries keeps its key with trading on.
@@ -1140,7 +1163,7 @@ function ChartComposerSurface({
1140
1163
  { id: "resolution", key: "b", label: "ars", title: "Bar Size", onPress: footerResolution },
1141
1164
  ...(anchorable ? [{ id: "vwap-anchor", key: "v", label: "wap anchor", title: "Anchor VWAP", onPress: footerAnchor }] : []),
1142
1165
  ],
1143
- }), [delistingKey, savedAt, loading, pickingAnchor, newestBeyondView, footerSeries, openIndicators, indicatorsDisabled, openFormulas, formulasDisabled, footerResolution, anchorable, footerAnchor, trade]);
1166
+ }), [delistingKey, savedAt, loading, pickingAnchor, newestBeyondView, quoteDelay, footerSeries, openIndicators, indicatorsDisabled, openFormulas, formulasDisabled, footerResolution, anchorable, footerAnchor, trade]);
1144
1167
 
1145
1168
  // A fixed window (a GIP session) highlights no range, so the bar names it.
1146
1169
  const dateWindowLabel = useMemo(() => {
@@ -1476,6 +1499,7 @@ export function ChartComposerResearchTab({ focused, width, height, onCapture }:
1476
1499
  footerId="chart-composer:research"
1477
1500
  onCapture={onCapture}
1478
1501
  nameSeries={false}
1502
+ delayFooter={false}
1479
1503
  />
1480
1504
  );
1481
1505
  }
@@ -8,8 +8,8 @@ import {
8
8
  listTimeSeriesFields,
9
9
  } from "../../../time-series/field-catalog";
10
10
  import {
11
- CANONICAL_EXCHANGE_ALIASES,
12
11
  canonicalExchange,
12
+ isKnownExchangeCode,
13
13
  publicTickerKey,
14
14
  } from "../../../utils/exchanges";
15
15
  import { MAX_CHART_COMPOSER_SERIES } from "./chart-spec";
@@ -73,7 +73,8 @@ export function normalizeInstrument(
73
73
  if (parts.length !== 2) return null;
74
74
  const symbol = normalizeBaseSymbol(parts[0]!);
75
75
  const exchangeToken = parts[1]!.trim().toUpperCase();
76
- const knownExchange = Object.prototype.hasOwnProperty.call(CANONICAL_EXCHANGE_ALIASES, exchangeToken);
76
+ // Every venue the app names, as a listing's key writes it (BDI:NEO, Cboe Canada), not only the spellings it reads as another.
77
+ const knownExchange = isKnownExchangeCode(exchangeToken);
77
78
  if (!symbol || !/^[A-Z0-9._-]{1,24}$/.test(exchangeToken) || (!knownExchange && !allowUnknownExchange)) {
78
79
  return null;
79
80
  }
@@ -1,7 +1,7 @@
1
1
  import type { ChartSpec, ChartStudyKind, ChartStudySpec, ResolvedSeries } from "../../../time-series/types";
2
2
  import { observationDate } from "../../../time-series/price-comparison";
3
3
  import { zonedWallClockToUtcMs } from "../../../utils/zoned-date-time";
4
- import { DEFAULT_PROFILE_ROWS, vwapAnchors, withVwapAnchors } from "../../../time-series/studies";
4
+ import { DEFAULT_PROFILE_ROWS, vwapAnchors, withVwapAnchors } from "../../../time-series/studies/vwap";
5
5
  import { reconcilePanels } from "./chart-spec-edit";
6
6
  import { CHART_FIELD_IDS } from "./series-expression";
7
7
 
@@ -916,7 +916,7 @@ export class ChatController {
916
916
  const channel = this.ensureChannelState(channelId);
917
917
  mergeChatMessages({
918
918
  channel,
919
- currentUserId: this.session.user?.id,
919
+ currentUser: this.session.user,
920
920
  messages,
921
921
  options,
922
922
  viewActive: this.appActive && channel.focusedViewCount > 0,
@@ -1,5 +1,6 @@
1
1
  import type { ChatAttachment, ChatMessage } from "../../../../api-client";
2
2
  import { toTimestampMillis } from "../../../../utils/timestamp";
3
+ import { isGloomWire } from "../ghost-user";
3
4
  import type { ChannelRuntimeState, MergeMessagesOptions } from "./state";
4
5
  import { PENDING_RECONCILE_WINDOW_MS } from "./state";
5
6
  import { chatMessageMentionsUsername, compareMessages, normalizeChatUsername } from "./utils";
@@ -186,16 +187,26 @@ function getMentionMessages(messages: ChatMessage[], user: CurrentChatUser): Cha
186
187
 
187
188
  interface MergeChatMessagesOptions {
188
189
  channel: ChannelRuntimeState;
189
- currentUserId?: string | null;
190
+ currentUser?: CurrentChatUser;
190
191
  messages: ChatMessage[];
191
192
  options?: MergeMessagesOptions;
192
193
  viewActive: boolean;
193
194
  markViewed: (persist?: boolean) => void;
194
195
  }
195
196
 
197
+ /**
198
+ * New messages from others count as unread, as the server counts them: a
199
+ * Gloom Wire post only when it mentions you.
200
+ */
201
+ function countUnread(messages: ChatMessage[], user: CurrentChatUser): number {
202
+ const fromPeople = messages.filter((message) => !isGloomWire(message.user));
203
+ const wireMentions = getMentionMessages(messages.filter((message) => isGloomWire(message.user)), user);
204
+ return fromPeople.length + wireMentions.length;
205
+ }
206
+
196
207
  export function mergeChatMessages({
197
208
  channel,
198
- currentUserId,
209
+ currentUser,
199
210
  messages,
200
211
  options,
201
212
  viewActive,
@@ -203,11 +214,11 @@ export function mergeChatMessages({
203
214
  }: MergeChatMessagesOptions): void {
204
215
  reconcilePendingMessages(channel, messages);
205
216
  const freshIncoming = mergeStoredMessages(channel, messages)
206
- .filter((message) => message.user.id !== currentUserId);
217
+ .filter((message) => message.user.id !== currentUser?.id);
207
218
  if (viewActive) {
208
219
  markViewed(false);
209
220
  } else if (freshIncoming.length > 0 && options?.countUnread !== false) {
210
- channel.unreadCount += freshIncoming.length;
221
+ channel.unreadCount += countUnread(freshIncoming, currentUser);
211
222
  }
212
223
  }
213
224
 
@@ -16,6 +16,14 @@ export function isManagedBot(user: Pick<ChatUserSummary, "accountType"> | null |
16
16
  return user?.accountType === "managed_llm";
17
17
  }
18
18
 
19
+ /** Gloom Wire: the account that posts headlines, the day's calendar and pull requests into the topic channels. */
20
+ const GLOOM_WIRE_USER_ID = "managed-llm-gloomwire";
21
+
22
+ /** A Gloom Wire post: there to read, so unread only when it mentions you. */
23
+ export function isGloomWire(user: Pick<ChatUserSummary, "id"> | null | undefined): boolean {
24
+ return user?.id === GLOOM_WIRE_USER_ID;
25
+ }
26
+
19
27
  /**
20
28
  * A reply quotes its author by name only, so whether that author is a managed
21
29
  * assistant comes from the users the pane has seen. A quoted announcement is
@@ -16,11 +16,9 @@ import { CHAT_KEEP_CONVERSATION_VALUE, createChatSearchProvider } from "./comman
16
16
  import { chatController } from "./controller";
17
17
  import { buildChatConversations, selectChatConversations } from "./conversations";
18
18
  import { pickChatPaneToReuse } from "./pane-state";
19
- import { ChatPane } from "./pane";
20
19
  import { chatSidebarStore } from "./sidebar-store";
21
20
  import { chatHandlePromptStore } from "./handle-prompt";
22
21
  import { ChatStatusWidget } from "./status-widget";
23
- import { UnreadInboxPane } from "./unread-inbox-pane";
24
22
  import { installPaneChatSharing } from "./share/flow";
25
23
  import { attachChatPaneLinkPanes } from "./pane-links/previews";
26
24
  import { attachChatCommands, cachedChatFunctions, chatFunctionsChanged } from "./pane-links/commands";
@@ -62,9 +60,10 @@ let disposeUnreadBadge: (() => void) | undefined;
62
60
  export const chatModule: PluginModule = {
63
61
  panes: [{
64
62
  id: "chat",
63
+ reportFreshness: { local: true },
65
64
  name: "Chat",
66
65
  icon: "C",
67
- component: ChatPane,
66
+ get component(): typeof import("./pane").ChatPane { const { ChatPane } = require("./pane"); return ChatPane; },
68
67
  defaultPosition: "right",
69
68
  defaultMode: "floating",
70
69
  defaultFloatingSize: { width: 80, height: 30 },
@@ -79,12 +78,12 @@ export const chatModule: PluginModule = {
79
78
  },
80
79
  }, {
81
80
  id: UNREAD_INBOX_PANE_ID,
82
- reportFreshness: { status: "not-a-feed", basis: "chat messages and missed alerts" },
81
+ reportFreshness: { status: "not-a-feed", basis: "chat messages and missed alerts", local: true },
83
82
  name: "Unread",
84
83
  // Mentions and replies from every conversation, DMs included.
85
84
  personalData: "messages",
86
85
  icon: "U",
87
- component: UnreadInboxPane,
86
+ get component(): typeof import("./unread-inbox-pane").UnreadInboxPane { const { UnreadInboxPane } = require("./unread-inbox-pane"); return UnreadInboxPane; },
88
87
  defaultPosition: "right",
89
88
  defaultMode: "floating",
90
89
  defaultFloatingSize: { width: 88, height: 16 },
@@ -96,6 +95,7 @@ export const chatModule: PluginModule = {
96
95
  description: "Open the floating chat window for a channel",
97
96
  keywords: ["new", "chat", "pane", "message"],
98
97
  shortcut: { prefix: "CHAT", argPlaceholder: "channel", argKind: "text" },
98
+ catalogSettings: {},
99
99
  createInstance: async (context, options) => {
100
100
  const rawArg = options?.arg?.trim() ?? "";
101
101
  // The command bar's New DM keeps the open pane on its conversation.
@@ -1,6 +1,5 @@
1
1
  import type { GloomPlugin, PaneTemplateCreateOptions } from "../../../types/plugin";
2
2
  import { normalizeTrialFilters, TRIAL_REPORT_OPTIONS, ALL } from "./model";
3
- import { TrialsPane } from "./pane";
4
3
  import { CLINICAL_TRIALS_PANE_ID, CLINICAL_TRIALS_PLUGIN_ID } from "./types";
5
4
 
6
5
  function createTrialsPaneInstance(options?: PaneTemplateCreateOptions) {
@@ -40,7 +39,7 @@ export const clinicalTrialsPlugin: GloomPlugin = {
40
39
  reportOptions: TRIAL_REPORT_OPTIONS,
41
40
  name: "Trials",
42
41
  icon: "C",
43
- component: TrialsPane,
42
+ get component(): typeof import("./pane").TrialsPane { const { TrialsPane } = require("./pane"); return TrialsPane; },
44
43
  defaultPosition: "right",
45
44
  defaultMode: "floating",
46
45
  defaultFloatingSize: { width: 100, height: 30 },
@@ -189,7 +189,7 @@ function LogoTile({ symbol, assetCategory, size }: { symbol: string; assetCatego
189
189
  return <Box alignItems="center" justifyContent="center" style={tile}>{letter}</Box>;
190
190
  }
191
191
  return (
192
- <ImageSurface src={src} alt={symbol} objectFit="contain" alignItems="center" justifyContent="center" style={tile}>
192
+ <ImageSurface src={src} alt={symbol} objectFit="contain" fallbackWhileLoading alignItems="center" justifyContent="center" style={tile}>
193
193
  {letter}
194
194
  </ImageSurface>
195
195
  );
@@ -1,4 +1,3 @@
1
- import { buildoutModule } from "../buildout";
2
1
  import { createGloomberbCloudPlugin } from "./plugin";
3
2
 
4
- export const gloomberbCloudPlugin = createGloomberbCloudPlugin([buildoutModule]);
3
+ export const gloomberbCloudPlugin = createGloomberbCloudPlugin();
@@ -2,6 +2,7 @@ import type { GloomPlugin } from "../../../types/plugin";
2
2
  import { apiClient } from "../../../api-client";
3
3
  import { createGloomberbCloudCapabilities, createGloomberbCloudProvider } from "../../../sources/gloomberb-cloud";
4
4
  import { accountManagementModule } from "../account-management";
5
+ import { buildoutModule } from "../buildout";
5
6
  import { chatModule } from "../chat";
6
7
  import {
7
8
  CONGRESS_TRADES_PANE_ID,
@@ -88,8 +89,10 @@ const askgModule: PluginModule = {
88
89
  },
89
90
  panes: [{
90
91
  id: ASKG_PANE_ID,
91
- reportFreshness: { status: "not-a-feed", basis: "generated answer" },
92
+ reportFreshness: { status: "not-a-feed", basis: "generated answer", local: true },
92
93
  reportLockNote: askgLockNote,
94
+ // Without a session the wall is all there is, so `fn ASKG` prints it at once.
95
+ signInWall: true,
93
96
  name: "Ask Gloom",
94
97
  personalData: "messages",
95
98
  icon: "K",
@@ -177,8 +180,7 @@ const congressTradesModule: PluginModule = {
177
180
  }],
178
181
  };
179
182
 
180
- /** The desktop and browser builds differ only in the modules they add here. */
181
- export function createGloomberbCloudPlugin(extraModules: readonly PluginModule[] = []): GloomPlugin {
183
+ export function createGloomberbCloudPlugin(): GloomPlugin {
182
184
  return composeBuiltinPlugin({
183
185
  id: "gloomberb-cloud",
184
186
  name: "Gloom Cloud",
@@ -195,7 +197,7 @@ export function createGloomberbCloudPlugin(extraModules: readonly PluginModule[]
195
197
  cloudAccountModule,
196
198
  askgModule,
197
199
  terminalRelayModule,
198
- ...extraModules,
200
+ buildoutModule,
199
201
  congressTradesModule,
200
202
  cloudTweetsModule,
201
203
  ],
@@ -24,7 +24,7 @@ export const teamModule: PluginModule = {
24
24
  capabilities: [createCloudTeamCapability(), createCloudViewsCapability()],
25
25
  panes: [{
26
26
  id: TEAM_PANE_ID,
27
- reportFreshness: { status: "not-a-feed", basis: "account data" },
27
+ reportFreshness: { status: "not-a-feed", basis: "account data", local: true },
28
28
  name: "Team",
29
29
  icon: "T",
30
30
  personalData: "account",
@@ -12,7 +12,7 @@ import { ThesisTickerTab } from "./ticker-tab";
12
12
  export const thesisModule: PluginModule = {
13
13
  panes: [{
14
14
  id: THESIS_PANE_ID,
15
- reportFreshness: { source: "Your theses", status: "not-a-feed", basis: "your theses" },
15
+ reportFreshness: { source: "Your theses", status: "not-a-feed", basis: "your theses", local: true },
16
16
  name: "Theses",
17
17
  personalData: "holdings",
18
18
  icon: "Θ",
@@ -14,7 +14,7 @@
14
14
  * Where the status bar has no room for it, as on a phone, it never shows and
15
15
  * neither arm is counted.
16
16
  */
17
- import { useCallback, useEffect, useRef, useState } from "react";
17
+ import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from "react";
18
18
  import { apiClient } from "../../../api-client";
19
19
  import { usePlanAccess } from "../../../api-client/plan-access";
20
20
  import { exposeWebExperiment } from "../../../api-client/research-activity";
@@ -88,6 +88,27 @@ interface TrialOfferStatusWidgetProps {
88
88
  delayMs?: number;
89
89
  }
90
90
 
91
+ let offerShowing = false;
92
+ const offerListeners = new Set<() => void>();
93
+
94
+ function setOfferShowing(next: boolean): void {
95
+ if (offerShowing === next) return;
96
+ offerShowing = next;
97
+ for (const listener of offerListeners) listener();
98
+ }
99
+
100
+ function subscribeOfferShowing(listener: () => void): () => void {
101
+ offerListeners.add(listener);
102
+ return () => {
103
+ offerListeners.delete(listener);
104
+ };
105
+ }
106
+
107
+ /** Whether the offer line is in the status bar, so the upgrade chip beside it does not pitch Pro twice. */
108
+ export function useTrialOfferShowing(): boolean {
109
+ return useSyncExternalStore(subscribeOfferShowing, () => offerShowing, () => offerShowing);
110
+ }
111
+
91
112
  export function TrialOfferStatusWidget(props: TrialOfferStatusWidgetProps) {
92
113
  if (getCurrentPluginTarget() !== "web") return null;
93
114
  return <WebTrialOffer {...props} />;
@@ -150,8 +171,10 @@ function WebTrialOffer({ expose = exposeWebExperiment, delayMs = TRIAL_OFFER_DEL
150
171
  useEffect(() => {
151
172
  if (!showing) return;
152
173
  hideShowingOffer = hide;
174
+ setOfferShowing(true);
153
175
  return () => {
154
176
  if (hideShowingOffer === hide) hideShowingOffer = null;
177
+ setOfferShowing(false);
155
178
  };
156
179
  }, [hide, showing]);
157
180