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
@@ -172,6 +172,34 @@ body.gloom-dragging {
172
172
  opacity: 1;
173
173
  pointer-events: auto;
174
174
  }
175
+ /* Message text can be selected and copied, like any chat. */
176
+ [data-gloom-role="chat-message-body"] {
177
+ -webkit-user-select: text;
178
+ user-select: text;
179
+ }
180
+ [data-gloom-role="chat-message-image"]:focus-visible {
181
+ outline: 2px solid var(--gloom-border-focused, currentColor);
182
+ outline-offset: 1px;
183
+ }
184
+ /* An upload whose progress the transport cannot report. */
185
+ .gloom-chat-upload-indeterminate {
186
+ animation: gloom-chat-upload-slide 1.2s ease-in-out infinite;
187
+ }
188
+ @keyframes gloom-chat-upload-slide {
189
+ from { transform: translateX(-100%); }
190
+ to { transform: translateX(250%); }
191
+ }
192
+ @media (prefers-reduced-motion: reduce) {
193
+ .gloom-chat-upload-indeterminate {
194
+ animation: none;
195
+ width: 100% !important;
196
+ opacity: 0.5;
197
+ }
198
+ }
199
+ /* The image viewer may use the whole window, not a dialog's usual width. */
200
+ .gloom-dialog:has([data-gloom-role="chat-image-lightbox"]) {
201
+ max-width: calc(100vw - 48px);
202
+ }
175
203
  [data-gloom-role="status-bar"] {
176
204
  flex: 0 0 var(--cell-h);
177
205
  height: var(--cell-h);
@@ -14,6 +14,7 @@ import {
14
14
  } from "../shared/protocol";
15
15
  import { decodeRpcResponse, decodeRpcValue, encodeRpcValue } from "../shared/rpc-codec";
16
16
  import { subscribeCapability, type CapabilitySubscriptionOptions } from "./capability-subscription";
17
+ import { nameRpcTimeout } from "./rpc-timeout";
17
18
  import type { RemoteControlRequest, RemoteControlResponse } from "../../../remote/types";
18
19
 
19
20
  type BackendMessages = ElectrobunDesktopRpcSchema["webview"]["messages"];
@@ -130,10 +131,10 @@ export async function backendRequest(
130
131
  payload: unknown = null,
131
132
  ): Promise<unknown> {
132
133
  await waitForBridgeReady();
133
- const result = await rpc.request["backend.request"]({
134
+ const result = await nameRpcTimeout(method, payload, () => rpc.request["backend.request"]({
134
135
  method,
135
136
  payload: encodeRpcValue(payload),
136
- });
137
+ }));
137
138
  return decodeRpcResponse(result);
138
139
  }
139
140
 
@@ -437,7 +437,7 @@ function ShotPane({ payload, registry }: { payload: DesktopPaneShotPayload; regi
437
437
  const width = payload.widthCells;
438
438
  const height = payload.heightCells;
439
439
  return (
440
- <PaneShotFrame paneId={instance.instanceId} title={title} width={width} height={height} preserveStatus={pane.id === "time-sales"}>
440
+ <PaneShotFrame paneId={instance.instanceId} title={title} width={width} height={height} preserveStatus={["time-sales", "gpu", "hiring", "apps", "catalysts", "litigation", "perps"].includes(pane.id)}>
441
441
  {(bodyFrame) => <PaneContent
442
442
  component={pane.component}
443
443
  paneId={instance.instanceId}
@@ -57,6 +57,8 @@ async function electrobunCloudApiFetch(url: string, init?: RequestInit): Promise
57
57
  const path = new URL(url).pathname;
58
58
  const timeoutMs = path.startsWith("/market/")
59
59
  ? CLOUD_MARKET_HTTP_TIMEOUT_MS
60
+ : path === "/telemetry/attention"
61
+ ? 5_000
60
62
  : path.endsWith("/feedback") && init?.method === "POST"
61
63
  ? FEEDBACK_SUBMIT_BUN_TIMEOUT_MS
62
64
  : undefined;
@@ -43,8 +43,9 @@ import { remoteNotesFilesIO, setNotesFilesIO } from "../../../plugins/builtin/no
43
43
  import { NOTES_FILES_CAPABILITY_ID } from "../../../capabilities";
44
44
  import { loadOfficialPluginIds } from "../../../plugins/builtin/plugin-marketplace/feed";
45
45
  import { crashReportsEnabled, installCrashReporter, reportCrash } from "../../../telemetry/crash-reports";
46
- import { installWindowCrashListeners } from "../../../telemetry/crash-reports-dom";
46
+ import { browserDoNotTrack, installWindowCrashListeners } from "../../../telemetry/crash-reports-dom";
47
47
  import { currentTelemetryConfig } from "../../../telemetry/live-config";
48
+ import { attentionCountsEnabled, installAttentionCounter } from "../../../telemetry/attention-counts";
48
49
  import { installUsageCounter, usageCountsEnabled } from "../../../telemetry/usage-counts";
49
50
  import { installWindowUsageFlush } from "../../../telemetry/usage-counts-dom";
50
51
 
@@ -135,6 +136,7 @@ async function boot() {
135
136
  getInstallId: () => init.telemetry.installId,
136
137
  officialPluginIds: loadOfficialPluginIds,
137
138
  });
139
+ installAttentionCounter(() => !init.telemetry.optedOut && !browserDoNotTrack() && attentionCountsEnabled(currentTelemetryConfig(init.config)));
138
140
  installElectrobunCapabilityStreamClient();
139
141
  installFocusScopeRelease();
140
142
  installElectrobunWindowFullscreenTracking();
@@ -0,0 +1,59 @@
1
+ import { getCloudApiBaseUrl } from "../../../api-client/request";
2
+ import type { DesktopBackendRequestMethod } from "../shared/protocol";
3
+
4
+ /** What Electrobun's request timer rejects with: no method, request id or caller. */
5
+ const ELECTROBUN_REQUEST_TIMEOUT_MESSAGE = "RPC request timed out.";
6
+
7
+ /**
8
+ * Runs one `backend.request` and, when Electrobun's request timer gives up on
9
+ * it, rethrows the timeout with the request named. Every other failure passes
10
+ * through untouched.
11
+ *
12
+ * The library's error is the same for every method, so crash reports could not
13
+ * tell a stalled update check from a stalled fetch. The name is the method,
14
+ * plus the capability operation for `capability.invoke`. An HTTP request to
15
+ * the Gloom API adds its host and first path segment; any other URL can come
16
+ * from the user's config (a broker gateway, a feed, a plugin endpoint) and is
17
+ * only called external. Never the payload, rest of the path, query or headers.
18
+ * Elapsed time is rounded to 10 s because crash reports are deduplicated per
19
+ * session on the message.
20
+ */
21
+ export async function nameRpcTimeout<T>(
22
+ method: DesktopBackendRequestMethod,
23
+ payload: unknown,
24
+ request: () => Promise<T>,
25
+ ): Promise<T> {
26
+ const startedAt = performance.now();
27
+ try {
28
+ return await request();
29
+ } catch (error) {
30
+ if (!(error instanceof Error) || error.message !== ELECTROBUN_REQUEST_TIMEOUT_MESSAGE) throw error;
31
+ const seconds = Math.round((performance.now() - startedAt) / 10_000) * 10;
32
+ throw new Error(`RPC request timed out: ${describeRequest(method, payload)} after ~${seconds}s`, { cause: error });
33
+ }
34
+ }
35
+
36
+ function describeRequest(method: DesktopBackendRequestMethod, payload: unknown): string {
37
+ if (!payload || typeof payload !== "object") return method;
38
+ const { capabilityId, operationId, url } = payload as Record<string, unknown>;
39
+ if (method === "capability.invoke" && typeof capabilityId === "string" && typeof operationId === "string") {
40
+ return `${method} ${capabilityId}.${operationId}`;
41
+ }
42
+ if (method === "http.fetch" || method === "http.stream.open") {
43
+ return `${method} ${gloomApiArea(url) ?? "external"}`;
44
+ }
45
+ return method;
46
+ }
47
+
48
+ /** `api.gloom.sh/market` for a Gloom API URL, null for anything else. */
49
+ function gloomApiArea(url: unknown): string | null {
50
+ if (typeof url !== "string") return null;
51
+ try {
52
+ const parsed = new URL(url);
53
+ if (parsed.origin !== new URL(getCloudApiBaseUrl()).origin) return null;
54
+ const segment = parsed.pathname.split("/")[1];
55
+ return segment ? `${parsed.host}/${segment}` : parsed.host;
56
+ } catch {
57
+ return null;
58
+ }
59
+ }
@@ -37,6 +37,7 @@ import {
37
37
  } from "../../telemetry/crash-reports-node";
38
38
  import { currentTelemetryConfig } from "../../telemetry/live-config";
39
39
  import { flushUsageCounts, installUsageCounter, usageCountsEnabled } from "../../telemetry/usage-counts";
40
+ import { attentionCountsEnabled, installAttentionCounter } from "../../telemetry/attention-counts";
40
41
 
41
42
  // Declared here rather than sniffed: the desktop view and the hosted browser
42
43
  // app are both browser contexts but differ in what plugins may do.
@@ -119,8 +120,10 @@ export async function startOpenTuiApp({ externalPlugins, cliLaunchRequest }: Sta
119
120
  getInstallId: () => readOrCreateInstallId(config.dataDir),
120
121
  officialPluginIds: loadOfficialPluginIds,
121
122
  });
123
+ const stopAttention = installAttentionCounter(() => attentionCountsEnabled(currentTelemetryConfig(config), process.env));
122
124
  host = await measurePerfAsync("startup.opentui.create-host", () => createOpenTuiHost());
123
125
  const renderer = host.renderer;
126
+ renderer.once("destroy", stopAttention);
124
127
  // The renderer already listens for uncaught errors and keeps the process
125
128
  // running; these listeners live exactly as long as it does, so nothing
126
129
  // changes about when the process exits.
package/src/shares/api.ts CHANGED
@@ -1,4 +1,3 @@
1
- import { apiClient } from "../api-client";
2
1
  import { ApiRequestError } from "../api-client/errors";
3
2
  import { parseSharePayload, type SharePayload } from "./payload";
4
3
 
@@ -52,6 +51,9 @@ export async function createShare(payload: SharePayload, fetchImpl?: ShareFetch)
52
51
  body = await readJson(response);
53
52
  } else {
54
53
  try {
54
+ // Public share pages only read/delete through fetch. Load the signed-in
55
+ // client when an interactive app creates a share, not on every public view.
56
+ const { apiClient } = await import("../api-client");
55
57
  body = await apiClient.createTerminalShare(validated);
56
58
  } catch (error) {
57
59
  if (error instanceof ApiRequestError && error.status === 401) throw new Error("Sign in to Gloom Cloud to share.");
@@ -38,7 +38,7 @@ import type { NewsArticle, NewsQuery } from "../../types/news-source";
38
38
  import { normalizeNewsFeed } from "../../news/news-model";
39
39
  import { resolveCurrencyUnit } from "../../utils/currency-units";
40
40
  import { canonicalExchange, canonicalTickerKey, parsePublicTickerKey } from "../../utils/exchanges";
41
- import { normalizePriceHistory } from "../../utils/price-history";
41
+ import { normalizePriceHistory, priceHistoryIntervalMs, reachesLatestSettledSession } from "../../utils/price-history";
42
42
  import { createProviderMiss } from "../provider-errors";
43
43
  import { publicListingTarget } from "../listing-target";
44
44
  import { canonicalHistoryInterval, HistoryRetentionError, parseHistoryRecoveryCandidate, parseHistoryRetention, type HistoryRetention } from "../history-retention";
@@ -132,6 +132,28 @@ function isStaleCloudResponse(response: CloudMarketResponse<unknown>): boolean {
132
132
  return response.stale === true || response.providerMeta?.stale === true;
133
133
  }
134
134
 
135
+ /**
136
+ * History with the pre-market and after-hours bars when the chart asks for
137
+ * them. A backend that cannot serve them answers the regular session, and one
138
+ * that ignores the request answers it without saying it served them.
139
+ */
140
+ async function loadCloudHistory(
141
+ interval: string,
142
+ load: (session?: "extended") => Promise<CloudMarketResponse<CloudPricePointPayload[]>>,
143
+ context: MarketDataRequestContext | undefined,
144
+ ): Promise<{ response: CloudMarketResponse<CloudPricePointPayload[]>; extendedHours: boolean }> {
145
+ if (context?.historySession !== "extended" || !/^\d+(min|h)$/i.test(interval)) return { response: await load(), extendedHours: false };
146
+ try {
147
+ const response = await load("extended");
148
+ if (response.status === "success" || response.status === "partial" || response.status === "empty") {
149
+ return { response, extendedHours: response.session === "extended" };
150
+ }
151
+ } catch {
152
+ // The regular session below.
153
+ }
154
+ return { response: await load(), extendedHours: false };
155
+ }
156
+
135
157
  function mapCloudPriceHistory(
136
158
  response: CloudMarketResponse<CloudPricePointPayload[]>,
137
159
  ticker: string,
@@ -139,11 +161,17 @@ function mapCloudPriceHistory(
139
161
  interval: string,
140
162
  requestedStart: Date,
141
163
  requestedEnd?: Date,
164
+ extendedHours = false,
142
165
  ): PriceHistoryResult {
143
166
  const coverageStart = parseHistoryCoverageStart(response.coverage);
144
167
  if (response.status === "empty" && coverageStart
145
168
  && requestedStart.getTime() < Date.parse(coverageStart)) throw new HistoryCoverageError(coverageStart);
146
- if (isStaleCloudResponse(response)) {
169
+ // The server marks a cached copy it could not refresh in time. Bars that
170
+ // reach the venue's latest settled session still answer, and the router's
171
+ // own current-window check applies to them as to any other copy. Older bars,
172
+ // or a stale answer without bars, are a miss.
173
+ const stale = isStaleCloudResponse(response);
174
+ if (stale && response.status !== "success" && response.status !== "partial") {
147
175
  throw createProviderMiss(`Cloud chart data is stale for ${ticker}`);
148
176
  }
149
177
  if (response.status === "unsupported" && response.reasonCode === "HISTORY_RETENTION" && requestedEnd) {
@@ -167,6 +195,9 @@ function mapCloudPriceHistory(
167
195
  `Cloud chart data is unavailable for ${ticker}`,
168
196
  ).map((point) => mapPricePoint(point, divisor, exchange)),
169
197
  );
198
+ if (stale && !reachesLatestSettledSession(points, Date.now(), { exchange, intervalMs: priceHistoryIntervalMs(interval) })) {
199
+ throw createProviderMiss(`Cloud chart data is stale for ${ticker}`);
200
+ }
170
201
  if (
171
202
  /^\d+(min|h)$/i.test(interval)
172
203
  && hasMalformedIntradayHistory(points)
@@ -193,6 +224,7 @@ function mapCloudPriceHistory(
193
224
  resolution,
194
225
  ...(session && matchingSource && matchingIdentity ? { session } : {}),
195
226
  ...(coverageStart ? { coverageStart } : {}),
227
+ ...(extendedHours ? { extendedHours: true } : {}),
196
228
  };
197
229
  }
198
230
 
@@ -542,11 +574,11 @@ export class GloomberbCloudProvider implements AssetDataProvider {
542
574
  const target = cloudInstrumentTarget(ticker, exchange);
543
575
  exchange = target.exchange ?? "";
544
576
  const request = toHistoryRequest(range);
545
- const response = await withCloudFallback(
546
- () => apiClient.getCloudHistory(target.symbol, exchange, request),
577
+ const { response, extendedHours } = await withCloudFallback(
578
+ () => loadCloudHistory(request.interval, (session) => apiClient.getCloudHistory(target.symbol, exchange, { ...request, ...(session ? { session } : {}) }), _context),
547
579
  `Cloud chart data is unavailable for ${ticker}`,
548
580
  );
549
- return mapCloudPriceHistory(response, target.symbol, exchange, request.interval, subtractTimeRange(new Date(), range));
581
+ return mapCloudPriceHistory(response, target.symbol, exchange, request.interval, subtractTimeRange(new Date(), range), undefined, extendedHours);
550
582
  }
551
583
 
552
584
  async getPriceHistoryForResolution(
@@ -570,15 +602,16 @@ export class GloomberbCloudProvider implements AssetDataProvider {
570
602
  const endDate = new Date();
571
603
  const startDate = subtractTimeRange(endDate, bufferRange);
572
604
  const includeTime = /^\d+(min|h)$/i.test(interval);
573
- const response = await withCloudFallback(
574
- () => apiClient.getCloudHistory(target.symbol, exchange, {
605
+ const { response, extendedHours } = await withCloudFallback(
606
+ () => loadCloudHistory(interval, (session) => apiClient.getCloudHistory(target.symbol, exchange, {
575
607
  interval,
576
608
  startDate: formatCloudDateTime(startDate, includeTime, exchange),
577
609
  endDate: formatCloudDateTime(endDate, includeTime, exchange),
578
- }),
610
+ ...(session ? { session } : {}),
611
+ }), _context),
579
612
  `Cloud chart data is unavailable for ${ticker}`,
580
613
  );
581
- return mapCloudPriceHistory(response, target.symbol, exchange, interval, startDate, endDate);
614
+ return mapCloudPriceHistory(response, target.symbol, exchange, interval, startDate, endDate, extendedHours);
582
615
  }
583
616
 
584
617
  async getDetailedPriceHistory(
@@ -601,16 +634,17 @@ export class GloomberbCloudProvider implements AssetDataProvider {
601
634
  const interval = toCloudInterval(barSize);
602
635
  const includeTime = /^\d+(min|h)$/i.test(interval);
603
636
  const historyRecovery = cloudHistoryRecovery(_context, ticker, target.symbol, exchange, interval, startDate, endDate);
604
- const response = await withCloudFallback(
605
- () => apiClient.getCloudHistory(target.symbol, exchange, {
637
+ const { response, extendedHours } = await withCloudFallback(
638
+ () => loadCloudHistory(interval, (session) => apiClient.getCloudHistory(target.symbol, exchange, {
606
639
  interval,
607
640
  startDate: formatCloudDateTime(startDate, includeTime, exchange),
608
641
  endDate: formatCloudDateTime(endDate, includeTime, exchange),
609
642
  ...(historyRecovery ? { historyRecovery } : {}),
610
- }),
643
+ ...(session ? { session } : {}),
644
+ }), historyRecovery ? undefined : _context),
611
645
  `Cloud detailed chart history is unavailable for ${ticker}`,
612
646
  );
613
- return mapCloudPriceHistory(response, target.symbol, exchange, interval, startDate, endDate);
647
+ return mapCloudPriceHistory(response, target.symbol, exchange, interval, startDate, endDate, extendedHours);
614
648
  }
615
649
 
616
650
  async getOptionsChain(ticker: string, exchange?: string, expirationDate?: number, _context?: MarketDataRequestContext): Promise<OptionsChain> {
@@ -40,6 +40,7 @@ export function normalizeHistoryResult(
40
40
  ...(session ? { session } : {}),
41
41
  ...(typeof result.sourceKey === "string" && /^(provider|broker):[^\r\n\0]+$/.test(result.sourceKey) ? { sourceKey: result.sourceKey } : {}),
42
42
  ...(isHistoryCoverageDate(result.coverageStart) ? { coverageStart: result.coverageStart } : {}),
43
+ ...(result.extendedHours === true ? { extendedHours: true } : {}),
43
44
  };
44
45
  }
45
46
 
@@ -1,5 +1,7 @@
1
+ import { formatShortDate } from "../utils/datetime-format";
1
2
  import { canonicalExchange, parsePublicTickerKey } from "../utils/exchanges";
2
3
  import { isRecord } from "../utils/guards";
4
+ import { zonedDateKey } from "../utils/zoned-date-time";
3
5
  import { parseBarInterval } from "../time-series/resolution";
4
6
 
5
7
  export const HISTORY_RETENTION_MAX_AGE_MS = 5 * 60_000;
@@ -132,6 +134,20 @@ export class HistoryRetentionError extends Error {
132
134
  }
133
135
  }
134
136
 
137
+ /**
138
+ * Shown while a chart at an explicit interval reaches back past what the
139
+ * source keeps at it: the first retained day, read in the listing's zone
140
+ * (UTC when unknown), and the length of the window the source keeps.
141
+ */
142
+ export function historyRetentionNotice(retention: HistoryRetention, interval: string, timeZone: string | null): string {
143
+ const zone = timeZone ?? "UTC";
144
+ const day = zonedDateKey(retention.availableStart, zone);
145
+ const sameYear = day.slice(0, 4) === zonedDateKey(retention.observedAt, zone).slice(0, 4);
146
+ const days = Math.round((retention.observedAt - retention.availableStart) / 86_400_000);
147
+ return `${interval} history starts ${formatShortDate(day, { utc: true, year: sameYear ? false : "numeric" })}; `
148
+ + `the source keeps the last ${days === 1 ? "day" : `${days} days`}.`;
149
+ }
150
+
135
151
  export function isHistoryRetentionError(value: unknown): value is HistoryRetentionError {
136
152
  return value instanceof HistoryRetentionError && parseHistoryRetention(value.retention) !== null;
137
153
  }
@@ -143,15 +143,17 @@ function makeHistoryRequestIdentity(
143
143
  fallbackVariantParts: Array<[string, string | number | undefined | null]>;
144
144
  },
145
145
  ): Pick<HistoryRequestDescriptor, "identity" | "cacheVariantKeys" | "exactCacheVariantKeys" | "target"> {
146
+ // Extended-hours bars are another history; they never answer for the regular session.
147
+ const session: [string, string | undefined] = ["session", input.context?.historySession];
146
148
  const identity = makeRouterRequestIdentity(deps, {
147
149
  kind: input.kind,
148
150
  ticker: input.ticker,
149
151
  context: input.context,
150
- variantParts: priceHistoryVariantParts(input.variantParts, input.exchange),
152
+ variantParts: priceHistoryVariantParts([...input.variantParts, session], input.exchange),
151
153
  });
152
154
  const cacheVariantKeys = [
153
155
  identity.variantKey,
154
- buildVariantKey(priceHistoryVariantParts(input.fallbackVariantParts, input.exchange)),
156
+ buildVariantKey(priceHistoryVariantParts([...input.fallbackVariantParts, session], input.exchange)),
155
157
  ];
156
158
  return {
157
159
  target: { symbol: input.ticker, exchange: input.exchange },
@@ -0,0 +1,36 @@
1
+ // SEC's fair-access policy asks every client to name itself and a contact in
2
+ // the User-Agent; EDGAR blocks requests that do not.
3
+
4
+ function sanitizeIdentityPart(value: string, fallback: string): string {
5
+ const sanitized = value.trim().toLowerCase().replace(/[^a-z0-9.-]+/g, "-").replace(/^-+|-+$/g, "");
6
+ return sanitized || fallback;
7
+ }
8
+
9
+ function extractEmail(value: string | undefined): string | null {
10
+ if (!value) return null;
11
+ const match = value.match(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/i);
12
+ return match?.[0] ?? null;
13
+ }
14
+
15
+ function getEnv(name: string): string | undefined {
16
+ return typeof process !== "undefined" ? process.env[name] : undefined;
17
+ }
18
+
19
+ function getRuntimeHostName(): string {
20
+ const maybeLocation = (globalThis as { location?: { hostname?: string } }).location;
21
+ if (maybeLocation?.hostname) {
22
+ return maybeLocation.hostname;
23
+ }
24
+ return "localhost";
25
+ }
26
+
27
+ const runtimeHostName = getRuntimeHostName();
28
+
29
+ export const DEFAULT_SEC_FROM =
30
+ getEnv("SEC_FROM_EMAIL")?.trim()
31
+ || extractEmail(getEnv("SEC_USER_AGENT"))
32
+ || `${sanitizeIdentityPart(getEnv("USER") ?? "gloomberb", "gloomberb")}@${sanitizeIdentityPart(`${runtimeHostName}.local`, "localhost.localdomain")}`;
33
+
34
+ export const DEFAULT_SEC_USER_AGENT =
35
+ getEnv("SEC_USER_AGENT")?.trim()
36
+ || `Gloomberb/0.1 (${sanitizeIdentityPart(runtimeHostName, "localhost")}; contact=${DEFAULT_SEC_FROM})`;
@@ -11,6 +11,7 @@ import {
11
11
  isPdfDocument,
12
12
  } from "./sec-edgar/content";
13
13
  import { parseSecAcceptanceTime } from "./sec-edgar/acceptance-time";
14
+ import { DEFAULT_SEC_FROM, DEFAULT_SEC_USER_AGENT } from "./sec-edgar/identity";
14
15
  import { secFourthQuarters, withGuardedFourthQuarters, type SecFourthQuarter } from "./sec-edgar/fourth-quarter";
15
16
 
16
17
  export { extractFilingContent } from "./sec-edgar/content";
@@ -20,40 +21,6 @@ const SUBMISSIONS_URL = "https://data.sec.gov/submissions";
20
21
  const COMPANY_FACTS_URL = "https://data.sec.gov/api/xbrl/companyfacts";
21
22
  const FETCH_TIMEOUT_MS = 15_000;
22
23
 
23
- function sanitizeIdentityPart(value: string, fallback: string): string {
24
- const sanitized = value.trim().toLowerCase().replace(/[^a-z0-9.-]+/g, "-").replace(/^-+|-+$/g, "");
25
- return sanitized || fallback;
26
- }
27
-
28
- function extractEmail(value: string | undefined): string | null {
29
- if (!value) return null;
30
- const match = value.match(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/i);
31
- return match?.[0] ?? null;
32
- }
33
-
34
- function getEnv(name: string): string | undefined {
35
- return typeof process !== "undefined" ? process.env[name] : undefined;
36
- }
37
-
38
- function getRuntimeHostName(): string {
39
- const maybeLocation = (globalThis as { location?: { hostname?: string } }).location;
40
- if (maybeLocation?.hostname) {
41
- return maybeLocation.hostname;
42
- }
43
- return "localhost";
44
- }
45
-
46
- const runtimeHostName = getRuntimeHostName();
47
-
48
- const DEFAULT_SEC_FROM =
49
- getEnv("SEC_FROM_EMAIL")?.trim()
50
- || extractEmail(getEnv("SEC_USER_AGENT"))
51
- || `${sanitizeIdentityPart(getEnv("USER") ?? "gloomberb", "gloomberb")}@${sanitizeIdentityPart(`${runtimeHostName}.local`, "localhost.localdomain")}`;
52
-
53
- const DEFAULT_SEC_USER_AGENT =
54
- getEnv("SEC_USER_AGENT")?.trim()
55
- || `Gloomberb/0.1 (${sanitizeIdentityPart(runtimeHostName, "localhost")}; contact=${DEFAULT_SEC_FROM})`;
56
-
57
24
  type LookupEntry = {
58
25
  cik: string;
59
26
  exchange?: string;
@@ -455,6 +455,7 @@ export function AppProvider({
455
455
  );
456
456
  const effectiveThemeId = getEffectiveThemeId(state);
457
457
  const previousRecentTickers = useRef(state.recentTickers);
458
+ const previousRecentCommands = useRef(state.config.recentCommands);
458
459
  const stateRef = useRef(state);
459
460
  const listenersRef = useRef(new Set<() => void>());
460
461
  const storeRef = useRef<AppContextStoreValue | null>(null);
@@ -528,6 +529,13 @@ export function AppProvider({
528
529
  );
529
530
  }, [state.config, state.recentTickers]);
530
531
 
532
+ // A recent pane run changes only config.recentCommands; nothing else saves it.
533
+ useEffect(() => {
534
+ if (previousRecentCommands.current === state.config.recentCommands) return;
535
+ previousRecentCommands.current = state.config.recentCommands;
536
+ scheduleConfigSave(() => ({ ...stateRef.current.config, recentTickers: stateRef.current.recentTickers }));
537
+ }, [state.config.recentCommands]);
538
+
531
539
  useEffect(() => {
532
540
  if (!desktopBridge) return;
533
541
  return desktopBridge.subscribeState((snapshot) => {
@@ -28,6 +28,7 @@ import {
28
28
  normalizeBuiltinPluginStateMap,
29
29
  } from "../plugins/ownership";
30
30
  import { isRecord } from "../utils/guards";
31
+ import { withoutMissingWatchlists } from "../plugins/builtin/portfolio-list/mutations";
31
32
 
32
33
  /**
33
34
  * What a saved layout mirrors from the live session rather than from the
@@ -365,6 +366,7 @@ function collectCoreConfigPayload(config: AppConfig) {
365
366
  chartPreferences: config.chartPreferences,
366
367
  valueFlashingEnabled: config.valueFlashingEnabled,
367
368
  recentTickers: config.recentTickers,
369
+ recentCommands: config.recentCommands,
368
370
  // Completion is monotonic in synced state. A device only advertises the
369
371
  // completed state, while resumable progress and incomplete state stay local.
370
372
  onboardingComplete: config.onboardingComplete === true ? true : undefined,
@@ -655,6 +657,7 @@ function mergeConfigPayload(
655
657
  assign("chartPreferences");
656
658
  assign("valueFlashingEnabled");
657
659
  assign("recentTickers");
660
+ assign("recentCommands");
658
661
  // Sync can complete onboarding on another device, but never reopen it.
659
662
  // Resumable progress remains local until this installation completes it.
660
663
  if (
@@ -920,6 +923,11 @@ export const coreCollectionsSyncContributor: SyncContributor = {
920
923
  if (!isRecord(payload)) return;
921
924
  hydrateProfileAnalytics(payload);
922
925
  const lastSyncedTickers = lastSyncedTickersById(baselinePayload);
926
+ // The config contributor applies first, so these are the watchlists this
927
+ // pull left. Tickers here and pulled ones drop the ids of any list that is
928
+ // gone (removed elsewhere, or deleted by an older build that left its id on
929
+ // the tickers), so this device never pushes them back.
930
+ const watchlistIds = new Set(getState().config.watchlists.map((watchlist) => watchlist.id));
923
931
  const incomingRecords: TickerRecord[] = [];
924
932
  const rawTickers = Array.isArray(payload.tickers) ? payload.tickers : [];
925
933
  for (const rawTicker of rawTickers) {
@@ -943,16 +951,26 @@ export const coreCollectionsSyncContributor: SyncContributor = {
943
951
  positions: syncedPositions,
944
952
  broker_contracts: current?.metadata.broker_contracts ?? [],
945
953
  });
946
- const record: TickerRecord = { metadata };
954
+ const record = withoutMissingWatchlists({ metadata }, watchlistIds);
947
955
  await tickerRepository.saveTicker(record);
948
956
  incomingRecords.push(record);
949
957
  }
950
958
 
951
- if (!isCurrent() || incomingRecords.length === 0) return;
959
+ if (!isCurrent()) return;
952
960
  const nextTickers = new Map(getState().tickers);
953
961
  for (const record of incomingRecords) {
954
962
  nextTickers.set(record.metadata.ticker, record);
955
963
  }
964
+ let pruned = false;
965
+ for (const ticker of nextTickers.values()) {
966
+ const next = withoutMissingWatchlists(ticker, watchlistIds);
967
+ if (next === ticker) continue;
968
+ if (!isCurrent()) return;
969
+ await tickerRepository.saveTicker(next);
970
+ nextTickers.set(next.metadata.ticker, next);
971
+ pruned = true;
972
+ }
973
+ if (!isCurrent() || (incomingRecords.length === 0 && !pruned)) return;
956
974
  dispatch({ type: "SET_TICKERS", tickers: nextTickers });
957
975
  },
958
976
  };