gloomberb 0.15.7 → 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 (524) hide show
  1. package/README.md +11 -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 +192 -3
  12. package/src/api-client/distress.ts +164 -0
  13. package/src/api-client/doe.ts +75 -0
  14. package/src/api-client/equity-screener.ts +11 -0
  15. package/src/api-client/exposure.ts +202 -0
  16. package/src/api-client/gpu.ts +93 -0
  17. package/src/api-client/hiring.ts +110 -0
  18. package/src/api-client/index.ts +78 -3
  19. package/src/api-client/normalizers.ts +31 -1
  20. package/src/api-client/paths.ts +3 -0
  21. package/src/api-client/perps.ts +127 -0
  22. package/src/api-client/power.ts +148 -0
  23. package/src/api-client/request.ts +58 -1
  24. package/src/api-client/research-activity.ts +172 -13
  25. package/src/api-client/socket.ts +75 -27
  26. package/src/api-client/supply-chain-graph.ts +40 -0
  27. package/src/api-client/supply-chain.ts +98 -0
  28. package/src/api-client/telemetry.ts +29 -0
  29. package/src/api-client/themes.ts +28 -0
  30. package/src/api-client/types.ts +33 -1
  31. package/src/api-client/wall-experiments.ts +100 -0
  32. package/src/api-client/wall-summary.ts +49 -0
  33. package/src/api-client/web-experiments.ts +136 -0
  34. package/src/app/pane-runtime/plugin-bindings.ts +5 -0
  35. package/src/app/pane-runtime/ticker-open-runtime.ts +8 -2
  36. package/src/app/runtime/update.ts +13 -1
  37. package/src/cli/commands/system.ts +2 -0
  38. package/src/cli/desktop-pane-shot.ts +3 -4
  39. package/src/cli/pane-functions/data.ts +78 -0
  40. package/src/cli/pane-functions/financial-ratio-shot.ts +160 -0
  41. package/src/cli/pane-functions/function-help.ts +220 -8
  42. package/src/cli/pane-functions/headless.ts +4 -0
  43. package/src/cli/pane-functions/screenshot-evidence.ts +21 -0
  44. package/src/cli/pane-functions/screenshot.ts +49 -5
  45. package/src/components/chart/composite/composite-chart.tsx +15 -2
  46. package/src/components/chart/composite/format.ts +24 -11
  47. package/src/components/chart/composite/rasterizer.ts +67 -16
  48. package/src/components/chart/composite/scene.ts +53 -2
  49. package/src/components/chart/composite/session-shading.ts +3 -2
  50. package/src/components/chart/composite/text-renderer.ts +52 -1
  51. package/src/components/chart/composite/time-axis.ts +100 -30
  52. package/src/components/chart/composite/tools.ts +2 -1
  53. package/src/components/chart/composite/types.ts +4 -0
  54. package/src/components/chart-table/index.ts +1 -0
  55. package/src/components/chart-table/layout.ts +15 -0
  56. package/src/components/command-bar/action-runtime.ts +1 -0
  57. package/src/components/command-bar/commands/direct/index.ts +32 -0
  58. package/src/components/command-bar/commands/registry.ts +7 -0
  59. package/src/components/command-bar/pane-templates/items.ts +11 -0
  60. package/src/components/command-bar/pane-templates/workflow.ts +19 -4
  61. package/src/components/command-bar/routes/root/command-items.ts +6 -0
  62. package/src/components/command-bar/routes/root/provider-search.ts +2 -0
  63. package/src/components/command-bar/routes/root/results.ts +64 -0
  64. package/src/components/command-bar/routes/root/runtime.ts +8 -0
  65. package/src/components/command-bar/routes/ticker-search/results.ts +89 -24
  66. package/src/components/command-bar/routes/ticker-search/route.ts +2 -0
  67. package/src/components/command-bar/surface/index.tsx +15 -0
  68. package/src/components/command-bar/view-model.ts +3 -0
  69. package/src/components/command-bar/workflow/collection-actions.ts +9 -7
  70. package/src/components/command-bar/workflow/ops.ts +19 -0
  71. package/src/components/command-bar/workflow/tickers.ts +5 -0
  72. package/src/components/company-logo.tsx +15 -2
  73. package/src/components/data-table/table-pane.ts +2 -1
  74. package/src/components/index.ts +1 -0
  75. package/src/components/layout/detached-pane-shell.tsx +12 -1
  76. package/src/components/layout/missing-pane.tsx +87 -23
  77. package/src/components/layout/pane/footer/index.tsx +64 -18
  78. package/src/components/layout/pane/footer/model.ts +85 -0
  79. package/src/components/layout/pane/sidebar.tsx +19 -1
  80. package/src/components/layout/shell/index.tsx +38 -28
  81. package/src/components/layout/shell/pane/management-shortcuts.ts +13 -1
  82. package/src/components/layout/status-bar.tsx +93 -28
  83. package/src/components/layout/status-widget-space.ts +72 -0
  84. package/src/components/onboarding/first-run-workspace.ts +8 -2
  85. package/src/components/onboarding/onboarding-wizard.tsx +35 -16
  86. package/src/components/onboarding/portfolio-step/positions-panel.tsx +14 -5
  87. package/src/components/onboarding/wizard-model.ts +13 -5
  88. package/src/components/onboarding/wizard-positions.ts +56 -7
  89. package/src/components/price-performance.tsx +31 -34
  90. package/src/components/ui/data-table/index.tsx +11 -8
  91. package/src/components/ui/display.tsx +15 -6
  92. package/src/components/ui/fields.tsx +5 -0
  93. package/src/components/ui/figure.tsx +94 -0
  94. package/src/components/ui/header-case.ts +25 -3
  95. package/src/components/ui/icon.tsx +3 -1
  96. package/src/components/ui/message-composer.tsx +8 -1
  97. package/src/components/ui/query-bar.tsx +8 -1
  98. package/src/components/ui/ratio-bar.tsx +84 -0
  99. package/src/components/ui/toggle.tsx +3 -1
  100. package/src/components/use-scrollbox-layout.ts +17 -0
  101. package/src/core/state/app/layout.ts +35 -1
  102. package/src/core/state/app/state.ts +15 -0
  103. package/src/core/state/app/types.ts +1 -0
  104. package/src/data/config/store/normalize.ts +24 -0
  105. package/src/i18n/es.ts +2 -0
  106. package/src/i18n/ja.ts +2 -0
  107. package/src/i18n/ko.ts +2 -0
  108. package/src/i18n/zh-cn.ts +2 -0
  109. package/src/i18n/zh-tw.ts +2 -0
  110. package/src/layout-marketplace/pane-imagery.ts +1 -0
  111. package/src/market-data/coordinator/entries.ts +13 -0
  112. package/src/market-data/coordinator/financials.ts +33 -8
  113. package/src/market-data/market/freshness.ts +16 -5
  114. package/src/market-data/market/trading-sessions.ts +65 -1
  115. package/src/market-data/published-cn-sessions.ts +25 -0
  116. package/src/market-data/published-nse-sessions.ts +20 -0
  117. package/src/news/aggregator.ts +4 -0
  118. package/src/plugins/builtin/account-management/model.ts +6 -1
  119. package/src/plugins/builtin/account-management/pane.tsx +36 -25
  120. package/src/plugins/builtin/alerts/catalysts.ts +77 -0
  121. package/src/plugins/builtin/alerts/events-pane.tsx +3 -0
  122. package/src/plugins/builtin/alerts/events.ts +10 -4
  123. package/src/plugins/builtin/alerts/history-pane.tsx +1 -1
  124. package/src/plugins/builtin/alerts/history.ts +1 -0
  125. package/src/plugins/builtin/alerts/index.tsx +6 -0
  126. package/src/plugins/builtin/alerts/research-builder.ts +10 -3
  127. package/src/plugins/builtin/alerts/research-rules.ts +15 -1
  128. package/src/plugins/builtin/analytics/risk-client.ts +15 -3
  129. package/src/plugins/builtin/analytics/risk-headless.ts +123 -18
  130. package/src/plugins/builtin/attention/client.ts +80 -0
  131. package/src/plugins/builtin/attention/evidence.ts +30 -0
  132. package/src/plugins/builtin/attention/headless.ts +33 -0
  133. package/src/plugins/builtin/attention/index.tsx +18 -0
  134. package/src/plugins/builtin/attention/model.ts +59 -0
  135. package/src/plugins/builtin/attention/pane.tsx +211 -0
  136. package/src/plugins/builtin/attention/trending.tsx +59 -0
  137. package/src/plugins/builtin/awards/alerts.ts +32 -0
  138. package/src/plugins/builtin/awards/client.ts +82 -0
  139. package/src/plugins/builtin/awards/detail.tsx +107 -0
  140. package/src/plugins/builtin/awards/evidence.ts +36 -0
  141. package/src/plugins/builtin/awards/headless.ts +55 -0
  142. package/src/plugins/builtin/awards/index.tsx +20 -0
  143. package/src/plugins/builtin/awards/model.ts +42 -0
  144. package/src/plugins/builtin/awards/pane.tsx +263 -0
  145. package/src/plugins/builtin/catalysts/client.ts +28 -0
  146. package/src/plugins/builtin/catalysts/detail.tsx +88 -0
  147. package/src/plugins/builtin/catalysts/evidence.ts +47 -0
  148. package/src/plugins/builtin/catalysts/headless.ts +42 -0
  149. package/src/plugins/builtin/catalysts/history.ts +26 -0
  150. package/src/plugins/builtin/catalysts/index.tsx +29 -0
  151. package/src/plugins/builtin/catalysts/model.ts +135 -0
  152. package/src/plugins/builtin/catalysts/pane.tsx +159 -0
  153. package/src/plugins/builtin/chart-composer/cli-options.ts +4 -0
  154. package/src/plugins/builtin/chart-composer/headless-schema.ts +6 -0
  155. package/src/plugins/builtin/chart-composer/index.tsx +8 -1
  156. package/src/plugins/builtin/chart-composer/pane.tsx +28 -1
  157. package/src/plugins/builtin/chat/attachments/desktop.tsx +610 -0
  158. package/src/plugins/builtin/chat/attachments/files.ts +45 -0
  159. package/src/plugins/builtin/chat/attachments/model.ts +164 -0
  160. package/src/plugins/builtin/chat/attachments/terminal.tsx +72 -0
  161. package/src/plugins/builtin/chat/attachments/transfer.ts +165 -0
  162. package/src/plugins/builtin/chat/content/channel-navigation.ts +13 -12
  163. package/src/plugins/builtin/chat/content/composer-runtime.ts +25 -4
  164. package/src/plugins/builtin/chat/content/composer.tsx +46 -4
  165. package/src/plugins/builtin/chat/content/footer.tsx +46 -8
  166. package/src/plugins/builtin/chat/content/index.tsx +263 -62
  167. package/src/plugins/builtin/chat/content/layout-metrics.ts +10 -2
  168. package/src/plugins/builtin/chat/content/open-targets.ts +17 -3
  169. package/src/plugins/builtin/chat/content/scroll.ts +7 -1
  170. package/src/plugins/builtin/chat/content/shortcuts.ts +36 -11
  171. package/src/plugins/builtin/chat/content/snapshot.ts +3 -0
  172. package/src/plugins/builtin/chat/content/transcript.tsx +17 -1
  173. package/src/plugins/builtin/chat/content/types.ts +7 -0
  174. package/src/plugins/builtin/chat/controller/attachments.ts +213 -0
  175. package/src/plugins/builtin/chat/controller/connections.ts +3 -1
  176. package/src/plugins/builtin/chat/controller/index.ts +114 -2
  177. package/src/plugins/builtin/chat/controller/lifecycle.ts +9 -0
  178. package/src/plugins/builtin/chat/controller/messages.ts +35 -2
  179. package/src/plugins/builtin/chat/controller/send.ts +128 -25
  180. package/src/plugins/builtin/chat/controller/state.ts +12 -1
  181. package/src/plugins/builtin/chat/controller/utils.ts +7 -5
  182. package/src/plugins/builtin/chat/controller/view.ts +2 -0
  183. package/src/plugins/builtin/chat/index.tsx +21 -0
  184. package/src/plugins/builtin/chat/layout.ts +9 -4
  185. package/src/plugins/builtin/chat/message/desktop.tsx +55 -20
  186. package/src/plugins/builtin/chat/message/header.tsx +30 -5
  187. package/src/plugins/builtin/chat/message/inline-tokens.tsx +9 -0
  188. package/src/plugins/builtin/chat/message/pending-send.ts +35 -0
  189. package/src/plugins/builtin/chat/message/profile-popover.tsx +5 -1
  190. package/src/plugins/builtin/chat/message/render-state.ts +12 -2
  191. package/src/plugins/builtin/chat/message/terminal.tsx +51 -4
  192. package/src/plugins/builtin/chat/message/types.ts +6 -1
  193. package/src/plugins/builtin/chat/pane-state.ts +59 -1
  194. package/src/plugins/builtin/chat/profile-popover.ts +50 -1
  195. package/src/plugins/builtin/chat/sidebar.tsx +8 -3
  196. package/src/plugins/builtin/chat/status-widget.tsx +29 -12
  197. package/src/plugins/builtin/chat/unread-inbox-pane.tsx +143 -0
  198. package/src/plugins/builtin/chat/unread-inbox.ts +91 -0
  199. package/src/plugins/builtin/clinical-trials/client.ts +237 -0
  200. package/src/plugins/builtin/clinical-trials/index.tsx +74 -0
  201. package/src/plugins/builtin/clinical-trials/pane.tsx +225 -0
  202. package/src/plugins/builtin/clinical-trials/types.ts +2 -0
  203. package/src/plugins/builtin/cloud/askg/controller.ts +37 -6
  204. package/src/plugins/builtin/cloud/askg/executor.ts +59 -44
  205. package/src/plugins/builtin/cloud/askg/host.ts +8 -1
  206. package/src/plugins/builtin/cloud/askg/manifest.ts +29 -2
  207. package/src/plugins/builtin/cloud/askg/model.ts +47 -13
  208. package/src/plugins/builtin/cloud/askg/notes.ts +100 -0
  209. package/src/plugins/builtin/cloud/askg/pane.tsx +50 -14
  210. package/src/plugins/builtin/cloud/askg/protocol.ts +39 -1
  211. package/src/plugins/builtin/cloud/askg/result-budget.ts +156 -0
  212. package/src/plugins/builtin/cloud/askg/user-data.ts +89 -0
  213. package/src/plugins/builtin/cloud/auth-actions.tsx +55 -31
  214. package/src/plugins/builtin/cloud/plugin.tsx +4 -1
  215. package/src/plugins/builtin/cloud/team/pane-sections.tsx +10 -1
  216. package/src/plugins/builtin/cloud/team/pane.tsx +3 -2
  217. package/src/plugins/builtin/cloud/thesis/board-pane.tsx +2 -2
  218. package/src/plugins/builtin/cloud/thesis/detail.tsx +1 -1
  219. package/src/plugins/builtin/cloud/thesis/ticker-tab.tsx +2 -2
  220. package/src/plugins/builtin/cloud/trial-offer-status-widget.tsx +202 -0
  221. package/src/plugins/builtin/cloud/upgrade-command.ts +1 -1
  222. package/src/plugins/builtin/cloud/upgrade-dialog.tsx +29 -3
  223. package/src/plugins/builtin/cloud/upgrade-status-widget.tsx +1 -1
  224. package/src/plugins/builtin/cloud-tweets/table.tsx +1 -1
  225. package/src/plugins/builtin/comment-letters/client.ts +342 -0
  226. package/src/plugins/builtin/comment-letters/index.tsx +74 -0
  227. package/src/plugins/builtin/comment-letters/pane.tsx +295 -0
  228. package/src/plugins/builtin/comment-letters/types.ts +43 -0
  229. package/src/plugins/builtin/company-attention/app-rank.tsx +79 -0
  230. package/src/plugins/builtin/company-attention/client.ts +69 -0
  231. package/src/plugins/builtin/company-attention/evidence.ts +45 -0
  232. package/src/plugins/builtin/company-attention/headless.ts +47 -0
  233. package/src/plugins/builtin/company-attention/index.tsx +30 -0
  234. package/src/plugins/builtin/company-attention/model.ts +129 -0
  235. package/src/plugins/builtin/company-attention/pane.tsx +183 -0
  236. package/src/plugins/builtin/company-kpis/client.ts +92 -0
  237. package/src/plugins/builtin/company-kpis/headless.ts +32 -0
  238. package/src/plugins/builtin/company-kpis/index.tsx +26 -0
  239. package/src/plugins/builtin/company-kpis/model.ts +124 -0
  240. package/src/plugins/builtin/company-kpis/pane.tsx +226 -0
  241. package/src/plugins/builtin/company-kpis/related.ts +11 -0
  242. package/src/plugins/builtin/company-kpis/screenshot-evidence.ts +44 -0
  243. package/src/plugins/builtin/composite-plugins.ts +34 -0
  244. package/src/plugins/builtin/correlation/history.ts +4 -1
  245. package/src/plugins/builtin/correlation/index.tsx +139 -80
  246. package/src/plugins/builtin/correlation/matrix/pair-chart.tsx +48 -0
  247. package/src/plugins/builtin/correlation/matrix/selection.ts +77 -0
  248. package/src/plugins/builtin/correlation/relationship/model.ts +2 -2
  249. package/src/plugins/builtin/cpi/client.ts +66 -0
  250. package/src/plugins/builtin/cpi/headless.ts +78 -0
  251. package/src/plugins/builtin/cpi/index.tsx +31 -0
  252. package/src/plugins/builtin/cpi/model.ts +198 -0
  253. package/src/plugins/builtin/cpi/pane.tsx +157 -0
  254. package/src/plugins/builtin/credit-documents/client.ts +99 -0
  255. package/src/plugins/builtin/credit-documents/detail.tsx +109 -0
  256. package/src/plugins/builtin/credit-documents/evidence.ts +64 -0
  257. package/src/plugins/builtin/credit-documents/gauge.tsx +55 -0
  258. package/src/plugins/builtin/credit-documents/headless.ts +40 -0
  259. package/src/plugins/builtin/credit-documents/index.tsx +19 -0
  260. package/src/plugins/builtin/credit-documents/model.ts +336 -0
  261. package/src/plugins/builtin/credit-documents/pane.tsx +185 -0
  262. package/src/plugins/builtin/custom-view/view-spec.ts +14 -2
  263. package/src/plugins/builtin/debt-maturities/pane.tsx +1 -0
  264. package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
  265. package/src/plugins/builtin/doe/client.ts +73 -0
  266. package/src/plugins/builtin/doe/headless.ts +93 -0
  267. package/src/plugins/builtin/doe/index.tsx +41 -0
  268. package/src/plugins/builtin/doe/model.ts +215 -0
  269. package/src/plugins/builtin/doe/pane.tsx +148 -0
  270. package/src/plugins/builtin/doe/range-bar.tsx +45 -0
  271. package/src/plugins/builtin/earnings/board.tsx +3 -0
  272. package/src/plugins/builtin/earnings/index.tsx +2 -0
  273. package/src/plugins/builtin/earnings-calls/pane.tsx +7 -1
  274. package/src/plugins/builtin/earnings-calls/transcript-view.tsx +7 -7
  275. package/src/plugins/builtin/earnings-ripple/client.ts +60 -0
  276. package/src/plugins/builtin/earnings-ripple/headless.ts +35 -0
  277. package/src/plugins/builtin/earnings-ripple/index.tsx +35 -0
  278. package/src/plugins/builtin/earnings-ripple/model.ts +98 -0
  279. package/src/plugins/builtin/earnings-ripple/pane.tsx +103 -0
  280. package/src/plugins/builtin/equity-screener/client.ts +1 -1
  281. package/src/plugins/builtin/equity-screener/criterion-editor.tsx +1 -1
  282. package/src/plugins/builtin/equity-screener/model.ts +74 -9
  283. package/src/plugins/builtin/equity-screener/pane.tsx +2 -0
  284. package/src/plugins/builtin/estimate-revisions/pane.tsx +3 -0
  285. package/src/plugins/builtin/executives/pane.tsx +7 -5
  286. package/src/plugins/builtin/exposure/charts.tsx +66 -0
  287. package/src/plugins/builtin/exposure/client.ts +53 -0
  288. package/src/plugins/builtin/exposure/drivers.ts +29 -0
  289. package/src/plugins/builtin/exposure/editor.tsx +61 -0
  290. package/src/plugins/builtin/exposure/evidence.ts +39 -0
  291. package/src/plugins/builtin/exposure/headless.ts +51 -0
  292. package/src/plugins/builtin/exposure/holdings.ts +56 -0
  293. package/src/plugins/builtin/exposure/index.tsx +13 -0
  294. package/src/plugins/builtin/exposure/model.ts +104 -0
  295. package/src/plugins/builtin/exposure/pane.tsx +247 -0
  296. package/src/plugins/builtin/fear-greed/charts.tsx +1 -1
  297. package/src/plugins/builtin/fear-greed/pane.tsx +2 -9
  298. package/src/plugins/builtin/filing-events/pane.tsx +2 -1
  299. package/src/plugins/builtin/gpu/board.tsx +163 -0
  300. package/src/plugins/builtin/gpu/changes.tsx +94 -0
  301. package/src/plugins/builtin/gpu/client.ts +130 -0
  302. package/src/plugins/builtin/gpu/equities.tsx +112 -0
  303. package/src/plugins/builtin/gpu/headless.ts +67 -0
  304. package/src/plugins/builtin/gpu/history.tsx +140 -0
  305. package/src/plugins/builtin/gpu/index.tsx +18 -0
  306. package/src/plugins/builtin/gpu/ladder.tsx +127 -0
  307. package/src/plugins/builtin/gpu/model.ts +317 -0
  308. package/src/plugins/builtin/gpu/pane.tsx +63 -0
  309. package/src/plugins/builtin/help/index.tsx +19 -2
  310. package/src/plugins/builtin/holders/pane.tsx +5 -9
  311. package/src/plugins/builtin/insider/detail.tsx +119 -0
  312. package/src/plugins/builtin/insider/display.ts +221 -0
  313. package/src/plugins/builtin/insider/headless.ts +24 -3
  314. package/src/plugins/builtin/insider/index.tsx +206 -108
  315. package/src/plugins/builtin/insider/insider-data.ts +31 -3
  316. package/src/plugins/builtin/insider/model.ts +53 -4
  317. package/src/plugins/builtin/insider/table-model.ts +226 -0
  318. package/src/plugins/builtin/insider/table.tsx +79 -0
  319. package/src/plugins/builtin/iv-history/screen-pane.tsx +1 -1
  320. package/src/plugins/builtin/jobs/pane.tsx +14 -7
  321. package/src/plugins/builtin/kelly-sizer/pane.tsx +1 -1
  322. package/src/plugins/builtin/macro-day/client.ts +50 -0
  323. package/src/plugins/builtin/macro-day/headless.ts +51 -0
  324. package/src/plugins/builtin/macro-day/index.tsx +21 -0
  325. package/src/plugins/builtin/macro-day/model.ts +137 -0
  326. package/src/plugins/builtin/macro-day/pane.tsx +118 -0
  327. package/src/plugins/builtin/macro-day/releases.ts +58 -0
  328. package/src/plugins/builtin/market-halts/model.ts +30 -17
  329. package/src/plugins/builtin/market-halts/pane.tsx +15 -21
  330. package/src/plugins/builtin/market-heatmap/data.ts +1 -1
  331. package/src/plugins/builtin/market-heatmap/index.tsx +198 -50
  332. package/src/plugins/builtin/market-heatmap/portfolio.ts +220 -0
  333. package/src/plugins/builtin/market-movers/index.tsx +1 -0
  334. package/src/plugins/builtin/market-movers/session-body.tsx +5 -2
  335. package/src/plugins/builtin/mna/distress/client.ts +326 -0
  336. package/src/plugins/builtin/mna/distress/detail.tsx +247 -0
  337. package/src/plugins/builtin/mna/distress/headless.ts +257 -0
  338. package/src/plugins/builtin/mna/distress/model.ts +427 -0
  339. package/src/plugins/builtin/mna/distress/pane.tsx +606 -0
  340. package/src/plugins/builtin/mna/index.tsx +15 -0
  341. package/src/plugins/builtin/mna/pane.tsx +42 -1
  342. package/src/plugins/builtin/news/index.tsx +62 -16
  343. package/src/plugins/builtin/news/wire/filter-articles.ts +54 -0
  344. package/src/plugins/builtin/news/wire/index.tsx +37 -3
  345. package/src/plugins/builtin/news/wire/industry-pane.tsx +29 -13
  346. package/src/plugins/builtin/news/wire/mutes.ts +253 -0
  347. package/src/plugins/builtin/news/wire/news/command-bar-search.ts +117 -0
  348. package/src/plugins/builtin/news/wire/news/footer.ts +4 -0
  349. package/src/plugins/builtin/news/wire/news/list-search.ts +34 -0
  350. package/src/plugins/builtin/news/wire/news/pop-out.tsx +158 -0
  351. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +64 -19
  352. package/src/plugins/builtin/openfda/client.ts +300 -0
  353. package/src/plugins/builtin/openfda/index.tsx +71 -0
  354. package/src/plugins/builtin/openfda/pane.tsx +382 -0
  355. package/src/plugins/builtin/openfda/types.ts +76 -0
  356. package/src/plugins/builtin/options/footer.ts +1 -0
  357. package/src/plugins/builtin/options/view.tsx +1 -1
  358. package/src/plugins/builtin/options-calculator/pane.tsx +0 -1
  359. package/src/plugins/builtin/options-positioning/index.tsx +4 -2
  360. package/src/plugins/builtin/options-positioning/pane.tsx +1 -1
  361. package/src/plugins/builtin/pe-band/client.ts +61 -0
  362. package/src/plugins/builtin/pe-band/headless.ts +51 -0
  363. package/src/plugins/builtin/pe-band/index.tsx +20 -0
  364. package/src/plugins/builtin/pe-band/model.ts +267 -0
  365. package/src/plugins/builtin/pe-band/pane.tsx +159 -0
  366. package/src/plugins/builtin/perps/client.ts +66 -0
  367. package/src/plugins/builtin/perps/equity-row.tsx +28 -0
  368. package/src/plugins/builtin/perps/headless.ts +34 -0
  369. package/src/plugins/builtin/perps/index.tsx +14 -0
  370. package/src/plugins/builtin/perps/model.ts +58 -0
  371. package/src/plugins/builtin/perps/pane.tsx +185 -0
  372. package/src/plugins/builtin/plugin-marketplace/pane.tsx +0 -1
  373. package/src/plugins/builtin/portfolio-list/cli/render.ts +177 -87
  374. package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +6 -20
  375. package/src/plugins/builtin/portfolio-list/command-bar.ts +6 -2
  376. package/src/plugins/builtin/portfolio-list/headless.ts +237 -0
  377. package/src/plugins/builtin/portfolio-list/index.tsx +2 -0
  378. package/src/plugins/builtin/portfolio-list/mutations.ts +70 -1
  379. package/src/plugins/builtin/portfolio-list/pane/data.ts +17 -0
  380. package/src/plugins/builtin/portfolio-list/pane/index.tsx +88 -5
  381. package/src/plugins/builtin/portfolio-list/pane/remove-ticker.ts +85 -0
  382. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +18 -12
  383. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +6 -0
  384. package/src/plugins/builtin/power/client.ts +100 -0
  385. package/src/plugins/builtin/power/detail.tsx +82 -0
  386. package/src/plugins/builtin/power/evidence.ts +56 -0
  387. package/src/plugins/builtin/power/generation.tsx +37 -0
  388. package/src/plugins/builtin/power/headless.ts +43 -0
  389. package/src/plugins/builtin/power/index.tsx +16 -0
  390. package/src/plugins/builtin/power/model.ts +89 -0
  391. package/src/plugins/builtin/power/pane.tsx +209 -0
  392. package/src/plugins/builtin/power/query.ts +25 -0
  393. package/src/plugins/builtin/relative-rotation/pane.tsx +3 -3
  394. package/src/plugins/builtin/research/analyst-pane.tsx +1 -1
  395. package/src/plugins/builtin/research/corporate-actions-pane.tsx +13 -4
  396. package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +11 -9
  397. package/src/plugins/builtin/research-search/pane.tsx +21 -7
  398. package/src/plugins/builtin/revenue-breakdown/pane.tsx +1 -1
  399. package/src/plugins/builtin/reverse-dcf/client.ts +35 -0
  400. package/src/plugins/builtin/reverse-dcf/headless.ts +44 -0
  401. package/src/plugins/builtin/reverse-dcf/index.tsx +20 -0
  402. package/src/plugins/builtin/reverse-dcf/model.ts +87 -0
  403. package/src/plugins/builtin/reverse-dcf/pane.tsx +107 -0
  404. package/src/plugins/builtin/risk-factors/pane.tsx +6 -5
  405. package/src/plugins/builtin/scanner/denied.tsx +4 -2
  406. package/src/plugins/builtin/scanner/feed.ts +1 -0
  407. package/src/plugins/builtin/scanner/flow-pane.tsx +3 -2
  408. package/src/plugins/builtin/scanner/hilo-pane.tsx +1 -1
  409. package/src/plugins/builtin/seasonality/client.ts +4 -1
  410. package/src/plugins/builtin/sec/command-bar-search.ts +217 -0
  411. package/src/plugins/builtin/sec/forms.ts +54 -0
  412. package/src/plugins/builtin/sec/headless.ts +31 -5
  413. package/src/plugins/builtin/sec/index.tsx +67 -11
  414. package/src/plugins/builtin/sec/model.ts +35 -5
  415. package/src/plugins/builtin/sectors/headless.ts +1 -0
  416. package/src/plugins/builtin/sectors/index.tsx +72 -26
  417. package/src/plugins/builtin/shared/cloud-upgrade.ts +143 -39
  418. package/src/plugins/builtin/shared/pro-feature-wall.tsx +8 -4
  419. package/src/plugins/builtin/shared/research-cells.tsx +107 -0
  420. package/src/plugins/builtin/shared/series-list-detail.tsx +5 -1
  421. package/src/plugins/builtin/shared/ticker-request.ts +1 -1
  422. package/src/plugins/builtin/shared/wall-teaser-catalog.ts +80 -0
  423. package/src/plugins/builtin/shared/wall-teaser.tsx +192 -0
  424. package/src/plugins/builtin/shared/wall-view-lifecycle.ts +43 -0
  425. package/src/plugins/builtin/short-interest/index.tsx +31 -0
  426. package/src/plugins/builtin/short-interest/watch-client.ts +70 -0
  427. package/src/plugins/builtin/short-interest/watch-headless.ts +49 -0
  428. package/src/plugins/builtin/short-interest/watch-model.ts +185 -0
  429. package/src/plugins/builtin/short-interest/watch-pane.tsx +169 -0
  430. package/src/plugins/builtin/short-volume/pane.tsx +1 -1
  431. package/src/plugins/builtin/social-mentions/pane.tsx +1 -1
  432. package/src/plugins/builtin/supply-chain/client.ts +104 -0
  433. package/src/plugins/builtin/supply-chain/disclosure-quote.tsx +17 -0
  434. package/src/plugins/builtin/supply-chain/evidence.ts +88 -0
  435. package/src/plugins/builtin/supply-chain/flow-layout.ts +133 -0
  436. package/src/plugins/builtin/supply-chain/flow.tsx +323 -0
  437. package/src/plugins/builtin/supply-chain/graph-client.ts +83 -0
  438. package/src/plugins/builtin/supply-chain/graph-evidence.ts +25 -0
  439. package/src/plugins/builtin/supply-chain/graph-model.ts +45 -0
  440. package/src/plugins/builtin/supply-chain/graph-pane.tsx +218 -0
  441. package/src/plugins/builtin/supply-chain/graph.tsx +163 -0
  442. package/src/plugins/builtin/supply-chain/headless.ts +105 -0
  443. package/src/plugins/builtin/supply-chain/index.tsx +17 -0
  444. package/src/plugins/builtin/supply-chain/model.ts +145 -0
  445. package/src/plugins/builtin/supply-chain/pane.tsx +276 -0
  446. package/src/plugins/builtin/supply-chain/row-evidence.tsx +96 -0
  447. package/src/plugins/builtin/supply-chain/trust.ts +34 -0
  448. package/src/plugins/builtin/themes/client.ts +39 -0
  449. package/src/plugins/builtin/themes/headless.ts +38 -0
  450. package/src/plugins/builtin/themes/model.ts +92 -0
  451. package/src/plugins/builtin/themes/pane.tsx +125 -0
  452. package/src/plugins/builtin/thirteenf/pane.tsx +6 -15
  453. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +5 -6
  454. package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +201 -0
  455. package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +327 -0
  456. package/src/plugins/builtin/ticker-detail/financials/period-end-history.ts +5 -2
  457. package/src/plugins/builtin/ticker-detail/financials/ratios.ts +60 -12
  458. package/src/plugins/builtin/ticker-detail/financials/tab.tsx +2 -1
  459. package/src/plugins/builtin/ticker-detail/headless.ts +7 -2
  460. package/src/plugins/builtin/ticker-detail/index.tsx +22 -0
  461. package/src/plugins/builtin/ticker-detail/overview/components.tsx +53 -30
  462. package/src/plugins/builtin/ticker-detail/overview/pane.tsx +2 -0
  463. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +112 -47
  464. package/src/plugins/builtin/ticker-detail/pane.tsx +50 -6
  465. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +21 -42
  466. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +3 -0
  467. package/src/plugins/builtin/time-sales/pane.tsx +1 -1
  468. package/src/plugins/builtin/world-indices/indices.ts +1 -0
  469. package/src/plugins/catalog-browser.ts +40 -0
  470. package/src/plugins/catalog-manifest.ts +3 -0
  471. package/src/plugins/catalog-ui.ts +6 -0
  472. package/src/plugins/current-target.ts +0 -9
  473. package/src/public/__snapshots__/public-api.test.ts.snap +1 -0
  474. package/src/renderers/browser/cloud-transport.ts +66 -1
  475. package/src/renderers/browser/main.tsx +2 -0
  476. package/src/renderers/dom/data-table/row.tsx +2 -0
  477. package/src/renderers/dom/desktop/controls.tsx +38 -25
  478. package/src/renderers/dom/desktop/icons.tsx +8 -0
  479. package/src/renderers/dom/host/chart-surface.tsx +36 -2
  480. package/src/renderers/dom/host/focus-scope.ts +11 -1
  481. package/src/renderers/dom/styles.css +28 -0
  482. package/src/renderers/electrobun/view/backend-rpc.ts +3 -2
  483. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -1
  484. package/src/renderers/electrobun/view/http-fetch.ts +2 -0
  485. package/src/renderers/electrobun/view/main.tsx +3 -1
  486. package/src/renderers/electrobun/view/rpc-timeout.ts +59 -0
  487. package/src/renderers/opentui/start.tsx +3 -0
  488. package/src/shares/api.ts +3 -1
  489. package/src/sources/gloomberb-cloud/index.ts +47 -13
  490. package/src/sources/history-result.ts +1 -0
  491. package/src/sources/history-retention.ts +16 -0
  492. package/src/sources/provider-router/history.ts +4 -2
  493. package/src/sources/sec-edgar/identity.ts +36 -0
  494. package/src/sources/sec-edgar.ts +1 -34
  495. package/src/state/app/context/index.tsx +8 -0
  496. package/src/sync/core-contributors.ts +20 -2
  497. package/src/telemetry/attention-counts.ts +164 -0
  498. package/src/telemetry/live-config.ts +7 -0
  499. package/src/test-support/rpc-loopback.ts +7 -3
  500. package/src/test-support/shell-pane-keys.tsx +34 -0
  501. package/src/tickers/instrument-kind.ts +12 -6
  502. package/src/tickers/open-target.ts +1 -1
  503. package/src/tickers/search/asset-classes.ts +91 -0
  504. package/src/tickers/search/index.ts +62 -15
  505. package/src/tickers/search/ranking.ts +22 -3
  506. package/src/tickers/search/upsert.ts +4 -1
  507. package/src/time-series/live-bars.ts +8 -3
  508. package/src/time-series/resolve.ts +224 -17
  509. package/src/time-series/spec.ts +1 -0
  510. package/src/time-series/types.ts +9 -1
  511. package/src/types/config.ts +23 -1
  512. package/src/types/data-provider.ts +2 -0
  513. package/src/types/headless.ts +17 -0
  514. package/src/types/price-history.ts +2 -0
  515. package/src/ui/host.tsx +6 -0
  516. package/src/utils/double-escape-close.ts +20 -0
  517. package/src/utils/exchanges.ts +1 -0
  518. package/src/utils/html-entities.ts +15 -0
  519. package/src/utils/http-proxy-response.ts +44 -7
  520. package/src/utils/plugin-proxy-hosts.json +3 -0
  521. package/src/utils/price-history.ts +35 -1
  522. package/src/utils/sec.ts +21 -2
  523. package/src/utils/text-wrap.ts +13 -8
  524. package/src/utils/zoned-date-time.ts +67 -0
