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,25 +1,68 @@
1
- import type { HeadlessPaneDefinition } from "../../../types/plugin";
2
- import { fetchPowerBoard, fetchAllPowerHistory } from "./client";
1
+ import type { DataTableColumn } from "../../../components";
2
+ import type { HeadlessPaneColumn, HeadlessPaneDefinition, HeadlessPaneRow } from "../../../types/plugin";
3
+ import { withoutQuietColumns } from "../shared/research-cells";
4
+ import { fetchPowerBoard, fetchAllPowerHistory, fetchPowerTickerScopes } from "./client";
3
5
  import { powerQuery } from "./query";
4
- import { POWER_TABS, powerTab } from "./model";
6
+ import { loadPowerPricesReport } from "./prices-headless";
7
+ import { isCarbonQuery, powerPriceMarket } from "./prices-model";
8
+ import {
9
+ COVERAGE_COLUMNS, EXPOSURE_COLUMNS, GENERATION_COLUMNS, HISTORY_COLUMNS, POWER_TABS, PROJECT_COLUMNS, RATE_COLUMNS, UTILITY_COLUMNS,
10
+ historyRows, powerCoverageNote, powerLoadedSources, powerNumber, powerPercent, powerRegion, powerScopeOptions, powerTab, powerTickerEmpty, projectRows, tickerLabel, titleCase, UNMAPPED, type PowerTab,
11
+ } from "./model";
12
+
13
+ /** Bars the pane draws beside the numbers they are made of; the report has the numbers. */
14
+ const GRAPHIC_COLUMNS = new Set(["outcome", "pipeline"]);
15
+ /** Columns that only repeat a default on every row, as the pane leaves them out. */
16
+ const QUIET_COLUMNS: Record<string, readonly string[]> = { scope: ["Project"], unknownCapacity: ["0"], unknown: ["0"], fuel: ["Load"], role: ["Direct"], kind: ["Queue"] };
17
+ const text = (value: unknown) => value == null || value === "" ? "-" : String(value);
18
+ const coded = (value: unknown) => typeof value === "string" && value ? titleCase(value) : "-";
19
+
20
+ /**
21
+ * The pane's columns for a tab, printed as the pane prints them: MW and MWh
22
+ * to a tenth with thousands separators, rates as percentages, register codes
23
+ * by name, and each record's listed company or "unmapped". Raw metrics and
24
+ * evidence stay in the exported rows, out of the table.
25
+ */
26
+ function reportColumns(tab: PowerTab, context: unknown): HeadlessPaneColumn[] {
27
+ const columns: DataTableColumn[] = tab === "capacity" && context === "generation" ? GENERATION_COLUMNS : tab === "capacity" && context === "utility" ? UTILITY_COLUMNS
28
+ : ["queue", "loads", "capacity"].includes(tab) ? PROJECT_COLUMNS : tab === "history" ? HISTORY_COLUMNS : tab === "outcomes" ? RATE_COLUMNS : tab === "utilities" ? EXPOSURE_COLUMNS : COVERAGE_COLUMNS;
29
+ // The coverage list names its register by its own id; everything else by the record's source.
30
+ const region = (value: unknown, row: HeadlessPaneRow) => typeof value !== "string" ? "-"
31
+ : tab === "utilities" ? value : powerRegion(value, String((tab === "coverage" ? row.id : row.sourceId) ?? ""));
32
+ const format = (column: DataTableColumn): HeadlessPaneColumn["format"] => {
33
+ if (column.id === "region") return region;
34
+ if (["fuel", "status", "kind", "role"].includes(column.id)) return coded;
35
+ if (column.id === "ticker") return (value) => typeof value === "string" && value ? value : UNMAPPED;
36
+ if (column.id === "developer") return (value, row) => text(value ?? row.utility);
37
+ if (column.id === "observedAt") return (value) => typeof value === "string" ? value.slice(0, 16).replace("T", " ") : "-";
38
+ if (/Rate$/.test(column.id)) return (value) => typeof value === "number" ? powerPercent(value) : "-";
39
+ if (column.align === "right") return (value) => typeof value === "number" ? `${column.id === "changeMw" && value > 0 ? "+" : ""}${powerNumber(value)}` : "-";
40
+ return text;
41
+ };
42
+ // The pane groups outcomes under region headings; the report names the region on each row.
43
+ return [...(tab === "outcomes" ? [{ id: "region", label: "Region", width: 16, align: "left" as const }] : []), ...columns].filter((column) => !GRAPHIC_COLUMNS.has(column.id))
44
+ .map((column) => ({ key: column.id, header: column.label, align: column.align === "right" ? "right" as const : "left" as const, format: format(column) }));
45
+ }
5
46
 
6
47
  export const powerHeadless: HeadlessPaneDefinition<"bundle"> = {
7
48
  shape: "bundle",
8
- freshness: { source: "Grid operator queues and utility filings", status: "not-a-feed", basis: "published records" },
49
+ freshness: { source: "Grid operator queues and utility filings", status: "not-a-feed", basis: "published records", dataset: true },
9
50
  argument: { kind: "free-text", optional: true, placeholder: "ticker", description: "Optional listed developer or utility ticker, with exchange suffix where needed." },
10
51
  options: [
11
- { key: "tab", type: "enum", settingKey: "tab", defaultValue: "queue", values: POWER_TABS.map(({ value }) => ({ value })), description: "Queue, history, outcomes, large loads, utility exposure, capacity or coverage." },
52
+ { key: "tab", type: "enum", settingKey: "tab", defaultValue: "queue", values: POWER_TABS.map(({ value }) => ({ value })), description: "Queue, history, outcomes, large loads, utility exposure, capacity, European day-ahead prices or coverage." },
12
53
  ...["country", "region", "fuel", "status", "search", "sourceId", "from", "to"].map((key) => ({ key, type: "string" as const, settingKey: key, description: `Filter by ${key}.` })),
13
54
  { key: "context", type: "enum", settingKey: "context", values: [{ value: "capacity" }, { value: "generation" }, { value: "utility" }], defaultValue: "capacity", description: "Generation capacity, monthly generation or utility context on the Capacity tab." },
14
55
  { key: "loadClass", aliases: ["load-class"], type: "enum", settingKey: "loadClass", values: [{ value: "datacenter" }], description: "Only loads explicitly classified as datacenters." },
15
56
  { key: "historical", type: "boolean", settingKey: "historical", defaultValue: false, description: "Use historical benchmark queues separately from direct observations." },
16
57
  ],
17
58
  discovery: { aliases: ["POWER"], dataRequirements: ["Gloom Cloud power queues and large-load disclosures"],
18
- limitations: ["Pro dataset; free accounts see three rows per section and limited history", "Proposed projects are not committed supply; requests and approvals are distinct", "Coverage varies by jurisdiction; missing MW and unavailable sources remain explicit"] },
59
+ limitations: ["Pro dataset; free accounts see three rows per section and limited history.", "Proposed projects are not committed supply; requests and approvals are distinct.", "Coverage varies by jurisdiction; missing MW and unavailable sources remain explicit."] },
19
60
  describe: "Power queues, large loads and utility exposure (Pro)",
