gloomberb 0.15.8 → 0.16.0

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 (473) hide show
  1. package/README.md +10 -2
  2. package/package.json +1 -1
  3. package/src/api-client/app-attention.ts +147 -0
  4. package/src/api-client/attention.ts +26 -0
  5. package/src/api-client/awards.ts +215 -0
  6. package/src/api-client/catalysts.ts +160 -0
  7. package/src/api-client/chat.ts +137 -14
  8. package/src/api-client/company-kpis.ts +153 -0
  9. package/src/api-client/cpi.ts +52 -0
  10. package/src/api-client/credit-documents.ts +107 -0
  11. package/src/api-client/data.ts +187 -3
  12. package/src/api-client/distress.ts +164 -0
  13. package/src/api-client/equity-screener.ts +11 -0
  14. package/src/api-client/exposure.ts +202 -0
  15. package/src/api-client/gpu.ts +93 -0
  16. package/src/api-client/hiring.ts +110 -0
  17. package/src/api-client/index.ts +77 -3
  18. package/src/api-client/normalizers.ts +31 -1
  19. package/src/api-client/perps.ts +127 -0
  20. package/src/api-client/power.ts +148 -0
  21. package/src/api-client/request.ts +58 -1
  22. package/src/api-client/research-activity.ts +172 -13
  23. package/src/api-client/socket.ts +75 -27
  24. package/src/api-client/supply-chain-graph.ts +40 -0
  25. package/src/api-client/supply-chain.ts +98 -0
  26. package/src/api-client/telemetry.ts +29 -0
  27. package/src/api-client/themes.ts +28 -0
  28. package/src/api-client/types.ts +31 -1
  29. package/src/api-client/wall-experiments.ts +100 -0
  30. package/src/api-client/wall-summary.ts +49 -0
  31. package/src/api-client/web-experiments.ts +136 -0
  32. package/src/app/pane-runtime/plugin-bindings.ts +5 -0
  33. package/src/app/pane-runtime/ticker-open-runtime.ts +8 -2
  34. package/src/app/runtime/update.ts +13 -1
  35. package/src/cli/commands/system.ts +2 -0
  36. package/src/cli/desktop-pane-shot.ts +3 -4
  37. package/src/cli/pane-functions/data.ts +78 -0
  38. package/src/cli/pane-functions/financial-ratio-shot.ts +160 -0
  39. package/src/cli/pane-functions/function-help.ts +205 -8
  40. package/src/cli/pane-functions/headless.ts +4 -0
  41. package/src/cli/pane-functions/screenshot-evidence.ts +21 -0
  42. package/src/cli/pane-functions/screenshot.ts +49 -5
  43. package/src/components/chart-table/index.ts +1 -0
  44. package/src/components/chart-table/layout.ts +15 -0
  45. package/src/components/command-bar/action-runtime.ts +1 -0
  46. package/src/components/command-bar/commands/direct/index.ts +32 -0
  47. package/src/components/command-bar/commands/registry.ts +7 -0
  48. package/src/components/command-bar/pane-templates/items.ts +11 -0
  49. package/src/components/command-bar/pane-templates/workflow.ts +19 -4
  50. package/src/components/command-bar/routes/root/command-items.ts +6 -0
  51. package/src/components/command-bar/routes/root/provider-search.ts +2 -0
  52. package/src/components/command-bar/routes/root/results.ts +64 -0
  53. package/src/components/command-bar/routes/root/runtime.ts +8 -0
  54. package/src/components/command-bar/routes/ticker-search/results.ts +89 -24
  55. package/src/components/command-bar/routes/ticker-search/route.ts +2 -0
  56. package/src/components/command-bar/surface/index.tsx +15 -0
  57. package/src/components/command-bar/view-model.ts +3 -0
  58. package/src/components/command-bar/workflow/ops.ts +19 -0
  59. package/src/components/command-bar/workflow/tickers.ts +5 -0
  60. package/src/components/company-logo.tsx +15 -2
  61. package/src/components/data-table/table-pane.ts +2 -1
  62. package/src/components/index.ts +1 -0
  63. package/src/components/layout/detached-pane-shell.tsx +12 -1
  64. package/src/components/layout/missing-pane.tsx +87 -23
  65. package/src/components/layout/pane/footer/index.tsx +64 -18
  66. package/src/components/layout/pane/footer/model.ts +85 -0
  67. package/src/components/layout/pane/sidebar.tsx +19 -1
  68. package/src/components/layout/shell/pane/management-shortcuts.ts +13 -1
  69. package/src/components/layout/status-bar.tsx +93 -28
  70. package/src/components/layout/status-widget-space.ts +72 -0
  71. package/src/components/onboarding/first-run-workspace.ts +8 -2
  72. package/src/components/onboarding/onboarding-wizard.tsx +35 -16
  73. package/src/components/price-performance.tsx +31 -34
  74. package/src/components/ui/data-table/index.tsx +11 -8
  75. package/src/components/ui/display.tsx +9 -3
  76. package/src/components/ui/figure.tsx +94 -0
  77. package/src/components/ui/icon.tsx +3 -1
  78. package/src/components/ui/message-composer.tsx +8 -1
  79. package/src/components/ui/ratio-bar.tsx +84 -0
  80. package/src/components/ui/toggle.tsx +3 -1
  81. package/src/components/use-scrollbox-layout.ts +17 -0
  82. package/src/core/state/app/layout.ts +35 -1
  83. package/src/core/state/app/state.ts +15 -0
  84. package/src/core/state/app/types.ts +1 -0
  85. package/src/data/config/store/normalize.ts +24 -0
  86. package/src/i18n/es.ts +1 -0
  87. package/src/i18n/ja.ts +1 -0
  88. package/src/i18n/ko.ts +1 -0
  89. package/src/i18n/zh-cn.ts +1 -0
  90. package/src/i18n/zh-tw.ts +1 -0
  91. package/src/layout-marketplace/pane-imagery.ts +1 -0
  92. package/src/market-data/coordinator/entries.ts +13 -0
  93. package/src/market-data/coordinator/financials.ts +33 -8
  94. package/src/market-data/market/freshness.ts +16 -5
  95. package/src/market-data/published-cn-sessions.ts +25 -0
  96. package/src/market-data/published-nse-sessions.ts +20 -0
  97. package/src/news/aggregator.ts +4 -0
  98. package/src/plugins/builtin/account-management/model.ts +6 -1
  99. package/src/plugins/builtin/account-management/pane.tsx +36 -25
  100. package/src/plugins/builtin/alerts/catalysts.ts +77 -0
  101. package/src/plugins/builtin/alerts/events-pane.tsx +3 -0
  102. package/src/plugins/builtin/alerts/events.ts +10 -4
  103. package/src/plugins/builtin/alerts/history-pane.tsx +1 -1
  104. package/src/plugins/builtin/alerts/history.ts +1 -0
  105. package/src/plugins/builtin/alerts/index.tsx +6 -0
  106. package/src/plugins/builtin/alerts/research-builder.ts +10 -3
  107. package/src/plugins/builtin/alerts/research-rules.ts +15 -1
  108. package/src/plugins/builtin/analytics/risk-client.ts +15 -3
  109. package/src/plugins/builtin/analytics/risk-headless.ts +123 -18
  110. package/src/plugins/builtin/attention/client.ts +80 -0
  111. package/src/plugins/builtin/attention/evidence.ts +30 -0
  112. package/src/plugins/builtin/attention/headless.ts +33 -0
  113. package/src/plugins/builtin/attention/index.tsx +18 -0
  114. package/src/plugins/builtin/attention/model.ts +59 -0
  115. package/src/plugins/builtin/attention/pane.tsx +211 -0
  116. package/src/plugins/builtin/attention/trending.tsx +59 -0
  117. package/src/plugins/builtin/awards/alerts.ts +32 -0
  118. package/src/plugins/builtin/awards/client.ts +82 -0
  119. package/src/plugins/builtin/awards/detail.tsx +107 -0
  120. package/src/plugins/builtin/awards/evidence.ts +36 -0
  121. package/src/plugins/builtin/awards/headless.ts +55 -0
  122. package/src/plugins/builtin/awards/index.tsx +20 -0
  123. package/src/plugins/builtin/awards/model.ts +42 -0
  124. package/src/plugins/builtin/awards/pane.tsx +263 -0
  125. package/src/plugins/builtin/catalysts/client.ts +28 -0
  126. package/src/plugins/builtin/catalysts/detail.tsx +88 -0
  127. package/src/plugins/builtin/catalysts/evidence.ts +47 -0
  128. package/src/plugins/builtin/catalysts/headless.ts +42 -0
  129. package/src/plugins/builtin/catalysts/history.ts +26 -0
  130. package/src/plugins/builtin/catalysts/index.tsx +29 -0
  131. package/src/plugins/builtin/catalysts/model.ts +135 -0
  132. package/src/plugins/builtin/catalysts/pane.tsx +159 -0
  133. package/src/plugins/builtin/chart-composer/index.tsx +8 -1
  134. package/src/plugins/builtin/chat/attachments/desktop.tsx +610 -0
  135. package/src/plugins/builtin/chat/attachments/files.ts +45 -0
  136. package/src/plugins/builtin/chat/attachments/model.ts +164 -0
  137. package/src/plugins/builtin/chat/attachments/terminal.tsx +72 -0
  138. package/src/plugins/builtin/chat/attachments/transfer.ts +165 -0
  139. package/src/plugins/builtin/chat/content/channel-navigation.ts +13 -12
  140. package/src/plugins/builtin/chat/content/composer-runtime.ts +25 -4
  141. package/src/plugins/builtin/chat/content/composer.tsx +46 -4
  142. package/src/plugins/builtin/chat/content/footer.tsx +46 -8
  143. package/src/plugins/builtin/chat/content/index.tsx +263 -62
  144. package/src/plugins/builtin/chat/content/layout-metrics.ts +10 -2
  145. package/src/plugins/builtin/chat/content/open-targets.ts +17 -3
  146. package/src/plugins/builtin/chat/content/scroll.ts +7 -1
  147. package/src/plugins/builtin/chat/content/shortcuts.ts +36 -11
  148. package/src/plugins/builtin/chat/content/snapshot.ts +3 -0
  149. package/src/plugins/builtin/chat/content/transcript.tsx +17 -1
  150. package/src/plugins/builtin/chat/content/types.ts +7 -0
  151. package/src/plugins/builtin/chat/controller/attachments.ts +213 -0
  152. package/src/plugins/builtin/chat/controller/connections.ts +3 -1
  153. package/src/plugins/builtin/chat/controller/index.ts +114 -2
  154. package/src/plugins/builtin/chat/controller/lifecycle.ts +9 -0
  155. package/src/plugins/builtin/chat/controller/messages.ts +35 -2
  156. package/src/plugins/builtin/chat/controller/send.ts +128 -25
  157. package/src/plugins/builtin/chat/controller/state.ts +12 -1
  158. package/src/plugins/builtin/chat/controller/utils.ts +7 -5
  159. package/src/plugins/builtin/chat/controller/view.ts +2 -0
  160. package/src/plugins/builtin/chat/index.tsx +21 -0
  161. package/src/plugins/builtin/chat/layout.ts +9 -4
  162. package/src/plugins/builtin/chat/message/desktop.tsx +55 -20
  163. package/src/plugins/builtin/chat/message/header.tsx +30 -5
  164. package/src/plugins/builtin/chat/message/inline-tokens.tsx +9 -0
  165. package/src/plugins/builtin/chat/message/pending-send.ts +35 -0
  166. package/src/plugins/builtin/chat/message/profile-popover.tsx +5 -1
  167. package/src/plugins/builtin/chat/message/render-state.ts +12 -2
  168. package/src/plugins/builtin/chat/message/terminal.tsx +51 -4
  169. package/src/plugins/builtin/chat/message/types.ts +6 -1
  170. package/src/plugins/builtin/chat/pane-state.ts +59 -1
  171. package/src/plugins/builtin/chat/profile-popover.ts +50 -1
  172. package/src/plugins/builtin/chat/sidebar.tsx +8 -3
  173. package/src/plugins/builtin/chat/status-widget.tsx +29 -12
  174. package/src/plugins/builtin/chat/unread-inbox-pane.tsx +143 -0
  175. package/src/plugins/builtin/chat/unread-inbox.ts +91 -0
  176. package/src/plugins/builtin/clinical-trials/client.ts +237 -0
  177. package/src/plugins/builtin/clinical-trials/index.tsx +74 -0
  178. package/src/plugins/builtin/clinical-trials/pane.tsx +225 -0
  179. package/src/plugins/builtin/clinical-trials/types.ts +2 -0
  180. package/src/plugins/builtin/cloud/askg/controller.ts +37 -6
  181. package/src/plugins/builtin/cloud/askg/executor.ts +59 -44
  182. package/src/plugins/builtin/cloud/askg/host.ts +8 -1
  183. package/src/plugins/builtin/cloud/askg/manifest.ts +29 -2
  184. package/src/plugins/builtin/cloud/askg/model.ts +47 -13
  185. package/src/plugins/builtin/cloud/askg/notes.ts +100 -0
  186. package/src/plugins/builtin/cloud/askg/pane.tsx +50 -14
  187. package/src/plugins/builtin/cloud/askg/protocol.ts +39 -1
  188. package/src/plugins/builtin/cloud/askg/result-budget.ts +156 -0
  189. package/src/plugins/builtin/cloud/askg/user-data.ts +89 -0
  190. package/src/plugins/builtin/cloud/auth-actions.tsx +55 -31
  191. package/src/plugins/builtin/cloud/plugin.tsx +4 -1
  192. package/src/plugins/builtin/cloud/team/pane-sections.tsx +10 -1
  193. package/src/plugins/builtin/cloud/team/pane.tsx +3 -2
  194. package/src/plugins/builtin/cloud/thesis/board-pane.tsx +2 -2
  195. package/src/plugins/builtin/cloud/thesis/detail.tsx +1 -1
  196. package/src/plugins/builtin/cloud/thesis/ticker-tab.tsx +2 -2
  197. package/src/plugins/builtin/cloud/trial-offer-status-widget.tsx +202 -0
  198. package/src/plugins/builtin/cloud/upgrade-command.ts +1 -1
  199. package/src/plugins/builtin/cloud/upgrade-dialog.tsx +29 -3
  200. package/src/plugins/builtin/cloud/upgrade-status-widget.tsx +1 -1
  201. package/src/plugins/builtin/cloud-tweets/table.tsx +1 -1
  202. package/src/plugins/builtin/comment-letters/client.ts +342 -0
  203. package/src/plugins/builtin/comment-letters/index.tsx +74 -0
  204. package/src/plugins/builtin/comment-letters/pane.tsx +295 -0
  205. package/src/plugins/builtin/comment-letters/types.ts +43 -0
  206. package/src/plugins/builtin/company-attention/app-rank.tsx +79 -0
  207. package/src/plugins/builtin/company-attention/client.ts +69 -0
  208. package/src/plugins/builtin/company-attention/evidence.ts +45 -0
  209. package/src/plugins/builtin/company-attention/headless.ts +47 -0
  210. package/src/plugins/builtin/company-attention/index.tsx +30 -0
  211. package/src/plugins/builtin/company-attention/model.ts +129 -0
  212. package/src/plugins/builtin/company-attention/pane.tsx +183 -0
  213. package/src/plugins/builtin/company-kpis/client.ts +92 -0
  214. package/src/plugins/builtin/company-kpis/headless.ts +32 -0
  215. package/src/plugins/builtin/company-kpis/index.tsx +26 -0
  216. package/src/plugins/builtin/company-kpis/model.ts +124 -0
  217. package/src/plugins/builtin/company-kpis/pane.tsx +226 -0
  218. package/src/plugins/builtin/company-kpis/related.ts +11 -0
  219. package/src/plugins/builtin/company-kpis/screenshot-evidence.ts +44 -0
  220. package/src/plugins/builtin/composite-plugins.ts +32 -0
  221. package/src/plugins/builtin/correlation/history.ts +4 -1
  222. package/src/plugins/builtin/correlation/index.tsx +139 -80
  223. package/src/plugins/builtin/correlation/matrix/pair-chart.tsx +48 -0
  224. package/src/plugins/builtin/correlation/matrix/selection.ts +77 -0
  225. package/src/plugins/builtin/correlation/relationship/model.ts +2 -2
  226. package/src/plugins/builtin/cpi/client.ts +66 -0
  227. package/src/plugins/builtin/cpi/headless.ts +78 -0
  228. package/src/plugins/builtin/cpi/index.tsx +31 -0
  229. package/src/plugins/builtin/cpi/model.ts +198 -0
  230. package/src/plugins/builtin/cpi/pane.tsx +157 -0
  231. package/src/plugins/builtin/credit-documents/client.ts +99 -0
  232. package/src/plugins/builtin/credit-documents/detail.tsx +109 -0
  233. package/src/plugins/builtin/credit-documents/evidence.ts +64 -0
  234. package/src/plugins/builtin/credit-documents/gauge.tsx +55 -0
  235. package/src/plugins/builtin/credit-documents/headless.ts +40 -0
  236. package/src/plugins/builtin/credit-documents/index.tsx +19 -0
  237. package/src/plugins/builtin/credit-documents/model.ts +336 -0
  238. package/src/plugins/builtin/credit-documents/pane.tsx +185 -0
  239. package/src/plugins/builtin/custom-view/view-spec.ts +14 -2
  240. package/src/plugins/builtin/debt-maturities/pane.tsx +1 -0
  241. package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
  242. package/src/plugins/builtin/earnings/board.tsx +3 -0
  243. package/src/plugins/builtin/earnings/index.tsx +2 -0
  244. package/src/plugins/builtin/earnings-calls/pane.tsx +7 -1
  245. package/src/plugins/builtin/earnings-ripple/client.ts +60 -0
  246. package/src/plugins/builtin/earnings-ripple/headless.ts +35 -0
  247. package/src/plugins/builtin/earnings-ripple/index.tsx +35 -0
  248. package/src/plugins/builtin/earnings-ripple/model.ts +98 -0
  249. package/src/plugins/builtin/earnings-ripple/pane.tsx +103 -0
  250. package/src/plugins/builtin/equity-screener/client.ts +1 -1
  251. package/src/plugins/builtin/equity-screener/criterion-editor.tsx +1 -1
  252. package/src/plugins/builtin/equity-screener/model.ts +74 -9
  253. package/src/plugins/builtin/equity-screener/pane.tsx +2 -0
  254. package/src/plugins/builtin/estimate-revisions/pane.tsx +3 -0
  255. package/src/plugins/builtin/executives/pane.tsx +3 -1
  256. package/src/plugins/builtin/exposure/charts.tsx +66 -0
  257. package/src/plugins/builtin/exposure/client.ts +53 -0
  258. package/src/plugins/builtin/exposure/drivers.ts +29 -0
  259. package/src/plugins/builtin/exposure/editor.tsx +61 -0
  260. package/src/plugins/builtin/exposure/evidence.ts +39 -0
  261. package/src/plugins/builtin/exposure/headless.ts +51 -0
  262. package/src/plugins/builtin/exposure/holdings.ts +56 -0
  263. package/src/plugins/builtin/exposure/index.tsx +13 -0
  264. package/src/plugins/builtin/exposure/model.ts +104 -0
  265. package/src/plugins/builtin/exposure/pane.tsx +247 -0
  266. package/src/plugins/builtin/fear-greed/pane.tsx +2 -9
  267. package/src/plugins/builtin/filing-events/pane.tsx +2 -1
  268. package/src/plugins/builtin/gpu/board.tsx +163 -0
  269. package/src/plugins/builtin/gpu/changes.tsx +94 -0
  270. package/src/plugins/builtin/gpu/client.ts +130 -0
  271. package/src/plugins/builtin/gpu/equities.tsx +112 -0
  272. package/src/plugins/builtin/gpu/headless.ts +67 -0
  273. package/src/plugins/builtin/gpu/history.tsx +140 -0
  274. package/src/plugins/builtin/gpu/index.tsx +18 -0
  275. package/src/plugins/builtin/gpu/ladder.tsx +127 -0
  276. package/src/plugins/builtin/gpu/model.ts +317 -0
  277. package/src/plugins/builtin/gpu/pane.tsx +63 -0
  278. package/src/plugins/builtin/help/index.tsx +19 -2
  279. package/src/plugins/builtin/holders/pane.tsx +4 -8
  280. package/src/plugins/builtin/insider/detail.tsx +119 -0
  281. package/src/plugins/builtin/insider/display.ts +221 -0
  282. package/src/plugins/builtin/insider/headless.ts +24 -3
  283. package/src/plugins/builtin/insider/index.tsx +206 -108
  284. package/src/plugins/builtin/insider/insider-data.ts +31 -3
  285. package/src/plugins/builtin/insider/model.ts +53 -4
  286. package/src/plugins/builtin/insider/table-model.ts +226 -0
  287. package/src/plugins/builtin/insider/table.tsx +79 -0
  288. package/src/plugins/builtin/iv-history/screen-pane.tsx +1 -1
  289. package/src/plugins/builtin/jobs/pane.tsx +14 -7
  290. package/src/plugins/builtin/macro-day/client.ts +50 -0
  291. package/src/plugins/builtin/macro-day/headless.ts +51 -0
  292. package/src/plugins/builtin/macro-day/index.tsx +21 -0
  293. package/src/plugins/builtin/macro-day/model.ts +137 -0
  294. package/src/plugins/builtin/macro-day/pane.tsx +118 -0
  295. package/src/plugins/builtin/macro-day/releases.ts +58 -0
  296. package/src/plugins/builtin/market-halts/model.ts +30 -17
  297. package/src/plugins/builtin/market-halts/pane.tsx +15 -21
  298. package/src/plugins/builtin/market-heatmap/data.ts +1 -1
  299. package/src/plugins/builtin/market-heatmap/index.tsx +198 -50
  300. package/src/plugins/builtin/market-heatmap/portfolio.ts +220 -0
  301. package/src/plugins/builtin/market-movers/index.tsx +1 -0
  302. package/src/plugins/builtin/market-movers/session-body.tsx +5 -2
  303. package/src/plugins/builtin/mna/distress/client.ts +326 -0
  304. package/src/plugins/builtin/mna/distress/detail.tsx +247 -0
  305. package/src/plugins/builtin/mna/distress/headless.ts +257 -0
  306. package/src/plugins/builtin/mna/distress/model.ts +427 -0
  307. package/src/plugins/builtin/mna/distress/pane.tsx +606 -0
  308. package/src/plugins/builtin/mna/index.tsx +15 -0
  309. package/src/plugins/builtin/mna/pane.tsx +42 -1
  310. package/src/plugins/builtin/news/index.tsx +62 -16
  311. package/src/plugins/builtin/news/wire/filter-articles.ts +54 -0
  312. package/src/plugins/builtin/news/wire/index.tsx +37 -3
  313. package/src/plugins/builtin/news/wire/industry-pane.tsx +29 -13
  314. package/src/plugins/builtin/news/wire/mutes.ts +253 -0
  315. package/src/plugins/builtin/news/wire/news/command-bar-search.ts +117 -0
  316. package/src/plugins/builtin/news/wire/news/footer.ts +4 -0
  317. package/src/plugins/builtin/news/wire/news/list-search.ts +34 -0
  318. package/src/plugins/builtin/news/wire/news/pop-out.tsx +158 -0
  319. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +64 -19
  320. package/src/plugins/builtin/openfda/client.ts +300 -0
  321. package/src/plugins/builtin/openfda/index.tsx +71 -0
  322. package/src/plugins/builtin/openfda/pane.tsx +382 -0
  323. package/src/plugins/builtin/openfda/types.ts +76 -0
  324. package/src/plugins/builtin/options/footer.ts +1 -0
  325. package/src/plugins/builtin/options-calculator/pane.tsx +0 -1
  326. package/src/plugins/builtin/options-positioning/index.tsx +4 -2
  327. package/src/plugins/builtin/options-positioning/pane.tsx +1 -1
  328. package/src/plugins/builtin/pe-band/client.ts +61 -0
  329. package/src/plugins/builtin/pe-band/headless.ts +51 -0
  330. package/src/plugins/builtin/pe-band/index.tsx +20 -0
  331. package/src/plugins/builtin/pe-band/model.ts +267 -0
  332. package/src/plugins/builtin/pe-band/pane.tsx +159 -0
  333. package/src/plugins/builtin/perps/client.ts +66 -0
  334. package/src/plugins/builtin/perps/equity-row.tsx +28 -0
  335. package/src/plugins/builtin/perps/headless.ts +34 -0
  336. package/src/plugins/builtin/perps/index.tsx +14 -0
  337. package/src/plugins/builtin/perps/model.ts +58 -0
  338. package/src/plugins/builtin/perps/pane.tsx +185 -0
  339. package/src/plugins/builtin/plugin-marketplace/pane.tsx +0 -1
  340. package/src/plugins/builtin/portfolio-list/cli/render.ts +177 -87
  341. package/src/plugins/builtin/portfolio-list/headless.ts +237 -0
  342. package/src/plugins/builtin/portfolio-list/index.tsx +2 -0
  343. package/src/plugins/builtin/portfolio-list/mutations.ts +17 -0
  344. package/src/plugins/builtin/portfolio-list/pane/data.ts +17 -0
  345. package/src/plugins/builtin/portfolio-list/pane/index.tsx +88 -5
  346. package/src/plugins/builtin/portfolio-list/pane/remove-ticker.ts +85 -0
  347. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +18 -12
  348. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +6 -0
  349. package/src/plugins/builtin/power/client.ts +100 -0
  350. package/src/plugins/builtin/power/detail.tsx +82 -0
  351. package/src/plugins/builtin/power/evidence.ts +56 -0
  352. package/src/plugins/builtin/power/generation.tsx +37 -0
  353. package/src/plugins/builtin/power/headless.ts +43 -0
  354. package/src/plugins/builtin/power/index.tsx +16 -0
  355. package/src/plugins/builtin/power/model.ts +89 -0
  356. package/src/plugins/builtin/power/pane.tsx +209 -0
  357. package/src/plugins/builtin/power/query.ts +25 -0
  358. package/src/plugins/builtin/relative-rotation/pane.tsx +3 -3
  359. package/src/plugins/builtin/research/analyst-pane.tsx +1 -1
  360. package/src/plugins/builtin/research/corporate-actions-pane.tsx +13 -4
  361. package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +5 -3
  362. package/src/plugins/builtin/research-search/pane.tsx +21 -7
  363. package/src/plugins/builtin/revenue-breakdown/pane.tsx +1 -1
  364. package/src/plugins/builtin/reverse-dcf/client.ts +35 -0
  365. package/src/plugins/builtin/reverse-dcf/headless.ts +44 -0
  366. package/src/plugins/builtin/reverse-dcf/index.tsx +20 -0
  367. package/src/plugins/builtin/reverse-dcf/model.ts +87 -0
  368. package/src/plugins/builtin/reverse-dcf/pane.tsx +107 -0
  369. package/src/plugins/builtin/risk-factors/pane.tsx +2 -1
  370. package/src/plugins/builtin/scanner/denied.tsx +4 -2
  371. package/src/plugins/builtin/scanner/feed.ts +1 -0
  372. package/src/plugins/builtin/scanner/flow-pane.tsx +3 -2
  373. package/src/plugins/builtin/scanner/hilo-pane.tsx +1 -1
  374. package/src/plugins/builtin/seasonality/client.ts +4 -1
  375. package/src/plugins/builtin/sec/command-bar-search.ts +217 -0
  376. package/src/plugins/builtin/sec/forms.ts +54 -0
  377. package/src/plugins/builtin/sec/headless.ts +31 -5
  378. package/src/plugins/builtin/sec/index.tsx +67 -11
  379. package/src/plugins/builtin/sec/model.ts +35 -5
  380. package/src/plugins/builtin/sectors/headless.ts +1 -0
  381. package/src/plugins/builtin/sectors/index.tsx +72 -26
  382. package/src/plugins/builtin/shared/cloud-upgrade.ts +143 -39
  383. package/src/plugins/builtin/shared/pro-feature-wall.tsx +8 -4
  384. package/src/plugins/builtin/shared/research-cells.tsx +107 -0
  385. package/src/plugins/builtin/shared/series-list-detail.tsx +5 -1
  386. package/src/plugins/builtin/shared/wall-teaser-catalog.ts +80 -0
  387. package/src/plugins/builtin/shared/wall-teaser.tsx +192 -0
  388. package/src/plugins/builtin/shared/wall-view-lifecycle.ts +43 -0
  389. package/src/plugins/builtin/short-interest/index.tsx +31 -0
  390. package/src/plugins/builtin/short-interest/watch-client.ts +70 -0
  391. package/src/plugins/builtin/short-interest/watch-headless.ts +49 -0
  392. package/src/plugins/builtin/short-interest/watch-model.ts +185 -0
  393. package/src/plugins/builtin/short-interest/watch-pane.tsx +169 -0
  394. package/src/plugins/builtin/short-volume/pane.tsx +1 -1
  395. package/src/plugins/builtin/social-mentions/pane.tsx +1 -1
  396. package/src/plugins/builtin/supply-chain/client.ts +104 -0
  397. package/src/plugins/builtin/supply-chain/disclosure-quote.tsx +17 -0
  398. package/src/plugins/builtin/supply-chain/evidence.ts +88 -0
  399. package/src/plugins/builtin/supply-chain/flow-layout.ts +133 -0
  400. package/src/plugins/builtin/supply-chain/flow.tsx +323 -0
  401. package/src/plugins/builtin/supply-chain/graph-client.ts +83 -0
  402. package/src/plugins/builtin/supply-chain/graph-evidence.ts +25 -0
  403. package/src/plugins/builtin/supply-chain/graph-model.ts +45 -0
  404. package/src/plugins/builtin/supply-chain/graph-pane.tsx +218 -0
  405. package/src/plugins/builtin/supply-chain/graph.tsx +163 -0
  406. package/src/plugins/builtin/supply-chain/headless.ts +105 -0
  407. package/src/plugins/builtin/supply-chain/index.tsx +17 -0
  408. package/src/plugins/builtin/supply-chain/model.ts +145 -0
  409. package/src/plugins/builtin/supply-chain/pane.tsx +276 -0
  410. package/src/plugins/builtin/supply-chain/row-evidence.tsx +96 -0
  411. package/src/plugins/builtin/supply-chain/trust.ts +34 -0
  412. package/src/plugins/builtin/themes/client.ts +39 -0
  413. package/src/plugins/builtin/themes/headless.ts +38 -0
  414. package/src/plugins/builtin/themes/model.ts +92 -0
  415. package/src/plugins/builtin/themes/pane.tsx +125 -0
  416. package/src/plugins/builtin/thirteenf/pane.tsx +6 -15
  417. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +5 -6
  418. package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +201 -0
  419. package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +327 -0
  420. package/src/plugins/builtin/ticker-detail/financials/period-end-history.ts +5 -2
  421. package/src/plugins/builtin/ticker-detail/financials/ratios.ts +18 -2
  422. package/src/plugins/builtin/ticker-detail/financials/tab.tsx +2 -1
  423. package/src/plugins/builtin/ticker-detail/headless.ts +7 -2
  424. package/src/plugins/builtin/ticker-detail/index.tsx +22 -0
  425. package/src/plugins/builtin/ticker-detail/overview/components.tsx +53 -30
  426. package/src/plugins/builtin/ticker-detail/overview/pane.tsx +2 -0
  427. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +111 -46
  428. package/src/plugins/builtin/ticker-detail/pane.tsx +50 -6
  429. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +20 -41
  430. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +3 -0
  431. package/src/plugins/builtin/time-sales/pane.tsx +1 -1
  432. package/src/plugins/builtin/world-indices/indices.ts +1 -0
  433. package/src/plugins/catalog-browser.ts +38 -0
  434. package/src/plugins/catalog-manifest.ts +3 -0
  435. package/src/plugins/catalog-ui.ts +6 -0
  436. package/src/plugins/current-target.ts +0 -9
  437. package/src/public/__snapshots__/public-api.test.ts.snap +1 -0
  438. package/src/renderers/browser/cloud-transport.ts +66 -1
  439. package/src/renderers/browser/main.tsx +2 -0
  440. package/src/renderers/dom/data-table/row.tsx +2 -0
  441. package/src/renderers/dom/desktop/controls.tsx +38 -25
  442. package/src/renderers/dom/desktop/icons.tsx +8 -0
  443. package/src/renderers/dom/host/chart-surface.tsx +36 -2
  444. package/src/renderers/dom/host/focus-scope.ts +11 -1
  445. package/src/renderers/dom/styles.css +28 -0
  446. package/src/renderers/electrobun/view/backend-rpc.ts +3 -2
  447. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -1
  448. package/src/renderers/electrobun/view/http-fetch.ts +2 -0
  449. package/src/renderers/electrobun/view/main.tsx +3 -1
  450. package/src/renderers/electrobun/view/rpc-timeout.ts +59 -0
  451. package/src/renderers/opentui/start.tsx +3 -0
  452. package/src/shares/api.ts +3 -1
  453. package/src/sources/sec-edgar/identity.ts +36 -0
  454. package/src/sources/sec-edgar.ts +1 -34
  455. package/src/state/app/context/index.tsx +8 -0
  456. package/src/sync/core-contributors.ts +2 -0
  457. package/src/telemetry/attention-counts.ts +164 -0
  458. package/src/telemetry/live-config.ts +7 -0
  459. package/src/test-support/rpc-loopback.ts +7 -3
  460. package/src/test-support/shell-pane-keys.tsx +34 -0
  461. package/src/tickers/instrument-kind.ts +12 -6
  462. package/src/tickers/search/asset-classes.ts +91 -0
  463. package/src/tickers/search/index.ts +18 -4
  464. package/src/tickers/search/ranking.ts +22 -3
  465. package/src/types/config.ts +23 -1
  466. package/src/types/headless.ts +17 -0
  467. package/src/ui/host.tsx +6 -0
  468. package/src/utils/double-escape-close.ts +20 -0
  469. package/src/utils/html-entities.ts +15 -0
  470. package/src/utils/http-proxy-response.ts +44 -7
  471. package/src/utils/plugin-proxy-hosts.json +3 -0
  472. package/src/utils/sec.ts +19 -1
  473. package/src/utils/text-wrap.ts +13 -8
