gloomberb 0.16.4 → 0.16.6

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 (852) hide show
  1. package/README.md +14 -91
  2. package/package.json +5 -1
  3. package/src/{plugins/builtin/cloud → account}/auth-form.tsx +9 -9
  4. package/src/{plugins/builtin/cloud → account}/auth-model.ts +4 -4
  5. package/src/account/billing.ts +57 -0
  6. package/src/{plugins/builtin/cloud → account}/device-signin-dialog.tsx +18 -12
  7. package/src/{plugins/builtin/cloud → account}/device-signin.ts +2 -2
  8. package/src/{plugins/builtin/cloud → account}/email-auth-attempt.ts +2 -2
  9. package/src/account/session.ts +16 -0
  10. package/src/{plugins/builtin/cloud → account}/upgrade-dialog.tsx +16 -15
  11. package/src/{plugins/builtin/cloud → account}/upgrade-personalization.ts +7 -7
  12. package/src/{plugins/builtin/cloud → account}/upgrade-tickers.ts +6 -6
  13. package/src/{plugins/builtin/cloud → account}/verification-panel.tsx +6 -6
  14. package/src/api-client/central-bank-rates.ts +6 -1
  15. package/src/api-client/chat.ts +26 -0
  16. package/src/api-client/credit-boards.ts +2 -0
  17. package/src/api-client/data.ts +73 -3
  18. package/src/api-client/earnings.ts +6 -0
  19. package/src/api-client/europe-energy.ts +106 -0
  20. package/src/api-client/futures-curve.ts +56 -0
  21. package/src/api-client/index.ts +13 -1
  22. package/src/api-client/market-discovery.ts +13 -1
  23. package/src/api-client/normalizers.ts +32 -0
  24. package/src/api-client/paths.ts +7 -0
  25. package/src/api-client/perps.ts +8 -0
  26. package/src/api-client/request.ts +9 -2
  27. package/src/api-client/research-activity.ts +15 -10
  28. package/src/api-client/socket.ts +29 -0
  29. package/src/api-client/themes.ts +22 -1
  30. package/src/api-client/types.ts +78 -2
  31. package/src/api-client/yield-curves.ts +8 -0
  32. package/src/app/global-shortcuts.ts +50 -2
  33. package/src/app/keybindings/actions.ts +12 -1
  34. package/src/app/pane-runtime/plugin-bindings.ts +2 -2
  35. package/src/app/pane-runtime/ticker-open-runtime.ts +16 -6
  36. package/src/app/runtime/desktop-deeplink.ts +10 -5
  37. package/src/app/runtime/startup.ts +1 -1
  38. package/src/app.tsx +6 -0
  39. package/src/architecture/core-builtin-allowlist.json +8 -26
  40. package/src/ask-gloom/pane-context.ts +114 -0
  41. package/src/ask-gloom/pane-request.ts +70 -0
  42. package/src/ask-gloom/pane-text.ts +201 -0
  43. package/src/brokers/signed-in/sign-in-dialog.tsx +11 -5
  44. package/src/capabilities/chart-series.ts +19 -2
  45. package/src/capabilities/factories.ts +1 -0
  46. package/src/cli/central-bank-events.ts +23 -0
  47. package/src/cli/command-options.ts +17 -5
  48. package/src/cli/commands/crypto-hints.ts +35 -2
  49. package/src/cli/commands/fred-series.ts +146 -0
  50. package/src/cli/commands/market.ts +501 -115
  51. package/src/cli/commands/news-hints.ts +62 -0
  52. package/src/cli/commands/news-search.ts +53 -0
  53. package/src/cli/commands/options-leaps.ts +5 -2
  54. package/src/cli/commands/overview.ts +132 -26
  55. package/src/cli/commands/remote.ts +2 -1
  56. package/src/cli/commands/search.ts +317 -13
  57. package/src/cli/commands/system.ts +55 -8
  58. package/src/cli/commands/ticker.ts +155 -18
  59. package/src/cli/context.ts +45 -14
  60. package/src/cli/desktop-pane-shot.ts +76 -7
  61. package/src/cli/earnings-calendar.ts +71 -0
  62. package/src/cli/errors.ts +7 -5
  63. package/src/cli/exit-codes.ts +8 -0
  64. package/src/cli/export-source.ts +58 -0
  65. package/src/cli/freshness.ts +18 -7
  66. package/src/cli/glossary.ts +483 -0
  67. package/src/cli/help.ts +10 -0
  68. package/src/cli/helpers.ts +2 -5
  69. package/src/cli/history-rows.ts +118 -7
  70. package/src/cli/index-roots.ts +7 -0
  71. package/src/cli/index.ts +29 -9
  72. package/src/cli/known-gaps.ts +84 -0
  73. package/src/cli/listing-arg.ts +551 -97
  74. package/src/cli/not-a-ticker.ts +24 -9
  75. package/src/cli/pane-functions/access-gate.ts +13 -2
  76. package/src/cli/pane-functions/capabilities.ts +4 -3
  77. package/src/cli/pane-functions/catalog.ts +214 -40
  78. package/src/cli/pane-functions/data.ts +42 -8
  79. package/src/cli/pane-functions/discovery.ts +1 -0
  80. package/src/cli/pane-functions/dom.ts +37 -8
  81. package/src/cli/pane-functions/freshness.ts +393 -49
  82. package/src/cli/pane-functions/function-help.ts +31 -15
  83. package/src/cli/pane-functions/headless.ts +98 -30
  84. package/src/cli/pane-functions/index.ts +101 -19
  85. package/src/cli/pane-functions/market-session.ts +22 -8
  86. package/src/cli/pane-functions/options.ts +71 -22
  87. package/src/cli/pane-functions/report-notices.ts +25 -7
  88. package/src/cli/pane-functions/report.ts +15 -8
  89. package/src/cli/pane-functions/resolver.ts +74 -19
  90. package/src/cli/pane-functions/screenshot.ts +250 -27
  91. package/src/cli/perpetual-contract.ts +60 -0
  92. package/src/cli/registry.ts +3 -1
  93. package/src/cli/report-tables.ts +26 -7
  94. package/src/cli/result.ts +45 -11
  95. package/src/cli/series-search.ts +111 -0
  96. package/src/cli/staged-tickers.ts +52 -0
  97. package/src/cli/symbol-args.ts +27 -0
  98. package/src/cli/symbol-list.ts +154 -0
  99. package/src/cli/symbol-rows.ts +154 -0
  100. package/src/cli/ticker-resolution.ts +311 -27
  101. package/src/cli/types.ts +5 -0
  102. package/src/components/chart/composite/axis-overlays.tsx +14 -3
  103. package/src/components/chart/composite/composite-chart.tsx +71 -11
  104. package/src/components/chart/composite/crosshair-readout.tsx +150 -0
  105. package/src/components/chart/composite/downsample.ts +1 -1
  106. package/src/components/chart/composite/format.ts +29 -0
  107. package/src/components/chart/composite/interactions.ts +9 -2
  108. package/src/components/chart/composite/legend.tsx +100 -33
  109. package/src/components/chart/composite/panel-bitmap.ts +70 -35
  110. package/src/components/chart/composite/panel-labels.ts +18 -17
  111. package/src/components/chart/composite/panel-series.ts +1 -0
  112. package/src/components/chart/composite/panel-surface.tsx +46 -21
  113. package/src/components/chart/composite/price-axis-labels.tsx +3 -1
  114. package/src/components/chart/composite/rasterizer.ts +50 -8
  115. package/src/components/chart/composite/scene.ts +2 -1
  116. package/src/components/chart/composite/time-scale.ts +26 -9
  117. package/src/components/chart/composite/types.ts +18 -0
  118. package/src/components/chart/composite/use-composite-viewport.ts +30 -2
  119. package/src/components/chart/native/raster/crosshair.ts +8 -3
  120. package/src/components/chart/native/raster/primitives.ts +85 -21
  121. package/src/components/chart/native/surface/manager.ts +33 -8
  122. package/src/components/chart/surface3d/chart.tsx +1 -1
  123. package/src/components/command-bar/action-runtime.ts +1 -1
  124. package/src/components/command-bar/commands/collection-list.ts +210 -0
  125. package/src/components/command-bar/commands/collection.ts +40 -1
  126. package/src/components/command-bar/commands/direct/index.ts +4 -0
  127. package/src/components/command-bar/commands/registry.ts +8 -0
  128. package/src/components/command-bar/panel/index.tsx +11 -3
  129. package/src/components/command-bar/panel/runtime.ts +3 -0
  130. package/src/components/command-bar/panel/types.ts +2 -0
  131. package/src/components/command-bar/routes/root/plugin-install.ts +4 -4
  132. package/src/components/command-bar/routes/root/provider-search.ts +4 -3
  133. package/src/components/command-bar/routes/ticker-search/results.ts +12 -11
  134. package/src/components/command-bar/routes/ticker-search/route.ts +4 -3
  135. package/src/components/command-bar/routing/actions.ts +1 -1
  136. package/src/components/command-bar/workflow/tickers.ts +42 -17
  137. package/src/components/layout/detached-pane-shell.tsx +68 -39
  138. package/src/components/layout/floating-pane.tsx +10 -1
  139. package/src/components/layout/header.tsx +25 -5
  140. package/src/components/layout/missing-pane.tsx +3 -3
  141. package/src/components/layout/pane/capture-stamp.ts +41 -0
  142. package/src/components/layout/pane/capture-status.ts +12 -0
  143. package/src/components/layout/pane/content.tsx +7 -13
  144. package/src/components/layout/pane/footer/index.tsx +6 -2
  145. package/src/components/layout/pane/footer/keyboard.ts +3 -0
  146. package/src/components/layout/pane/footer/model.ts +41 -1
  147. package/src/components/layout/pane/header.tsx +50 -1
  148. package/src/components/layout/pane/index.tsx +21 -3
  149. package/src/components/layout/pane/sidebar.tsx +88 -21
  150. package/src/components/layout/shell/active-drag.ts +21 -5
  151. package/src/components/layout/shell/cursor-occlusion.ts +2 -1
  152. package/src/components/layout/shell/drag/index.ts +39 -9
  153. package/src/components/layout/shell/drag/live.ts +61 -51
  154. package/src/components/layout/shell/drag/runtime.ts +54 -12
  155. package/src/components/layout/shell/index.tsx +84 -65
  156. package/src/components/layout/shell/menu.ts +14 -1
  157. package/src/components/layout/shell/native/pointer-runtime.ts +3 -1
  158. package/src/components/layout/shell/pane/actions.ts +32 -10
  159. package/src/components/layout/shell/pane/ask.ts +131 -0
  160. package/src/components/layout/shell/pane/capture.ts +162 -0
  161. package/src/components/layout/shell/pane/layers.tsx +218 -50
  162. package/src/components/layout/shell/pane/management-shortcuts.ts +46 -1
  163. package/src/components/layout/shell/shortcuts.ts +21 -4
  164. package/src/components/layout/shell/terminal-pointer-runtime.ts +24 -0
  165. package/src/components/markdown-text.tsx +62 -20
  166. package/src/components/metric-treemap/heat-surface.tsx +8 -1
  167. package/src/components/onboarding/account-step/form-panel.tsx +1 -1
  168. package/src/components/onboarding/account-step/index.tsx +1 -1
  169. package/src/components/onboarding/account-step/qr-panel.tsx +1 -1
  170. package/src/components/onboarding/account-step/signed-in-panel.tsx +1 -1
  171. package/src/components/onboarding/onboarding-keyboard.ts +1 -1
  172. package/src/components/onboarding/onboarding-wizard.tsx +2 -2
  173. package/src/components/onboarding/use-onboarding-upgrade.ts +3 -3
  174. package/src/components/onboarding/wizard-account.ts +2 -2
  175. package/src/components/pane-template-wizard.tsx +9 -1
  176. package/src/components/price-selector-dialog.tsx +11 -2
  177. package/src/components/reader/article-reader.tsx +642 -0
  178. package/src/components/reader/article-text.ts +527 -0
  179. package/src/components/sign-in-gate.tsx +3 -3
  180. package/src/components/ticker/list-table-view.tsx +43 -2
  181. package/src/components/ui/action-row.tsx +2 -1
  182. package/src/components/ui/button.tsx +7 -1
  183. package/src/components/ui/choice-dialog.tsx +6 -4
  184. package/src/components/ui/confirm-dialog.tsx +28 -18
  185. package/src/components/ui/data-table/index.tsx +18 -6
  186. package/src/components/ui/data-table/opentui/index.tsx +37 -6
  187. package/src/components/ui/data-table/types.ts +12 -0
  188. package/src/components/ui/dialog-actions.tsx +64 -0
  189. package/src/components/ui/display.tsx +17 -3
  190. package/src/components/ui/frame.tsx +35 -2
  191. package/src/components/ui/icon.tsx +6 -1
  192. package/src/components/ui/query-bar.tsx +10 -1
  193. package/src/components/ui/text-prompt-dialog.tsx +11 -9
  194. package/src/data/cached-query.ts +2 -0
  195. package/src/data/config/store/migrations.ts +7 -0
  196. package/src/data/config/store/normalize.ts +7 -2
  197. package/src/data/config/store/portfolio-as-of-column.ts +57 -0
  198. package/src/data/ticker-repository.ts +5 -0
  199. package/src/data/ticker-store.ts +15 -0
  200. package/src/docs/content.ts +215 -0
  201. package/src/docs/load.ts +139 -0
  202. package/src/docs/requests.ts +39 -0
  203. package/src/docs/snapshot.chunk.ts +8 -0
  204. package/src/docs/snapshot.json +7555 -0
  205. package/src/docs/topics.ts +216 -0
  206. package/src/i18n/es.ts +65 -5
  207. package/src/i18n/ja.ts +65 -5
  208. package/src/i18n/ko.ts +65 -5
  209. package/src/i18n/zh-cn.ts +65 -5
  210. package/src/i18n/zh-tw.ts +65 -5
  211. package/src/layout/desks.ts +1 -1
  212. package/src/layout/pane-manager/dock-tree.ts +18 -0
  213. package/src/layout/pane-manager/index.ts +1 -0
  214. package/src/layout-marketplace/gallery.tsx +1 -1
  215. package/src/market-data/coordinator/chart.ts +10 -4
  216. package/src/market-data/coordinator/index.ts +6 -3
  217. package/src/{plugins/builtin/futures/contracts.ts → market-data/futures-board.ts} +81 -28
  218. package/src/market-data/futures-contract.ts +151 -0
  219. package/src/market-data/hooks.tsx +10 -2
  220. package/src/market-data/market/format.ts +39 -21
  221. package/src/market-data/market/freshness.ts +84 -20
  222. package/src/market-data/market/session-close-note.ts +3 -2
  223. package/src/market-data/market/status.ts +19 -23
  224. package/src/market-data/published-apac-sessions.ts +23 -1
  225. package/src/market-data/quotes/contributions.ts +5 -0
  226. package/src/market-data/quotes/metadata.ts +19 -24
  227. package/src/market-data/quotes/resolution.ts +5 -0
  228. package/src/market-data/quotes/screener-live-quotes.ts +5 -4
  229. package/src/market-data/result-types.ts +2 -0
  230. package/src/notifications/app-notifier.ts +17 -1
  231. package/src/notifications/notification-log.ts +180 -0
  232. package/src/plugins/auto-update-node.ts +2 -2
  233. package/src/plugins/auto-update.ts +2 -2
  234. package/src/plugins/builtin/account-management/index.tsx +1 -0
  235. package/src/plugins/builtin/account-management/model.ts +1 -56
  236. package/src/plugins/builtin/account-management/pane.tsx +6 -4
  237. package/src/plugins/builtin/account-management/password-dialog.tsx +1 -1
  238. package/src/plugins/builtin/alerts/catalysts.ts +1 -1
  239. package/src/plugins/builtin/alerts/cli.ts +313 -47
  240. package/src/plugins/builtin/alerts/events-pane.tsx +45 -7
  241. package/src/plugins/builtin/alerts/events.ts +23 -2
  242. package/src/plugins/builtin/alerts/focus.ts +27 -0
  243. package/src/plugins/builtin/alerts/history-pane.tsx +7 -2
  244. package/src/plugins/builtin/alerts/history.ts +2 -0
  245. package/src/plugins/builtin/alerts/index.tsx +47 -11
  246. package/src/plugins/builtin/alerts/pane.tsx +27 -1
  247. package/src/plugins/builtin/alerts/research-builder.ts +8 -3
  248. package/src/plugins/builtin/alerts/research-rules.ts +18 -2
  249. package/src/plugins/builtin/alerts/scheduled-check.ts +122 -0
  250. package/src/plugins/builtin/alerts/scheduled-sources.ts +25 -0
  251. package/src/plugins/builtin/alerts/scheduled.ts +254 -0
  252. package/src/plugins/builtin/alerts/targets.ts +92 -0
  253. package/src/plugins/builtin/analytics/index.tsx +8 -3
  254. package/src/plugins/builtin/analytics/risk-headless.ts +60 -27
  255. package/src/plugins/builtin/analytics/risk-math.ts +91 -27
  256. package/src/plugins/builtin/analytics/risk-model.ts +35 -5
  257. package/src/plugins/builtin/analytics/risk-pane.tsx +15 -8
  258. package/src/plugins/builtin/attention/headless.ts +1 -1
  259. package/src/plugins/builtin/attention/index.tsx +2 -0
  260. package/src/plugins/builtin/attention/pane.tsx +3 -1
  261. package/src/plugins/builtin/attention/series.ts +26 -0
  262. package/src/plugins/builtin/awards/headless.ts +88 -18
  263. package/src/plugins/builtin/awards/index.tsx +2 -0
  264. package/src/plugins/builtin/awards/model.ts +41 -1
  265. package/src/plugins/builtin/awards/pane.tsx +22 -13
  266. package/src/plugins/builtin/awards/series.ts +29 -0
  267. package/src/plugins/builtin/backtest/headless.ts +2 -2
  268. package/src/plugins/builtin/bond-calculator/model.ts +2 -1
  269. package/src/plugins/builtin/broker-manager/index.tsx +1 -0
  270. package/src/plugins/builtin/catalysts/headless.ts +1 -1
  271. package/src/plugins/builtin/catalysts/pane.tsx +3 -1
  272. package/src/plugins/builtin/cds/client.ts +49 -12
  273. package/src/plugins/builtin/cds/headless.ts +119 -19
  274. package/src/plugins/builtin/cds/index.tsx +3 -1
  275. package/src/plugins/builtin/cds/model.ts +214 -32
  276. package/src/plugins/builtin/cds/pane.tsx +58 -14
  277. package/src/plugins/builtin/cds/series.ts +19 -0
  278. package/src/plugins/builtin/central-bank-rates/client.ts +4 -0
  279. package/src/plugins/builtin/central-bank-rates/headless.ts +46 -16
  280. package/src/plugins/builtin/central-bank-rates/index.tsx +3 -1
  281. package/src/plugins/builtin/central-bank-rates/model.ts +99 -0
  282. package/src/plugins/builtin/central-bank-rates/pane.tsx +1 -1
  283. package/src/plugins/builtin/central-bank-rates/series.ts +36 -0
  284. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +193 -255
  285. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +84 -46
  286. package/src/plugins/builtin/chart-composer/editor.tsx +5 -4
  287. package/src/plugins/builtin/chart-composer/field-series.ts +40 -0
  288. package/src/plugins/builtin/chart-composer/headless-schema.ts +1 -1
  289. package/src/plugins/builtin/chart-composer/headless.ts +70 -5
  290. package/src/plugins/builtin/chart-composer/index.tsx +15 -3
  291. package/src/plugins/builtin/chart-composer/pane.tsx +81 -38
  292. package/src/plugins/builtin/chart-composer/series-catalog.ts +58 -50
  293. package/src/plugins/builtin/chart-composer/series-expression.ts +18 -10
  294. package/src/plugins/builtin/chart-composer/use-series-catalog.ts +59 -3
  295. package/src/plugins/builtin/chart-composer/user-series.ts +84 -0
  296. package/src/plugins/builtin/chat/content/composer-runtime.ts +6 -1
  297. package/src/plugins/builtin/chat/content/composer.tsx +5 -1
  298. package/src/plugins/builtin/chat/content/footer.tsx +12 -0
  299. package/src/plugins/builtin/chat/content/index.tsx +89 -5
  300. package/src/plugins/builtin/chat/content/new-dm-dialog.tsx +10 -2
  301. package/src/plugins/builtin/chat/content/new-messages.tsx +89 -0
  302. package/src/plugins/builtin/chat/content/row-activity.ts +132 -0
  303. package/src/plugins/builtin/chat/content/scroll.ts +127 -27
  304. package/src/plugins/builtin/chat/content/shortcuts.ts +2 -2
  305. package/src/plugins/builtin/chat/content/transcript.tsx +145 -78
  306. package/src/plugins/builtin/chat/content/types.ts +1 -0
  307. package/src/plugins/builtin/chat/controller/fetch.ts +8 -2
  308. package/src/plugins/builtin/chat/controller/index.ts +123 -1
  309. package/src/plugins/builtin/chat/controller/message-loading.ts +3 -0
  310. package/src/plugins/builtin/chat/controller/messages.ts +7 -2
  311. package/src/plugins/builtin/chat/controller/realtime.ts +10 -1
  312. package/src/plugins/builtin/chat/controller/send.ts +61 -1
  313. package/src/plugins/builtin/chat/controller/state.ts +8 -0
  314. package/src/plugins/builtin/chat/ghost-user.ts +20 -0
  315. package/src/plugins/builtin/chat/index.tsx +23 -5
  316. package/src/plugins/builtin/chat/layout.ts +78 -5
  317. package/src/plugins/builtin/chat/message/ai-badge.tsx +18 -0
  318. package/src/plugins/builtin/chat/message/desktop.tsx +55 -6
  319. package/src/plugins/builtin/chat/message/header.tsx +16 -4
  320. package/src/plugins/builtin/chat/message/profile-popover.tsx +5 -1
  321. package/src/plugins/builtin/chat/message/reactions.tsx +210 -0
  322. package/src/plugins/builtin/chat/message/render-state.ts +18 -2
  323. package/src/plugins/builtin/chat/message/terminal.tsx +43 -4
  324. package/src/plugins/builtin/chat/message/types.ts +12 -0
  325. package/src/plugins/builtin/chat/reactions.ts +197 -0
  326. package/src/plugins/builtin/chat/share/flow.tsx +87 -0
  327. package/src/plugins/builtin/chat/share/targets.ts +27 -0
  328. package/src/plugins/builtin/chat/status-widget.tsx +6 -3
  329. package/src/plugins/builtin/chat/unread-inbox-pane.tsx +148 -27
  330. package/src/plugins/builtin/chat/unread-inbox.ts +22 -0
  331. package/src/plugins/builtin/cloud/askg/attached-pane.tsx +69 -0
  332. package/src/plugins/builtin/cloud/askg/controller.ts +111 -19
  333. package/src/plugins/builtin/cloud/askg/executor.ts +57 -3
  334. package/src/plugins/builtin/cloud/askg/manifest.ts +32 -7
  335. package/src/plugins/builtin/cloud/askg/model.ts +27 -8
  336. package/src/plugins/builtin/cloud/askg/pane-attachment.ts +94 -0
  337. package/src/plugins/builtin/cloud/askg/pane.tsx +141 -12
  338. package/src/plugins/builtin/cloud/askg/pending-question.ts +11 -0
  339. package/src/plugins/builtin/cloud/askg/protocol.ts +10 -1
  340. package/src/plugins/builtin/cloud/askg/tool-display.ts +3 -0
  341. package/src/plugins/builtin/cloud/askg/undo.ts +2 -0
  342. package/src/plugins/builtin/cloud/auth-actions.tsx +1 -1
  343. package/src/plugins/builtin/cloud/auth-commands.ts +1 -1
  344. package/src/plugins/builtin/cloud/auth-dialog.tsx +3 -3
  345. package/src/plugins/builtin/cloud/company-picker.tsx +1 -1
  346. package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +3 -0
  347. package/src/plugins/builtin/cloud/plugin.tsx +19 -2
  348. package/src/plugins/builtin/cloud/team/model.ts +5 -0
  349. package/src/plugins/builtin/cloud/team/module.tsx +1 -0
  350. package/src/plugins/builtin/cloud/team/store.ts +8 -3
  351. package/src/plugins/builtin/cloud/terminal-relay/engine.ts +77 -9
  352. package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +6 -1
  353. package/src/plugins/builtin/cloud/terminal-relay/policy.ts +9 -0
  354. package/src/plugins/builtin/cloud/terminal-relay/prompts.tsx +35 -11
  355. package/src/plugins/builtin/cloud/thesis/exposure.ts +1 -1
  356. package/src/plugins/builtin/cloud/thesis/module.tsx +1 -0
  357. package/src/plugins/builtin/cloud/thesis/prompts.tsx +2 -1
  358. package/src/plugins/builtin/company-attention/app-rank.tsx +1 -1
  359. package/src/plugins/builtin/company-attention/headless.ts +21 -7
  360. package/src/plugins/builtin/company-attention/index.tsx +2 -0
  361. package/src/plugins/builtin/company-attention/model.ts +26 -7
  362. package/src/plugins/builtin/company-attention/pane.tsx +12 -9
  363. package/src/plugins/builtin/company-attention/series.ts +52 -0
  364. package/src/plugins/builtin/company-kpis/headless.ts +97 -10
  365. package/src/plugins/builtin/company-kpis/model.ts +24 -2
  366. package/src/plugins/builtin/company-kpis/pane.tsx +17 -16
  367. package/src/plugins/builtin/composite-plugins.ts +19 -13
  368. package/src/plugins/builtin/connections/index.ts +1 -0
  369. package/src/plugins/builtin/cot/headless.ts +1 -1
  370. package/src/plugins/builtin/cot/index.tsx +3 -1
  371. package/src/plugins/builtin/cot/model.ts +40 -11
  372. package/src/plugins/builtin/cot/series.ts +27 -0
  373. package/src/plugins/builtin/cpi/headless.ts +3 -3
  374. package/src/plugins/builtin/credit-boards/cdx-pane.tsx +10 -1
  375. package/src/plugins/builtin/credit-boards/client.ts +6 -2
  376. package/src/plugins/builtin/credit-boards/headless.ts +108 -31
  377. package/src/plugins/builtin/credit-boards/index.tsx +2 -0
  378. package/src/plugins/builtin/credit-boards/model.ts +11 -1
  379. package/src/plugins/builtin/credit-boards/series.ts +66 -0
  380. package/src/plugins/builtin/credit-conditions/client.ts +12 -9
  381. package/src/plugins/builtin/credit-conditions/headless.ts +13 -2
  382. package/src/plugins/builtin/credit-conditions/index.tsx +18 -9
  383. package/src/plugins/builtin/credit-conditions/model.ts +19 -11
  384. package/src/plugins/builtin/credit-conditions/series.ts +16 -0
  385. package/src/plugins/builtin/credit-documents/headless.ts +33 -3
  386. package/src/plugins/builtin/credit-documents/pane.tsx +3 -1
  387. package/src/plugins/builtin/crypto-board/headless.ts +2 -3
  388. package/src/plugins/builtin/crypto-board/index.tsx +2 -0
  389. package/src/plugins/builtin/crypto-board/model.ts +12 -0
  390. package/src/plugins/builtin/crypto-board/pane.tsx +19 -12
  391. package/src/plugins/builtin/crypto-board/series.ts +48 -0
  392. package/src/plugins/builtin/custom-view/live-quotes.ts +2 -2
  393. package/src/plugins/builtin/debt-maturities/headless.ts +17 -11
  394. package/src/plugins/builtin/debt-maturities/index.tsx +2 -0
  395. package/src/plugins/builtin/debt-maturities/series.ts +45 -0
  396. package/src/plugins/builtin/dividend-yield/client.ts +3 -0
  397. package/src/plugins/builtin/dividend-yield/headless.ts +7 -4
  398. package/src/plugins/builtin/dividend-yield/index.tsx +3 -1
  399. package/src/plugins/builtin/dividend-yield/pane.tsx +3 -1
  400. package/src/plugins/builtin/dividend-yield/series.ts +20 -0
  401. package/src/plugins/builtin/dividend-yield/types.ts +2 -0
  402. package/src/plugins/builtin/docs/blocks.tsx +181 -0
  403. package/src/plugins/builtin/docs/index.tsx +68 -0
  404. package/src/plugins/builtin/docs/pane.tsx +426 -0
  405. package/src/plugins/builtin/docs/targets.ts +72 -0
  406. package/src/plugins/builtin/docs/use-docs.ts +60 -0
  407. package/src/plugins/builtin/doe/headless.ts +12 -8
  408. package/src/plugins/builtin/doe/index.tsx +2 -0
  409. package/src/plugins/builtin/doe/series.ts +62 -0
  410. package/src/plugins/builtin/earnings/client.ts +3 -2
  411. package/src/plugins/builtin/earnings/format.ts +1 -5
  412. package/src/plugins/builtin/earnings/headless.ts +58 -6
  413. package/src/plugins/builtin/earnings/history-model.ts +15 -1
  414. package/src/plugins/builtin/earnings/history-table.ts +13 -2
  415. package/src/plugins/builtin/earnings/history.tsx +7 -3
  416. package/src/plugins/builtin/earnings/index.tsx +9 -0
  417. package/src/plugins/builtin/earnings-calls/headless.ts +28 -2
  418. package/src/plugins/builtin/earnings-calls/index.tsx +1 -1
  419. package/src/plugins/builtin/earnings-ripple/index.tsx +2 -0
  420. package/src/plugins/builtin/econ/fred-catalog.ts +198 -0
  421. package/src/plugins/builtin/econ/fred-series-map.ts +0 -46
  422. package/src/plugins/builtin/econ/index.tsx +2 -0
  423. package/src/plugins/builtin/econ-statistics/defs.ts +5 -0
  424. package/src/plugins/builtin/econ-statistics/headless.ts +19 -4
  425. package/src/plugins/builtin/econ-statistics/stats.ts +1 -0
  426. package/src/plugins/builtin/equity-screener/headless.ts +3 -3
  427. package/src/plugins/builtin/equity-screener/pane.tsx +4 -11
  428. package/src/plugins/builtin/estimate-revisions/headless.ts +3 -3
  429. package/src/plugins/builtin/exposure/drivers.ts +13 -0
  430. package/src/plugins/builtin/exposure/headless.ts +205 -13
  431. package/src/plugins/builtin/exposure/holdings.ts +1 -1
  432. package/src/plugins/builtin/exposure/index.tsx +1 -0
  433. package/src/plugins/builtin/exposure/model.ts +23 -3
  434. package/src/plugins/builtin/exposure/pane.tsx +14 -12
  435. package/src/plugins/builtin/fear-greed/index.tsx +18 -10
  436. package/src/plugins/builtin/fear-greed/series.ts +52 -0
  437. package/src/plugins/builtin/futures/index.tsx +43 -3
  438. package/src/plugins/builtin/futures/model.ts +1 -1
  439. package/src/plugins/builtin/futures/returns.ts +1 -1
  440. package/src/plugins/builtin/futures/series.ts +13 -0
  441. package/src/plugins/builtin/futures/table.tsx +1 -1
  442. package/src/plugins/builtin/futures/use-front-returns.ts +1 -1
  443. package/src/plugins/builtin/futures-curve/client.ts +12 -3
  444. package/src/plugins/builtin/futures-curve/headless.ts +71 -10
  445. package/src/plugins/builtin/futures-curve/index.tsx +1 -1
  446. package/src/plugins/builtin/futures-curve/model.ts +66 -11
  447. package/src/plugins/builtin/futures-curve/pane.tsx +52 -18
  448. package/src/plugins/builtin/fx-matrix/client.ts +63 -0
  449. package/src/plugins/builtin/fx-matrix/export.ts +2 -1
  450. package/src/plugins/builtin/fx-matrix/headless.ts +154 -0
  451. package/src/plugins/builtin/fx-matrix/index.tsx +4 -4
  452. package/src/plugins/builtin/gpu/board.tsx +13 -10
  453. package/src/plugins/builtin/gpu/changes.tsx +4 -4
  454. package/src/plugins/builtin/gpu/client.ts +19 -1
  455. package/src/plugins/builtin/gpu/equities.tsx +14 -10
  456. package/src/plugins/builtin/gpu/headless.ts +58 -27
  457. package/src/plugins/builtin/gpu/history.tsx +3 -3
  458. package/src/plugins/builtin/gpu/index.tsx +2 -0
  459. package/src/plugins/builtin/gpu/model.ts +101 -16
  460. package/src/plugins/builtin/gpu/pane.tsx +28 -12
  461. package/src/plugins/builtin/gpu/series.ts +38 -0
  462. package/src/plugins/builtin/help/function-card.tsx +25 -6
  463. package/src/plugins/builtin/help/function-index.ts +2 -0
  464. package/src/plugins/builtin/help/index.tsx +31 -9
  465. package/src/plugins/builtin/holders/beneficial-model.ts +90 -3
  466. package/src/plugins/builtin/holders/beneficial-report.ts +57 -5
  467. package/src/plugins/builtin/holders/beneficial-view.tsx +55 -11
  468. package/src/plugins/builtin/holders/client.ts +22 -3
  469. package/src/plugins/builtin/holders/format.ts +18 -3
  470. package/src/plugins/builtin/holders/headless.ts +36 -133
  471. package/src/plugins/builtin/holders/pane.tsx +11 -4
  472. package/src/plugins/builtin/holders/report-header.ts +40 -5
  473. package/src/plugins/builtin/holders/report-rows.ts +240 -0
  474. package/src/plugins/builtin/insider/client.ts +35 -20
  475. package/src/plugins/builtin/insider/headless.ts +41 -11
  476. package/src/plugins/builtin/insider/index.tsx +26 -9
  477. package/src/plugins/builtin/insider/insider-data.ts +10 -0
  478. package/src/plugins/builtin/insider/issuer.ts +49 -0
  479. package/src/plugins/builtin/insider/model.ts +3 -0
  480. package/src/plugins/builtin/ipo-calendar/headless.ts +1 -1
  481. package/src/plugins/builtin/iv-history/client.ts +20 -6
  482. package/src/plugins/builtin/iv-history/format.ts +9 -0
  483. package/src/plugins/builtin/iv-history/headless.ts +56 -21
  484. package/src/plugins/builtin/iv-history/index.tsx +2 -0
  485. package/src/plugins/builtin/iv-history/model.ts +143 -8
  486. package/src/plugins/builtin/iv-history/screen-pane.tsx +5 -9
  487. package/src/plugins/builtin/iv-history/series.ts +29 -0
  488. package/src/plugins/builtin/jobs/headless.ts +26 -3
  489. package/src/plugins/builtin/jobs/index.tsx +3 -1
  490. package/src/plugins/builtin/jobs/series.ts +19 -0
  491. package/src/plugins/builtin/kelly-sizer/index.tsx +2 -0
  492. package/src/plugins/builtin/kelly-sizer/pane.tsx +2 -1
  493. package/src/plugins/builtin/kelly-sizer/portfolio.ts +0 -39
  494. package/src/plugins/builtin/macro-day/client.ts +44 -3
  495. package/src/plugins/builtin/macro-day/headless.ts +13 -10
  496. package/src/plugins/builtin/macro-day/model.ts +56 -10
  497. package/src/plugins/builtin/macro-day/pane.tsx +30 -20
  498. package/src/plugins/builtin/market-heatmap/headless.ts +20 -5
  499. package/src/plugins/builtin/market-heatmap/index.tsx +6 -0
  500. package/src/plugins/builtin/market-heatmap/model.ts +19 -11
  501. package/src/plugins/builtin/market-heatmap/portfolio.ts +2 -2
  502. package/src/plugins/builtin/market-movers/client.ts +7 -5
  503. package/src/plugins/builtin/market-movers/index.tsx +3 -2
  504. package/src/plugins/builtin/market-movers/model.ts +30 -0
  505. package/src/plugins/builtin/market-movers/screener.ts +31 -0
  506. package/src/plugins/builtin/market-valuation/chart-series.ts +29 -69
  507. package/src/plugins/builtin/market-valuation/index.tsx +3 -3
  508. package/src/plugins/builtin/market-valuation/indicators.ts +0 -5
  509. package/src/plugins/builtin/members/headless.ts +8 -3
  510. package/src/plugins/builtin/members/model.ts +31 -3
  511. package/src/plugins/builtin/members/pane.tsx +6 -8
  512. package/src/plugins/builtin/mna/distress/headless.ts +4 -4
  513. package/src/plugins/builtin/mna/headless.ts +18 -6
  514. package/src/plugins/builtin/money-markets/headless.ts +5 -4
  515. package/src/plugins/builtin/money-markets/index.tsx +2 -0
  516. package/src/plugins/builtin/money-markets/series.ts +41 -0
  517. package/src/plugins/builtin/news/index.tsx +0 -2
  518. package/src/plugins/builtin/news/wire/news/detail-view.tsx +53 -104
  519. package/src/plugins/builtin/news/wire/news/footer.ts +2 -25
  520. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +0 -2
  521. package/src/plugins/builtin/notes/cli.ts +12 -7
  522. package/src/plugins/builtin/notes/index.tsx +1 -0
  523. package/src/plugins/builtin/options/index.tsx +3 -0
  524. package/src/plugins/builtin/options/view.tsx +1 -1
  525. package/src/plugins/builtin/options-calculator/inputs.ts +2 -1
  526. package/src/plugins/builtin/options-positioning/client.ts +7 -3
  527. package/src/plugins/builtin/options-positioning/headless.ts +4 -3
  528. package/src/plugins/builtin/options-positioning/pane.tsx +2 -2
  529. package/src/plugins/builtin/options-scenario/client.ts +2 -1
  530. package/src/plugins/builtin/options-scenario/headless.ts +1 -0
  531. package/src/plugins/builtin/options-scenario/hedge.ts +2 -1
  532. package/src/plugins/builtin/perps/client.ts +90 -16
  533. package/src/plugins/builtin/perps/equity-row.tsx +52 -17
  534. package/src/plugins/builtin/perps/freshness.ts +66 -0
  535. package/src/plugins/builtin/perps/headless.ts +47 -32
  536. package/src/plugins/builtin/perps/index.tsx +23 -4
  537. package/src/plugins/builtin/perps/list-perpetuals.ts +118 -0
  538. package/src/plugins/builtin/perps/model.ts +20 -5
  539. package/src/plugins/builtin/perps/open-market.ts +15 -0
  540. package/src/plugins/builtin/perps/pane.tsx +76 -20
  541. package/src/plugins/builtin/perps/series.ts +76 -0
  542. package/src/plugins/builtin/plugin-marketplace/index.tsx +1 -1
  543. package/src/plugins/builtin/plugin-marketplace/pane.tsx +73 -67
  544. package/src/plugins/builtin/plugin-module.ts +3 -0
  545. package/src/plugins/builtin/portfolio-list/account-metrics.ts +1 -1
  546. package/src/plugins/builtin/portfolio-list/allocation.ts +5 -4
  547. package/src/plugins/builtin/portfolio-list/cell-version.ts +8 -1
  548. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +161 -0
  549. package/src/plugins/builtin/portfolio-list/cli/import-13f.ts +109 -0
  550. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +384 -74
  551. package/src/plugins/builtin/portfolio-list/cli/render.ts +437 -71
  552. package/src/plugins/builtin/portfolio-list/cli/shared.ts +21 -4
  553. package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +121 -47
  554. package/src/plugins/builtin/portfolio-list/column-values.ts +104 -26
  555. package/src/plugins/builtin/portfolio-list/grid.tsx +1 -1
  556. package/src/plugins/builtin/portfolio-list/headless.ts +84 -18
  557. package/src/plugins/builtin/portfolio-list/imported-from.ts +8 -0
  558. package/src/plugins/builtin/portfolio-list/index.tsx +5 -1
  559. package/src/plugins/builtin/portfolio-list/live-valuation.ts +6 -2
  560. package/src/plugins/builtin/portfolio-list/mutations.ts +2 -9
  561. package/src/plugins/builtin/portfolio-list/pane/data.ts +5 -1
  562. package/src/plugins/builtin/portfolio-list/pane/index.tsx +52 -9
  563. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +33 -11
  564. package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +21 -1
  565. package/src/plugins/builtin/portfolio-list/settings.ts +17 -0
  566. package/src/plugins/builtin/portfolio-list/summary/index.tsx +12 -0
  567. package/src/plugins/builtin/portfolio-list/summary/totals.ts +9 -4
  568. package/src/plugins/builtin/portfolio-list/table.tsx +37 -7
  569. package/src/plugins/builtin/power/client.ts +16 -0
  570. package/src/plugins/builtin/power/evidence.ts +63 -10
  571. package/src/plugins/builtin/power/headless.ts +77 -9
  572. package/src/plugins/builtin/power/index.tsx +28 -3
  573. package/src/plugins/builtin/power/model.ts +73 -2
  574. package/src/plugins/builtin/power/pane.tsx +37 -9
  575. package/src/plugins/builtin/power/prices-client.ts +139 -0
  576. package/src/plugins/builtin/power/prices-headless.ts +137 -0
  577. package/src/plugins/builtin/power/prices-model.ts +103 -0
  578. package/src/plugins/builtin/power/prices.tsx +179 -0
  579. package/src/plugins/builtin/rate-path/headless.ts +46 -18
  580. package/src/plugins/builtin/realized-vol/headless.ts +5 -0
  581. package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
  582. package/src/plugins/builtin/research/corporate-actions-pane.tsx +5 -2
  583. package/src/plugins/builtin/research/event-model.ts +68 -24
  584. package/src/plugins/builtin/research/events-headless.ts +2 -1
  585. package/src/plugins/builtin/research/headless.ts +2 -1
  586. package/src/plugins/builtin/research/index.tsx +2 -0
  587. package/src/plugins/builtin/research-search/article.ts +65 -0
  588. package/src/plugins/builtin/research-search/document-view.tsx +33 -215
  589. package/src/plugins/builtin/research-search/model.ts +0 -9
  590. package/src/plugins/builtin/research-search/pane.tsx +41 -30
  591. package/src/plugins/builtin/research-search/snippet.ts +0 -29
  592. package/src/plugins/builtin/revenue-breakdown/headless.ts +18 -6
  593. package/src/plugins/builtin/seasonality/client.ts +15 -7
  594. package/src/plugins/builtin/seasonality/headless.ts +36 -12
  595. package/src/plugins/builtin/seasonality/index.tsx +2 -2
  596. package/src/plugins/builtin/seasonality/model.ts +119 -0
  597. package/src/plugins/builtin/seasonality/pane.tsx +108 -37
  598. package/src/plugins/builtin/sec/headless.ts +25 -3
  599. package/src/plugins/builtin/sec/index.tsx +2 -1
  600. package/src/plugins/builtin/series-inventory.ts +93 -0
  601. package/src/plugins/builtin/shared/cloud-denied.ts +15 -0
  602. package/src/plugins/builtin/shared/cloud-upgrade.ts +1 -1
  603. package/src/plugins/builtin/shared/command-tab.ts +30 -12
  604. package/src/plugins/builtin/shared/earnings-report.ts +55 -0
  605. package/src/plugins/builtin/shared/fred-points.ts +20 -0
  606. package/src/plugins/builtin/shared/free-preview.ts +47 -0
  607. package/src/plugins/builtin/shared/fx-rate-freshness.ts +101 -0
  608. package/src/plugins/builtin/shared/open-description.ts +29 -0
  609. package/src/plugins/builtin/shared/report-freshness.ts +12 -1
  610. package/src/plugins/builtin/shared/series-warning.ts +5 -0
  611. package/src/plugins/builtin/shared/volatility/options.ts +4 -1
  612. package/src/plugins/builtin/short-interest/headless.ts +1 -0
  613. package/src/plugins/builtin/short-interest/index.tsx +2 -0
  614. package/src/plugins/builtin/short-interest/series.ts +36 -0
  615. package/src/plugins/builtin/short-interest/watch-headless.ts +3 -2
  616. package/src/plugins/builtin/short-interest/watch-model.ts +9 -5
  617. package/src/plugins/builtin/short-volume/headless.ts +2 -1
  618. package/src/plugins/builtin/short-volume/index.tsx +2 -0
  619. package/src/plugins/builtin/short-volume/series.ts +23 -0
  620. package/src/plugins/builtin/social-mentions/headless.ts +1 -1
  621. package/src/plugins/builtin/social-mentions/index.tsx +2 -0
  622. package/src/plugins/builtin/social-mentions/series.ts +23 -0
  623. package/src/plugins/builtin/supply-chain/graph-pane.tsx +3 -2
  624. package/src/plugins/builtin/supply-chain/headless.ts +43 -11
  625. package/src/plugins/builtin/supply-chain/model.ts +13 -0
  626. package/src/plugins/builtin/supply-chain/pane.tsx +8 -6
  627. package/src/plugins/builtin/themes/headless.ts +74 -10
  628. package/src/plugins/builtin/themes/model.ts +71 -4
  629. package/src/plugins/builtin/themes/pane.tsx +22 -5
  630. package/src/plugins/builtin/thirteenf/api.ts +1 -0
  631. package/src/plugins/builtin/thirteenf/data.ts +6 -4
  632. package/src/plugins/builtin/thirteenf/headless.ts +217 -36
  633. package/src/plugins/builtin/thirteenf/model.ts +39 -1
  634. package/src/plugins/builtin/thirteenf/overlap.ts +75 -0
  635. package/src/plugins/builtin/thirteenf/pane.tsx +10 -1
  636. package/src/plugins/builtin/thirteenf/report-notes.ts +103 -1
  637. package/src/plugins/builtin/thirteenf/types.ts +2 -0
  638. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +114 -0
  639. package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +57 -2
  640. package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +19 -55
  641. package/src/plugins/builtin/ticker-detail/headless-schema.ts +30 -0
  642. package/src/plugins/builtin/ticker-detail/headless.ts +86 -0
  643. package/src/plugins/builtin/ticker-detail/index.tsx +26 -11
  644. package/src/plugins/builtin/ticker-detail/overview/components.tsx +1 -1
  645. package/src/plugins/builtin/ticker-detail/overview/futures-contract.ts +31 -0
  646. package/src/plugins/builtin/ticker-detail/overview/model.ts +3 -3
  647. package/src/plugins/builtin/ticker-detail/overview/pane.tsx +2 -1
  648. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +49 -5
  649. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +13 -3
  650. package/src/plugins/builtin/time-sales/headless.ts +2 -1
  651. package/src/plugins/builtin/time-sales/index.tsx +2 -1
  652. package/src/plugins/builtin/treasury-auctions/headless.ts +16 -2
  653. package/src/plugins/builtin/treasury-auctions/index.tsx +1 -0
  654. package/src/plugins/builtin/treasury-auctions/model.ts +8 -0
  655. package/src/plugins/builtin/vol-surface/evidence.ts +7 -1
  656. package/src/plugins/builtin/vol-surface/headless.ts +51 -17
  657. package/src/plugins/builtin/vol-surface/heat-grid.tsx +113 -0
  658. package/src/plugins/builtin/vol-surface/model.ts +39 -6
  659. package/src/plugins/builtin/vol-surface/pane.tsx +14 -13
  660. package/src/plugins/builtin/vol-surface/stored.ts +63 -3
  661. package/src/plugins/builtin/vol-surface/surface.tsx +30 -2
  662. package/src/plugins/builtin/volatility/index.tsx +2 -0
  663. package/src/plugins/builtin/volatility/series.ts +84 -0
  664. package/src/plugins/builtin/world-venue-map/basemap-layer.tsx +6 -5
  665. package/src/plugins/builtin/world-venue-map/basemap.ts +6 -3
  666. package/src/plugins/builtin/world-venue-map/geo-draw.tsx +2 -1
  667. package/src/plugins/builtin/world-venue-map/geo-series.ts +26 -1
  668. package/src/plugins/builtin/world-venue-map/geo-svg.tsx +6 -6
  669. package/src/plugins/builtin/world-venue-map/headless.ts +4 -1
  670. package/src/plugins/builtin/world-venue-map/index.ts +2 -1
  671. package/src/plugins/builtin/world-venue-map/layered-pane.tsx +24 -13
  672. package/src/plugins/builtin/world-venue-map/layers.ts +29 -0
  673. package/src/plugins/builtin/world-venue-map/map.tsx +144 -13
  674. package/src/plugins/builtin/world-venue-map/model.ts +56 -19
  675. package/src/plugins/builtin/world-venue-map/pane.tsx +5 -2
  676. package/src/plugins/builtin/yield-curve/chart.ts +2 -2
  677. package/src/plugins/builtin/yield-curve/curves.ts +21 -8
  678. package/src/plugins/builtin/yield-curve/headless.ts +71 -18
  679. package/src/plugins/builtin/yield-curve/index.tsx +4 -2
  680. package/src/plugins/builtin/yield-curve/series.ts +86 -0
  681. package/src/plugins/catalog-browser.ts +2 -1
  682. package/src/plugins/installer.ts +2 -2
  683. package/src/plugins/loader.ts +15 -0
  684. package/src/plugins/manager-node.ts +1 -1
  685. package/src/plugins/{builtin/plugin-marketplace → marketplace}/feed.ts +2 -2
  686. package/src/plugins/{builtin/plugin-marketplace → marketplace}/model.ts +6 -6
  687. package/src/plugins/{builtin/plugin-marketplace → marketplace}/store.ts +1 -1
  688. package/src/plugins/registry/host-actions.ts +11 -0
  689. package/src/plugins/registry/index.ts +89 -4
  690. package/src/portfolio/collection-name.ts +31 -0
  691. package/src/{plugins/builtin/portfolio-list → portfolio}/position-metrics.ts +6 -6
  692. package/src/portfolio/position-value.ts +40 -0
  693. package/src/portfolio/valuation-basis.ts +79 -0
  694. package/src/public/__snapshots__/public-api.test.ts.snap +7 -0
  695. package/src/public/time-series.ts +17 -0
  696. package/src/react/auto-refresh.ts +13 -3
  697. package/src/react/input.ts +33 -1
  698. package/src/react/key-repeat.ts +86 -0
  699. package/src/remote/app-host.tsx +20 -4
  700. package/src/remote/confirm-change.tsx +85 -0
  701. package/src/remote/controller-utils.ts +8 -1
  702. package/src/remote/controller.ts +144 -7
  703. package/src/remote/pane-screenshot.ts +47 -0
  704. package/src/remote/schema.ts +65 -2
  705. package/src/remote/types.ts +30 -0
  706. package/src/remote/watchlist.ts +306 -0
  707. package/src/renderers/browser/main.tsx +4 -3
  708. package/src/renderers/browser/research-entry.ts +4 -2
  709. package/src/renderers/cloudflare/worker.ts +1 -1
  710. package/src/renderers/dom/data-table/frozen-column.ts +3 -1
  711. package/src/renderers/dom/data-table/index.tsx +33 -6
  712. package/src/renderers/dom/data-table/row.tsx +30 -1
  713. package/src/renderers/dom/desktop/control-styles.ts +63 -1
  714. package/src/renderers/dom/desktop/controls.tsx +30 -23
  715. package/src/renderers/dom/desktop/dialog-actions.tsx +170 -0
  716. package/src/renderers/dom/desktop/icons.tsx +16 -0
  717. package/src/renderers/dom/dom-ui-host.tsx +3 -0
  718. package/src/renderers/dom/host/block-glyph-canvas.tsx +6 -3
  719. package/src/renderers/dom/host/box.tsx +8 -0
  720. package/src/renderers/dom/host/chart-surface.tsx +33 -2
  721. package/src/renderers/dom/host/live-frame.ts +6 -2
  722. package/src/renderers/dom/host/overflow-fade.ts +14 -1
  723. package/src/renderers/dom/host/scroll-box.tsx +32 -0
  724. package/src/renderers/dom/host/size-observer.ts +41 -0
  725. package/src/renderers/dom/host/sticky-scroll.ts +128 -0
  726. package/src/renderers/dom/host/style.ts +1 -1
  727. package/src/renderers/dom/host/surface-3d.tsx +19 -7
  728. package/src/renderers/dom/host/surface-fit.ts +406 -0
  729. package/src/renderers/dom/host/text.tsx +1 -1
  730. package/src/renderers/dom/input-host.tsx +1 -0
  731. package/src/renderers/dom/styles.css +102 -5
  732. package/src/renderers/electrobun/bun/application-menu/click.ts +18 -0
  733. package/src/renderers/electrobun/bun/index.ts +3 -2
  734. package/src/renderers/electrobun/shared/protocol.ts +1 -1
  735. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +29 -2
  736. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +63 -20
  737. package/src/renderers/electrobun/view/cli-pane-shot-health.ts +10 -26
  738. package/src/renderers/electrobun/view/main.tsx +5 -3
  739. package/src/renderers/electrobun/view/remote/asset-data-client.ts +2 -0
  740. package/src/renderers/opentui/chart-surface.tsx +1 -0
  741. package/src/renderers/opentui/frame-capture.ts +33 -0
  742. package/src/renderers/opentui/host.tsx +10 -13
  743. package/src/renderers/opentui/pane-image/glyphs-license.txt +107 -0
  744. package/src/renderers/opentui/pane-image/glyphs.json +1 -0
  745. package/src/renderers/opentui/pane-image/index.ts +107 -0
  746. package/src/renderers/opentui/pane-image/raster.ts +428 -0
  747. package/src/renderers/opentui/start.tsx +3 -3
  748. package/src/renderers/opentui/test-utils.tsx +3 -0
  749. package/src/renderers/opentui/ui-host.tsx +29 -1
  750. package/src/series/capability.ts +75 -0
  751. package/src/series/csv.ts +160 -0
  752. package/src/series/files.ts +107 -0
  753. package/src/series/registry.ts +198 -0
  754. package/src/series/resolve.ts +108 -0
  755. package/src/series/standalone.ts +48 -0
  756. package/src/series/types.ts +153 -0
  757. package/src/series/validate.ts +171 -0
  758. package/src/shares/chat.ts +34 -0
  759. package/src/shares/dialog.tsx +295 -0
  760. package/src/shares/flow.tsx +56 -0
  761. package/src/shares/live.ts +14 -22
  762. package/src/shares/pane-image.ts +152 -0
  763. package/src/shares/personal-pane.ts +64 -0
  764. package/src/shares/portable-layout.ts +4 -2
  765. package/src/shares/prepare.ts +20 -0
  766. package/src/sources/gloomberb-cloud/index.ts +109 -10
  767. package/src/sources/gloomberb-cloud/normalizers.ts +3 -0
  768. package/src/sources/gloomberb-cloud/perpetuals.ts +249 -0
  769. package/src/sources/history-result.ts +1 -0
  770. package/src/sources/history-retention.ts +11 -0
  771. package/src/sources/listing-symbols.ts +4 -1
  772. package/src/sources/listing-target.ts +4 -0
  773. package/src/sources/provider-router/batches.ts +4 -1
  774. package/src/sources/provider-router/cache.ts +4 -2
  775. package/src/sources/provider-router/cached-routes.ts +1 -1
  776. package/src/sources/provider-router/financial-routes.ts +1 -0
  777. package/src/sources/provider-router/financials.ts +13 -0
  778. package/src/sources/provider-router/history.ts +43 -25
  779. package/src/sources/provider-router/index.ts +14 -0
  780. package/src/sources/provider-router/search.ts +1 -0
  781. package/src/state/hooks/inline-tickers.ts +49 -11
  782. package/src/sync/core-contributors.ts +1 -0
  783. package/src/test-support/shell-pane-keys.tsx +1 -0
  784. package/src/theme/theme-context.tsx +15 -1
  785. package/src/tickers/company-name.ts +75 -0
  786. package/src/tickers/instrument-kind.ts +34 -1
  787. package/src/tickers/listing-name.ts +34 -0
  788. package/src/tickers/listing-venues.ts +132 -0
  789. package/src/tickers/open-target.ts +13 -4
  790. package/src/tickers/perpetual-listing.ts +151 -0
  791. package/src/tickers/prediction-listing.ts +26 -0
  792. package/src/tickers/search/index.ts +72 -11
  793. package/src/tickers/search/ranking.ts +98 -20
  794. package/src/tickers/search/types.ts +7 -1
  795. package/src/tickers/ticker-changes.ts +213 -0
  796. package/src/tickers/us-listing.ts +34 -0
  797. package/src/time-series/economic.ts +2 -6
  798. package/src/time-series/hooks.ts +9 -0
  799. package/src/time-series/ice-credit.ts +39 -0
  800. package/src/time-series/resolve.ts +211 -29
  801. package/src/time-series/studies.ts +1 -0
  802. package/src/time-series/types.ts +12 -2
  803. package/src/time-series/use-chart-resolution.ts +32 -0
  804. package/src/types/config.ts +10 -2
  805. package/src/types/data-provider.ts +11 -0
  806. package/src/types/financials.ts +62 -1
  807. package/src/types/headless.ts +113 -0
  808. package/src/types/instrument.ts +4 -0
  809. package/src/types/plugin.ts +52 -2
  810. package/src/types/price-history.ts +7 -0
  811. package/src/types/ticker-changes.ts +26 -0
  812. package/src/types/ticker.ts +14 -0
  813. package/src/ui/context-menu.tsx +13 -1
  814. package/src/ui/host.tsx +57 -3
  815. package/src/ui/live-resize.ts +91 -0
  816. package/src/ui/live-stretch.ts +153 -0
  817. package/src/ui/measured-cells.ts +58 -0
  818. package/src/ui/sticky-scroll.ts +7 -0
  819. package/src/utils/basis-points.ts +17 -0
  820. package/src/utils/central-banks.ts +76 -0
  821. package/src/utils/cli-output.ts +131 -15
  822. package/src/utils/concurrency.ts +25 -0
  823. package/src/utils/crypto-pair.ts +20 -0
  824. package/src/utils/currency-pair.ts +19 -0
  825. package/src/utils/currency-units.ts +11 -0
  826. package/src/utils/datetime-format.ts +27 -6
  827. package/src/utils/decimal-comma.ts +19 -0
  828. package/src/utils/dom-screenshot-css.ts +265 -0
  829. package/src/utils/dom-screenshot-rasters.ts +276 -0
  830. package/src/utils/dom-screenshot.ts +439 -52
  831. package/src/utils/double-escape-close.ts +15 -1
  832. package/src/utils/exchange-rate-snapshot.ts +6 -2
  833. package/src/utils/exchanges.ts +143 -1
  834. package/src/utils/format.ts +35 -5
  835. package/src/utils/forward-dividend.ts +14 -0
  836. package/src/utils/fundamentals.ts +66 -0
  837. package/src/utils/futures-generic.ts +6 -12
  838. package/src/utils/fx-market-hours.ts +15 -5
  839. package/src/utils/fx-status.ts +6 -1
  840. package/src/utils/inline-content-tokenizer.ts +22 -3
  841. package/src/utils/market-capitalization.ts +8 -0
  842. package/src/utils/one-shot.ts +73 -0
  843. package/src/utils/options.ts +37 -8
  844. package/src/utils/price-history.ts +99 -42
  845. package/src/utils/sec-eps-basis.ts +24 -11
  846. package/src/utils/sec.ts +61 -7
  847. package/src/utils/utc-time.ts +63 -4
  848. package/src/plugins/builtin/fx-matrix/freshness.ts +0 -65
  849. /package/src/plugins/{builtin/plugin-marketplace → marketplace}/activation.ts +0 -0
  850. /package/src/plugins/{builtin/plugin-marketplace → marketplace}/ids.ts +0 -0
  851. /package/src/plugins/{builtin/plugin-marketplace → marketplace}/packs.ts +0 -0
  852. /package/src/{plugins/builtin/cloud → tickers}/starter-symbols.ts +0 -0