20
61
  async load(args, ctx) {
21
62
  const tab = powerTab(args.options.tab);
22
63
  const symbol = (Array.isArray(args.argument) ? args.argument.join(" ") : args.argument)?.trim();
64
+ // Prices are their own board (EPWR); a typed market narrows it, a ticker does not apply.
65
+ if (tab === "prices") return loadPowerPricesReport(powerPriceMarket(symbol) || isCarbonQuery(symbol) ? symbol : undefined, ctx.apiClient);
23
66
  const filter = { ...powerQuery(args.options, symbol), limit: 500 };
24
67
  const board = await fetchPowerBoard(filter, ctx.apiClient);
25
68
  const projects = [...board.projects];
@@ -36,10 +79,35 @@ export const powerHeadless: HeadlessPaneDefinition<"bundle"> = {
36
79
  projects.push(...page.projects); offset = page.nextOffset; hasMore = page.hasMore;
37
80
  }
38
81
  const history = tab === "history" ? await fetchAllPowerHistory(filter, ctx.apiClient) : null;
39
- const rows = tab === "history" ? history!.points : tab === "outcomes" ? board.rates : tab === "utilities" ? board.exposure : tab === "coverage" ? board.coverage : projects;
82
+ // Exported rows keep every field the server sent; the pane's derived cells (the ticker map, location, scope) ride beside them.
83
+ const cells = projectRows(projects);
84
+ const rows: HeadlessPaneRow[] = tab === "history" ? historyRows(history!.points).map((point) => ({ ...point, date: point.basis === "published" ? point.period : point.observedAt.slice(0, 16).replace("T", " ") }))
85
+ : tab === "outcomes" ? board.rates.map((rate) => ({ ...rate }))
86
+ : tab === "utilities" ? board.exposure.map((exposure) => ({ ...exposure, ticker: tickerLabel(exposure) }))
87
+ : tab === "coverage" ? board.coverage.map((coverage) => ({ ...coverage }))
88
+ : projects.map((project, index) => {
89
+ const { ticker, location, scope, generationMwh, summerPeakDemandMw, winterPeakDemandMw, salesMwh, revenueUsd } = cells[index]!.cells;
90
+ return { ...project, ticker, location, scope, generationMwh, summerPeakDemandMw, winterPeakDemandMw, salesMwh, revenueUsd };
91
+ });
40
92
  const locked = history?.locked ?? (tab === "outcomes" ? board.locked.rates : tab === "utilities" ? board.locked.exposure : tab === "coverage" ? 0 : board.locked.projects);
41
- return { complete: locked === 0, sections: [{ title: POWER_TABS.find((t) => t.value === tab)!.label, rows: rows.map((r) => ({ ...r })) }],
93
+ const columns = reportColumns(tab, args.options.context);
94
+ const shown = withoutQuietColumns(columns.map((column) => ({ id: column.key, label: column.header, width: 0, align: "left" as const })), rows,
95
+ (row, id) => columns.find((column) => column.key === id)!.format!(row[id], row), QUIET_COLUMNS).map((column) => column.id);
96
+ // A ticker with nothing on this tab says where its records are instead; a narrower filter is the filters' doing.
97
+ const narrowed = ["country", "region", "fuel", "status", "search", "sourceId", "from", "to", "loadClass", "historical"].some((key) => !!args.options[key]);
98
+ const empty = !rows.length && symbol && !narrowed && tab !== "coverage" ? await fetchPowerTickerScopes(symbol, tab, String(args.options.context ?? "capacity"), ctx.apiClient)
99
+ .then((scopes) => {
100
+ const message = powerTickerEmpty(symbol, tab, String(args.options.context ?? "capacity"), scopes);
101
+ const where = message.found.map((scope) => `${powerNumber(scope.total)} ${scope.label} (${powerScopeOptions(scope)})`).join(", ");
102
+ const loaded = powerLoadedSources(tab, board.coverage);
103
+ return [message.title, where ? `${symbol} has ${where}.` : message.hint?.replace(/\.$/, " (--search <name>)."),
104
+ loaded ? `Sources loaded now: ${loaded}.` : null].filter(Boolean).join(" ");
105
+ }) : "No reported records match these filters.";
106
+ // Missing or unsupported sources are coverage, not failures: one plain line, details in --tab coverage.
107
+ const coverageNote = tab === "capacity" ? null : powerCoverageNote(board.coverage);
108
+ return { complete: locked === 0, sections: [{ title: POWER_TABS.find((t) => t.value === tab)!.label, columns: columns.filter((column) => shown.includes(column.key)), rows, empty }],
42
109
  metadata: { generatedAt: board.generatedAt, access: board.access, locked, total: board.total, units: "MW unless specified", filters: filter, aggregates: board.aggregates, coverage: board.coverage },
43
- errors: [...(locked ? ["Additional records and history need Gloom Pro."] : []), ...board.coverage.filter((c) => c.status !== "current").map((c) => `${c.region}: ${c.status}${c.reason ? `: ${c.reason}` : ""}`)] };
110
+ ...(coverageNote ? { notes: [coverageNote] } : {}),
111
+ ...(locked ? { errors: ["Additional records and history need Gloom Pro."] } : {}) };
44
112
  },
45
113
  };
@@ -2,15 +2,40 @@ import type { PluginModule } from "../plugin-module";
2
2
  import { powerBoardCache, powerDetailCache, powerHistoryCache } from "./client";
3
3
  import { powerHeadless } from "./headless";
4
4
  import { PowerPane } from "./pane";
5
+ import { carbonHistoryCache, carbonPriceCache, powerPriceHistoryCache, powerPricesCache } from "./prices-client";
6
+ import { powerPricesHeadless } from "./prices-headless";
7
+ import { CARBON_SELECTION, CARBON_SERIES, DAY_AHEAD_SERIES, isCarbonQuery, POWER_PRICE_MARKETS, powerPriceMarket } from "./prices-model";
8
+
9
+ const CACHES = [powerBoardCache, powerHistoryCache, powerDetailCache, powerPricesCache, powerPriceHistoryCache, carbonPriceCache, carbonHistoryCache];
5
10
 