@@ -1,12 +1,13 @@
1
- import { Box, ScrollBox, Text, type InputRenderable, type ScrollBoxRenderable } from "../../../ui";
2
- import { useCallback, useMemo, useRef, useState } from "react";
1
+ import { Box, ScrollBox, Text, TextAttributes, type InputRenderable, type ScrollBoxRenderable } from "../../../ui";
2
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
3
  import { PaneStatusBody, QueryBar, usePaneFooter, usePaneNoticeFooter } from "../../../components";
4
4
  import { handleRefreshKey } from "../../../components/data-table/table-pane";
5
+ import { afterLayout } from "../../../components/ui/reveal-in-scroll-box";
5
6
  import type { PaneProps } from "../../../types/plugin";
6
7
  import type { PluginModule } from "../plugin-module";
7
8
  import { TICKER_RESEARCH_PANE_ID } from "../../../types/config";
8
9
  import { colors } from "../../../theme/colors";
9
- import { usePluginTickerActions } from "../../runtime";
10
+ import { usePluginAppActions, usePluginTickerActions } from "../../runtime";
10
11
  import { useAppSelector, usePaneInstance, usePaneSettingValue } from "../../../state/app/context";
11
12
  import { useChartQueries } from "../../../market-data/hooks";
12
13
  import { getSharedMarketDataCoordinator } from "../../../market-data/coordinator";