@@ -0,0 +1,136 @@
1
+ /**
2
+ * A/B experiments on the hosted web terminal (term.gloom.sh).
3
+ *
4
+ * The registry, the bucketing and the bot rules live with the API
5
+ * (gloomberb-platform `shared/experiments.ts`). The terminal asks for its arm
6
+ * at the moment an experiment's change would show, in every arm, and that
7
+ * request is the exposure: the API answers with the variant and counts the
8
+ * visitor in it. Turning an experiment off there takes effect here without a
9
+ * release.
10
+ *
11
+ * The first variant this browser is given is kept and sent back, so a visitor
12
+ * whose anonymous id changes later stays in the same arm, and it rides on every
13
+ * later milestone as `experiments`, which the API turns into `exp_<key>` and
14
+ * saves to the account once they sign in.
15
+ */
16
+
17
+ export interface StorageLike {
18
+ getItem(key: string): string | null;
19
+ setItem(key: string, value: string): void;
20
+ }
21
+
22
+ export interface ExperimentAnswer {
23
+ accepted: boolean;
24
+ variant?: string;
25
+ }
26
+
27
+ export interface ExperimentExposureRequest {
28
+ experiment: string;
29
+ /** The variant this browser was given before, if any. */
30
+ variant?: string;
31
+ }
32
+
33
+ export interface WebExperimentEnvironment {
34
+ /** Absent with Do Not Track or Global Privacy Control: the terminal never minted one. */
35
+ anonymousId: string | undefined;
36
+ signedIn: boolean;
37
+ /** A browser driven by automation (`navigator.webdriver`). */
38
+ automated: boolean;
39
+ /** Kept across visits: the first variant of each experiment. */
40
+ local: StorageLike;
41
+ /** Kept for this browser session: the experiments already counted in it. */
42
+ session: StorageLike;
43
+ ask(request: ExperimentExposureRequest): Promise<ExperimentAnswer>;
44
+ }
45
+
46
+ export const EXPERIMENTS_STORAGE_KEY = "gloomberb.web.experiments";
47
+ const EXPOSED_SESSION_KEY = "gloomberb.web.experiments-exposed";
48
+
49
+ const ASSIGNMENT = /^([a-z][a-z0-9_]{0,47}):([a-z][a-z0-9_]{0,47})$/;
50
+ const MAX_ASSIGNMENTS_LENGTH = 500;
51
+
52
+ /** "web_terminal_trial_offer:offer,other:control", the API's format, to an ordered map. */
53
+ function parseExperimentAssignments(value: string | null | undefined): Map<string, string> {
54
+ const assignments = new Map<string, string>();
55
+ for (const part of (value ?? "").split(",")) {
56
+ const match = ASSIGNMENT.exec(part.trim());
57
+ if (match && !assignments.has(match[1]!)) assignments.set(match[1]!, match[2]!);
58
+ }
59
+ return assignments;
60
+ }
61
+
62
+ function serializeExperimentAssignments(assignments: ReadonlyMap<string, string>): string | undefined {
63
+ let serialized = "";
64
+ for (const [key, variant] of assignments) {
65
+ const part = `${key}:${variant}`;
66
+ if (!ASSIGNMENT.test(part)) continue;
67
+ const next = serialized ? `${serialized},${part}` : part;
68
+ if (next.length > MAX_ASSIGNMENTS_LENGTH) break;
69
+ serialized = next;
70
+ }
71
+ return serialized || undefined;
72
+ }
73
+
74
+ function read(storage: StorageLike, key: string): string | null {
75
+ try {
76
+ return storage.getItem(key);
77
+ } catch {
78
+ return null;
79
+ }
80
+ }
81
+
82
+ function write(storage: StorageLike, key: string, value: string): void {
83
+ try {
84
+ storage.setItem(key, value);
85
+ } catch {
86
+ /* Private browsing still runs the terminal; the arm is just not remembered. */
87
+ }
88
+ }
89
+
90
+ /** The stored assignments as the attribution string, or undefined when there are none. */
91
+ export function storedExperimentAssignments(storage: StorageLike): string | undefined {
92
+ return serializeExperimentAssignments(parseExperimentAssignments(read(storage, EXPERIMENTS_STORAGE_KEY)));
93
+ }
94
+
95
+ const pending = new Map<string, Promise<string | null>>();
96
+
97
+ /**
98
+ * The variant to show, or null to show nothing: the visitor is signed in, has
99
+ * no anonymous id, is an automated browser, was already counted in this
100
+ * browser session, or the API did not put them in the experiment (a bot, the
101
+ * experiment ended, the request failed). Call it when the change would show,
102
+ * in every arm.
103
+ */
104
+ export function exposeExperiment(experiment: string, env: WebExperimentEnvironment): Promise<string | null> {
105
+ if (!env.anonymousId || env.signedIn || env.automated) return Promise.resolve(null);
106
+ if (parseSessionList(read(env.session, EXPOSED_SESSION_KEY)).has(experiment)) return Promise.resolve(null);
107
+ const inFlight = pending.get(experiment);
108
+ if (inFlight) return inFlight;
109
+
110
+ const request = (async () => {
111
+ const assignments = parseExperimentAssignments(read(env.local, EXPERIMENTS_STORAGE_KEY));
112
+ let answer: ExperimentAnswer;
113
+ try {
114
+ answer = await env.ask({ experiment, variant: assignments.get(experiment) });
115
+ } catch {
116
+ // Not counted, so the next page load in this session may ask again.
117
+ return null;
118
+ }
119
+ const exposed = parseSessionList(read(env.session, EXPOSED_SESSION_KEY));
120
+ exposed.add(experiment);
121
+ write(env.session, EXPOSED_SESSION_KEY, [...exposed].join(","));
122
+ if (!answer.accepted || !answer.variant || !ASSIGNMENT.test(`${experiment}:${answer.variant}`)) return null;
123
+ if (!assignments.has(experiment)) {
124
+ assignments.set(experiment, answer.variant);
125
+ const serialized = serializeExperimentAssignments(assignments);
126
+ if (serialized) write(env.local, EXPERIMENTS_STORAGE_KEY, serialized);
127
+ }
128
+ return answer.variant;
129
+ })().finally(() => pending.delete(experiment));
130
+ pending.set(experiment, request);
131
+ return request;
132
+ }
133
+
134
+ function parseSessionList(value: string | null): Set<string> {
135
+ return new Set((value ?? "").split(",").filter(Boolean));
136
+ }
@@ -27,6 +27,8 @@ import {
27
27
  import type { PluginRegistry } from "../../plugins/registry";
28
28
  import { reportCrash } from "../../telemetry/crash-reports";
29
29
  import { recordFunctionOpen, usageFunctionForPane } from "../../telemetry/usage-counts";
30
+ import { captureAttentionAction } from "../../telemetry/attention-counts";
31
+ import { publicTickerKey } from "../../utils/exchanges";
30
32
  import {
31
33
  resolveTickerNavigationReplacementPane,
32
34
  shouldFocusTickerNavigationTarget,
@@ -252,6 +254,7 @@ export function bindAppPanePluginRegistry({
252
254
  },
253
255
  navigateTicker: (rawSymbol, options) => {
254
256
  if (isDetachedWindow) return;
257
+ const recordAttention = captureAttentionAction();
255
258
  recordFunctionOpen(usageFunctionForPane(pluginRegistry, TICKER_RESEARCH_PANE_ID));
256
259
  const sourcePaneId = options?.sourcePaneId ?? stateRef.current.focusedPaneId;
257
260
  const requests = tickerNavigationRequests.get(pluginRegistry) ?? new Map<string | null, symbol>();
@@ -311,12 +314,14 @@ export function bindAppPanePluginRegistry({
311
314
  };
312
315
  persistLayout(nextLayout);
313
316
  focusIfStillOwned(detailPane.instanceId);
317
+ recordAttention(publicTickerKey(symbol, target.listing?.exchange ?? target.ticker.metadata.exchange), "des");
314
318
  } else if (shouldFocusTickerNavigationTarget({
315
319
  sourcePaneId,
316
320
  currentFocusedPaneId: stateRef.current.focusedPaneId,
317
321
  targetPaneId: null,
318
322
  })) {
319
323
  placePinnedTickerTarget(target, { floating: false });
324
+ recordAttention(publicTickerKey(symbol, target.listing?.exchange ?? target.ticker.metadata.exchange), "des");
320
325
  }
321
326
  } catch (err) {
322
327
  if (!canPresentFeedback()) return;
@@ -24,7 +24,8 @@ import {
24
24
  } from "../../tickers/open-target";
25
25
  import { AmbiguousTickerError, findExactTickerSearchMatch } from "../../tickers/search";
26
26
  import { isDialogOpen } from "../../ui/dialog-stack";
27
- import { parsePublicTickerKey } from "../../utils/exchanges";
27
+ import { parsePublicTickerKey, publicTickerKey } from "../../utils/exchanges";
28
+ import { attentionActionForPane, captureAttentionAction } from "../../telemetry/attention-counts";
28
29
  import { tickerHasListingSuffix } from "../../sources/listing-symbols";
29
30
  import { instrumentFromTicker } from "../../market-data/request-types";
30
31
  import { tickerInstrumentLabel } from "../../tickers/instrument-label";
@@ -185,13 +186,18 @@ export function useAppTickerOpenRuntime({
185
186
  ]);
186
187
 
187
188
  const openPinnedTicker = useCallback(async (rawSymbol: string, options?: PinTickerOptions) => {
189
+ const recordAttention = captureAttentionAction();
188
190
  const selectedTicker = options?.instrument !== undefined && (options.instrument || options.listing) ? await tickerRepository.loadTicker(rawSymbol) : null;
189
191
  const target = selectedTicker
190
192
  ? { symbol: selectedTicker.metadata.ticker, ticker: selectedTicker, created: false, instrument: options?.instrument, listing: options?.listing }
191
193
  : await resolveOpenTickerTarget(rawSymbol, options?.instrument === null);
192
194
  if (!target) return;
193
195
  placePinnedTickerTarget(target, options);
194
- }, [placePinnedTickerTarget, resolveOpenTickerTarget, tickerRepository]);
196
+ const action = attentionActionForPane(options?.paneType ?? TICKER_RESEARCH_PANE_ID);
197
+ if (action && pluginRegistry.panes.has(options?.paneType ?? TICKER_RESEARCH_PANE_ID)) {
198
+ recordAttention(publicTickerKey(target.symbol, target.listing?.exchange ?? target.ticker.metadata.exchange), action);
199
+ }
200
+ }, [placePinnedTickerTarget, pluginRegistry, resolveOpenTickerTarget, tickerRepository]);
195
201
 
196
202
  return {
197
203
  openPinnedTicker,
@@ -7,8 +7,10 @@ import {
7
7
  checkForUpdateDetailed,
8
8
  performUpdate,
9
9
  type ReleaseInfo,
10
+ type UpdateCheckResult,
10
11
  } from "../../updater";
11
12
  import { VERSION } from "../../version";
13
+ import { reportCrash } from "../../telemetry/crash-reports";
12
14
  import { runAutomated } from "../../telemetry/usage-counts";
13
15
 
14
16
  const UPDATE_CHECK_INTERVAL_MS = 60 * 60_000; // hourly
@@ -48,7 +50,17 @@ export function useAppUpdateRuntime({
48
50
  dispatch({ type: "SET_UPDATE_NOTICE", notice: null });
49
51
  }
50
52
 
51
- const result = await checkForUpdateDetailed(VERSION);
53
+ // The desktop host can reject (its RPC request timed out). That is a
54
+ // failed check like any other: the hourly one must not end as an
55
+ // unhandled rejection, and a manual one must clear its progress state.
56
+ // The timeout is still reported, as it was when it went unhandled.
57
+ const result = await checkForUpdateDetailed(VERSION).catch((error: unknown): UpdateCheckResult => {
58
+ const message = error instanceof Error ? error.message : "";
59
+ if (message.startsWith("RPC request timed out")) {
60
+ reportCrash(error, { kind: "unhandled-rejection" });
61
+ }
62
+ return { kind: "error", error: message || "Update check failed" };
63
+ });
52
64
 
53
65
  if (!manual) {
54
66
  if (result.kind === "available") {
@@ -19,6 +19,7 @@ const DOCTOR_COUNT_UNITS: Record<string, string> = { plugins: "loaded", capabili
19
19
  const TELEMETRY_CONFIG_KEYS = {
20
20
  "telemetry.crashReports": "crashReports",
21
21
  "telemetry.usage": "usage",
22
+ "telemetry.attention": "attention",
22
23
  } as const satisfies Record<string, keyof TelemetryConfig>;
23
24
  type TelemetryConfigKey = keyof typeof TELEMETRY_CONFIG_KEYS;
24
25
  const EDITABLE_CONFIG_KEYS = ["baseCurrency", "refreshIntervalMinutes", "theme", "valueFlashingEnabled", ...Object.keys(TELEMETRY_CONFIG_KEYS)];
@@ -195,6 +196,7 @@ export function createSystemCliCommands(): CliCommandDef[] {
195
196
  valueFlashingEnabled: context.config.valueFlashingEnabled,
196
197
  "telemetry.crashReports": context.config.telemetry?.crashReports !== false,
197
198
  "telemetry.usage": context.config.telemetry?.usage !== false,
199
+ "telemetry.attention": context.config.telemetry?.attention === true,
198
200
  };
199
201
 
200
202
  if (action === "list") {
@@ -546,7 +546,6 @@ async function readRenderedPaneState(session: CdpSession): Promise<DesktopPaneSh
546
546
  .forEach((table, tableIndex) => {
547
547
  const tableMetadata = semanticTables[tableIndex] && semanticTables[tableIndex].metadata || {};
548
548
  const semanticColumns = Array.isArray(tableMetadata.columns) ? tableMetadata.columns : [];
549
- const semanticRows = Array.isArray(tableMetadata.rows) ? tableMetadata.rows : [];
550
549
  const headers = [...table.querySelectorAll('[data-gloom-role="data-table-header-cell"]')]
551
550
  .map((cell) => normalize(cell.innerText || cell.textContent));
552
551
  const rowElements = [...table.querySelectorAll(
@@ -555,7 +554,7 @@ async function readRenderedPaneState(session: CdpSession): Promise<DesktopPaneSh
555
554
  rowElements.forEach((row, rowIndex) => {
556
555
  const cellElements = [...row.querySelectorAll('[data-gloom-role="data-table-cell"]')];
557
556
  const values = cellElements.length > 0 ? cellElements : [row];
558
- const semanticRow = semanticRows[rowIndex] || {};
557
+ const rowKey = row.getAttribute("data-gloom-row-key");
559
558
  const cells = values.map((cell, cellIndex) => {
560
559
  const semanticColumn = semanticColumns[cellIndex] || {};
561
560
  const text = normalize(cell.innerText || cell.textContent);
@@ -582,8 +581,8 @@ async function readRenderedPaneState(session: CdpSession): Promise<DesktopPaneSh
582
581
  rows.push({
583
582
  tableIndex,
584
583
  rowIndex,
585
- ...(typeof semanticRow.key === "string" ? { key: semanticRow.key } : {}),
586
- selected: row.getAttribute("data-selected") === "true" || semanticRow.selected === true,
584
+ ...(rowKey ? { key: rowKey } : {}),
585
+ selected: row.getAttribute("data-selected") === "true",
587
586
  cells,
588
587
  });
589
588
  });
@@ -9,6 +9,14 @@ import { cleanTickerInput } from "./options";
9
9
  import { parsePublicTickerKey, publicTickerKey } from "../../utils/exchanges";
10
10
  import type { ResolvedPaneFunction } from "./resolver";
11
11
  import { toMarketDataContext } from "../../market-data/selectors";
12
+ import { loadSeasonalityHistory, SEASONALITY_HISTORY_RESOLUTION } from "../../plugins/builtin/seasonality/client";
13
+ import type { InstrumentRef } from "../../market-data/request-types";
14
+ import { CORRELATION_HISTORY_RESOLUTION, loadCorrelationHistory } from "../../plugins/builtin/correlation/history";
15
+ import type { TimeRange } from "../../time-series/range";
16
+ import { loadPeriodEndHistory, PERIOD_END_HISTORY_RESOLUTION } from "../../plugins/builtin/ticker-detail/financials/period-end-history";
17
+ import { latestFinancialPeriodEnd } from "../../plugins/builtin/ticker-detail/financials/model";
18
+ import { oldestRatioPeriodEnd, periodEndClose, ratioStatements } from "../../plugins/builtin/ticker-detail/financials/ratios";
19
+ import { shotFinancialPeriod } from "./financial-ratio-shot";
12
20
 
13
21
  const SHOT_PRICE_HISTORY_RANGE = "5Y" as const;
14
22
  const FINANCIAL_ANALYSIS_PANE_ID = "financial-analysis";
@@ -57,6 +65,76 @@ export async function withShotPriceHistory(
57
65
  }
58
66
  }
59
67
 
68
+ /**
69
+ * SEAS reads monthly closes. Captured history answers every request, so the
70
+ * weekly seed would stand in for them and price each month at a week's close.
71
+ * The pane's own load fetches them, tagged so no other cadence is answered.
72
+ */
73
+ export async function withShotSeasonalityHistory(
74
+ context: MarketContext,
75
+ instrument: InstrumentRef,
76
+ financials: TickerFinancials,
77
+ ): Promise<TickerFinancials> {
78
+ const monthly = await loadSeasonalityHistory({ instrument, forceRefresh: context.refresh }, context.dataProvider);
79
+ if (monthly.error || monthly.stale) {
80
+ throw new Error(`${instrument.symbol}: ${monthly.error ?? "monthly price history is stale"}`);
81
+ }
82
+ return { ...financials, priceHistory: monthly.history, priceHistoryResolution: SEASONALITY_HISTORY_RESOLUTION };
83
+ }
84
+
85
+ /** Correlation and relationship panes read daily returns at every range. */
86
+ export function readsDailyReturns(resolved: ResolvedPaneFunction): boolean {
87
+ return resolved.capability.id === "return-correlation" || resolved.capability.id === "security-relationship";
88
+ }
89
+
90
+ /**
91
+ * A range capture is weekly at 5Y and monthly at ALL. Captured history answers
92
+ * the panes' daily request, so it would turn their daily returns into weekly or
93
+ * monthly ones.
94
+ */
95
+ export async function withShotDailyReturns(
96
+ context: MarketContext,
97
+ instrument: InstrumentRef,
98
+ exchange: string,
99
+ range: TimeRange,
100
+ financials: TickerFinancials,
101
+ ): Promise<TickerFinancials> {
102
+ const priceHistory = await loadCorrelationHistory(context.dataProvider, instrument.symbol, exchange, range,
103
+ { ...toMarketDataContext(instrument), ...(context.refresh ? { cacheMode: "refresh" as const } : {}) });
104
+ return { ...financials, priceHistory, priceHistoryResolution: CORRELATION_HISTORY_RESOLUTION };
105
+ }
106
+
107
+ /**
108
+ * Valuation prices each column at its period-end close and accepts only daily
109
+ * bars. Captured history answers every request, so the weekly seed stood in
110
+ * for them and every cell read "no price". The pane's own load fetches daily
111
+ * closes back to its oldest column, tagged so no other cadence is answered.
112
+ */
113
+ export async function withShotPeriodEndHistory(
114
+ context: MarketContext,
115
+ instrument: InstrumentRef,
116
+ exchange: string,
117
+ resolved: ResolvedPaneFunction,
118
+ financials: TickerFinancials,
119
+ ): Promise<TickerFinancials> {
120
+ const statements = ratioStatements(financials, shotFinancialPeriod(resolved, financials));
121
+ const oldest = oldestRatioPeriodEnd(statements);
122
+ // Without statements the pane shows its empty state, which the shot reports.
123
+ if (!oldest) return financials;
124
+ const fail = (reason: string) => new Error(`${instrument.symbol}: ${reason}`);
125
+ const priceHistory = await loadPeriodEndHistory(context.dataProvider, instrument.symbol, exchange, oldest,
126
+ { ...toMarketDataContext(instrument), ...(context.refresh ? { cacheMode: "refresh" as const } : {}) })
127
+ .catch((error: unknown) => { throw fail(error instanceof Error ? error.message : String(error)); });
128
+ const last = priceHistory.reduce((latest, point) => Math.max(latest, new Date(point.date).getTime() || -Infinity), -Infinity);
129
+ if (!Number.isFinite(last)) throw fail("daily price history is unavailable");
130
+ const lastClose = new Date(last).toISOString().slice(0, 10);
131
+ const newest = latestFinancialPeriodEnd(statements)!;
132
+ if (lastClose < newest && periodEndClose(priceHistory, newest) === undefined) {
133
+ throw fail(`daily price history is stale: the last close is ${lastClose}, before the ${newest} period end`);
134
+ }
135
+ return { ...financials, priceHistory, priceHistoryResolution: PERIOD_END_HISTORY_RESOLUTION };
136
+ }
137
+
60
138
  export function isFinancialAnalysisFunction(resolved: ResolvedPaneFunction): boolean {
61
139
  if (resolved.pane.id === FINANCIAL_ANALYSIS_PANE_ID) return true;
62
140
  if (resolved.template?.id === FINANCIAL_ANALYSIS_TEMPLATE_ID) return true;
@@ -0,0 +1,160 @@
1
+ import type { TickerFinancials } from "../../types/financials";
2
+ import type { DesktopPaneShotPayload, DesktopPaneShotRenderResult } from "../desktop-pane-shot";
3
+ import type { ResolvedPaneFunction } from "./resolver";
4
+ import { createValuationCurrencyContext } from "../../time-series/valuation-currency";
5
+ import {
6
+ resolveFinancialPeriod,
7
+ resolveFinancialPeriodOption,
8
+ type FinancialPeriod,
9
+ } from "../../plugins/builtin/ticker-detail/financials/model";
10
+ import { PERIOD_END_HISTORY_RESOLUTION } from "../../plugins/builtin/ticker-detail/financials/period-end-history";
11
+ import {
12
+ findRatioTab,
13
+ formatRatioInput,
14
+ formatRatioValue,
15
+ ratioPeriodEnd,
16
+ ratioTableForFinancials,
17
+ resolveFinancialSectionKey,
18
+ type RatioTabDef,
19
+ type RatioTableModel,
20
+ } from "../../plugins/builtin/ticker-detail/financials/ratios";
21
+
22
+ export interface PaneScreenshotFinancialRatioEvidence {
23
+ kind: "financial-ratios";
24
+ symbol: string;
25
+ statement: string;
26
+ period: FinancialPeriod;
27
+ /** The newest column with a ratio, and the close it was priced at. */
28
+ latest: {
29
+ date: string;
30
+ periodEnd: string;
31
+ price: number | null;
32
+ ratios: Array<{ id: string; label: string; value: number }>;
33
+ };
34
+ }
35
+
36
+ /** The ratio tab an FA shot opens, or null for a statement tab. */
37
+ export function shotRatioTab(resolved: ResolvedPaneFunction): RatioTabDef | null {
38
+ if (resolved.capability.id !== "financial-statements") return null;
39
+ return findRatioTab(resolveFinancialSectionKey(String(resolved.options.statement ?? ""))) ?? null;
40
+ }
41
+
42
+ /** The period FA shows: the requested one, or the other when it has no statements. */
43
+ export function shotFinancialPeriod(resolved: ResolvedPaneFunction, financials: TickerFinancials): FinancialPeriod {
44
+ const hasAnnual = financials.annualStatements.length > 0;
45
+ const hasQuarterly = financials.quarterlyStatements.length > 0;
46
+ const requested = typeof resolved.options.period === "string" ? resolveFinancialPeriodOption(resolved.options.period) : undefined;
47
+ return resolveFinancialPeriod(requested ?? (hasAnnual ? "annual" : "quarterly"), hasAnnual, hasQuarterly);
48
+ }
49
+
50
+ interface RatioShot {
51
+ symbol: string;
52
+ financials: TickerFinancials;
53
+ tab: RatioTabDef;
54
+ period: FinancialPeriod;
55
+ model: RatioTableModel;
56
+ }
57
+
58
+ /** The table the page computes from the captured data, with every ratio expanded. */
59
+ function ratioShot(resolved: ResolvedPaneFunction, payload: DesktopPaneShotPayload): RatioShot | null {
60
+ const tab = shotRatioTab(resolved);
61
+ const [symbol, financials] = payload.financials[0] ?? [];
62
+ if (!tab || !symbol || !financials) return null;
63
+ const period = shotFinancialPeriod(resolved, financials);
64
+ // The page answers Valuation's daily request only from a daily capture.
65
+ const history = tab.key === "valuation" && financials.priceHistoryResolution === PERIOD_END_HISTORY_RESOLUTION
66
+ ? financials.priceHistory : null;
67
+ return { symbol, financials, tab, period, model: ratioTableForFinancials(financials, tab, period, history) };
68
+ }
69
+
70
+ const hasRatio = ({ model, tab }: RatioShot, index: number) => tab.ratios.some((def) => (
71
+ typeof model.cells.get(def.id)?.[index]?.value === "number"
72
+ ));
73
+
74
+ /** Why the captured data cannot make a usable ratio shot, or null when it can. */
75
+ export function financialRatioShotGap(resolved: ResolvedPaneFunction, payload: DesktopPaneShotPayload): string | null {
76
+ const shot = ratioShot(resolved, payload);
77
+ if (!shot) return null;
78
+ if (shot.tab.key === "valuation") {
79
+ const unpriced = shot.model.periods.flatMap(({ statement, price }) => typeof price === "number" ? [] : [statement]);
80
+ // A close in another currency than the statements prices nothing; the pane's footer says so.
81
+ if (unpriced.length) {
82
+ return createValuationCurrencyContext(shot.financials).warning(unpriced)
83
+ ?? `Valuation has no period-end close for ${unpriced.map(ratioPeriodEnd).join(", ")}, so those columns read "no price".`;
84
+ }
85
+ }
86
+ if (!shot.model.periods.some((_, index) => hasRatio(shot, index))) {
87
+ return `Every ${shot.tab.name} ratio is unreported or not meaningful.`;
88
+ }
89
+ return null;
90
+ }
91
+
92
+ export function financialRatioShotEvidence(
93
+ resolved: ResolvedPaneFunction,
94
+ payload: DesktopPaneShotPayload,
95
+ ): PaneScreenshotFinancialRatioEvidence | null {
96
+ const shot = ratioShot(resolved, payload);
97
+ if (!shot || financialRatioShotGap(resolved, payload)) return null;
98
+ const index = shot.model.periods.findIndex((_, column) => hasRatio(shot, column));
99
+ const { statement, price } = shot.model.periods[index]!;
100
+ return {
101
+ kind: "financial-ratios",
102
+ symbol: shot.symbol,
103
+ statement: shot.tab.key,
104
+ period: shot.period,
105
+ latest: {
106
+ date: statement.date,
107
+ periodEnd: ratioPeriodEnd(statement),
108
+ price: typeof price === "number" ? price : null,
109
+ ratios: shot.tab.ratios.flatMap((def) => {
110
+ const value = shot.model.cells.get(def.id)![index]!.value;
111
+ return typeof value === "number" ? [{ id: def.id, label: def.label, value }] : [];
112
+ }),
113
+ },
114
+ };
115
+ }
116
+
117
+ const STATEMENT_COLUMN = /^statement:(.+):(\d+)$/;
118
+
119
+ /**
120
+ * Rendered ratio cells that differ from the table the captured data computes.
121
+ * A page that drew "no price" or "--" where the capture has a close, or drew
122
+ * other periods, is not a picture of the data.
123
+ */
124
+ export function financialRatioRenderMismatches(
125
+ resolved: ResolvedPaneFunction,
126
+ payload: DesktopPaneShotPayload,
127
+ render: Pick<DesktopPaneShotRenderResult, "rows">,
128
+ ): string[] {
129
+ const shot = ratioShot(resolved, payload);
130
+ if (!shot) return [];
131
+ const { model, tab } = shot;
132
+ const rows = new Map(model.rows.map((row) => [row.id, row]));
133
+ const mismatches: string[] = [];
134
+ let compared = 0;
135
+ for (const rendered of render.rows) {
136
+ const row = rendered.key ? rows.get(rendered.key) : undefined;
137
+ if (!row) continue;
138
+ for (const cell of rendered.cells) {
139
+ const column = STATEMENT_COLUMN.exec(cell.columnId ?? "");
140
+ if (!column) continue;
141
+ const index = Number(column[2]);
142
+ const period = model.periods[index];
143
+ const header = cell.columnLabel.trim();
144
+ if (period?.statement.date !== column[1]) {
145
+ mismatches.push(`${tab.name} column ${header} is not a captured period`);
146
+ continue;
147
+ }
148
+ const amounts = model.cells.get(row.def.id)![index]!;
149
+ const expected = row.kind === "ratio"
150
+ ? formatRatioValue(row.def.format, amounts.value)
151
+ : formatRatioInput(row.def.inputs[row.index]!.format, amounts.inputs[row.index]!, row.divisor);
152
+ compared += 1;
153
+ if (cell.text.trim() !== expected.trim()) {
154
+ mismatches.push(`${tab.name} ${row.label} ${header} reads "${cell.text.trim()}", the captured data gives "${expected.trim()}"`);
155
+ }
156
+ }
157
+ }
158
+ if (compared === 0) mismatches.push(`${tab.name} ratio cells are missing`);
159
+ return mismatches;
160
+ }