6
11
  export const powerModule: PluginModule = {
12
+ series: [...DAY_AHEAD_SERIES, ...CARBON_SERIES],
7
13
  panes: [{ id: "power", name: "Power & Grid", icon: "P", component: PowerPane, defaultPosition: "right", defaultMode: "floating",
8
14
  defaultFloatingSize: { width: 132, height: 34 }, tableExport: true, headless: powerHeadless }],
9
15
  paneTemplates: [{ id: "power-pane", paneId: "power", label: "Power & Grid",
10
16
  description: "Pro: interconnection queues, historical MW and outcomes, datacenter loads, utility exposure and generation capacity, with primary evidence.",
11
17
  keywords: ["power", "electricity", "interconnection", "grid", "datacenter", "utility", "renewables", "storage", "capacity"],
12
18
  shortcut: { prefix: "POWER", argKind: "text", argPlaceholder: "ticker", argOptional: true }, headless: powerHeadless,
13
- createInstance: (_context, options) => ({ placement: "floating", params: { symbol: options?.symbol ?? options?.arg?.trim().toUpperCase() ?? "" } }) }],
14
- setup(ctx) { for (const cache of [powerBoardCache, powerHistoryCache, powerDetailCache]) cache.attach(ctx.persistence); },
15
- dispose() { for (const cache of [powerBoardCache, powerHistoryCache, powerDetailCache]) cache.reset(); },
19
+ createInstance: (_context, options) => {
20
+ const symbol = options?.symbol ?? options?.arg?.trim().toUpperCase() ?? "";
21
+ return { placement: "floating", params: { symbol }, ...(symbol ? { title: `POWER ${symbol}` } : {}) };
22
+ } }, {
23
+ id: "power-prices-pane", paneId: "power", label: "EU Power & Carbon",
24
+ description: "Day-ahead power prices for 32 European markets in EUR/MWh and the EU carbon allowance (EUA) auction price in EUR/tCO2: the latest day, 7- and 30-day averages, a year ago and the 52-week range, with daily history.",
25
+ keywords: ["day-ahead", "day ahead", "power price", "power prices", "electricity price", "electricity prices", "wholesale power",
26
+ "spot power", "baseload", "europe", "european", "eur/mwh", "carbon", "eu carbon", "eua", "eu ets", "carbon price",
27
+ "carbon allowance", "emission allowances", "co2", "eur/tco2"],
28
+ shortcut: { prefix: "EPWR", argKind: "text", argPlaceholder: "market", argOptional: true,
29
+ argOptions: () => [{ value: "EUA", label: "EU carbon allowance" }, ...POWER_PRICE_MARKETS.map(({ code, name }) => ({ value: code, label: name }))] },
30
+ headless: powerPricesHeadless,
31
+ createInstance: (_context, options) => {
32
+ const input = options?.arg?.trim();
33
+ const market = powerPriceMarket(input);
34
+ // EPWR EUA opens the tab with the carbon allowance charted above the board.
35
+ const carbon = isCarbonQuery(input);
36
+ if (input && !market && !carbon) throw new Error("Use a market such as DE, FR, GB or Spain, or EUA");
37
+ return { placement: "floating", settings: { tab: "prices" }, ...(market || carbon ? { params: { market: market?.code ?? CARBON_SELECTION } } : {}) };
38
+ } }],
39
+ setup(ctx) { for (const cache of CACHES) cache.attach(ctx.persistence); },
40
+ dispose() { for (const cache of CACHES) cache.reset(); },
16
41
  };
@@ -2,16 +2,87 @@ import type { PowerAggregate, PowerBoard, PowerCoverage, PowerExposure, PowerHis
2
2
  import { staticSeries, type DataTableColumn } from "../../../components";
3
3
 
4
4
  export const POWER_TABS = [{ value: "queue", label: "Queue" }, { value: "history", label: "History" }, { value: "outcomes", label: "Outcomes" },
5
- { value: "loads", label: "Loads" }, { value: "utilities", label: "Utilities" }, { value: "capacity", label: "Capacity" }, { value: "coverage", label: "Coverage" }] as const;
5
+ { value: "loads", label: "Loads" }, { value: "utilities", label: "Utilities" }, { value: "capacity", label: "Capacity" }, { value: "prices", label: "Prices" }, { value: "coverage", label: "Coverage" }] as const;
6
6
  export type PowerTab = typeof POWER_TABS[number]["value"];
7
7
  export const powerTab = (value: unknown): PowerTab => POWER_TABS.find((tab) => tab.value === value)?.value ?? "queue";
8
8
  const REGISTERS: Record<string, string> = { "neso-tec": "GB transmission", "neso-embedded": "GB embedded", "neso-interconnector": "GB interconnectors" };
9
9
  const REGIONS: Record<string, string> = { LBNL: "US project queues", "LBNL-ANNUAL": "US annual queues", EIA860: "US generation capacity", EIA861: "US utilities", EIA923: "US generation output" };
10
10
  export const powerRegion = (value: string, sourceId?: string) => REGISTERS[sourceId ?? ""] ?? REGIONS[value] ?? value;
11
+ /** A source with stored records, even when its latest refresh failed. */
12
+ const loadedSource = (c: PowerCoverage) => (c.status === "current" || c.status === "stale") && c.records > 0;
13
+ const names = (rows: readonly PowerCoverage[]) => [...new Set(rows.map((c) => c.region === "EirGrid" ? "Ireland" : c.region))].join(", ");
14
+ /**
15
+ * Which US grid operators' queues loaded, in plain words. Source errors and
16
+ * unsupported registers stay in the Coverage tab and on the server; a missing
17
+ * queue is a gap in coverage, never zero.
18
+ */
19
+ export function powerCoverageNote(coverage: readonly PowerCoverage[]): string | null {
20
+ const queues = coverage.filter((c) => c.country === "US" && c.role === "direct" && c.kind === "queue");
21
+ const missing = queues.filter((c) => !loadedSource(c));
22
+ if (!missing.length) return null;
23
+ const loaded = queues.filter(loadedSource);
24
+ return `US queues: ${loaded.length ? `${names(loaded)} loaded; ` : ""}${names(missing)} not available`;
25
+ }
26
+ /**
27
+ * The registers carrying records right now, named, for a ticker that has none
28
+ * in them: grid operator and TSO registers, or the EIA filings behind the
29
+ * Capacity tab. Null when nothing is loaded.
30
+ */
31
+ export function powerLoadedSources(tab: PowerTab, coverage: readonly PowerCoverage[]): string | null {
32
+ const loaded = coverage.filter((c) => loadedSource(c) && (tab === "capacity" ? c.role === "context" : c.role === "direct"));
33
+ if (!loaded.length) return null;
34
+ return tab === "capacity" ? "US EIA generation and utility filings" : names([...loaded.filter((c) => c.country === "US"), ...loaded.filter((c) => c.country !== "US")]);
35
+ }
11
36
  export const titleCase = (value: string) => value.replace(/(^|[ _-])\w/g, (match) => match.toUpperCase());