@@ -45,12 +46,21 @@ import {
45
46
  } from "./matrix/model";
46
47
  import { SymbolLabelCell } from "./matrix/symbol-cell";
47
48
  import { correlationHeadless, relationshipHeadless } from "./headless";
49
+ import { CORRELATION_HISTORY_RESOLUTION } from "./history";
50
+ import { buildMatrixPairHistory, clampMatrixCursor, matrixChartRows, matrixSelection, moveMatrixCursor, type MatrixCursor } from "./matrix/selection";
51
+ import { MatrixPairChart } from "./matrix/pair-chart";
48
52
 
49
53
  function CorrelationMatrixPane({ focused, width, height }: PaneProps) {
50
54
  const pane = usePaneInstance();
51
55
  const { navigateTicker, pinTicker } = usePluginTickerActions();
56
+ const { createPaneFromTemplate } = usePluginAppActions();
52
57
  const tickers = useAppSelector((state) => state.tickers);
53
- const [hoveredSymbol, setHoveredSymbol] = useState<string | null>(null);
58
+ const [{ cursor: cellCursor, hoveredSymbol }, setCursorState] = useState<{ cursor: MatrixCursor; hoveredSymbol: string | null }>({
59
+ cursor: { row: 0, column: 1 }, hoveredSymbol: null,
60
+ });
61
+ const hoverSymbol = useCallback((symbol: string) => {
62
+ setCursorState((current) => ({ ...current, hoveredSymbol: symbol }));
63
+ }, []);
54
64
  const settings = useMemo(() => getCorrelationPaneSettings(pane?.settings), [pane?.settings]);
55
65
  const [rangePreset, setRangePreset] = usePaneSettingValue<CorrelationRangePreset>("rangePreset", settings.rangePreset);
56
66
  const [symbolsText, setSymbolsText] = usePaneSettingValue<string>("symbolsText", settings.symbolsText);
@@ -63,6 +73,7 @@ function CorrelationMatrixPane({ focused, width, height }: PaneProps) {
63
73
  // Remounts the query bar so the field drops the draft Esc threw away.
64
74
  const [queryBarRevision, setQueryBarRevision] = useState(0);
65
75
  const matrixScrollRef = useRef<ScrollBoxRenderable | null>(null);
76
+ const horizontalScrollRef = useRef<ScrollBoxRenderable | null>(null);
66
77
 
67
78
  const instruments = useMemo(() => {
68
79
  if (settings.symbolsError) return [];
@@ -85,7 +96,7 @@ function CorrelationMatrixPane({ focused, width, height }: PaneProps) {
85
96
  },
86
97
  bufferRange: settings.rangePreset,
87
98
  granularity: "resolution" as const,
88
- resolution: "1d" as const,
99
+ resolution: CORRELATION_HISTORY_RESOLUTION,
89
100
  })),
90
101
  [instrumentKey, settings.rangePreset],
91
102
  );