@@ -8,7 +8,7 @@ import type {
8
8
  HeadlessSeriesResult,
9
9
  HeadlessSnapshotResult,
10
10
  } from "../../types/plugin";
11
- import { parseReportTime, type ReportTime } from "../../utils/utc-time";
11
+ import { localTimeSuffix, parseReportTime, type ReportTime } from "../../utils/utc-time";
12
12
  import { isRecord } from "../../utils/guards";
13
13
  import { latestRegularSessionClose } from "../../market-data/market/freshness";
14
14
  import { reportMarketSession, type ReportMarket, type SessionObservation } from "./market-session";
@@ -27,6 +27,8 @@ export interface ReportFreshness {
27
27
  asOf: string | null;
28
28
  /** Oldest observation, when it is more than a day older than `asOf`. */
29
29
  oldest?: string;
30
+ /** With a declared `citeOldest`: the oldest observation whenever it is older than `asOf`. Exports cite it. */
31
+ oldestObservation?: string;
30
32
  status: ReportFreshnessStatus;
31
33
  /** How far the data is held back; the most of its rows when they differ... */
32
34
  delayMinutes?: number;
@@ -40,14 +42,54 @@ export interface ReportFreshness {
40
42
  /** ...and what the rest are. */
41
43
  feed?: "live" | "delayed";
42
44
  basis?: string;
45
+ /** Set when the dates are the start of a period: the status line then reads `Sep 2026 (monthly)`. */
46
+ periodicity?: "monthly" | "quarterly" | "annual";
43
47
  /** The local date of the session close `asOf` is, when the newest observation is its venue's latest close. */
44
48
  asOfClose?: string;
49
+ /**
50
+ * Whose trading day the dated as-of (`asOfClose`, or a date-only `asOf`) is,
51
+ * when one market's calendar dates it: `US`, or a venue code such as `ASX`.
52
+ */
53
+ tradingDayMarket?: string;
45
54
  /** Where the venues of a quote report stand now, from their session calendars. */
46
55
  market?: ReportMarket;
56
+ /**
57
+ * For a dataset (declared `dataset`, with no quote among its rows): whole UTC
58
+ * days from the last day its newest observation covers to when this report
59
+ * was built. A capture's status line then reads `observed Sun 4 Oct · lag 6d`.
60
+ */
61
+ lagDays?: number;
62
+ /**
63
+ * What the newest observation is, for a dataset that names it (`last
64
+ * trade`): every line then reads `Source: DTCC · last trade 2026-10-09 · lag 2 days`.
65
+ */
66
+ observation?: string;
67
+ /**
68
+ * Each source's own newest observation, in the order declared, when the data
69
+ * comes from several sources polled apart: `asOf` is the newest of them.
70
+ */
71
+ sources?: ReportSourceAsOf[];
72
+ /**
73
+ * Whole minutes from the oldest of `sources` (or, declared `oldestAge`, the
74
+ * oldest observation) to when this report was built: `oldest 9 min`.
75
+ */
76
+ oldestAgeMinutes?: number;
77
+ /** What the figures are, said after the status: `funding = current estimate, pays 08:00 UTC`. */
78
+ note?: string;
79
+ /** Who published the figures, when the data names them. */
80
+ publisher?: string;
81
+ /** When the publisher released the data, an ISO instant in UTC or an ISO date, when the data states it. */
82
+ publishedAt?: string;
47
83
  /** When this report was built. */
48
84
  retrievedAt: string;
49
85
  }
50
86
 
87
+ /** One source's newest observation: an ISO instant in UTC. */
88
+ interface ReportSourceAsOf {
89
+ source: string;
90
+ asOf: string;
91
+ }
92
+
51
93
  const DEFAULT_REPORT_SOURCE = "Gloom Cloud";
52
94
 
53
95
  /** Top-level row and metadata keys that say when a value was observed. */
@@ -159,12 +201,12 @@ function collect(
159
201
  definition: Pick<HeadlessPaneDefinition, "shape">,
160
202
  result: HeadlessPaneResult,
161
203
  now: number,
162
- { observedKey, ignoreStaleFlags }: Pick<HeadlessPaneFreshness, "observedKey" | "ignoreStaleFlags">,
204
+ { observedKey, ignoreStaleFlags, observations: declaredObservations }: Pick<HeadlessPaneFreshness, "observedKey" | "ignoreStaleFlags" | "observations">,
163
205
  ): { signals: FeedSignals; observations: Observations } {
164
206
  const signals = emptySignals();
165
207
  const observations: Observations = { units: [], extra: [] };
166
208
  const rowKeys = observedKey ? [observedKey] : OBSERVATION_KEYS;
167
- for (const row of resultRecords(definition, result)) {
209
+ for (const row of declaredObservations ?? resultRecords(definition, result)) {
168
210
  if (!isRecord(row)) continue;
169
211
  readFeedSignals(row, signals);
170
212
  const time = observedTime(row, now, rowKeys)
@@ -173,7 +215,7 @@ function collect(
173
215
  readSession(row, time, signals);
174
216
  if (time) observations.units.push(time);
175
217
  }
176
- if (definition.shape === "series") {
218
+ if (definition.shape === "series" && !declaredObservations) {
177
219
  for (const series of (result as HeadlessSeriesResult).series) {
178
220
  const record = series as unknown as Record<string, unknown>;
179
221
  readFeedSignals(record, signals);
@@ -190,6 +232,8 @@ function collect(
190
232
  const time = observedKey ? null : observedTime(result.metadata, now)
191
233
  ?? (observations.units.length ? null : observedTime(result.metadata, now, UPDATE_KEYS));
192
234
  if (time) observations.extra.push(time);
235
+ // A board whose figures follow one venue's sessions (HM's) names it as a quote row does.
236
+ readSession(result.metadata, time, signals);
193
237
  }
194
238
  if (definition.shape === "snapshot" && !observedKey) {
195
239
  const time = parseReportTime((result as HeadlessSnapshotResult).asOf);
@@ -230,13 +274,17 @@ function worstFeedStatus(statuses: Array<"live" | "delayed" | "stale">): "live"
230
274
  ), null);
231
275
  }
232
276
 
233
- /** Completed US sessions after the day of `newest`, counted back from `now`, up to `limit`. */
234
- function sessionsAfter(newest: ReportTime, now: number, limit: number): number {
277
+ /**
278
+ * Completed sessions after the day of `newest`, counted back from `now`, up to
279
+ * `limit`: the US's, or `exchange`'s when the app knows its sessions.
280
+ */
281
+ function sessionsAfter(newest: ReportTime, now: number, limit: number, exchange?: string): number {
235
282
  const newestDate = new Date(newest.time).toISOString().slice(0, 10);
283
+ const venue = exchange && latestRegularSessionClose(exchange, now) ? exchange : "NYSE";
236
284
  let count = 0;
237
285
  let cursor = now;
238
286
  while (count < limit) {
239
- const close = latestRegularSessionClose("NYSE", cursor);
287
+ const close = latestRegularSessionClose(venue, cursor);
240
288
  if (!close || close.date <= newestDate) break;
241
289
  count += 1;
242
290
  cursor = close.close - 1;
@@ -250,7 +298,26 @@ function overdue(declared: HeadlessPaneFreshness, asOf: ReportTime | null, now:
250
298
  if (nextExpected != null && now > nextExpected.time + RELEASE_GRACE_MS) return true;
251
299
  if (!asOf) return false;
252
300
  if (declared.maxAgeMinutes != null && now - asOf.time > declared.maxAgeMinutes * 60_000) return true;
253
- return declared.cadence === "daily" && sessionsAfter(asOf, now, 2) > 1;
301
+ return declared.cadence === "daily" && sessionsAfter(asOf, now, 2, declared.cadenceExchange) > 1;
302
+ }
303
+
304
+ /**
305
+ * Whole UTC days from the last day an observation covers to `now`: its own
306
+ * day, or the last day of the month, quarter or year a periodic date starts.
307
+ * September's figure read on 10 Oct lags 10 days, not 39.
308
+ */
309
+ function datasetLagDays(observed: ReportTime, periodicity: ReportFreshness["periodicity"], now: number): number {
310
+ const day = new Date(observed.time);
311
+ const year = day.getUTCFullYear();
312
+ const month = day.getUTCMonth();
313
+ const through = !periodicity ? Date.UTC(year, month, day.getUTCDate())
314
+ // Day 0 of the month after the period is its last day.
315
+ : periodicity === "monthly" ? Date.UTC(year, month + 1, 0)
316
+ : periodicity === "quarterly" ? Date.UTC(year, month - (month % 3) + 3, 0)
317
+ : Date.UTC(year, 12, 0);
318
+ const today = new Date(now);
319
+ const todayStart = Date.UTC(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate());
320
+ return Math.max(0, Math.round((todayStart - through) / DAY_MS));
254
321
  }
255
322
 
256
323
  interface FreshnessInputs {
@@ -260,13 +327,29 @@ interface FreshnessInputs {
260
327
  now: number;
261
328
  }
262
329
 
330
+ /** The declared sources that name themselves and carry a time, in the order declared. */
331
+ function declaredSources(declared: HeadlessPaneFreshness, now: number): Array<{ source: string; time: ReportTime }> {
332
+ return (declared.sources ?? []).flatMap(({ source, asOf }) => {
333
+ const name = source.trim();
334
+ const time = observedTime({ asOf }, now, ["asOf"]);
335
+ return name && time && !time.dateOnly ? [{ source: name, time }] : [];
336
+ });
337
+ }
338
+
263
339
  function resolveFreshness({ declared, signals, observations, now }: FreshnessInputs): ReportFreshness {
264
- const declaredAsOf = observedTime({ asOf: declared.asOf }, now, ["asOf"]);
340
+ const sources = declaredSources(declared, now);
341
+ // Sources date the data by their own observations, as a declared as-of does.
342
+ const declaredAsOf = observedTime({ asOf: declared.asOf }, now, ["asOf"]) ?? newest(sources.map(({ time }) => time));
265
343
  const asOf = declaredAsOf ?? newest([...observations.units, ...observations.extra]);
344
+ // The oldest source, not the newest, says how far behind the view can be.
345
+ const agedFrom = sources.length ? oldest(sources.map(({ time }) => time))
346
+ : declared.oldestAge ? oldest(observations.units) ?? asOf : null;
347
+ const oldestAgeMinutes = agedFrom && !agedFrom.dateOnly ? Math.max(0, Math.floor((now - agedFrom.time) / 60_000)) : null;
266
348
  // A loader that dates its data itself also says how old the oldest part is, or nothing;
267
349
  // `oldest: null` says the rows are a history, whose first entry is not stale data.
268
350
  const oldestTime = declared.oldest != null ? parseReportTime(declared.oldest)
269
351
  : declaredAsOf || declared.oldest === null ? null : oldest(observations.units);
352
+ const oldestUnit = declared.citeOldest ? oldest(observations.units) : null;
270
353
  const late = overdue(declared, asOf, now);
271
354
 
272
355
  let status: ReportFreshnessStatus;
@@ -320,22 +403,44 @@ function resolveFreshness({ declared, signals, observations, now }: FreshnessInp
320
403
  }
321
404
  }
322
405
  }
323
- const { market, closeDate } = reportMarketSession(signals.sessions, now);
406
+ const { market, closeDate, closeMarket } = reportMarketSession(signals.sessions, now);
324
407
  // The close wording dates the newest observation, so only when that is what `asOf` is.
325
408
  const asOfClose = closeDate && asOf && !declaredAsOf ? closeDate : null;
326
-
409
+ const tradingDayMarket = asOfClose ? closeMarket
410
+ : asOf?.dateOnly ? declared.tradingDayMarket?.trim() || null : null;
411
+ const periodicity = declared.periodicity && asOf?.dateOnly ? declared.periodicity : undefined;
412
+ // Rows that are quotes (a feed, a venue's session) keep the market line, even on a dataset's pane.
413
+ const quoted = signals.live || signals.delayed || signals.sessions.length > 0;
414
+ // A dataset disseminated with a delay (DTCC's swap tape) lags its newest observation all the same.
415
+ const lagDays = declared.dataset && asOf && !quoted && (status === "not-a-feed" || status === "stale" || status === "delayed")
416
+ ? datasetLagDays(asOf, periodicity, now) : null;
417
+ const observation = lagDays != null ? declared.observation?.trim() : undefined;
418
+
419
+ const note = declared.note?.trim();
420
+ const publisher = declared.publisher?.trim();
421
+ const publishedAt = parseReportTime(declared.publishedAt);
327
422
  return {
328
- source: declared.source?.trim() || DEFAULT_REPORT_SOURCE,
423
+ source: declared.source?.trim() || (sources.length ? [...new Set(sources.map(({ source }) => source))].join(", ") : DEFAULT_REPORT_SOURCE),
329
424
  asOf: asOf ? isoTime(asOf) : null,
330
425
  ...(asOf && oldestTime && asOf.time - oldestTime.time > OLDEST_GAP_MS ? { oldest: isoTime(oldestTime) } : {}),
426
+ ...(asOf && oldestUnit && oldestUnit.time < asOf.time ? { oldestObservation: isoTime(oldestUnit) } : {}),
331
427
  status,
332
428
  ...(delayMinutes != null && delayMinutes > 0 ? { delayMinutes } : {}),
333
429
  ...(minDelayMinutes != null && minDelayMinutes > 0 ? { minDelayMinutes } : {}),
334
430
  ...(status === "stale" && staleAt ? { ageMinutes: Math.max(0, Math.floor((now - staleAt.time) / 60_000)) } : {}),
335
431
  ...(partial ?? {}),
336
432
  ...(status === "not-a-feed" && declared.basis ? { basis: declared.basis } : {}),
433
+ ...(periodicity ? { periodicity } : {}),
337
434
  ...(asOfClose ? { asOfClose } : {}),
435
+ ...(tradingDayMarket ? { tradingDayMarket } : {}),
338
436
  ...(market ? { market } : {}),
437
+ ...(lagDays != null ? { lagDays } : {}),
438
+ ...(observation ? { observation } : {}),
439
+ ...(sources.length ? { sources: sources.map(({ source, time }) => ({ source, asOf: isoTime(time) })) } : {}),
440
+ ...(oldestAgeMinutes != null ? { oldestAgeMinutes } : {}),
441
+ ...(note ? { note } : {}),
442
+ ...(publisher ? { publisher } : {}),
443
+ ...(publishedAt ? { publishedAt: isoTime(publishedAt) } : {}),
339
444
  retrievedAt: new Date(now).toISOString(),
340
445
  };
341
446
  }
@@ -401,20 +506,52 @@ const DAY_MS = 24 * 60 * 60_000;
401
506
  /** A reopen further ahead than this reads with its date, not just its weekday. */
402
507
  const WEEKDAY_ONLY_MS = 6 * DAY_MS;
403
508
 
404
- /** `Fri 9 Oct` for a `YYYY-MM-DD` date, with the year when it is not the year of `now`. */
405
- function dayText(date: string, now: number): string {
509
+ /**
510
+ * `Fri 9 Oct 2026` for a `YYYY-MM-DD` date. Always with its year: a report
511
+ * read later, or pasted somewhere, still says which day it means.
512
+ */
513
+ function dayText(date: string): string {
514
+ const day = new Date(`${date.slice(0, 10)}T00:00:00Z`);
515
+ if (!Number.isFinite(day.getTime())) return date;
516
+ return `${WEEKDAYS[day.getUTCDay()]} ${day.getUTCDate()} ${MONTHS[day.getUTCMonth()]} ${day.getUTCFullYear()}`;
517
+ }
518
+
519
+ /**
520
+ * `US trading day Fri 9 Oct 2026`: a date names whose day it is when one
521
+ * market's calendar dates it, so a reader a day ahead of New York does not take
522
+ * the US Friday for their own. Never shifted to the reader's zone.
523
+ */
524
+ function tradingDayText(date: string, market: string | undefined): string {
525
+ return market ? `${market} trading day ${dayText(date)}` : dayText(date);
526
+ }
527
+
528
+ /** `Sep 2026`, `Q2 2026` or `2026` for the period a date starts. */
529
+ function periodText(date: string, periodicity: NonNullable<ReportFreshness["periodicity"]>): string {
406
530
  const day = new Date(`${date.slice(0, 10)}T00:00:00Z`);
407
531
  if (!Number.isFinite(day.getTime())) return date;
408
- const year = day.getUTCFullYear() === new Date(now).getUTCFullYear() ? "" : ` ${day.getUTCFullYear()}`;
409
- return `${WEEKDAYS[day.getUTCDay()]} ${day.getUTCDate()} ${MONTHS[day.getUTCMonth()]}${year}`;
532
+ const year = day.getUTCFullYear();
533
+ const month = day.getUTCMonth();
534
+ return periodicity === "monthly" ? `${MONTHS[month]} ${year}`
535
+ : periodicity === "quarterly" ? `Q${Math.floor(month / 3) + 1} ${year}`
536
+ : `${year}`;
410
537
  }
411
538
 
412
- /** `Fri 9 Oct 23:59 UTC`, or `Fri 9 Oct` for a date. */
413
- function timeText(value: string, now: number): string {
539
+ /** The period a FRED series frequency ("Monthly", "Quarterly") publishes in; null for daily and weekly series, whose dates are days. */
540
+ export function periodicityOf(frequency: string | null | undefined): ReportFreshness["periodicity"] | null {
541
+ const word = frequency?.trim().toLowerCase() ?? "";
542
+ return word.startsWith("monthly") ? "monthly" : word.startsWith("quarterly") ? "quarterly" : word.startsWith("annual") ? "annual" : null;
543
+ }
544
+
545
+ /**
546
+ * `Fri 9 Oct 2026 23:59 UTC`, or `Fri 9 Oct 2026` for a date. With `local`, the
547
+ * reader's own time follows when they set a zone: `(Sat 10 Oct 08:59 Asia/Tokyo)`.
548
+ */
549
+ function timeText(value: string, local: boolean): string {
414
550
  const time = parseReportTime(value);
415
551
  if (!time) return value;
416
552
  const iso = new Date(time.time).toISOString();
417
- return time.dateOnly ? dayText(iso, now) : `${dayText(iso, now)} ${iso.slice(11, 16)} UTC`;
553
+ if (time.dateOnly) return dayText(iso);
554
+ return `${dayText(iso)} ${iso.slice(11, 16)} UTC${local ? localTimeSuffix(time.time) : ""}`;
418
555
  }
419
556
 
420
557
  /**
@@ -449,6 +586,16 @@ function ageText(minutes: number): string {
449
586
  return `${Math.floor(hours / 24)} days`;
450
587
  }
451
588
 
589
+ /**
590
+ * How many rows are stale when only some are: `2 of 16 stale`, or `2 stale`
591
+ * when `short`. Null when the report does not count them. One wording for a
592
+ * report's status line, a capture's and the status a capture prints.
593
+ */
594
+ export function staleRowsText(freshness: Pick<ReportFreshness, "staleCount" | "observationCount">, short = false): string | null {
595
+ if (freshness.staleCount == null || freshness.observationCount == null) return null;
596
+ return `${short ? freshness.staleCount : `${freshness.staleCount} of ${freshness.observationCount}`} stale`;
597
+ }
598
+
452
599
  /** `short` counts stale rows without their total (`2 stale`), for a narrow capture. */
453
600
  function statusText(freshness: ReportFreshness, short = false): string {
454
601
  switch (freshness.status) {
@@ -457,12 +604,13 @@ function statusText(freshness: ReportFreshness, short = false): string {
457
604
  case "delayed":
458
605
  return delayedText(freshness);
459
606
  case "stale": {
460
- if (freshness.staleCount != null && freshness.observationCount != null) {
607
+ const stale = staleRowsText(freshness, short);
608
+ if (stale) {
461
609
  const rest = freshness.feed === "live" ? "live" : freshness.feed === "delayed" ? delayedText(freshness) : null;
462
- const count = short ? `${freshness.staleCount}` : `${freshness.staleCount} of ${freshness.observationCount}`;
463
- return `${rest ? `${rest}, ` : ""}${count} stale`;
610
+ return `${rest ? `${rest}, ` : ""}${stale}`;
464
611
  }
465
- return freshness.ageMinutes != null ? `stale (${ageText(freshness.ageMinutes)} old)` : "stale";
612
+ // A line that says how old its oldest observation is does not say it twice.
613
+ return freshness.ageMinutes != null && freshness.oldestAgeMinutes == null ? `stale (${ageText(freshness.ageMinutes)} old)` : "stale";
466
614
  }
467
615
  case "not-a-feed":
468
616
  return freshness.basis ? `not a live feed (${freshness.basis})` : "not a live feed";
@@ -475,32 +623,66 @@ function statusText(freshness: ReportFreshness, short = false): string {
475
623
  }
476
624
  }
477
625
 
626
+ /** Whether an ISO instant falls on the UTC day the report was built. */
627
+ function onRetrievalDay(iso: string, retrievedAt: string): boolean {
628
+ return iso.slice(0, 10) === retrievedAt.slice(0, 10);
629
+ }
630
+
478
631
  /**
479
632
  * The dated as-of: a session close, a UTC time, a date, or when the report was
480
- * retrieved; with the oldest observation unless `short`.
633
+ * retrieved; with the oldest observation unless `short`, or when the line says
634
+ * how old the oldest is instead. `undated` leaves the day off a time of the
635
+ * day the report was built, as a pane footer reads today.
636
+ */
637
+ function asOfText(freshness: ReportFreshness, short: boolean, local: boolean, undated = false): string {
638
+ if (!freshness.asOf) return `retrieved ${timeText(freshness.retrievedAt, local)}`;
639
+ const market = freshness.tradingDayMarket;
640
+ const period = freshness.periodicity;
641
+ const time = parseReportTime(freshness.asOf);
642
+ const asOf = period ? `${periodText(freshness.asOf, period)} (${period})`
643
+ : freshness.asOfClose
644
+ ? `${tradingDayText(freshness.asOfClose, market)} close`
645
+ : time?.dateOnly ? tradingDayText(freshness.asOf, market)
646
+ : undated && time && onRetrievalDay(new Date(time.time).toISOString(), freshness.retrievedAt)
647
+ ? `${new Date(time.time).toISOString().slice(11, 16)} UTC` : timeText(freshness.asOf, local);
648
+ const oldest = freshness.oldest && (period ? periodText(freshness.oldest, period) : dayText(freshness.oldest));
649
+ return oldest && !short && freshness.oldestAgeMinutes == null ? `${asOf} (oldest ${oldest})` : asOf;
650
+ }
651
+
652
+ /**
653
+ * Each source's own as-of, the zone named once at the end:
654
+ * `Sun 11 Oct 2026 · Binance 02:15 · Bybit 02:24 UTC`. The day leads, the newest source's,
655
+ * and a source observed on another day carries its own (`Binance Sat 10 Oct
656
+ * 23:58`). `undated` leaves the leading day off when it is the day the report
657
+ * was built.
481
658
  */
482
- function asOfText(freshness: ReportFreshness, now: number, short = false): string {
483
- if (!freshness.asOf) return `retrieved ${timeText(freshness.retrievedAt, now)}`;
484
- const asOf = freshness.asOfClose ? `${dayText(freshness.asOfClose, now)} close` : timeText(freshness.asOf, now);
485
- return freshness.oldest && !short ? `${asOf} (oldest ${dayText(freshness.oldest, now)})` : asOf;
659
+ function sourcesAsOfParts(freshness: ReportFreshness, sources: readonly ReportSourceAsOf[], undated: boolean): string[] {
660
+ const times = sources.map(({ source, asOf }) => ({ source, iso: new Date(Date.parse(asOf)).toISOString() }));
661
+ const day = times.reduce((latest, { iso }) => (iso > latest ? iso : latest), "").slice(0, 10);
662
+ const parts = times.map(({ source, iso }) => {
663
+ const date = iso.slice(0, 10) === day ? "" : `${dayText(iso).replace(/ \d{4}$/, "")} `;
664
+ return `${source} ${date}${iso.slice(11, 16)}`;
665
+ });
666
+ parts[parts.length - 1] += " UTC";
667
+ return undated && onRetrievalDay(day, freshness.retrievedAt) ? parts : [dayText(day), ...parts];
486
668
  }
487
669
 
488
670
  /**
489
671
  * When the first venue opens again: its UTC time when that is later the same
490
672
  * UTC day, else its weekday at the venue (`Mon`), with the date a week out.
491
673
  */
492
- function reopenText(market: ReportMarket, now: number): string | null {
674
+ function reopenText(market: ReportMarket, now: number, local: boolean): string | null {
493
675
  if (!market.reopensAt || !market.reopensOn) return null;
494
676
  const at = Date.parse(market.reopensAt);
495
677
  if (!Number.isFinite(at)) return null;
496
678
  const iso = new Date(at).toISOString();
497
- if (iso.slice(0, 10) === new Date(now).toISOString().slice(0, 10)) return `${iso.slice(11, 16)} UTC`;
498
- return at - now > WEEKDAY_ONLY_MS ? dayText(market.reopensOn, now) : WEEKDAYS[new Date(`${market.reopensOn}T00:00:00Z`).getUTCDay()]!;
679
+ if (iso.slice(0, 10) === new Date(now).toISOString().slice(0, 10)) return `${iso.slice(11, 16)} UTC${local ? localTimeSuffix(at) : ""}`;
680
+ return at - now > WEEKDAY_ONLY_MS ? dayText(market.reopensOn) : WEEKDAYS[new Date(`${market.reopensOn}T00:00:00Z`).getUTCDay()]!;
499
681
  }
500
682
 
501
683
  /** `markets closed until Mon`; `short` is the compact form a narrow capture falls back to (`reopens Mon`). */
502
- function marketText(market: ReportMarket, now: number, short = false): string {
503
- const reopen = reopenText(market, now);
684
+ function marketText(market: ReportMarket, now: number, short: boolean, local: boolean): string {
685
+ const reopen = reopenText(market, now, local);
504
686
  switch (market.state) {
505
687
  case "open":
506
688
  return short ? "open" : "markets open";
@@ -525,18 +707,31 @@ interface StatusLineDetail {
525
707
  status?: "short";
526
708
  /** `reopens Mon` for `markets closed until Mon`; `none` leaves the market out. */
527
709
  market?: "short" | "none";
710
+ /** Leave out the reader's local time beside UTC times. */
711
+ local?: "none";
712
+ /** Leave the day off a time of the day the report was built. */
713
+ date?: "none";
714
+ /** Leave out what the figures are (`funding = current estimate`). */
715
+ note?: "none";
528
716
  }
529
717
 
530
718
  /**
531
- * The parts of a report's status line: the dated as-of, how current the data
532
- * is (`15 min delayed`, `live`), and where its markets stand, when known.
719
+ * The parts of a report's status line: the dated as-of (each source's, when
720
+ * it names several), how old the oldest observation is when it says, how
721
+ * current the data is (`15 min delayed`, `live`), where its markets stand,
722
+ * and what the figures are, when known.
533
723
  */
534
724
  function statusLineParts(freshness: ReportFreshness, detail: StatusLineDetail = {}): string[] {
535
725
  const now = Date.parse(freshness.retrievedAt);
726
+ const local = detail.local !== "none";
727
+ const undated = detail.date === "none";
536
728
  return [
537
- asOfText(freshness, now, detail.asOf === "short"),
729
+ ...(freshness.sources?.length ? sourcesAsOfParts(freshness, freshness.sources, undated)
730
+ : [asOfText(freshness, detail.asOf === "short", local, undated)]),
731
+ ...(freshness.oldestAgeMinutes != null ? [`oldest ${ageText(freshness.oldestAgeMinutes)}`] : []),
538
732
  statusText(freshness, detail.status === "short"),
539
- ...(freshness.market && detail.market !== "none" ? [marketText(freshness.market, now, detail.market === "short")] : []),
733
+ ...(freshness.market && detail.market !== "none" ? [marketText(freshness.market, now, detail.market === "short", local)] : []),
734
+ ...(freshness.note && detail.note !== "none" ? [freshness.note] : []),
540
735
  ];
541
736
  }
542
737
 
@@ -544,36 +739,185 @@ function capitalize(text: string): string {
544
739
  return text ? `${text[0]!.toUpperCase()}${text.slice(1)}` : text;
545
740
  }
546
741
 
742
+ /** `Sun 4 Oct`, with the year only when it is not the year of the capture, which the lag counts from. */
743
+ function observedDayText(date: string, retrievedAt: string): string {
744
+ const text = dayText(date);
745
+ return date.slice(0, 4) === retrievedAt.slice(0, 4) ? text.replace(/ \d{4}$/, "") : text;
746
+ }
747
+
748
+ interface DatasetLineDetail {
749
+ /** Leave out the source. */
750
+ source?: "none";
751
+ /** Leave out the oldest observation. */
752
+ oldest?: "none";
753
+ /** Count stale rows without their total. */
754
+ status?: "short";
755
+ }
756
+
757
+ /** `lag 2 days`, `lag 1 day`. */
758
+ function lagText(days: number): string {
759
+ return `lag ${days} ${days === 1 ? "day" : "days"}`;
760
+ }
761
+
762
+ /**
763
+ * A dataset's status line: its source, the newest observation's date (the
764
+ * period, for monthly to annual data) and how many days it lags, then which
765
+ * rows are stale, if any. No market part: a dataset has no session. A
766
+ * dataset that names its observation reads as its rows do, by ISO date:
767
+ * `Source: DTCC · last trade 2026-10-09 · lag 2 days`.
768
+ */
769
+ function datasetLineParts(freshness: ReportFreshness, asOf: string, lagDays: number, detail: DatasetLineDetail = {}): string[] {
770
+ if (freshness.observation) {
771
+ return [
772
+ ...(detail.source === "none" ? [] : [`Source: ${freshness.source}`]),
773
+ `${freshness.observation} ${asOf.slice(0, 10)}`,
774
+ lagText(lagDays),
775
+ ...(freshness.status === "stale" ? ["stale"] : []),
776
+ ];
777
+ }
778
+ const period = freshness.periodicity;
779
+ const dated = (date: string) => (period ? periodText(date, period) : observedDayText(date, freshness.retrievedAt));
780
+ const oldest = freshness.oldest && detail.oldest !== "none" ? ` (oldest ${dated(freshness.oldest)})` : "";
781
+ const stale = freshness.status !== "stale" ? null : staleRowsText(freshness, detail.status === "short") ?? "stale";
782
+ return [
783
+ ...(detail.source === "none" ? [] : [`Source: ${freshness.source}`]),
784
+ `observed ${dated(asOf)}${period ? ` (${period})` : ""}${oldest}`,
785
+ `lag ${lagDays}d`,
786
+ ...(stale ? [stale] : []),
787
+ ];
788
+ }
789
+
547
790
  /**
548
- * The plain status line of a report or capture, without its source:
549
- * `Fri 9 Oct close · 15 min delayed · markets closed until Mon`.
791
+ * The plain status line of a capture: the longest of `statusLineVariants`,
792
+ * `Fri 9 Oct close · 15 min delayed · markets closed until Mon` for quotes.
550
793
  */
551
794
  export function formatStatusLine(freshness: ReportFreshness): string {
552
- return capitalize(statusLineParts(freshness).join(" · "));
795
+ return statusLineVariants(freshness)[0]!;
796
+ }
797
+
798
+ /** Whether the status line dates the data by its newest observation and lag (a dataset's), not by a market session. */
799
+ function datesObservation(freshness: ReportFreshness): freshness is ReportFreshness & { asOf: string; lagDays: number } {
800
+ return freshness.lagDays != null && !!freshness.asOf;
801
+ }
802
+
803
+ /**
804
+ * The status line a pane's footer shows, the one its report and capture end
805
+ * with: without the reader's local time, and without the day when the data is
806
+ * from the day it is read.
807
+ */
808
+ export function paneStatusLine(freshness: ReportFreshness): string {
809
+ return paneStatusLineVariants(freshness)[0]!;
810
+ }
811
+
812
+ /**
813
+ * `paneStatusLine` from longest to shortest, for a footer to show the first
814
+ * that fits beside its keys: the market part shortens, the stale total goes,
815
+ * then what the figures are.
816
+ */
817
+ export function paneStatusLineVariants(freshness: ReportFreshness): string[] {
818
+ // A dataset's line has no market or local time to leave out: it reads as its report and capture do.
819
+ if (datesObservation(freshness)) return statusLineVariants(freshness);
820
+ const pane: StatusLineDetail = { local: "none", date: "none" };
821
+ const details: StatusLineDetail[] = [pane, { ...pane, market: "short" }, { ...pane, market: "short", status: "short" }, { ...pane, market: "short", status: "short", note: "none" }];
822
+ return [...new Set(details.map((detail) => capitalize(statusLineParts(freshness, detail).join(" · "))))];
553
823
  }
554
824
 
555
825
  /**
556
826
  * The status line from longest to shortest, for a capture to take the first
557
- * that fits its width. The date and the delay stay to the last; the market
558
- * part shortens, then the oldest observation and the stale total go, then
559
- * the market part.
827
+ * that fits its width. Quotes and other reports leave out their source: the
828
+ * date and the delay stay to the last; the market part shortens, then the
829
+ * reader's local time, the oldest observation and the stale total go, then
830
+ * the market part. With `source`, the line starts as `fn` ends,
831
+ * `Source: CFTC · Tue 6 Oct 2026 · not a live feed (weekly report)`, shortens
832
+ * the same way and gives up its source last. A dataset reads
833
+ * `Source: Gloom Cloud · observed Sun 4 Oct · lag 6d` and gives up its oldest
834
+ * observation, the stale total, then its source.
560
835
  */
561
- export function statusLineVariants(freshness: ReportFreshness): string[] {
836
+ export function statusLineVariants(freshness: ReportFreshness, { source = false }: { source?: boolean } = {}): string[] {
837
+ if (datesObservation(freshness)) {
838
+ const { asOf, lagDays } = freshness;
839
+ const datasetDetails: DatasetLineDetail[] = [
840
+ {},
841
+ { oldest: "none" },
842
+ { oldest: "none", status: "short" },
843
+ { source: "none", oldest: "none", status: "short" },
844
+ ];
845
+ return [...new Set(datasetDetails.map((detail) => capitalize(datasetLineParts(freshness, asOf, lagDays, detail).join(" · "))))];
846
+ }
562
847
  const details: StatusLineDetail[] = [
563
848
  {},
564
849
  { market: "short" },
565
- { asOf: "short", market: "short" },
566
- { asOf: "short", status: "short", market: "short" },
567
- { asOf: "short", status: "short", market: "none" },
850
+ { market: "short", local: "none" },
851
+ { asOf: "short", market: "short", local: "none" },
852
+ { asOf: "short", status: "short", market: "short", local: "none" },
853
+ { asOf: "short", status: "short", market: "none", local: "none" },
568
854
  ];
569
- return [...new Set(details.map((detail) => capitalize(statusLineParts(freshness, detail).join(" · "))))];
855
+ // A line that dates each source keeps every source's time and their age to
856
+ // the last: its day goes first, then what the figures are.
857
+ const shortest = details.at(-1)!;
858
+ const variants: StatusLineDetail[] = freshness.sources?.length
859
+ ? [{}, ...details.map((detail) => ({ ...detail, date: "none" as const })), { ...shortest, date: "none", note: "none" }]
860
+ : freshness.note ? [...details, { ...shortest, note: "none" }] : details;
861
+ const lines = [...new Set(variants.map((detail) => capitalize(statusLineParts(freshness, detail).join(" · "))))];
862
+ // A line that dates each source names them already.
863
+ if (!source || freshness.sources?.length) return lines;
864
+ const sourced = [...new Set(variants.map((detail) => [`Source: ${freshness.source}`, ...statusLineParts(freshness, detail)].join(" · ")))];
865
+ const floor = sourced.at(-1)!.length;
866
+ return [...sourced, ...lines.filter((line) => line.length < floor)];
867
+ }
868
+
869
+ /** A capture's status line: the facts it states (its `freshness` in JSON) and its text, longest first. */
870
+ export interface CaptureStatusLine {
871
+ freshness: ReportFreshness;
872
+ variants: string[];
873
+ /**
874
+ * What the line dates: `data` when it comes from the pane's own report or
875
+ * the view the pane dates itself, so the pane's own `as of` text says it
876
+ * twice; `quotes` for the quotes on screen, which may not be what the pane's
877
+ * `as of` dates; null when it dates nothing it can name.
878
+ */
879
+ dates: "data" | "quotes" | null;
880
+ }
881
+
882
+ /**
883
+ * What a capture's status line says about its data, from the first that applies:
884
+ * - a dataset's report (CHOKE's transits, HIRE's postings): its source, newest observation and lag;
885
+ * - a pane that dates its own view (PERP's venues, FXC's rates, the news wire's delay): what it publishes;
886
+ * - quotes on screen, when the pane does not say what its data is: their session, delay and market;
887
+ * - the pane's report, when it says how current its data is: the line `fn` ends with, source first;
888
+ * - otherwise what the rendered view carries, which may be nothing.
889
+ */
890
+ export function captureStatusLine({ report, selfDated, quoted, rendered }: {
891
+ /** The pane's own report, loaded for the capture: what `fn` would say. */
892
+ report?: ReportFreshness;
893
+ /** Whether the rendered pane published how current its view is. */
894
+ selfDated: boolean;
895
+ /** The quotes the capture shows, read when the pane does not say what its data is. */
896
+ quoted?: ReportFreshness;
897
+ rendered: () => ReportFreshness;
898
+ }): CaptureStatusLine {
899
+ const plain = (freshness: ReportFreshness, dates: CaptureStatusLine["dates"]): CaptureStatusLine => ({ freshness, variants: statusLineVariants(freshness), dates });
900
+ const dated = (freshness: ReportFreshness) => (freshness.asOf ? "data" as const : null);
901
+ if (report && datesObservation(report)) return plain(report, "data");
902
+ if (selfDated) {
903
+ const view = rendered();
904
+ return plain(view, dated(view));
905
+ }
906
+ if (quoted) return plain(quoted, "quotes");
907
+ if (report && report.status !== "unreported") return { freshness: report, variants: statusLineVariants(report, { source: true }), dates: dated(report) };
908
+ return plain(rendered(), null);
570
909
  }
571
910
 
572
911
  /**
573
912
  * The one line every text report ends with, its source then its status line:
574
913
  * `Source: Gloom Cloud · Fri 9 Oct close · 15 min delayed · markets closed until Mon`.
575
914
  * Without a dated observation it says when the report was retrieved instead.
915
+ * A line that dates each of its sources names them there, so it is the status
916
+ * line alone: `Sun 11 Oct 2026 · Binance 02:15 · Bybit 02:24 UTC · oldest 9 min · delayed`.
576
917
  */
577
918
  export function formatFreshnessLine(freshness: ReportFreshness): string {
578
- return [`Source: ${freshness.source}`, ...statusLineParts(freshness)].join(" · ");
919
+ // A dataset that names its observation ends `fn` as its pane and capture do.
920
+ if (freshness.observation && datesObservation(freshness)) return statusLineVariants(freshness)[0]!;
921
+ const parts = statusLineParts(freshness);
922
+ return (freshness.sources?.length ? parts : [`Source: ${freshness.source}`, ...parts]).join(" · ");
579
923
  }