12
37
  export const powerNumber = (n: number | null | undefined) => n == null ? "--" : n.toLocaleString("en-US", { maximumFractionDigits: 1 });
13
38
  export const powerPercent = (n: number | null) => n == null ? "--" : `${(n * 100).toFixed(1)}%`;
14
- const tickerLabel = (row: { entities: PowerProject["entities"] }) => [...new Set(row.entities.flatMap((e) => e.tickers.map((t) => t.ticker)))].join(", ") || "--";
39
+ /** The listed companies a record names, or null when its register names none: the ticker map. */
40
+ export const tickerLabel = (row: { entities: PowerProject["entities"] }) => [...new Set(row.entities.flatMap((e) => e.tickers.map((t) => t.ticker)))].join(", ") || null;
41
+ /** What the ticker column says for a record no listed company is mapped to. */
42
+ export const UNMAPPED = "unmapped";
43
+
44
+ /** Where else a ticker's records can sit: each tab, and the capacity tab's contexts, with what they list. */
45
+ const POWER_TICKER_SCOPES = [
46
+ { tab: "queue", context: "capacity", label: "interconnection queue records", view: "Queue" },
47
+ { tab: "loads", context: "capacity", label: "large-load requests", view: "Loads" },
48
+ { tab: "capacity", context: "capacity", label: "generation capacity records", view: "Capacity" },
49
+ { tab: "capacity", context: "generation", label: "monthly generation records", view: "Generation" },
50
+ { tab: "capacity", context: "utility", label: "utility sales records", view: "Utility sales" },
51
+ ] as const satisfies readonly { tab: PowerTab; context: string; label: string; view: string }[];
52
+ /** `total` is null for a scope that did not answer, which is no evidence either way. */
53
+ export type PowerTickerScope = (typeof POWER_TICKER_SCOPES)[number] & { total: number | null };
54
+ const sameScope = (scope: { tab: string; context: string }, tab: PowerTab, context: string) =>
55
+ scope.tab === tab && (tab !== "capacity" || scope.context === context)
56
+ // Utilities are the large loads summed by utility: a ticker with no loads has no exposure either.
57
+ || tab === "utilities" && scope.tab === "loads";
58
+ /** The scopes to count a ticker's records in when the open tab has none of them. */
59
+ export const powerTickerScopes = (tab: PowerTab, context: string) => POWER_TICKER_SCOPES.filter((scope) => !sameScope(scope, tab, context));
60
+
61
+ /** What the open tab lists, for the sentence that says none of it maps to a ticker. */
62
+ function powerTabRecords(tab: PowerTab, context: string): string {
63
+ if (tab === "capacity") return POWER_TICKER_SCOPES.find((scope) => scope.tab === "capacity" && scope.context === context)?.label ?? "capacity records";
64
+ return { queue: "interconnection queue records", history: "queue history records", outcomes: "queue outcomes", loads: "large-load requests",
65
+ utilities: "large-load requests or utility exposure", prices: "records", coverage: "records" }[tab];
66
+ }
67
+
68
+ /**
69
+ * Why a ticker's tab is empty, and where its records are instead: the title
70
+ * says what does not map to it, the hint where it has records, or, once every
71
+ * other tab has answered with none, that a name search is what is left.
72
+ */
73
+ export function powerTickerEmpty(symbol: string, tab: PowerTab, context: string, scopes: readonly PowerTickerScope[]) {
74
+ const found = scopes.filter((scope): scope is PowerTickerScope & { total: number } => (scope.total ?? 0) > 0);
75
+ const answered = scopes.length > 0 && scopes.every((scope) => scope.total != null);
76
+ return {
77
+ title: `No ${powerTabRecords(tab, context)} map to ${symbol}.`,
78
+ found,
79
+ hint: found.length ? `${symbol} has ${found.map((scope) => `${powerNumber(scope.total)} ${scope.label}`).join(", ")}.`
80
+ : answered ? `No power records map to ${symbol}; search by the company's or a subsidiary's name instead.` : null,
81
+ };
82
+ }
83
+ /** The options that open a scope from the command line. */
84
+ export const powerScopeOptions = (scope: { tab: string; context: string }) =>
85
+ scope.tab === "capacity" && scope.context !== "capacity" ? `--tab capacity --context ${scope.context}` : `--tab ${scope.tab}`;
15
86
  const historyKey = (p: PowerHistoryPoint) => JSON.stringify([p.sourceId, p.country, p.region, p.kind, p.fuel, p.status, p.historical, p.basis]);
16
87
  /** Annual benchmarks use their stated period. Current queues use when we observed them, never a proposed completion date. */