@@ -106,6 +117,15 @@ function CorrelationMatrixPane({ focused, width, height }: PaneProps) {
106
117
 
107
118
  const symbols = instruments.map((instrument) => instrument.symbol);
108
119
  const symbolsKey = symbols.join(",");
120
+ const cursor = clampMatrixCursor(cellCursor, symbols.length);
121
+ const selection = matrixSelection(symbols, cursor);
122
+ const pair = selection && selection[0] !== selection[1] ? selection : null;
123
+ useEffect(() => {
124
+ setCursorState((current) => ({ ...current, cursor: clampMatrixCursor(current.cursor, symbols.length) }));
125
+ }, [symbolsKey]);
126
+ const pairHistory = useMemo(() => pair ? buildMatrixPairHistory(
127
+ seriesBySymbol.get(pair[0]), seriesBySymbol.get(pair[1]), settings.rangePreset,
128
+ ) : null, [pair?.[0], pair?.[1], seriesBySymbol, settings.rangePreset]);
109
129
 
110
130
  const matrix = useMemo(() => {
111
131
  return buildCorrelationMatrix(symbols, seriesBySymbol);
@@ -129,34 +149,31 @@ function CorrelationMatrixPane({ focused, width, height }: PaneProps) {
129
149
  navigateTicker(symbol);
130
150
  }, [navigateTicker, pinTicker, tickers]);
131
151
 
132
- /** The row cursor shares the hover highlight, so mouse and keys point at one symbol. */
133
- const moveSymbolCursor = useCallback((offset: -1 | 1) => {
152
+ /** Hovered symbols remain a keyboard starting point; Left reaches their row cursor. */
153
+ const moveCursor = (key: string) => {
134
154
  if (symbols.length === 0) return;
135
- const index = hoveredSymbol ? symbols.indexOf(hoveredSymbol) : -1;
136
- const next = index < 0
137
- ? (offset > 0 ? 0 : symbols.length - 1)
138
- : Math.max(0, Math.min(symbols.length - 1, index + offset));
139
- setHoveredSymbol(symbols[next]!);
140
- const scroll = matrixScrollRef.current;
141
- if (!scroll) return;
142
- const viewportHeight = Math.max(1, scroll.viewport?.height ?? 1);
143
- if (next < scroll.scrollTop) scroll.scrollTo(next);
144
- else if (next + 1 > scroll.scrollTop + viewportHeight) scroll.scrollTo(next + 1 - viewportHeight);
145
- }, [hoveredSymbol, symbolsKey]);
155
+ setCursorState((current) => {
156
+ const hoveredRow = current.hoveredSymbol ? symbols.indexOf(current.hoveredSymbol) : -1;
157
+ const start = hoveredRow >= 0 ? { row: hoveredRow, column: -1 } : current.cursor;
158
+ return { cursor: moveMatrixCursor(start, key, symbols.length), hoveredSymbol: null };
159
+ });
160
+ };
146
161
 
147
162
  useShortcut((event) => {
148
163
  if (!focused || symbolsEditing || event.defaultPrevented) return;
149
164
  if (handleRefreshKey(event, refresh, { stopPropagation: true })) return;
150
- if (isPlainKey(event, "j", "down", "k", "up")) {
165
+ if (isPlainKey(event, "j", "down", "k", "up", "h", "left", "l", "right")) {
151
166
  event.preventDefault();
152
167
  event.stopPropagation();
153
- moveSymbolCursor(event.name === "j" || event.name === "down" ? 1 : -1);
154
- } else if (isPlainKey(event, "return", "enter") && hoveredSymbol && symbols.includes(hoveredSymbol)) {
168
+ moveCursor(event.name ?? event.key ?? "");
169
+ } else if (isPlainKey(event, "return", "enter") && selection) {
155
170
  event.preventDefault();
156
171
  event.stopPropagation();
157
- openSymbol(hoveredSymbol);
172
+ if (hoveredSymbol && symbols.includes(hoveredSymbol)) openSymbol(hoveredSymbol);
173
+ else if (pair) createPaneFromTemplate("relationship-graph-pane", { symbols: pair, arg: formatTickerListInput(pair) });
174
+ else openSymbol(selection[0]);
158
175
  }
159
- });
176
+ }, { phase: "before", scope: "correlation:matrix" });
160
177
 
161
178
  // Esc cancels an edit of the list instead of clearing it: the field's own
162
179
  // Esc empties the draft, which would fall back to the default tickers.
@@ -177,7 +194,7 @@ function CorrelationMatrixPane({ focused, width, height }: PaneProps) {
177
194
  registrationId: "correlation-warnings", focused,
178
195
  notices: [...seriesBySymbol.values()].flatMap((series) => series.refreshError
179
196
  ? [`${series.symbol}: ${series.refreshError}${series.fetchedAt ? ` Retained history retrieved ${new Date(series.fetchedAt).toISOString()}.` : ""}`]
180
- : []),
197
+ : []).concat(pair && pairHistory?.unavailable ? [`${pair.join("/")}: ${pairHistory.unavailable}`] : []),
181
198
  });
