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
@@ -1,15 +1,194 @@
1
- import type { HeadlessPaneDefinition } from "../../../types/plugin";
2
- import type { ExposureRequest } from "../../../api-client/exposure";
1
+ import type { HeadlessPaneColumn, HeadlessPaneDefinition, HeadlessPaneRow } from "../../../types/plugin";
2
+ import type { ExposureExtensionObservation, ExposurePayload, ExposureRequest } from "../../../api-client/exposure";
3
+ import { cliTerminalWidth } from "../../../utils/cli-output";
4
+ import { clipToDisplayWidth, displayWidth } from "../../../utils/format";
5
+ import { fiscalLabel } from "../company-kpis/model";
6
+ import { humanLabel } from "../shared/research-cells";
3
7
  import { fetchExposure, fetchScenarios } from "./client";
4
- import { DEFAULT_SCENARIO, parseCustomScenario, parseHoldings, TABS } from "./model";
8
+ import { DEFAULT_SCENARIO, exposureCellText, parseCustomScenario, parseHoldings, pathRows, portfolioRows, scenarioTitle, tableRows, TABS, type ExposureRow } from "./model";
5
9
  import { portfolioHoldings, watchlistHoldings } from "./holdings";
10
+ import { describeCollections, findNamedCollection } from "../../../portfolio/collection-name";
11
+ import { driverAmount, driverRows, driverScope } from "./drivers";
12
+
13
+ /*
14
+ * The text report prints each section as its pane tab draws it: exposure
15
+ * ranges and weights as percents, a driver's value compact with its unit
16
+ * (201.18B USD), its fiscal period as KPIS prints it (FY2024) and its scope in
17
+ * words. The title names the scenario, so a one-shock scenario needs no shock
18
+ * column. Every table fits 100 columns, or a narrower terminal: names and
19
+ * scopes take the room the figures leave and are cut at a word only when they
20
+ * cannot fit. Caveats print under their row; a holding's own caveats are the
21
+ * report's error lines. CSV, NDJSON and JSON keep the records whole.
22
+ */
23
+ type CellId = Parameters<typeof exposureCellText>[1];
24
+ type TextRow = ExposureRow & { kind?: string; note?: string | null };
25
+ /** A column's cells, and how far it may narrow to fit: `min` lets it give way, cut at a word; `long` is a fuller form printed only when the whole table fits with it. */
26
+ interface TextColumn { key: string; header: string; read: (row: TextRow) => string; right?: boolean; min?: number; long?: (row: TextRow) => string }
27
+ type ColumnOptions = Omit<TextColumn, "key" | "header" | "read">;
28
+ const REPORT_WIDTH = 100;
29
+ /** The space the report renderer puts between columns. */
30
+ const GAP = 2;
31
+
32
+ const column = (key: string, header: string, read: TextColumn["read"], options: ColumnOptions = {}): TextColumn => ({ key, header, read, ...options });
33
+ const cell = (id: CellId, header: string, options?: ColumnOptions) => column(id, header, row => exposureCellText(row, id), options);
34
+ const holding = (header: string) => column("symbol", header, row => row.symbol);
35
+ /** The narrowest a name may get before the table gives up fitting, as the report renderer allows. */
36
+ const NAME = 8;
37
+ const period = cell("period", "Period");
38
+ const note: HeadlessPaneColumn = { key: "note", header: "Note", underRow: true };
39
+ const textRows = (items: TextRow[]) => items as unknown as HeadlessPaneRow[];
40
+ /** Caveats that belong to one row, printed under it; null prints nothing. */
41
+ const withNotes = (items: ExposureRow[], read: (row: ExposureRow) => string[]): TextRow[] => items.map(row => ({ ...row, note: read(row).join("\n") || null }));
42
+
43
+ /** Whole words that fit `width`, then an ellipsis, with no separator left dangling; a first word too long for it is cut. */
44
+ function clipWords(text: string, width: number): string {
45
+ if (displayWidth(text) <= width) return text;
46
+ let kept = "";
47
+ for (const word of text.split(" ")) {
48
+ const next = kept ? `${kept} ${word}` : word;
49
+ if (displayWidth(next) > width - 1) break;
50
+ kept = next;
51
+ }
52
+ kept = kept.replace(/[\s·,;:(/→-]+$/, "");
53
+ return kept ? `${kept}…` : clipToDisplayWidth(text, width);
54
+ }
55
+ /** The narrowest width past `width` at which a cut cell shows more of its text. */
56
+ function revealWidth(text: string, width: number): number {
57
+ const shown = clipWords(text, width);
58
+ for (let next = displayWidth(shown) + 1; next < displayWidth(text); next++) if (clipWords(text, next) !== shown) return next;
59
+ return displayWidth(text);
60
+ }
61
+
62
+ /**
63
+ * Column widths for `rows` within `budget`. The fuller forms print when the
64
+ * whole table fits with them. Otherwise the widest narrowing column gives up
65
+ * a cell at a time until the table fits, or each is at its floor; then the
66
+ * room a word cut leaves goes back, a word at a time, to the cut cell that
67
+ * needs the least of it.
68
+ */
69
+ function fit(columns: TextColumn[], rows: TextRow[], budget: number): { read: TextColumn["read"]; width: number }[] {
70
+ const header = (index: number) => displayWidth(columns[index]!.header);
71
+ const measure = (reads: TextColumn["read"][]) => reads.map((read, index) => Math.max(header(index), ...rows.map(row => displayWidth(read(row)))));
72
+ const total = (widths: number[]) => widths.reduce((sum, width) => sum + width, 0) + GAP * (widths.length - 1);
73
+ const long = columns.map(item => item.long ?? item.read);
74
+ const fuller = measure(long);
75
+ if (total(fuller) <= budget) return columns.map((_, index) => ({ read: long[index]!, width: fuller[index]! }));
76
+ const reads = columns.map(item => item.read);
77
+ const narrowing = columns.flatMap((item, index) => item.min === undefined ? [] : [index]);
78
+ const floor = (index: number) => Math.max(columns[index]!.min!, header(index));
79
+ let widths = measure(reads);
80
+ for (let overflow = total(widths) - budget; overflow > 0; overflow--) {
81
+ const widest = narrowing.reduce((pick, index) => widths[index]! > floor(index) && (pick < 0 || widths[index]! > widths[pick]!) ? index : pick, -1);
82
+ if (widest < 0) break;
83
+ widths[widest]!--;
84
+ }
85
+ const shown = (allotted: number[]) => allotted.map((width, index) => !narrowing.includes(index) ? width
86
+ : Math.max(header(index), ...rows.map(row => displayWidth(clipWords(reads[index]!(row), width)))));
87
+ widths = shown(widths);
88
+ for (;;) {
89
+ const spare = budget - total(widths);
90
+ let best: { index: number; width: number } | null = null;
91
+ for (const index of narrowing) for (const row of rows) {
92
+ const text = reads[index]!(row);
93
+ if (displayWidth(text) <= widths[index]!) continue;
94
+ const width = revealWidth(text, widths[index]!);
95
+ if (width - widths[index]! <= spare && (!best || width - widths[index]! < best.width - widths[best.index]!)) best = { index, width };
96
+ }
97
+ if (!best) break;
98
+ widths = shown(widths.map((width, index) => index === best!.index ? best!.width : width));
99
+ }
100
+ return columns.map((_, index) => ({ read: reads[index]!, width: widths[index]! }));
101
+ }
102
+
103
+ /** The text report's columns for these rows, fitted when they print, to 100 columns or the terminal when it is narrower. */
104
+ function fitted(columns: TextColumn[], rows: TextRow[], under: HeadlessPaneColumn[] = []): HeadlessPaneColumn[] {
105
+ const layouts = new Map<number, ReturnType<typeof fit>>();
106
+ const layout = () => {
107
+ const budget = Math.min(REPORT_WIDTH, cliTerminalWidth() ?? REPORT_WIDTH);
108
+ if (!layouts.has(budget)) layouts.set(budget, fit(columns, rows, budget));
109
+ return layouts.get(budget)!;
110
+ };
111
+ return [...columns.map((item, index): HeadlessPaneColumn => ({ key: item.key, header: item.header, ...(item.right ? { align: "right" as const } : {}),
112
+ format: (_value, row) => { const { read, width } = layout()[index]!; return clipWords(read(row as unknown as TextRow), width); } })), ...under];
113
+ }
114
+
115
+ function holdingColumns(data: ExposurePayload): TextColumn[] {
116
+ return [holding("Holding"), ...(data.scenario.shocks.length > 1 ? [cell("label", "Shock", { min: NAME })] : []), cell("basis", "Basis"),
117
+ cell("exposure", "Exposure %", { right: true }), cell("classification", "Evidence", { min: 10 }), cell("impact", "Stress est. %", { right: true }), period, cell("weight", "NAV wt. %", { right: true })];
118
+ }
119
+ const PATH_COLUMNS = [holding("Holding"), cell("label", "Path", { min: NAME }), cell("basis", "Basis"), cell("exposure", "Exposure %", { right: true }),
120
+ cell("classification", "Evidence", { min: 10 }), cell("impact", "Stress est. %", { right: true }), period];
121
+
122
+ /** A credit line's figures repeat the instrument their scope names: "$1.0 billion revolver: drawn" reads Drawn. */
123
+ function driverLabel(driver: ExposureExtensionObservation): string {
124
+ const named = Object.values(driver.dimensions ?? {}).find(value => driver.label.startsWith(`${value}: `));
125
+ const rest = named ? driver.label.slice(named.length + 2) : driver.label;
126
+ return rest.charAt(0).toUpperCase() + rest.slice(1);
127
+ }
128
+ /** The scope in words, after the basis when the figure is not the reported one: Adjusted · diluted. */
129
+ function driverScopeText(driver: ExposureExtensionObservation): string {
130
+ return [driver.basis && driver.basis !== "reported" ? humanLabel(driver.basis) : "", driverScope(driver)].filter(Boolean).join(" · ") || "--";
131
+ }
132
+ const MONTHS = ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"];
133
+ /**
134
+ * A disclosure's fiscal period as KPIS prints it, FY2024 or Q3 FY2026, with
135
+ * the date it ended (FY2024 · Sep 28, 2024) as the fuller form. The source
136
+ * words it freely; wording that is not a fiscal year or quarter prints as is.
137
+ */
138
+ function driverPeriod(driver: ExposureExtensionObservation): { short: string; long: string } {
139
+ const text = driver.period?.trim() || "--";
140
+ const match = /^(?:full\s+)?(?:Q([1-4])\s+)?(?:fiscal(?:\s+year)?\s+|FY\s?)(\d{4})(?:\s+ended\s+(.+))?$/i.exec(text);
141
+ if (!match) return { short: text, long: text };
142
+ const quarter = match[1] ? Number(match[1]) : null;
143
+ const short = fiscalLabel({ period: { start: null, end: null, kind: quarter ? "quarter" : "year", fiscalYear: Number(match[2]), fiscalQuarter: quarter, label: text } });
144
+ const ended = /^([A-Za-z]{3})[a-z]*\.?\s+(\d{1,2}),?\s+(\d{4})$/.exec(match[3]?.trim() ?? "");
145
+ const month = ended && MONTHS.includes(ended[1]!.toLowerCase()) ? `${ended[1]!.charAt(0).toUpperCase()}${ended[1]!.slice(1).toLowerCase()}` : null;
146
+ return { short, long: month ? `${short} · ${month} ${ended![2]}, ${ended![3]}` : short };
147
+ }
148
+ const DRIVER_COLUMNS = [holding("Holding"), column("label", "Company driver", row => row.driver ? driverLabel(row.driver) : row.label, { min: NAME }),
149
+ column("value", "Value", row => row.driver ? driverAmount(row.driver) : "--", { right: true }),
150
+ column("period", "Period", row => row.driver ? driverPeriod(row.driver).short : "--", { min: 10, long: row => row.driver ? driverPeriod(row.driver).long : "--" }),
151
+ column("scope", "Scope", row => row.driver ? driverScopeText(row.driver) : "--", { min: NAME })];
152
+
153
+ /**
154
+ * An anonymous counterparty leads with the name its filing gives it, and the
155
+ * Kind column already says what it is: "Undisclosed customer (Customer one)"
156
+ * reads "Customer one (undisclosed)", which still tells two apart when cut.
157
+ */
158
+ const targetName = (row: TextRow) => {
159
+ const match = row.kind ? new RegExp(`^Undisclosed ${row.kind} \\((.+)\\)$`, "i").exec(row.label) : null;
160
+ return match ? `${match[1]} (undisclosed)` : row.label;
161
+ };
162
+
163
+ /**
164
+ * Stress lines are always estimates, so the unknown share of holdings under a
165
+ * line says how partial it is. A concentration's share is unknown or an
166
+ * estimate, so it says only when its bound is partial, leaving out
167
+ * relationships without a share. With one holding, which
168
+ * holdings a line covers and their weight say nothing the Holdings table does
169
+ * not.
170
+ */
171
+ function portfolioColumns(data: ExposurePayload, concentration: boolean, items: ExposureRow[]): TextColumn[] {
172
+ const several = data.holdings.length > 1;
173
+ // Signed exposure differs from gross only where a holding is short.
174
+ const signed = concentration && items.some(row => JSON.stringify(row.impact) !== JSON.stringify(row.exposure));
175
+ const partial = (row: ExposureRow) => row.incomplete && row.exposure !== null;
176
+ const holdings = several ? [holding("Holdings")] : [];
177
+ if (concentration) return [column("kind", "Kind", row => humanLabel(row.kind ?? "")), ...holdings, column("label", "Target", targetName, { min: NAME }),
178
+ cell("basis", "Basis"), cell("exposure", "Gross exp. %", { right: true }), ...(signed ? [cell("impact", "Signed exp. %", { right: true })] : []),
179
+ ...(items.some(partial) ? [column("bound", "Bound", row => partial(row) ? "Partial" : "")] : []), period, ...(several ? [cell("weight", "Gross wt. %", { right: true })] : [])];
180
+ return [...holdings, ...(data.scenario.shocks.length > 1 ? [cell("label", "Target", { min: NAME })] : []), cell("basis", "Basis"),
181
+ cell("exposure", "Exposure %", { right: true }), cell("impact", "Stress est. %", { right: true }), period, cell("weight", "Covered wt. %", { right: true })];
182
+ }
183
+
184
+ const CONCENTRATION_VIEWS = ["country", "supplier", "customer"] as const;
6
185
 
7
186
  export const exposureHeadless: HeadlessPaneDefinition<"bundle"> = {
8
187
  shape: "bundle",
9
188
  freshness: { status: "not-a-feed", basis: "company disclosures" },
10
189
  argument: { kind: "free-text", optional: true, placeholder: "AAPL=60% NVDA=40%", description: "Tickers, signed NAV weights, PORT:<id>, or WATCH:<id>. Unweighted lists and watchlists use equal weights." },
11
190
  discovery: { aliases: ["EXPO"], dataRequirements: ["Gloom Cloud geographic revenue, disclosed supply chains, KPIs, guidance and credit disclosures", "Portfolio NAV for local PORT weights"],
12
- limitations: ["Pro: full holdings and up to four-hop paths; free: one holding, one hop", "Operating exposure estimates, never predicted stock returns", "Missing relationships and sensitivities remain unknown", "Basis and reporting period are kept separate"] },
191
+ limitations: ["Pro: full holdings and up to four-hop paths; free: one holding, one hop.", "Operating exposure estimates, never predicted stock returns.", "Missing relationships and sensitivities remain unknown.", "Basis and reporting period are kept separate."] },
13
192
  options: [
14
193
  { key: "scenario", type: "string", settingKey: "scenario", defaultValue: "taiwan-disruption", description: "Library scenario id, or use --custom." },
15
194
  { key: "custom", type: "string", settingKey: "customScenario", description: "JSON scenario with label and shocks; supports multiple shocks and explicit transmission assumptions." },
@@ -19,19 +198,23 @@ export const exposureHeadless: HeadlessPaneDefinition<"bundle"> = {
19
198
  { key: "tab", type: "enum", settingKey: "tab", defaultValue: "table", values: TABS.map(t => ({ value: t.value })), description: "Pane tab." },
20
199
  { key: "view", type: "enum", settingKey: "view", defaultValue: "stress", values: ["stress", "country", "supplier", "customer"].map(value => ({ value })), description: "Portfolio stress or concentration grouping." },
21
200
  ],
22
- describe: args => `Exposure | ${args.rawArgument || "holdings"}`,
201
+ describe: (args, result) => {
202
+ const payload = result?.metadata?.payload as ExposurePayload | undefined;
203
+ return `Exposure | ${args.rawArgument || "holdings"}${payload ? ` | ${scenarioTitle(payload.scenario)}` : ""}`;
204
+ },
23
205
  async load(args, ctx) {
24
206
  const input = args.rawArgument.trim();
25
207
  const source = /^(PORT|WATCH):(.+)$/i.exec(input);
26
208
  let holdings: ExposureRequest["holdings"];
27
209
  if (source?.[1]?.toUpperCase() === "PORT") {
28
210
  const local = await ctx.resolvePortfolio?.(source[2]!);
29
- if (!local) throw new Error(`Unknown local portfolio: ${source[2]}`);
211
+ if (!local) throw new Error(`Unknown local portfolio: ${source[2]}.${ctx.config.portfolios.length ? ` Portfolios: ${describeCollections(ctx.config.portfolios)}.` : ""}`);
30
212
  holdings = await portfolioHoldings(local.portfolio, local.tickers, Number(args.options.nav), ctx.marketData);
31
213
  } else if (source) {
32
214
  const tickers = await ctx.resolveWatchlist?.(source[2]!);
33
- if (!tickers) throw new Error(`Unknown local watchlist: ${source[2]}`);
34
- holdings = watchlistHoldings(tickers, source[2]!);
215
+ if (!tickers) throw new Error(`Unknown local watchlist: ${source[2]}.${ctx.config.watchlists.length ? ` Watchlists: ${describeCollections(ctx.config.watchlists)}.` : ""}`);
216
+ // The host reads the name in any case; membership is kept under the configured id.
217
+ holdings = watchlistHoldings(tickers, findNamedCollection(ctx.config.watchlists, source[2])?.id ?? source[2]!);
35
218
  } else holdings = parseHoldings(input);
36
219
  const id = String(args.options.scenario ?? DEFAULT_SCENARIO.id);
37
220
  const scenario = args.options.custom ? parseCustomScenario(String(args.options.custom)) : (await fetchScenarios(ctx.apiClient)).find(s => s.id === id);
@@ -40,14 +223,23 @@ export const exposureHeadless: HeadlessPaneDefinition<"bundle"> = {
40
223
  if (cashWeight !== undefined && (!Number.isFinite(cashWeight) || Math.abs(cashWeight) > 10)) throw new Error("Cash must be a NAV fraction between -10 and 10.");
41
224
  if (ctx.signal.aborted) throw new Error("Exposure analysis cancelled.");
42
225
  const data = await fetchExposure({ holdings, scenario, depth: Number(args.options.depth ?? 2), ...(cashWeight === undefined ? {} : { cashWeight }) }, ctx.apiClient);
226
+ const drivers = withNotes(driverRows(data), row => row.driver?.notes ?? []);
227
+ const stress = withNotes(portfolioRows(data, "stress"), row => row.incomplete ? row.unknowns : []);
228
+ const concentrations = CONCENTRATION_VIEWS.flatMap(kind => portfolioRows(data, kind).map(row => ({ ...row, kind })));
229
+ const holdingRows = tableRows(data), paths = pathRows(data);
43
230
  return { complete: data.lockedHoldings === 0 && data.holdings.every(h => h.status === "quantified") && data.unknowns.length === 0,
44
231
  errors: [...data.unknowns, ...(data.access === "preview" ? ["Free preview: one holding, depth one. Full analysis requires Pro."] : [])],
45
232
  sections: [
46
- { title: "Holdings", rows: data.holdings.map(h => ({ symbol: h.symbol, weight: h.weight, status: h.status, measures: h.measures, coverage: h.coverage, unknowns: h.unknowns })) },
47
- { title: "Company drivers", rows: data.holdings.flatMap(h => (h.extensions ?? []).map(row => ({ symbol: h.symbol, ...row }))) },
48
- { title: "Paths and evidence", rows: data.holdings.flatMap(h => h.components.map(c => ({ symbol: h.symbol, ...c }))) },
49
- { title: "Portfolio operating stress", rows: data.portfolio.measures.map(m => ({ ...m })) },
50
- { title: "Concentrations", rows: data.portfolio.concentrations.map(c => ({ ...c })) },
233
+ { title: "Holdings", rows: data.holdings.map(h => ({ symbol: h.symbol, weight: h.weight, status: h.status, measures: h.measures, coverage: h.coverage, unknowns: h.unknowns })),
234
+ textColumns: fitted(holdingColumns(data), holdingRows), textRows: textRows(holdingRows) },
235
+ { title: "Company drivers", rows: data.holdings.flatMap(h => (h.extensions ?? []).map(row => ({ symbol: h.symbol, ...row }))),
236
+ textColumns: fitted(DRIVER_COLUMNS, drivers, [note]), textRows: textRows(drivers) },
237
+ { title: "Paths and evidence", rows: data.holdings.flatMap(h => h.components.map(c => ({ symbol: h.symbol, ...c }))),
238
+ textColumns: fitted(PATH_COLUMNS, paths), textRows: textRows(paths) },
239
+ { title: "Portfolio operating stress", rows: data.portfolio.measures.map(m => ({ ...m })),
240
+ textColumns: fitted(portfolioColumns(data, false, stress), stress, [note]), textRows: textRows(stress) },
241
+ { title: "Concentrations", rows: data.portfolio.concentrations.map(c => ({ ...c })),
242
+ textColumns: fitted(portfolioColumns(data, true, concentrations), concentrations), textRows: textRows(concentrations) },
51
243
  ], metadata: { payload: data } };
52
244
  },
53
245
  };
@@ -4,7 +4,7 @@ import type { Portfolio, TickerRecord } from "../../../types/ticker";
4
4
  import { publicTickerKey } from "../../../utils/exchanges";
5
5
  import { resolveCurrencyUnit } from "../../../utils/currency-units";
6
6
  import { convertCurrency } from "../../../utils/format";
7
- import { getPortfolioPositionMetrics, getPortfolioQuoteDisplay, resolvePortfolioMarketValue } from "../portfolio-list/position-metrics";
7
+ import { getPortfolioPositionMetrics, getPortfolioQuoteDisplay, resolvePortfolioMarketValue } from "../../../portfolio/position-metrics";
8
8
 
9
9
  /** Explicit NAV preserves cash, shorts and leverage; net holdings are never used as NAV. */
10
10
  export async function portfolioHoldings(portfolio: Portfolio, tickers: TickerRecord[], nav: number, market: Pick<DataProvider, "getQuote" | "getExchangeRate">): Promise<ExposureHolding[]> {
@@ -4,6 +4,7 @@ import { ExposurePane } from "./pane";
4
4
 
5
5
  export const exposureModule: PluginModule = {
6
6
  panes: [{ id: "exposure", name: "Exposure", icon: "E", component: ExposurePane, defaultPosition: "right", defaultMode: "floating", defaultFloatingSize: { width: 138, height: 31 }, tableExport: true, headless: exposureHeadless,
7
+ personalData: "holdings",
7
8
  portableShare: { private: { params: true, settings: true, state: true } } }],
8
9
  paneTemplates: [{ id: "exposure-pane", paneId: "exposure", label: "Exposure", description: "Pro scenario exposures across holdings, geographic revenue and supply chains, with source evidence and portfolio concentrations.",
9
10
  keywords: ["exposure", "scenario", "stress", "portfolio", "supply", "country", "tariff", "pro"],
@@ -1,4 +1,5 @@
1
1
  import type { ExposureBasis, ExposureComponent, ExposureExtensionObservation, ExposureHolding, ExposurePayload, ExposureRange, ExposureScenario } from "../../../api-client/exposure";
2
+ import { humanLabel } from "../shared/research-cells";
2
3
 
3
4
  export const TABS = [{ value: "table", label: "Table" }, { value: "drivers", label: "Drivers" }, { value: "paths", label: "Paths" }, { value: "portfolio", label: "Portfolio" }];
4
5
  export const DEFAULT_SCENARIO: ExposureScenario = { id: "taiwan-disruption", label: "Taiwan disruption", shocks: [{ id: "taiwan", kind: "country", target: "TW", changePct: -100 }] };
@@ -61,12 +62,31 @@ export interface ExposureRow {
61
62
  exposure: ExposureRange | null; impact: ExposureRange | null; weight: number | null;
62
63
  classification: string; incomplete: boolean; concentration?: boolean; components: ExposureComponent[]; unknowns: string[];
63
64
  }
64
- function shockLabel(data: ExposurePayload, id: string): string {
65
- const shock = data.scenario.shocks.find(s => s.id === id);
66
- if (!shock) return id;
65
+ /** A table cell's text as the pane draws it; `fn EXPO` prints the same words. */
66
+ export function exposureCellText(row: ExposureRow, column: "label" | "basis" | "exposure" | "classification" | "impact" | "period" | "weight"): string {
67
+ switch (column) {
68
+ case "label": return row.label;
69
+ case "basis": return row.basis ? humanLabel(basisLabel(row.basis)) : "--";
70
+ case "exposure": return row.exposure ? rangeText(row.exposure) : "Unknown";
71
+ // What the number rests on, in words: disclosed by the company, estimated by a chain, or not visible.
72
+ case "classification": return `${humanLabel(row.classification)}${row.incomplete && row.classification !== "unknown" ? " · partial" : ""}`;
73
+ case "impact": return row.impact ? rangeText(row.impact) : "--";
74
+ case "period": return row.period ?? "--";
75
+ case "weight": return fraction(row.weight ?? 0);
76
+ }
77
+ }
78
+ function shockText(shock: ExposureScenario["shocks"][number]): string {
67
79
  const change = shock.changeBps ?? shock.changePct;
68
80
  return `${shock.target}${change == null ? "" : ` ${change > 0 ? "+" : ""}${change}${shock.kind === "rate" ? "bp" : "%"}`}`;
69
81
  }
82
+ function shockLabel(data: ExposurePayload, id: string): string {
83
+ const shock = data.scenario.shocks.find(s => s.id === id);
84
+ return shock ? shockText(shock) : id;
85
+ }
86
+ /** The scenario as the pane's scenario picker names it, with its one shock when it has one: Taiwan disruption (TW -100%). */
87
+ export function scenarioTitle(scenario: ExposureScenario): string {
88
+ return `${scenario.label}${scenario.shocks.length === 1 ? ` (${shockText(scenario.shocks[0]!)})` : ""}`;
89
+ }
70
90
  export function tableRows(data: ExposurePayload): ExposureRow[] {
71
91
  return data.holdings.flatMap<ExposureRow>(holding => [
72
92
  ...holding.measures.map((measure, i): ExposureRow => ({
@@ -10,6 +10,7 @@ import type { PaneProps } from "../../../types/plugin";
10
10
  import { Box, ScrollBox, Text, useRendererHost, useUiCapabilities } from "../../../ui";
11
11
  import { SignInWall } from "../cloud/auth-actions";
12
12
  import { CLOUD_PLAN_KEY, useCloudUpgradeAction } from "../shared/cloud-upgrade";
13
+ import { useOpenDescription } from "../shared/open-description";
13
14
  import { Blurred, LockedOverlay, UpgradeLabel } from "../shared/locked-rows";
14
15
  import { humanLabel, listingCell, missingCell } from "../shared/research-cells";
15
16
  import { isCloudSessionRequired, useResearchCloudSession } from "../shared/research-cloud-session";
@@ -17,9 +18,9 @@ import { ExposurePathDiagram, ExposureRanges } from "./charts";
17
18
  import { fetchExposure, fetchScenarios, validateExposure } from "./client";
18
19
  import { ExposureEditor, type ExposureInputs } from "./editor";
19
20
  import { portfolioHoldings, watchlistHoldings } from "./holdings";
20
- import { basisLabel, DEFAULT_SCENARIO, fraction, parseCustomScenario, parseHoldings, pathRows, portfolioRows, rangeText, tableRows, TABS, type ExposureRow } from "./model";
21
+ import { basisLabel, DEFAULT_SCENARIO, exposureCellText, fraction, parseCustomScenario, parseHoldings, pathRows, portfolioRows, rangeText, tableRows, TABS, type ExposureRow } from "./model";
21
22
  import { useExposureEvidence } from "./evidence";
22
- import { driverRows, driverUnits, driverValue } from "./drivers";
23
+ import { driverRows, driverScope, driverUnits, driverValue } from "./drivers";
23
24
 
24
25
  function EvidenceItem({ item }: { item: ExposureEvidence }) {
25
26
  const colors = useThemeColors();
@@ -79,6 +80,7 @@ export function ExposurePane({ width, height, focused }: PaneProps) {
79
80
  const accessKey = `${session.requestKey}:${access.hasProAccess ? "full" : "preview"}`;
80
81
  const upgrade = useCloudUpgradeAction("expo");
81
82
  const { createPaneFromTemplate } = usePluginAppActions();
83
+ const openDescription = useOpenDescription();
82
84
  const [openingScenario] = usePaneSettingValue("scenario", "taiwan-disruption");
83
85
  const [customSetting] = usePaneSettingValue("customScenario", "");
84
86
  const [openingDepth] = usePaneSettingValue("depth", 2);
@@ -130,7 +132,7 @@ export function ExposurePane({ width, height, focused }: PaneProps) {
130
132
  usePaneRefreshKey(() => void resource.reload(), { focused: focused && !editing });
131
133
  const rawRows = useMemo(() => !data ? [] : tab === "drivers" ? driverRows(data) : tab === "paths" ? pathRows(data) : tab === "portfolio" ? portfolioRows(data, view) : tableRows(data), [data, tab, view]);
132
134
  const rows = useMemo(() => [...rawRows].sort((a, b) => {
133
- const get = (r: ExposureRow) => sort.column === "value" ? r.driver?.value ?? r.driver?.range?.high ?? -Infinity : sort.column === "units" ? r.driver ? driverUnits(r.driver) : "" : sort.column === "scope" ? Object.values(r.driver?.dimensions ?? {}).join(" · ") : sort.column === "classification" ? r.classification : sort.column === "label" ? r.label : sort.column === "period" ? r.period ?? "" : sort.column === "exposure" ? r.exposure?.high ?? -Infinity : sort.column === "impact" ? r.impact?.low ?? -Infinity : sort.column === "weight" ? r.weight ?? -Infinity : sort.column === "basis" ? r.basis ?? "" : r.symbol;
135
+ const get = (r: ExposureRow) => sort.column === "value" ? r.driver?.value ?? r.driver?.range?.high ?? -Infinity : sort.column === "units" ? r.driver ? driverUnits(r.driver) : "" : sort.column === "scope" ? r.driver ? driverScope(r.driver) : "" : sort.column === "classification" ? r.classification : sort.column === "label" ? r.label : sort.column === "period" ? r.period ?? "" : sort.column === "exposure" ? r.exposure?.high ?? -Infinity : sort.column === "impact" ? r.impact?.low ?? -Infinity : sort.column === "weight" ? r.weight ?? -Infinity : sort.column === "basis" ? r.basis ?? "" : r.symbol;
134
136
  const av = get(a), bv = get(b); const delta = typeof av === "number" && typeof bv === "number" ? av - bv : String(av).localeCompare(String(bv));
135
137
  return (sort.direction === "asc" ? delta : -delta) || a.id.localeCompare(b.id);
136
138
  }), [rawRows, sort]);
@@ -150,7 +152,7 @@ export function ExposurePane({ width, height, focused }: PaneProps) {
150
152
  ...(symbol ? [
151
153
  ...(activeRow?.driver ? [{ id: "dataset", key: "a", label: "ll disclosures", title: "Open company disclosures", onPress: () => createPaneFromTemplate(activeRow.driver!.kind === "kpi" ? "company-kpis-pane" : activeRow.driver!.kind === "guidance" ? "company-guidance-pane" : "credit-documents-pane", { symbol }) }] : []),
152
154
  { id: "supply", key: "c", label: "hain", title: "Supply chain", onPress: () => createPaneFromTemplate("supply-chain-pane", { symbol }) },
153
- { id: "des", key: "d", label: "es", onPress: () => createPaneFromTemplate("new-ticker-detail-pane", { symbol }) },
155
+ { id: "des", key: "d", label: "es", onPress: () => openDescription(symbol) },
154
156
  { id: "fa", key: "f", label: "a", onPress: () => createPaneFromTemplate("financial-analysis-pane", { symbol }) },
155
157
  { id: "chart", key: "g", label: "raph", onPress: () => createPaneFromTemplate("chart-composer-pane", { arg: symbol }) },
156
158
  ] : []),
@@ -190,17 +192,17 @@ export function ExposurePane({ width, height, focused }: PaneProps) {
190
192
  case "symbol": return row.symbol.includes(",") ? { text: row.symbol, color: colors.textBright } : listingCell(row.symbol, colors, state.selected);
191
193
  case "value": return { text: row.driver ? driverValue(row.driver) : "--", value: row.driver?.value ?? null, color: colors.textBright };
192
194
  case "units": return { text: row.driver ? driverUnits(row.driver) : "", color: colors.textDim };
193
- case "scope": return { text: Object.values(row.driver?.dimensions ?? {}).join(" · "), color: colors.textDim };
194
- case "label": return { text: row.label, color: unknown ? colors.textDim : colors.text };
195
- case "basis": return row.basis ? { text: humanLabel(basisLabel(row.basis)), color: colors.textDim } : missingCell(colors);
196
- case "exposure": return row.exposure ? { text: rangeText(row.exposure), value: row.exposure.high, color: colors.textBright } : { text: "Unknown", value: null, color: colors.textMuted };
195
+ case "scope": return { text: row.driver ? driverScope(row.driver) : "", color: colors.textDim };
196
+ case "label": return { text: exposureCellText(row, "label"), color: unknown ? colors.textDim : colors.text };
197
+ case "basis": return row.basis ? { text: exposureCellText(row, "basis"), color: colors.textDim } : missingCell(colors);
198
+ case "exposure": return row.exposure ? { text: exposureCellText(row, "exposure"), value: row.exposure.high, color: colors.textBright } : { text: exposureCellText(row, "exposure"), value: null, color: colors.textMuted };
197
199
  // Disclosed figures stand out; estimates read plainly, and the word says which is which.
198
- case "classification": return { text: `${humanLabel(row.classification)}${row.incomplete && !unknown ? " · partial" : ""}`,
200
+ case "classification": return { text: exposureCellText(row, "classification"),
199
201
  color: row.classification === "disclosed" ? colors.borderFocused : unknown ? colors.textMuted : colors.textDim, keepColorWhenSelected: row.classification === "disclosed" };
200
- case "impact": return !row.impact ? missingCell(colors) : { text: rangeText(row.impact), value: row.impact.low,
202
+ case "impact": return !row.impact ? missingCell(colors) : { text: exposureCellText(row, "impact"), value: row.impact.low,
201
203
  color: row.concentration ? colors.text : row.impact.high < 0 ? colors.negative : row.impact.low > 0 ? colors.positive : colors.text };
202
- case "period": return row.period ? { text: row.period, color: colors.textDim } : missingCell(colors);
203
- default: return { text: fraction(row.weight ?? 0), value: row.weight === null ? null : row.weight * 100, color: colors.text };
204
+ case "period": return row.period ? { text: exposureCellText(row, "period"), color: colors.textDim } : missingCell(colors);
205
+ default: return { text: exposureCellText(row, "weight"), value: row.weight === null ? null : row.weight * 100, color: colors.text };
204
206
  }
205
207
  };
206
208
  const lockedRows = data?.access === "preview" ? Math.min(3, data.lockedHoldings) : 0;
@@ -1,4 +1,5 @@
1
1
  import { CLI_COMMAND_GROUPS } from "../../../cli/help";
2
+ import { rowsFreshness } from "../../../cli/freshness";
2
3
  import type { GloomPlugin } from "../../../types/plugin";
3
4
  import {
4
5
  attachFearGreedPersistence,
@@ -7,6 +8,7 @@ import {
7
8
  } from "./cache";
8
9
  import { FearGreedPane } from "./pane";
9
10
  import { fearGreedHeadless } from "./headless";
11
+ import { FEAR_GREED_SERIES } from "./series";
10
12
 
11
13
  export const fearGreedPlugin: GloomPlugin = {
12
14
  id: "fear-greed",
@@ -29,6 +31,9 @@ export const fearGreedPlugin: GloomPlugin = {
29
31
  resetFearGreedPersistence();
30
32
  },
31
33
 
34
+ // The index and its seven readings, chartable against anything else.
35
+ series: FEAR_GREED_SERIES,
36
+
32
37
  panes: [
33
38
  {
34
39
  id: "fear-greed",
@@ -62,17 +67,20 @@ export const fearGreedPlugin: GloomPlugin = {
62
67
  description: "Fetch CNN Fear & Greed gauge data",
63
68
  help: { group: CLI_COMMAND_GROUPS.markets },
64
69
  async execute(_args, ctx) {
65
- const { data } = await loadFearGreed(ctx.cliOptions.refresh);
70
+ const { data, stale } = await loadFearGreed(ctx.cliOptions.refresh);
71
+ const rows = [{
72
+ score: data.overall.score,
73
+ rating: data.overall.rating,
74
+ updatedAt: data.overall.updatedAt?.toISOString() ?? "",
75
+ previousClose: data.overall.previousClose,
76
+ previousWeek: data.overall.previousWeek,
77
+ previousMonth: data.overall.previousMonth,
78
+ previousYear: data.overall.previousYear,
79
+ }];
66
80
  ctx.printResult({
67
- data: [{
68
- score: data.overall.score,
69
- rating: data.overall.rating,
70
- updatedAt: data.overall.updatedAt?.toISOString() ?? "",
71
- previousClose: data.overall.previousClose,
72
- previousWeek: data.overall.previousWeek,
73
- previousMonth: data.overall.previousMonth,
74
- previousYear: data.overall.previousYear,
75
- }],
81
+ data: rows,
82
+ // Exports cite CNN's reading as `fn FNG` does; the text stays the row alone.
83
+ exportFreshness: rowsFreshness(rows, { source: "CNN", status: stale ? "stale" : "not-a-feed", basis: "published index" }),
76
84
  metadata: {
77
85
  indicators: data.indicators.map((indicator) => ({
78
86
  id: indicator.definition.id,
@@ -0,0 +1,52 @@
1
+ import type { SeriesDefinition, SeriesLoadResult, SeriesPoint } from "../../../series/types";
2
+ import { cachedSeriesWarning } from "../shared/series-warning";
3
+ import { loadFearGreed } from "./cache";
4
+ import type { FearGreedData } from "./data";
5
+
6
+ /** The index refreshes through the session; the pane's cache keeps a copy for five minutes. */
7
+ const REFRESH_MS = 5 * 60_000;
8
+
9
+ type Reading = (data: FearGreedData) => SeriesPoint[];
10
+
11
+ function component(id: string, pick: "primary" | "secondary"): Reading {
12
+ return (data) => {
13
+ const indicator = data.indicators.find((entry) => entry.definition.id === id);
14
+ if (!indicator) throw new Error("This component is missing from the latest reading.");
15
+ if (pick === "primary") return indicator.points.map((point) => ({ date: point.date, value: point.close }));
16
+ return indicator.secondaryPoints.flatMap((point) => {
17
+ const date = indicator.points[point.index]?.date;
18
+ return date ? [{ date, value: point.value }] : [];
19
+ });
20
+ };
21
+ }
22
+
23
+ function series(id: string, name: string, unit: string, read: Reading, tags: readonly string[]): SeriesDefinition {
24
+ return {
25
+ id: `fear-greed:${id}`,
26
+ name,
27
+ unit,
28
+ frequency: "daily",
29
+ group: "Sentiment",
30
+ tags: ["fear and greed", ...tags],
31
+ refreshMs: REFRESH_MS,
32
+ load: async (): Promise<SeriesLoadResult> => {
33
+ const result = await loadFearGreed();
34
+ const warning = cachedSeriesWarning(result);
35
+ return { points: read(result.data), ...(warning ? { warning } : {}) };
36
+ },
37
+ };
38
+ }
39
+
40
+ /** The index and the seven readings it is built from, as the pane charts them. */
41
+ export const FEAR_GREED_SERIES: readonly SeriesDefinition[] = [
42
+ series("index", "Fear & Greed index", "score", (data) => data.overall.history.map((point) => ({ date: point.date, value: point.close })), ["sentiment"]),
43
+ series("market-momentum", "Fear & Greed: S&P 500 momentum", "index", component("market-momentum", "primary"), ["momentum", "S&P 500"]),
44
+ series("market-momentum-average", "Fear & Greed: S&P 500 125-day average", "index", component("market-momentum", "secondary"), ["momentum", "moving average"]),
45
+ series("stock-price-strength", "Fear & Greed: net new 52-week highs", "%", component("stock-price-strength", "primary"), ["breadth", "highs and lows"]),
46
+ series("stock-price-breadth", "Fear & Greed: McClellan volume summation", "index", component("stock-price-breadth", "primary"), ["breadth", "McClellan"]),
47
+ series("put-call-options", "Fear & Greed: 5-day put/call ratio", "ratio", component("put-call-options", "primary"), ["options", "put call"]),
48
+ series("market-volatility", "Fear & Greed: VIX", "index", component("market-volatility", "primary"), ["volatility", "VIX"]),
49
+ series("market-volatility-average", "Fear & Greed: VIX 50-day average", "index", component("market-volatility", "secondary"), ["volatility", "moving average"]),
50
+ series("safe-haven-demand", "Fear & Greed: stocks minus bonds, 20-day return", "%", component("safe-haven-demand", "primary"), ["safe haven", "bonds"]),
51
+ series("junk-bond-demand", "Fear & Greed: junk bond spread", "%", component("junk-bond-demand", "primary"), ["credit", "high yield", "junk"]),
52
+ ];
@@ -13,6 +13,7 @@ import {
13
13
  type PaneFooterSegment,
14
14
  } from "../../../components";
15
15
  import { handleRefreshKey } from "../../../components/data-table/table-pane";
16
+ import { useRemoteUiNode } from "../../../remote/semantic-tree";
16
17
  import { usePaneVisible } from "../../../state/app/activity";
17
18
  import { usePaneInstance } from "../../../state/app/context";
18
19
  import { TICKER_RESEARCH_PANE_ID } from "../../../types/config";
@@ -22,7 +23,9 @@ import { isPlainArrowUp, stopSearchFocusNavigation } from "../../../utils/search
22
23
  import { cycleSortPreference, nextHeaderSort } from "../../../utils/sort-values";
23
24
  import { useAssetData, usePluginTickerActions } from "../../runtime";
24
25
  import type { PluginModule } from "../plugin-module";
26
+ import { FUTURES_SERIES } from "./series";
25
27
  import { useLiveStreamingSetting } from "../../../state/hooks/live-streaming";
28
+ import { quoteObservation } from "../shared/report-freshness";
26
29
  import {
27
30
  boardErrorMessage,
28
31
  quoteBoardFooterInfo,
@@ -34,9 +37,10 @@ import {
34
37
  import {
35
38
  FUTURES_CONTRACTS,
36
39
  FUTURES_SECTOR_LABELS,
40
+ futuresBoardContract,
37
41
  getContractsBySector,
38
42
  type FuturesSector,
39
- } from "./contracts";
43
+ } from "../../../market-data/futures-board";
40
44
  import {
41
45
  buildFuturesRows,
42
46
  DEFAULT_FUTURES_SORT,
@@ -75,7 +79,8 @@ function FuturesPane({ focused, width, height }: PaneProps) {
75
79
  const liveStreaming = useLiveStreamingSetting();
76
80
  const [selectedId, setSelectedId] = useState<string | null>(null);
77
81
  const [sortPreference, setSortPreference] = useState<FuturesSortPreference>(DEFAULT_FUTURES_SORT);
78
- const [searchQuery, setSearchQuery] = useState("");
82
+ // `FUT TTF` opens the board searched for the contract it names.
83
+ const [searchQuery, setSearchQuery] = useState(() => paneInstance?.params?.query ?? "");
79
84
  const { active: searchFocused, focus: focusSearch, searchProps } = useQueryBarSearch();
80
85
  const [collapsedSectors, setCollapsedSectors] = useState<ReadonlySet<FuturesSector>>(new Set());
81
86
  const [visibleRange, setVisibleRange] = useState<DataTableVisibleRange | null>(null);
@@ -196,6 +201,17 @@ function FuturesPane({ focused, width, height }: PaneProps) {
196
201
  return handlePaneKey(event);
197
202
  }, [focusSearch, handlePaneKey]);
198
203
 
204
+ // `fn FUT` and `shot FUT` are dated by the quotes the board shows, as `quote` dates them:
205
+ // the newest, its delay, and whether the markets are open.
206
+ useRemoteUiNode(quotes.size > 0 ? {
207
+ role: "report-freshness",
208
+ label: "Quote freshness",
209
+ getMetadata: () => {
210
+ const now = Date.now();
211
+ return { observations: [...quotes.values()].flatMap((state) => (state.quote ? [quoteObservation(state.quote, now)] : [])) };
212
+ },
213
+ } : null);
214
+
199
215
  const status = quoteBoardStatus(quotes);
200
216
  const errorMessage = boardErrorMessage(quotes);
201
217
  // Every load came back empty: a board of dashes reads as broken, so the
@@ -265,6 +281,7 @@ function FuturesPane({ focused, width, height }: PaneProps) {
265
281
  renderSectionHeader={renderSectorHeader}
266
282
  renderCell={renderCell}
267
283
  selectedTextOverridesCellColor
284
+ reportEveryRow
268
285
  emptyStateTitle={searchQuery.trim()
269
286
  ? "No matching contracts."
270
287
  : "No market data provider connected."}
@@ -288,6 +305,7 @@ function FuturesPane({ focused, width, height }: PaneProps) {
288
305
  }
289
306
 
290
307
  export const futuresModule: PluginModule = {
308
+ series: FUTURES_SERIES,
291
309
  panes: [
292
310
  {
293
311
  id: FUTURES_PANE_ID,
@@ -298,6 +316,7 @@ export const futuresModule: PluginModule = {
298
316
  defaultMode: "floating",
299
317
  defaultFloatingSize: { width: 76, height: 34 },
300
318
  tableExport: true,
319
+ shotFitsRows: true,
301
320
  // Resolved per open so the dialog shows the full default set until the
302
321
  // user saves a narrower selection.
303
322
  settings: (context) => ({
@@ -341,11 +360,32 @@ export const futuresModule: PluginModule = {
341
360
  "lumber",
342
361
  "aluminum",
343
362
  "ttf",
363
+ "energy",
364
+ "natural gas",
365
+ "gas",
366
+ "lng",
367
+ "brent",
368
+ "wti",
369
+ "heating oil",
370
+ "gasoline",
344
371
  "treasuries",
345
372
  "contracts",
346
373
  "cme",
347
374
  ],
348
- shortcut: { prefix: "FUT", aliases: ["GLCO"] },
375
+ shortcut: {
376
+ prefix: "FUT",
377
+ aliases: ["GLCO"],
378
+ argKind: "text",
379
+ argPlaceholder: "code or name",
380
+ argOptional: true,
381
+ },
382
+ // A board code or symbol searches for that contract alone (NG=F: a bare NG also matches Orange Juice); other text searches as typed.
383
+ createInstance: (_context, options) => {
384
+ const input = options?.arg?.trim();
385
+ if (!input) return {};
386
+ const query = futuresBoardContract(input)?.symbol ?? input;
387
+ return { title: `FUT ${input.toUpperCase()}`, params: { query } };
388
+ },
349
389
  },
350
390
  ],
351
391
  };
@@ -1,7 +1,7 @@
1
1
  import type { Quote } from "../../../types/financials";
2
2
  import { compareSortValues, type SortDirection } from "../../../utils/sort-values";
3
3
  import type { BoardQuoteMap } from "../shared/use-quote-board";
4
- import { FUTURES_SECTOR_ORDER, type FuturesContract, type FuturesSector } from "./contracts";
4
+ import { FUTURES_SECTOR_ORDER, type FuturesContract, type FuturesSector } from "../../../market-data/futures-board";
5
5
  import type { FuturesReturnHorizon } from "./returns";
6
6
  import type { FrontReturnsMap } from "./use-front-returns";
7
7