17
88
  export function historyDate(p: PowerHistoryPoint): Date {
@@ -2,25 +2,28 @@ import { useCallback, useEffect, useMemo, useRef } from "react";
2
2
  import type { PowerBoard, PowerFilter, PowerHistory } from "../../../api-client/power";
3
3
  import { isAccessDenied } from "../../../api-client/errors";
4
4
  import { usePlanAccess } from "../../../api-client/plan-access";
5
- import { ChartTableHeader, DataTableStackView, PaneStatusBody, QueryBar, useChartTableSelection, usePagedRows, usePaneNoticeFooter, usePaneStatusFooter, usePaneTabs, useQueryBarSearch, useTableLoadMore, type DataTableCell, type PaneHint, type QueryBarFilter } from "../../../components";
5
+ import { Button, ChartTableHeader, DataTableStackView, EmptyState, PaneStatusBody, QueryBar, useChartTableSelection, usePagedRows, usePaneNoticeFooter, usePaneStatusFooter, usePaneTabs, useQueryBarSearch, useTableLoadMore, type DataTableCell, type PaneHint, type QueryBarFilter } from "../../../components";
6
6
  import { usePaneRefreshKey } from "../../../components/data-table/table-pane";
7
- import { useAutoRefresh, usePaneSettingValue, usePluginAppActions, usePluginPaneState } from "../../../public/react";
7
+ import { useAsyncResource, useAutoRefresh, usePaneSettingValue, usePluginAppActions, usePluginPaneState } from "../../../public/react";
8
8
  import { useThemeColors } from "../../../theme/theme-context";
9
9
  import type { PaneProps } from "../../../types/plugin";
10
10
  import { Box, Text, TextAttributes, useRendererHost, useUiCapabilities, type ScrollBoxRenderable } from "../../../ui";
11
11
  import { publicTickerKey } from "../../../utils/exchanges";
12
12
  import { getSharedRegistry } from "../../registry/shared";
13
+ import { usePaneInstance } from "../../../state/app/context";
13
14
  import { SignInWall } from "../cloud/auth-actions";
14
15
  import { CLOUD_PLAN_KEY, useCloudUpgradeAction } from "../shared/cloud-upgrade";
16
+ import { useOpenDescription } from "../shared/open-description";
15
17
  import { Blurred, LockedOverlay, UpgradeLabel } from "../shared/locked-rows";
16
18
  import { isCloudSessionRequired, useResearchCloudSession } from "../shared/research-cloud-session";
17
- import { loadPowerBoard, loadPowerHistory, validatePowerBoard, validatePowerHistory } from "./client";
19
+ import { fetchPowerTickerScopes, loadPowerBoard, loadPowerHistory, validatePowerBoard, validatePowerHistory } from "./client";
18
20
  import { usePowerEvidence } from "./evidence";
19
21
  import { POWER_SORT_COLUMNS, powerQuery } from "./query";
20
22
  import { PowerDetailView } from "./detail";
23
+ import { PowerPricesView } from "./prices";
21
24
  import { blendHex } from "../../../theme/colors";
22
25
  import { missingCell, PartsBar, signedColor, toneColor, withoutQuietColumns, type StateTone } from "../shared/research-cells";
23
- import { COVERAGE_COLUMNS, EXPOSURE_COLUMNS, HISTORY_COLUMNS, GENERATION_COLUMNS, UTILITY_COLUMNS, POWER_TABS, PROJECT_COLUMNS, RATE_COLUMNS, historyDate, historyId, historySeries, otherRows, powerFigures, powerRegion, powerNumber, powerPercent, powerTab, projectRows, titleCase, type PowerRow } from "./model";
26
+ import { COVERAGE_COLUMNS, EXPOSURE_COLUMNS, HISTORY_COLUMNS, GENERATION_COLUMNS, UTILITY_COLUMNS, POWER_TABS, PROJECT_COLUMNS, RATE_COLUMNS, historyDate, historyId, historySeries, otherRows, powerCoverageNote, powerFigures, powerLoadedSources, powerRegion, powerNumber, powerPercent, powerTab, powerTickerEmpty, projectRows, titleCase, UNMAPPED, type PowerRow, type PowerTickerScope } from "./model";
24
27
 
25
28
  const rowId = (r: PowerRow) => r.id;
26
29
  const isRecord = (r: PowerRow) => !r.section;
@@ -36,7 +39,11 @@ const UTILITY_SORT: typeof DEFAULT_SORT = { column: "salesMwh", direction: "desc
36
39
  export function PowerPane(props: PaneProps) {
37
40
  const session = useResearchCloudSession();
38
41
  const access = usePlanAccess();
42
+ const [openingTab] = usePaneSettingValue("tab", "queue");
43
+ const [savedTab] = usePluginPaneState("power:tab", openingTab);
39
44
  const scope = `${session.requestKey}:${access.hasProAccess ? "full" : "preview"}`;
45
+ // Prices are open data from another source: their own view, free and without the queue board.
46
+ if (powerTab(savedTab) === "prices") return <PowerPricesView {...props} />;
40
47
  return <PowerView key={scope} {...props} scope={scope} needsVerification={session.needsVerification} />;
41
48
  }
42
49
  function PowerView({ width, height, focused, scope, needsVerification }: PaneProps & { scope: string; needsVerification: boolean }) {
@@ -44,6 +51,7 @@ function PowerView({ width, height, focused, scope, needsVerification }: PanePro
44
51
  const host = useRendererHost();
45
52
  const desktop = !!useUiCapabilities().nativePaneChrome;
46
53
  const { createPaneFromTemplate } = usePluginAppActions();
54
+ const openDescription = useOpenDescription();
47
55
  const upgrade = useCloudUpgradeAction("power");
48
56
  const [openingTab] = usePaneSettingValue("tab", "queue");
49
57
  const [savedTab, setTab] = usePluginPaneState("power:tab", openingTab);
@@ -58,7 +66,10 @@ function PowerView({ width, height, focused, scope, needsVerification }: PanePro
58
66
  const [fuel, setFuel] = usePluginPaneState("power:fuel", initialFuel);
59
67
  const [initialStatus] = usePaneSettingValue("status", "");
60
68
  const [status, setStatus] = usePluginPaneState("power:status", initialStatus);
61
- const [symbol] = usePaneSettingValue("symbol", "");
69
+ // `POWER VST` opens with the ticker in the instance params; a saved setting still wins.
70
+ const params = usePaneInstance()?.params;
71
+ const [settingSymbol] = usePaneSettingValue("symbol", "");
72
+ const symbol = settingSymbol || params?.symbol || "";
62
73
  const [initialHistorical] = usePaneSettingValue("historical", false);
63
74
  const [historical, setHistorical] = usePluginPaneState("power:historical", initialHistorical);
64
75
  const [initialContext] = usePaneSettingValue("context", "capacity");
@@ -73,8 +84,9 @@ function PowerView({ width, height, focused, scope, needsVerification }: PanePro
73
84
  const [selectedId, setSelectedId] = usePluginPaneState<string | null>(`power:selected:${tab}`, null);
74
85
  const [openId, setOpenId] = usePluginPaneState<string | null>(`power:open:${tab}`, null);
75
86
  const searchFocus = useQueryBarSearch();
76
- const [snapshotSetting] = usePaneSettingValue<{ board: PowerBoard; history?: PowerHistory } | null>("powerSnapshot", null);
77
- const snapshot = useMemo(() => { try { return snapshotSetting ? { board: validatePowerBoard(snapshotSetting.board), history: snapshotSetting.history ? validatePowerHistory(snapshotSetting.history) : undefined } : null; } catch { return null; } }, [snapshotSetting]);
87
+ const [snapshotSetting] = usePaneSettingValue<{ board: PowerBoard; history?: PowerHistory; tickerScopes?: PowerTickerScope[] } | null>("powerSnapshot", null);
88
+ const snapshot = useMemo(() => { try { return snapshotSetting ? { board: validatePowerBoard(snapshotSetting.board), history: snapshotSetting.history ? validatePowerHistory(snapshotSetting.history) : undefined,
89
+ tickerScopes: Array.isArray(snapshotSetting.tickerScopes) ? snapshotSetting.tickerScopes : null } : null; } catch { return null; } }, [snapshotSetting]);
78
90
  const projectTab = ["queue", "loads", "capacity"].includes(tab);
79
91
  const filter = useMemo(() => powerQuery({ tab, country, region, fuel, status, search, historical, context, loadClass, sourceId, from, to,
80
92
  sort: sort.column, direction: sort.direction }, symbol), [tab, country, region, fuel, status, search, historical, context, loadClass, sourceId, from, to, sort.column, sort.direction, symbol]);
@@ -106,6 +118,14 @@ function PowerView({ width, height, focused, scope, needsVerification }: PanePro
106
118
  usePowerEvidence(board, historyData?.payload, tab, rows.map((row) => row.id), pages.loading || historyPages.loading || historyPages.hasMore);
107
119
  const locked = board ? tab === "history" ? historyData?.payload.locked ?? 0 : tab === "outcomes" ? board.locked.rates : tab === "utilities" ? board.locked.exposure : projectTab ? board.locked.projects : 0 : 0;
108
120
  const items = useMemo(() => [...rows, ...Array.from({ length: Math.min(3, locked) }, (_, i): PowerRow => ({ id: `locked:${i}`, label: "Pro", cells: {}, locked: true }))], [rows, locked]);
121
+ // A ticker with nothing on this tab says where its records are instead, and opens them; a narrower filter is the filters' doing.
122
+ const narrowed = !!(country || region || fuel || status || search || loadClass || historical || sourceId || from || to);
123
+ const tickerEmpty = !!board && !!symbol && !narrowed && tab !== "coverage" && !items.length && !pages.loading && !historyPages.loading;
124
+ const scopesLoader = useCallback(() => fetchPowerTickerScopes(symbol, tab, context), [symbol, tab, context]);
125
+ const scopes = useAsyncResource(tickerEmpty && !snapshot ? scopesLoader : null);
126
+ const tickerScopes = snapshot ? snapshot.tickerScopes : scopes.data;
127
+ const tickerMessage = tickerEmpty ? powerTickerEmpty(symbol, tab, context, tickerScopes ?? []) : null;
128
+ const loadedSources = tickerEmpty && board ? powerLoadedSources(tab, board.coverage) : null;
109
129
  const selected = rows.find((r) => r.id === selectedId && isRecord(r)) ?? rows.find(isRecord);
110
130
  const openRow = rows.find((r) => r.id === openId);
111
131
  const scrollRef = useRef<ScrollBoxRenderable>(null);
@@ -120,7 +140,7 @@ function PowerView({ width, height, focused, scope, needsVerification }: PanePro
120
140
  const hints: PaneHint[] = [
121
141
  ...(sourceUrl ? [{ id: "source", key: "o", label: "pen source", onPress: () => void host.openExternal(sourceUrl) }] : []),
122
142
  ...(tickerKey ? [
123
- { id: "description", key: "d", label: "es", onPress: () => navigate("new-ticker-detail-pane") },
143
+ { id: "description", key: "d", label: "es", onPress: () => openDescription(tickerKey) },
124
144
  { id: "financials", key: "f", label: "a", onPress: () => navigate("financial-analysis-pane") },
125
145
  { id: "chart", key: "g", label: "raph", onPress: () => createPaneFromTemplate("chart-composer-pane", { arg: tickerKey }) },
126
146
  { id: "supply", key: "s", label: "plc", onPress: () => navigate("supply-chain-pane") },
@@ -134,8 +154,9 @@ function PowerView({ width, height, focused, scope, needsVerification }: PanePro
134
154
  error: board ? error : null, stale: pages.pages.some((p) => p.stale) || historyData?.stale,
135
155
  info: [...(pages.loadingMore || historyPages.loadingMore ? [{ id: "more", parts: [{ text: "loading more", tone: "muted" as const }] }] : []),
136
156
  ...(active?.project ? [{ id: "as-of", parts: [{ text: active.project.asOf ? `as of ${active.project.asOf}` : `observed ${active.project.observedAt.slice(0, 10)}`, tone: "muted" as const }] }] : [])], hints });
157
+ // Unavailable queues are a coverage gap in plain words; per-source reasons live on the Coverage tab.
137
158
  const notices = [pages.moreError?.message, historyPages.moreError?.message, ...pages.pages.map((p) => p.refreshError), historyData?.refreshError,
138
- ...(board?.coverage.filter((c) => c.status !== "current").map((c) => `${powerRegion(c.region)} ${titleCase(c.kind)}: ${c.status}${c.reason ? ` · ${c.reason}` : ""}`) ?? [])].filter((v): v is string => !!v);
159
+ board && tab !== "capacity" ? powerCoverageNote(board.coverage) : null].filter((v): v is string => !!v);
139
160
  usePaneNoticeFooter({ registrationId: "power:notices", focused, notices: [...new Set(notices)], enabled: !openRow });
140
161
  const { strip, rows: tabRows } = usePaneTabs(board ? { tabs: [...POWER_TABS], activeValue: tab, onSelect: setTab, focused, dense: true } : null);
141
162
  const baseColumns = tab === "capacity" && context === "generation" ? GENERATION_COLUMNS : tab === "capacity" && context === "utility" ? UTILITY_COLUMNS : projectTab ? PROJECT_COLUMNS : tab === "history" ? HISTORY_COLUMNS : tab === "outcomes" ? RATE_COLUMNS : tab === "utilities" ? EXPOSURE_COLUMNS : COVERAGE_COLUMNS;
@@ -174,6 +195,8 @@ function PowerView({ width, height, focused, scope, needsVerification }: PanePro
174
195
  { id: "operating", value: e.operatingMw, color: colors.positive }, { id: "approved", value: Math.max(0, e.approvedMw - e.operatingMw), color: blendHex(colors.bg, colors.borderFocused, 0.55) },
175
196
  { id: "requested", value: Math.max(0, e.requestedMw - Math.max(e.approvedMw, e.operatingMw)), color: blendHex(colors.bg, colors.textDim, 0.4) }]} /> }; }
176
197
  const value = row.cells[column.id];
198
+ // The ticker map: a record no listed company is mapped to says so.
199
+ if (column.id === "ticker" && value == null && (row.project || row.exposure)) return { text: UNMAPPED, color: colors.textMuted };
177
200
  if (value == null || value === "") return missingCell(colors);
178
201
  const text = typeof value === "number" ? /Rate$/.test(column.id) ? powerPercent(value) : powerNumber(value) : String(value);
179
202
  const numeric = typeof value === "number" && /Rate$/.test(column.id) ? value * 100 : value;
@@ -202,6 +225,11 @@ function PowerView({ width, height, focused, scope, needsVerification }: PanePro
202
225
  chart={tab === "history" ? { series, ...chartLink, formatValue: (n) => `${powerNumber(n)} MW`, loading: historyPages.loading || historyPages.hasMore && !historyPages.moreError,
203
226
  empty: series.length ? undefined : historyPages.moreError ? "Full history could not load. Refresh to retry." : historyData?.payload.access === "preview" ? "Full history is available with Pro." : "History is accumulating from recorded snapshots.", remoteKind: "power-history" } : null} />}
204
227
  emptyStateTitle={tab === "history" ? historyPages.error?.message ?? "No recorded history for these filters." : "No reported records match these filters."}
228
+ emptyContent={tickerMessage ? <Box paddingX={1} paddingY={1}>
229
+ <EmptyState title={tickerMessage.title} hint={[tickerMessage.hint, loadedSources ? `Sources loaded now: ${loadedSources}.` : null].filter(Boolean).join(" ") || undefined}
230
+ actions={tickerMessage.found.length ? tickerMessage.found.map((scope) => <Button key={`${scope.tab}:${scope.context}`} label={`Open ${scope.view}`}
231
+ onPress={() => { if (scope.tab === "capacity") setContext(scope.context); setTab(scope.tab); }} />) : undefined} />
232
+ </Box> : undefined}
205
233
  bodyAfter={locked && desktop ? <LockedOverlay rows={Math.min(3, locked)} text="Unlock full power data with Pro" onPress={upgrade} role="power-upgrade" /> : undefined}
206
234
  /> : null}
207
235
  </PaneStatusBody>
@@ -0,0 +1,139 @@
1
+ import { apiClient } from "../../../api-client";
2
+ import type { CarbonBoard, CarbonHistory, EuropePowerBoard, EuropePowerHistory, PowerPriceRow } from "../../../api-client/europe-energy";
3
+ import { createPluginCache } from "../../../data/plugin-cache";
4
+ import { cachedCloudResource, loadCloudResource, unavailableOnServer } from "../shared/cloud-resource";
5
+
6
+ /** What the tab and the series say while the server has no prices to serve: before the backend ships, or before its first read. */
7
+ export const POWER_PRICES_NOT_AVAILABLE = "European power prices are not available yet.";
8
+ const CARBON_PRICE_NOT_AVAILABLE = "The EU carbon auction price is not available yet.";
9
+
10
+ // Daily data, rewritten once a day: a quarter hour is soon enough to catch it.
11
+ const policy = { staleMs: 15 * 60_000, expireMs: 14 * 86_400_000 };
12
+ export const powerPricesCache = createPluginCache<EuropePowerBoard>({ kind: "europe-power-board", source: "gloom-cloud", schemaVersion: 1, policy });
13
+ export const powerPriceHistoryCache = createPluginCache<EuropePowerHistory>({ kind: "europe-power-history", source: "gloom-cloud", schemaVersion: 1, policy });
14
+ export const carbonPriceCache = createPluginCache<CarbonBoard>({ kind: "europe-carbon-board", source: "gloom-cloud", schemaVersion: 1, policy });
15
+ export const carbonHistoryCache = createPluginCache<CarbonHistory>({ kind: "europe-carbon-history", source: "gloom-cloud", schemaVersion: 1, policy });
16
+
17
+ const record = (v: unknown): v is Record<string, any> => !!v && typeof v === "object" && !Array.isArray(v);
18
+ const text = (v: unknown): v is string => typeof v === "string";
19
+ const finite = (v: unknown): v is number => typeof v === "number" && Number.isFinite(v);
20
+ const date = (v: unknown) => text(v) && /^\d{4}-\d{2}-\d{2}$/.test(v) && new Date(`${v}T00:00:00Z`).toISOString().slice(0, 10) === v;
21
+ const dateOrNull = (v: unknown) => v === null || date(v);
22
+ const finiteOrNull = (v: unknown) => v === null || finite(v);
23
+ const instantOrNull = (v: unknown) => v === null || text(v) && Number.isFinite(Date.parse(v));
24
+ const source = (v: unknown) => record(v) && text(v.name) && text(v.licence) && text(v.url);
25
+ const publisher = (v: unknown) => record(v) && text(v.name) && text(v.url);
26
+ const nullableText = (v: unknown) => v === null || text(v);
27
+ const nonnegativeOrNull = (v: unknown) => v === null || finite(v) && v >= 0;
28
+ const points = (v: unknown) => Array.isArray(v) && v.every((p) => record(p) && date(p.date) && finite(p.value));
29
+
30
+ function validRow(row: PowerPriceRow, code = /^[A-Z]{2}$/): boolean {
31
+ return record(row) && code.test(row.code) && text(row.name) && dateOrNull(row.date) && finiteOrNull(row.price)
32
+ && (row.price == null) === (row.date == null) && dateOrNull(row.previousDate) && dateOrNull(row.yearAgoDate)
33
+ && [row.change, row.average7, row.average30, row.yearAgo, row.low52w, row.high52w].every(finiteOrNull) && points(row.trail);
34
+ }
35
+
36
+ /** A board that breaks the contract is refused whole rather than drawn with holes in it. */
37
+ export function validatePowerPrices(data: EuropePowerBoard): EuropePowerBoard {
38
+ if (!record(data) || !text(data.generatedAt) || !instantOrNull(data.generatedAt) || !["available", "unavailable"].includes(data.status)
39
+ || data.market !== "day-ahead" || data.unit !== "EUR/MWh" || !source(data.source)
40
+ || !instantOrNull(data.checkedAt) || !instantOrNull(data.publishedAt)
41
+ || !Array.isArray(data.gaps) || !data.gaps.every(text)
42
+ || !Array.isArray(data.rows) || !data.rows.every((row) => validRow(row)) || new Set(data.rows.map((row) => row.code)).size !== data.rows.length) {
43
+ throw new Error("The server returned unreadable power prices.");
44
+ }
45
+ return data;
46
+ }
47
+
48
+ function validateHistory(data: EuropePowerHistory): EuropePowerHistory {
49
+ if (!record(data) || !/^[A-Z]{2}$/.test(data.code) || !text(data.name) || data.unit !== "EUR/MWh" || !source(data.source) || !points(data.points)) {
50
+ throw new Error("The server returned unreadable power price history.");
51
+ }
52
+ return data;
53
+ }
54
+
55
+ /** An EUA board that breaks the contract is refused whole. */
56
+ export function validateCarbonPrice(data: CarbonBoard): CarbonBoard {
57
+ const row = data?.row;
58
+ if (!record(data) || !text(data.generatedAt) || !instantOrNull(data.generatedAt) || !["available", "unavailable"].includes(data.status)
59
+ || data.market !== "primary auction" || data.unit !== "EUR/tCO2" || !publisher(data.source)
60
+ || !instantOrNull(data.checkedAt) || !instantOrNull(data.publishedAt) || !Array.isArray(data.gaps) || !data.gaps.every(text)
61
+ || !record(row) || !validRow(row, /^EUA$/) || !nonnegativeOrNull(row.volume) || !nonnegativeOrNull(row.auctions)
62
+ || !Array.isArray(data.auctions) || !data.auctions.every((a) => record(a) && date(a.date) && text(a.auction) && text(a.contract) && text(a.status)
63
+ && nullableText(a.zone) && [a.price, a.minBid, a.maxBid, a.meanBid, a.medianBid].every(finiteOrNull) && finite(a.volume)
64
+ && [a.coverRatio, a.bidders, a.successfulBidders].every(nonnegativeOrNull) && typeof a.aviation === "boolean")) {
65
+ throw new Error("The server returned an unreadable EU carbon price.");
66
+ }
67
+ return data;
68
+ }
69
+
70
+ function validateCarbonHistory(data: CarbonHistory): CarbonHistory {
71
+ if (!record(data) || data.code !== "EUA" || data.unit !== "EUR/tCO2" || !publisher(data.source) || !points(data.points)) {
72
+ throw new Error("The server returned an unreadable EU carbon price history.");
73
+ }
74
+ return data;
75
+ }
76
+
77
+ type PricesApi = Pick<typeof apiClient, "getCloudEuropePower" | "getCloudEuropePowerHistory" | "getCloudEuropeCarbon" | "getCloudEuropeCarbonHistory">;
78
+
79
+ /**
80
+ * The board, or the not-available message when the server does not serve the
81
+ * route yet (404), cannot read its tables (503) or has no days stored.
82
+ */
83
+ export async function fetchPowerPrices(client: Pick<PricesApi, "getCloudEuropePower"> = apiClient): Promise<EuropePowerBoard> {
84
+ let board: EuropePowerBoard;
85
+ try {
86
+ board = validatePowerPrices(await client.getCloudEuropePower());
87
+ } catch (error) {
88
+ throw unavailableOnServer(error, POWER_PRICES_NOT_AVAILABLE, [404, 503]);
89
+ }
90
+ if (board.status === "unavailable") throw new Error(POWER_PRICES_NOT_AVAILABLE);
91
+ return board;
92
+ }
93
+
94
+ /** One market's days from `from`, all of them without one. */
95
+ export async function fetchPowerPriceHistory(
96
+ code: string, from?: string | null, client: Pick<PricesApi, "getCloudEuropePowerHistory"> = apiClient,
97
+ ): Promise<EuropePowerHistory> {
98
+ try {
99
+ return validateHistory(await client.getCloudEuropePowerHistory(code, from));
100
+ } catch (error) {
101
+ throw unavailableOnServer(error, POWER_PRICES_NOT_AVAILABLE, [404, 503]);
102
+ }
103
+ }
104
+
105
+ /** The EUA board, or the not-available message before the server serves it or has an auction stored. */
106
+ export async function fetchCarbonPrice(client: Pick<PricesApi, "getCloudEuropeCarbon"> = apiClient): Promise<CarbonBoard> {
107
+ let board: CarbonBoard;
108
+ try {
109
+ board = validateCarbonPrice(await client.getCloudEuropeCarbon());
110
+ } catch (error) {
111
+ throw unavailableOnServer(error, CARBON_PRICE_NOT_AVAILABLE, [404, 503]);
112
+ }
113
+ if (board.status === "unavailable") throw new Error(CARBON_PRICE_NOT_AVAILABLE);
114
+ return board;
115
+ }
116
+
117
+ /** The daily EUA price from `from`, every auction day since 2020 without one. */
118
+ export async function fetchCarbonHistory(
119
+ from?: string | null, client: Pick<PricesApi, "getCloudEuropeCarbonHistory"> = apiClient,
120
+ ): Promise<CarbonHistory> {
121
+ try {
122
+ return validateCarbonHistory(await client.getCloudEuropeCarbonHistory(from));
123
+ } catch (error) {
124
+ throw unavailableOnServer(error, CARBON_PRICE_NOT_AVAILABLE, [404, 503]);
125
+ }
126
+ }
127
+
128
+ export const getCachedPowerPrices = () => cachedCloudResource(powerPricesCache, "board", validatePowerPrices);
129
+ export const loadPowerPrices = (force = false) =>
130
+ loadCloudResource(powerPricesCache, "board", () => fetchPowerPrices(), { force, validate: validatePowerPrices });
131
+ export const getCachedCarbonPrice = () => cachedCloudResource(carbonPriceCache, "board", validateCarbonPrice);
132
+ export const loadCarbonPrice = (force = false) =>
133
+ loadCloudResource(carbonPriceCache, "board", () => fetchCarbonPrice(), { force, validate: validateCarbonPrice });
134
+ /** Every auction day of the EUA since 2020. */
135
+ export const loadCarbonHistory = (force = false) =>
136
+ loadCloudResource(carbonHistoryCache, "EUA", () => fetchCarbonHistory(), { force, validate: validateCarbonHistory });
137
+ /** The whole history of one market, cached per market. */
138
+ export const loadPowerPriceHistory = (code: string, force = false) =>
139
+ loadCloudResource(powerPriceHistoryCache, code, () => fetchPowerPriceHistory(code), { force, validate: validateHistory });