182
199
 
183
200
  // The ticker set and range are visible in-pane and an invalid list shows in
@@ -189,7 +206,7 @@ function CorrelationMatrixPane({ focused, width, height }: PaneProps) {
189
206
  }), [settings.symbolsError, statusSummary, symbols.length]);
190
207
 
191
208
  const clearHoveredSymbol = useCallback((symbol: string) => {
192
- setHoveredSymbol((current) => (current === symbol ? null : current));
209
+ setCursorState((current) => current.hoveredSymbol === symbol ? { ...current, hoveredSymbol: null } : current);
193
210
  }, []);
194
211
 
195
212
  const headerBg = colors.panel;
@@ -203,6 +220,27 @@ function CorrelationMatrixPane({ focused, width, height }: PaneProps) {
203
220
  // Rows are exactly as wide as the matrix (after the one-cell inset), so the
204
221
  // zebra and hover bands stop at the last column instead of running on.
205
222
  const matrixRowWidth = 1 + rowHeaderWidth + symbols.length * cellWidth;
223
+ const chartRows = pair ? matrixChartRows(width, height, symbols.length, matrixRowWidth) : 0;
224
+ const matrixHeight = Math.max(1, height - 1 - (chartRows > 0 ? chartRows + 1 : 0));
225
+ const horizontalBarRows = matrixRowWidth > width - 1 ? 1 : 0;
226
+
227
+ // Scroll after React commits a movement, including several keys in one input batch.
228
+ useEffect(() => afterLayout(() => {
229
+ const scroll = matrixScrollRef.current;
230
+ if (scroll) {
231
+ const viewportHeight = Math.max(1, scroll.viewport?.height ?? 1);
232
+ if (cursor.row < scroll.scrollTop) scroll.scrollTo(cursor.row);
233
+ else if (cursor.row + 1 > scroll.scrollTop + viewportHeight) scroll.scrollTo(cursor.row + 1 - viewportHeight);
234
+ }
235
+ const horizontal = horizontalScrollRef.current;
236
+ if (!horizontal) return;
237
+ const left = cursor.column < 0 ? 0 : 1 + rowHeaderWidth + cursor.column * cellWidth;
238
+ const right = left + (cursor.column < 0 ? rowHeaderWidth : cellWidth);
239
+ const scrollLeft = horizontal.scrollLeft ?? 0;
240
+ const viewportWidth = horizontal.viewport?.width || width;
241
+ if (left < scrollLeft) horizontal.scrollTo({ x: left, y: 0 });
242
+ else if (right > scrollLeft + viewportWidth) horizontal.scrollTo({ x: right - viewportWidth, y: 0 });
243
+ }), [cursor.row, cursor.column, matrixHeight, rowHeaderWidth, cellWidth, width]);
206
244
  const bodyStatus = settings.symbolsError
207
245
  ? <PaneStatusBody error={settings.symbolsError} />
208
246
  : symbols.length < 2
@@ -210,7 +248,7 @@ function CorrelationMatrixPane({ focused, width, height }: PaneProps) {
210
248
  : null;
211
249
 
212
250
  return (
213
- <Box flexDirection="column" width={width} height={height}>
251
+ <Box flexDirection="column" width={width} height={height} overflow="hidden">
214
252
  <QueryBar
215
253
  key={queryBarRevision}
216
254
  width={width}
@@ -243,67 +281,88 @@ function CorrelationMatrixPane({ focused, width, height }: PaneProps) {
243
281
  />
244
282
  {bodyStatus ?? (
245
283
  <>
246
- {/* Column header row */}
247
- <Box flexDirection="row" paddingLeft={1} height={1} width={matrixRowWidth} backgroundColor={headerBg}>
248
- <Box width={rowHeaderWidth} flexShrink={0} />
249
- {symbols.map((sym) => (
250
- <SymbolLabelCell
251
- key={sym}
252
- symbol={sym}
253
- width={cellWidth}
254
- align="flex-end"
255
- color={colors.textDim}
256
- hovered={hoveredSymbol === sym}
257
- onHover={setHoveredSymbol}
258
- onLeave={clearHoveredSymbol}
259
- onOpen={openSymbol}
260
- />
261
- ))}
262
- </Box>
284
+ <ScrollBox ref={horizontalScrollRef} height={matrixHeight} flexShrink={1} minHeight={Math.min(matrixHeight, symbols.length + 1, 5)} scrollX scrollY={false} focusable={false}>
285
+ <Box flexDirection="column" width={Math.max(width - 1, matrixRowWidth)} height={matrixHeight - horizontalBarRows}>
286
+ {/* Column header row */}
287
+ <Box flexDirection="row" paddingLeft={1} height={1} flexShrink={0} width={matrixRowWidth} backgroundColor={headerBg}>
288
+ <Box width={rowHeaderWidth} flexShrink={0} />
289
+ {symbols.map((sym) => (
290
+ <SymbolLabelCell
291
+ key={sym}
292
+ symbol={sym}
293
+ width={cellWidth}
294
+ align="flex-end"
295
+ color={colors.textDim}
296
+ hovered={hoveredSymbol === sym || selection?.[1] === sym}
297
+ onHover={hoverSymbol}
298
+ onLeave={clearHoveredSymbol}
299
+ onOpen={openSymbol}
300
+ />
301
+ ))}
302
+ </Box>
263
303
 
264
- {/* Matrix rows */}
265
- <ScrollBox ref={matrixScrollRef} flexGrow={1} scrollY scrollX focusable={false}>
266
- <Box flexDirection="column">
267
- {symbols.map((rowSym, rowIndex) => (
268
- <Box key={rowSym} flexDirection="row" paddingLeft={1} width={matrixRowWidth} backgroundColor={rowIndex % 2 === 0 ? colors.bg : undefined}>
269
- {/* Row header */}
270
- <Box
271
- width={rowHeaderWidth}
272
- flexShrink={0}
273
- overflow="hidden"
274
- >
275
- <SymbolLabelCell
276
- symbol={rowSym}
277
- width={rowHeaderWidth}
278
- color={rowHeaderColor(seriesBySymbol.get(rowSym)?.status ?? "loading")}
279
- hovered={hoveredSymbol === rowSym}
280
- onHover={setHoveredSymbol}
281
- onLeave={clearHoveredSymbol}
282
- onOpen={openSymbol}
283
- />
284
- </Box>
285
- {/* Cells */}
286
- {symbols.map((colSym) => {
287
- const r = matrix.results.get(pairKey(rowSym, colSym))?.correlation ?? null;
288
- const cellColors = resolveHeatCellColors(r, { quiet: rowSym === colSym });
289
- const text = formatCorrelation(r);
290
- return (
304
+ {/* Matrix rows */}
305
+ <ScrollBox ref={matrixScrollRef} flexGrow={1} flexBasis={0} minHeight={0} scrollY scrollX={false} focusable={false}>
306
+ <Box flexDirection="column">
307
+ {symbols.map((rowSym, rowIndex) => (
308
+ <Box key={rowSym} flexDirection="row" paddingLeft={1} width={matrixRowWidth} backgroundColor={rowIndex % 2 === 0 ? colors.bg : undefined}>
309
+ {/* Row header */}
291
310
  <Box
292
- key={colSym}
293
- width={cellWidth}
294
- flexDirection="row"
295
- justifyContent="flex-end"
296
- paddingRight={1}
297
- backgroundColor={cellColors.background}
311
+ width={rowHeaderWidth}
312
+ flexShrink={0}
313
+ overflow="hidden"
298
314
  >
299
- <Text fg={cellColors.foreground}>{text}</Text>
315
+ <SymbolLabelCell
316
+ symbol={rowSym}
317
+ width={rowHeaderWidth}
318
+ color={rowHeaderColor(seriesBySymbol.get(rowSym)?.status ?? "loading")}
319
+ hovered={hoveredSymbol === rowSym || selection?.[0] === rowSym}
320
+ onHover={hoverSymbol}
321
+ onLeave={clearHoveredSymbol}
322
+ onOpen={openSymbol}
323
+ />
300
324
  </Box>
301
- );
302
- })}
325
+ {/* Cells */}
326
+ {symbols.map((colSym, colIndex) => {
327
+ const r = matrix.results.get(pairKey(rowSym, colSym))?.correlation ?? null;
328
+ const cellColors = resolveHeatCellColors(r, { quiet: rowSym === colSym });
329
+ const text = formatCorrelation(r);
330
+ const selected = cursor.row === rowIndex && cursor.column === colIndex;
331
+ return (
332
+ <Box
333
+ key={colSym}
334
+ width={cellWidth}
335
+ flexShrink={0}
336
+ flexDirection="row"
337
+ justifyContent="flex-end"
338
+ paddingRight={1}
339
+ backgroundColor={cellColors.background}
340
+ style={{ cursor: "pointer" }}
341
+ onMouseDown={(event: any) => {
342
+ event.preventDefault?.();
343
+ event.stopPropagation?.();
344
+ setCursorState({ cursor: { row: rowIndex, column: colIndex }, hoveredSymbol: null });
345
+ }}
346
+ >
347
+ <Text fg={cellColors.foreground} attributes={selected ? TextAttributes.BOLD | TextAttributes.UNDERLINE : TextAttributes.NONE}>{text}</Text>
348
+ </Box>
349
+ );
350
+ })}
351
+ </Box>
352
+ ))}
303
353
  </Box>
304
- ))}
354
+ </ScrollBox>
305
355
  </Box>
306
356
  </ScrollBox>
357
+ {chartRows > 0 && pair && pairHistory ? (
358
+ <MatrixPairChart
359
+ pair={pair}
360
+ history={pairHistory}
361
+ fullPeriod={matrix.results.get(pairKey(...pair))?.correlation ?? null}
362
+ width={width}
363
+ height={chartRows}
364
+ />
365
+ ) : null}
307
366
  </>
308
367
  )}
309
368
  </Box>
@@ -0,0 +1,48 @@
1
+ import { useMemo } from "react";
2
+ import { CompositeChart } from "../../../../components";
3
+ import { scalarPoint, staticSeries } from "../../../../components/chart/static/series";
4
+ import { colors } from "../../../../theme/colors";
5
+ import { Box, Text } from "../../../../ui";
6
+ import type { buildMatrixPairHistory } from "./selection";
7
+ import { matrixPairRead } from "./selection";
8
+
9
+ const PANELS = [{ id: "main" }];
10
+ const formatValue = (value: number) => value.toFixed(2);
11
+
12
+ export function MatrixPairChart({ pair, history, fullPeriod, width, height }: {
13
+ pair: [string, string];
14
+ history: ReturnType<typeof buildMatrixPairHistory>;
15
+ fullPeriod: number | null;
16
+ width: number;
17
+ height: number;
18
+ }) {
19
+ const series = useMemo(() => [
20
+ staticSeries(history.points.map(({ date, value }) => scalarPoint(date, value)), {
21
+ id: "rolling", label: `${pair[0]}/${pair[1]} (${history.window} day)`, color: "#f6c85f",
22
+ }),
23
+ staticSeries(history.points.map(({ date }) => scalarPoint(date, fullPeriod)), {
24
+ id: "full-period", label: "Full period", color: colors.textDim,
25
+ }),
26
+ ], [history, pair[0], pair[1], fullPeriod]);
27
+ return (
28
+ <Box flexDirection="column" flexGrow={1} flexBasis={0} minHeight={0} overflow="hidden">
29
+ <Box flexGrow={1} flexBasis={0} minHeight={0} overflow="hidden">
30
+ <CompositeChart
31
+ series={series}
32
+ panels={PANELS}
33
+ width={width}
34
+ height={height}
35
+ focused={false}
36
+ navigable={false}
37
+ showTimeAxis
38
+ formatAxisValue={formatValue}
39
+ formatValue={formatValue}
40
+ emptyMessage={history.unavailable ?? "No paired history"}
41
+ />
42
+ </Box>
43
+ <Box paddingX={1} height={1} flexShrink={0} overflow="hidden">
44
+ <Text fg={colors.textMuted} wrapMode="none">{matrixPairRead(pair, fullPeriod, history)}</Text>
45
+ </Box>
46
+ </Box>
47
+ );
48
+ }
@@ -0,0 +1,77 @@
1
+ import { chartTableLayout } from "../../../../components/chart-table/layout";
2
+ import { alignDailyCloses } from "../compute";
3
+ import { buildRelationshipReturns, buildRollingCorrelationPoints } from "../relationship/model";
4
+ import type { CorrelationRangePreset } from "../settings";
5
+ import type { CorrelationSeries } from "./model";
6
+
7
+ /** Column -1 is the existing symbol-row cursor, reachable from the first cell. */
8
+ export interface MatrixCursor { row: number; column: number }
9
+
10
+ export function clampMatrixCursor(cursor: MatrixCursor, count: number): MatrixCursor {
11
+ const last = Math.max(0, count - 1);
12
+ return {
13
+ row: Math.max(0, Math.min(last, cursor.row)),
14
+ column: Math.max(-1, Math.min(last, cursor.column)),
15
+ };
16
+ }
17
+
18
+ export function moveMatrixCursor(cursor: MatrixCursor, key: string, count: number): MatrixCursor {
19
+ const next = clampMatrixCursor(cursor, count);
20
+ if (key === "j" || key === "down") next.row++;
21
+ if (key === "k" || key === "up") next.row--;
22
+ if (key === "h" || key === "left") next.column--;
23
+ if (key === "l" || key === "right") next.column++;
24
+ return clampMatrixCursor(next, count);
25
+ }
26
+
27
+ export function matrixSelection(symbols: readonly string[], cursor: MatrixCursor): [string, string] | null {
28
+ if (!symbols.length) return null;
29
+ const { row, column } = clampMatrixCursor(cursor, symbols.length);
30
+ return [symbols[row]!, symbols[column < 0 ? row : column]!];
31
+ }
32
+
33
+ /** The kit budget includes the read line. No compact strip: the matrix wins. */
34
+ export function matrixChartRows(width: number, height: number, count: number, matrixWidth: number): number {
35
+ const layout = chartTableLayout({
36
+ width, height, queryRows: 1, tableRows: count,
37
+ tableChromeRows: matrixWidth > width - 1 ? 2 : 1,
38
+ chart: { minRows: 7, strip: false },
39
+ });
40
+ return layout.mode === "full" ? layout.chartRows - 1 : 0;
41
+ }
42
+
43
+ export function buildMatrixPairHistory(
44
+ left: CorrelationSeries | undefined,
45
+ right: CorrelationSeries | undefined,
46
+ range: CorrelationRangePreset,
47
+ ) {
48
+ const window = range === "1M" ? 20 : 60;
49
+ const aligned = alignDailyCloses(left?.prices ?? [], right?.prices ?? []).map((point) => ({
50
+ ...point, date: new Date(`${point.dateKey}T00:00:00Z`),
51
+ }));
52
+ const returns = buildRelationshipReturns(aligned);
53
+ const points = buildRollingCorrelationPoints(returns, window);
54
+ const byTime = new Map(points.map((point) => [point.date.getTime(), point.close]));
55
+ const values = points.map((point) => point.close);
56
+ const latest = byTime.get(returns.at(-1)?.date.getTime() ?? NaN) ?? null;
57
+ return {
58
+ window,
59
+ // Explicit gaps retain the warm-up period and zero-variance windows.
60
+ points: aligned.map(({ date }) => ({ date, value: byTime.get(date.getTime()) ?? null })),
61
+ latest,
62
+ min: values.length ? Math.min(...values) : null,
63
+ max: values.length ? Math.max(...values) : null,
64
+ unavailable: returns.length < window
65
+ ? `Rolling correlation needs ${window} shared returns; ${returns.length} available.`
66
+ : latest === null ? `Rolling correlation unavailable: zero return variance in the latest ${window} shared returns.` : null,
67
+ };
68
+ }
69
+
70
+ export function matrixPairRead(
71
+ pair: readonly [string, string],
72
+ fullPeriod: number | null,
73
+ history: Pick<ReturnType<typeof buildMatrixPairHistory>, "window" | "latest" | "min" | "max">,
74
+ ): string {
75
+ const value = (number: number | null) => number !== null && Number.isFinite(number) ? number.toFixed(2) : "—";
76
+ return `${pair[0]} and ${pair[1]}: full period ${value(fullPeriod)}, now ${value(history.latest)} (${history.window} day), range ${value(history.min)} to ${value(history.max)}`;
77
+ }
@@ -72,7 +72,7 @@ function alignRelationshipPrices(leftPoints: PricePoint[], rightPoints: PricePoi
72
72
  }));
73
73
  }
74
74
 
75
- function buildRelationshipReturns(aligned: RelationshipAlignedPoint[]): RelationshipReturnPoint[] {
75
+ export function buildRelationshipReturns(aligned: readonly Omit<RelationshipAlignedPoint, "ratio">[]): RelationshipReturnPoint[] {
76
76
  const returns: RelationshipReturnPoint[] = [];
77
77
  for (let index = 1; index < aligned.length; index++) {
78
78
  const previous = aligned[index - 1]!;
@@ -91,7 +91,7 @@ function buildRelationshipReturns(aligned: RelationshipAlignedPoint[]): Relation
91
91
  return returns;
92
92
  }
93
93
 
94
- function buildRollingCorrelationPoints(
94
+ export function buildRollingCorrelationPoints(
95
95
  returns: RelationshipReturnPoint[],
96
96
  windowSize: number,
97
97
  ): ProjectedChartPoint[] {
@@ -0,0 +1,66 @@
1
+ import { apiClient } from "../../../api-client";
2
+ import type { CpiBoardPayload, CpiRow } from "../../../api-client/cpi";
3
+ import { ApiRequestError } from "../../../api-client/errors";
4
+ import { createPluginCache } from "../../../data/plugin-cache";
5
+ import { cachedCloudResource, loadCloudResource } from "../shared/cloud-resource";
6
+
7
+ /** What the pane says while the server has no months to serve: before the backend ships, or before its first read. */
8
+ export const CPI_NOT_AVAILABLE = "US consumer prices are not available yet.";
9
+
10
+ export const cpiBoardCache = createPluginCache<CpiBoardPayload>({
11
+ kind: "cpi-board", source: "gloom-cloud", schemaVersion: 1,
12
+ // Monthly data: a quarter hour is soon enough to catch a release.
13
+ policy: { staleMs: 15 * 60_000, expireMs: 45 * 24 * 60 * 60_000 },
14
+ });
15
+
16
+ const month = (value: unknown) => typeof value === "string" && /^\d{4}-(0[1-9]|1[0-2])$/.test(value);
17
+ const monthOrNull = (value: unknown) => value === null || month(value);
18
+ const instantOrNull = (value: unknown) => value === null || typeof value === "string" && Number.isFinite(Date.parse(value));
19
+ const finite = (value: unknown): value is number => typeof value === "number" && Number.isFinite(value);
20
+ const finiteOrNull = (value: unknown) => value === null || finite(value);
21
+
22
+ function validRow(row: CpiRow): boolean {
23
+ return typeof row.id === "string" && typeof row.label === "string" && Number.isInteger(row.depth) && row.depth >= 0
24
+ && (row.code === null || typeof row.code === "string") && (row.parent === null || typeof row.parent === "string")
25
+ && typeof row.pinned === "boolean" && typeof row.derived === "boolean"
26
+ && [row.weight, row.change, row.annualized3m, row.annualized6m, row.yoy, row.contribution, row.contributionYoy].every(finiteOrNull)
27
+ && (row.read === null || typeof row.read === "string")
28
+ && Array.isArray(row.history) && row.history.every((point) => Array.isArray(point) && month(point[0])
29
+ && finiteOrNull(point[1]) && finiteOrNull(point[2]));
30
+ }
31
+
32
+ /** A board that breaks the contract is refused whole rather than drawn with holes in it. */
33
+ function validateCpiBoard(payload: CpiBoardPayload): CpiBoardPayload {
34
+ const release = payload?.release;
35
+ if (!payload || payload.source !== "BLS" || !Number.isFinite(Date.parse(payload.generatedAt))
36
+ || !instantOrNull(payload.retrievedAt ?? null)
37
+ || !["available", "partial", "unavailable"].includes(payload.status)
38
+ || !Array.isArray(payload.gaps) || !payload.gaps.every((gap) => typeof gap === "string")
39
+ || !release || !monthOrNull(release.period) || !monthOrNull(release.weightsPeriod) || !monthOrNull(release.nextPeriod)
40
+ || !instantOrNull(release.releasedAt) || !instantOrNull(release.nextReleaseAt)
41
+ || !Array.isArray(payload.rows) || new Set(payload.rows.map((row) => row.id)).size !== payload.rows.length
42
+ || !payload.rows.every(validRow)) {
43
+ throw new Error("The server returned an invalid consumer price board");
44
+ }
45
+ return payload;
46
+ }
47
+
48
+ /**
49
+ * The board, or the not-available message when the server does not serve the
50
+ * route yet (404), cannot read its tables yet (503) or has no months stored.
51
+ */
52
+ export async function fetchCpiBoard(client: Pick<typeof apiClient, "getCloudCpiBoard"> = apiClient): Promise<CpiBoardPayload> {
53
+ let payload: CpiBoardPayload;
54
+ try {
55
+ payload = await client.getCloudCpiBoard();
56
+ } catch (error) {
57
+ if (error instanceof ApiRequestError && (error.status === 404 || error.status === 503)) throw new Error(CPI_NOT_AVAILABLE);
58
+ throw error;
59
+ }
60
+ const board = validateCpiBoard(payload);
61
+ if (board.status === "unavailable" || !board.release.period || !board.rows.length) throw new Error(CPI_NOT_AVAILABLE);
62
+ return board;
63
+ }
64
+
65
+ export const getCachedCpiBoard = () => cachedCloudResource(cpiBoardCache, "board", validateCpiBoard);
66
+ export const loadCpiBoard = (force = false) => loadCloudResource(cpiBoardCache, "board", () => fetchCpiBoard(), { force, validate: validateCpiBoard });
@@ -0,0 +1,78 @@
1
+ import type { CpiRow } from "../../../api-client/cpi";
2
+ import type { HeadlessPaneDefinition } from "../../../types/plugin";
3
+ import { fetchCpiBoard } from "./client";
4
+ import { cpiHeaderLine, cpiMonth, cpiRowOption, cpiRows } from "./model";
5
+
6
+ /** Two decimals; a value that rounds to zero reads 0.00, never -0.00. */
7
+ const percent = (value: unknown) => typeof value === "number" ? (Number(value.toFixed(2)) === 0 ? "0.00" : value.toFixed(2)) : "--";
8
+ const weight = (value: unknown) => typeof value === "number" ? value.toFixed(3) : "--";
9
+ const text = (value: unknown) => typeof value === "string" ? value : "--";
10
+
11
+ /** One row per component in the release's order; numbers stay raw for JSON, rounded for text. */
12
+ function reportRow(row: CpiRow, indent = true) {
13
+ return {
14
+ id: row.id, component: `${indent ? " ".repeat(row.depth) : ""}${row.label}`, depth: row.depth, weight: row.weight, monthChange: row.change,
15
+ annualized3m: row.annualized3m, annualized6m: row.annualized6m, yearChange: row.yoy,
16
+ contributionMonth: row.contribution, contributionYear: row.contributionYoy, read: row.read,
17
+ };
18
+ }
19
+
20
+ const SUMMARY_COLUMNS = [
21
+ { key: "component", header: "Component" },
22
+ { key: "weight", header: "Weight %", align: "right" as const, format: weight },
23
+ { key: "monthChange", header: "M/M % SA", align: "right" as const, format: percent },
24
+ { key: "annualized3m", header: "3M ann %", align: "right" as const, format: percent },
25
+ { key: "annualized6m", header: "6M ann %", align: "right" as const, format: percent },
26
+ { key: "yearChange", header: "Y/Y % NSA", align: "right" as const, format: percent },
27
+ { key: "contributionMonth", header: "M/M pts", align: "right" as const, format: percent },
28
+ { key: "contributionYear", header: "Y/Y pts", align: "right" as const, format: percent },
29
+ ];
30
+
31
+ /** The selected row's months against the headline's year over year: the chart as a table. */
32
+ function historyRows(row: CpiRow, headline: CpiRow | null) {
33
+ const headlineYear = new Map((headline?.history ?? []).map(([month, , yoy]) => [month, yoy]));
34
+ return [...row.history].reverse().map(([month, change, yoy]) => ({
35
+ month, monthChange: change, yearChange: yoy, headlineYearChange: headlineYear.get(month) ?? null,
36
+ }));
37
+ }
38
+
39
+ /** `CPI` reads every component; a component argument adds its monthly history. */
40
+ export const cpiHeadless: HeadlessPaneDefinition<"bundle"> = {
41
+ shape: "bundle",
42
+ argument: { kind: "free-text", optional: true, placeholder: "component", description: "A component such as shelter, core, gasoline or supercore; omit for the table." },
43
+ options: [],
44
+ discovery: { aliases: ["CPI", "ECAN"], dataRequirements: ["Gloom Cloud US consumer price history"],
45
+ limitations: ["Monthly data, published by BLS at 08:30 ET on its release schedule",
46
+ "Weights and contributions are BLS's for the month; services ex shelter is computed from two BLS series",
47
+ "No October 2025 index was published, so changes that need it are empty"] },
48
+ describe: "US consumer prices by component",
49
+ async load(args) {
50
+ const data = await fetchCpiBoard();
51
+ const header = cpiHeaderLine(data.release) ?? "US consumer prices";
52
+ const headline = data.rows.find((row) => row.code === "SA0") ?? null;
53
+ const argument = Array.isArray(args.argument) ? args.argument.join(" ") : args.argument;
54
+ const metadata = { ...data, complete: data.status === "available" };
55
+ if (argument) {
56
+ const option = cpiRowOption(argument);
57
+ const row = option ? data.rows.find((entry) => entry.id === option.value) : null;
58
+ if (!row) throw new Error("Use a component such as shelter, core, gasoline or supercore");
59
+ return {
60
+ sections: [
61
+ { title: header, columns: [...SUMMARY_COLUMNS, { key: "read", header: "Read", format: text }], rows: [reportRow(row, false)] },
62
+ { title: `${row.label} by month to ${data.release.period ? cpiMonth(data.release.period) : "date"}`,
63
+ columns: [{ key: "month", header: "Month" }, { key: "monthChange", header: "M/M % SA", align: "right" as const, format: percent },
64
+ { key: "yearChange", header: "Y/Y % NSA", align: "right" as const, format: percent },
65
+ ...(headline && headline.id !== row.id ? [{ key: "headlineYearChange", header: "All items Y/Y %", align: "right" as const, format: percent }] : [])],
66
+ rows: historyRows(row, headline) },
67
+ ],
68
+ errors: data.gaps,
69
+ metadata: { ...metadata, rows: [row] },
70
+ };
71
+ }
72
+ return {
73
+ sections: [{ title: header, columns: SUMMARY_COLUMNS, rows: cpiRows(data).map((row) => reportRow(row)) }],
74
+ errors: data.gaps,
75
+ metadata,
76
+ };
77
+ },
78
+ };
@@ -0,0 +1,31 @@
1
+ import type { PaneTemplateCreateOptions, PaneTemplateInstanceConfig } from "../../../types/plugin";
2
+ import type { PluginModule } from "../plugin-module";
3
+ import { cpiBoardCache } from "./client";
4
+ import { cpiHeadless } from "./headless";
5
+ import { CPI_ROW_OPTIONS, cpiRowOption } from "./model";
6
+ import { CpiPane } from "./pane";
7
+
8
+ /** Opens on the table, or on the component typed after the mnemonic. */
9
+ function cpiInstance(options?: PaneTemplateCreateOptions): PaneTemplateInstanceConfig {
10
+ const input = options?.arg?.trim();
11
+ const row = cpiRowOption(input);
12
+ if (input && !row) throw new Error("Use a component such as shelter, core, gasoline or supercore");
13
+ return { placement: "floating", ...(row ? { params: { row: row.value } } : {}) };
14
+ }
15
+
16
+ export const cpiModule: PluginModule = {
17
+ panes: [{ id: "cpi", name: "US Consumer Prices", icon: "C", component: CpiPane,
18
+ defaultPosition: "right", defaultMode: "floating", defaultFloatingSize: { width: 100, height: 36 },
19
+ tableExport: true, headless: cpiHeadless }],
20
+ paneTemplates: [{
21
+ id: "cpi-pane", paneId: "cpi", label: "US Consumer Prices",
22
+ description: "The US consumer price index by component: weights, monthly, annualised and yearly changes, and contributions to the headline.",
23
+ keywords: ["cpi", "ecan", "inflation", "consumer prices", "bls", "core", "shelter", "supercore", "rent", "gasoline"],
24
+ shortcut: { prefix: "CPI", aliases: ["ECAN"], argKind: "text", argPlaceholder: "component", argOptional: true,
25
+ argOptions: () => CPI_ROW_OPTIONS.map(({ value, label }) => ({ value, label })) },
26
+ headless: cpiHeadless,
27
+ createInstance: (_context, options) => cpiInstance(options),
28
+ }],
29
+ setup(ctx) { cpiBoardCache.attach(ctx.persistence); },
30
+ dispose() { cpiBoardCache.reset(); },
31
+ };