gloomberb 0.16.6 → 0.17.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 (348) hide show
  1. package/package.json +2 -1
  2. package/src/api-client/chat-pane-link.ts +47 -0
  3. package/src/api-client/chat.ts +9 -1
  4. package/src/api-client/data.ts +37 -1
  5. package/src/api-client/equity-screener.ts +3 -0
  6. package/src/api-client/europe-energy.ts +91 -0
  7. package/src/api-client/index.ts +6 -0
  8. package/src/api-client/letters.ts +41 -0
  9. package/src/api-client/normalizers.ts +36 -6
  10. package/src/api-client/organization-tickers.ts +44 -0
  11. package/src/api-client/request.ts +3 -1
  12. package/src/api-client/types.ts +27 -1
  13. package/src/app/runtime/desktop-deeplink.ts +48 -15
  14. package/src/architecture/core-builtin-allowlist.json +1 -1
  15. package/src/architecture/dom-cell-metrics-allowlist.json +28 -0
  16. package/src/ask-gloom/pane-context.ts +23 -19
  17. package/src/ask-gloom/pane-request.ts +2 -2
  18. package/src/cli/commands/fred-series.ts +77 -11
  19. package/src/cli/commands/market.ts +77 -20
  20. package/src/cli/commands/overview.ts +29 -20
  21. package/src/cli/commands/ticker.ts +64 -11
  22. package/src/cli/desktop-pane-shot-view.ts +114 -0
  23. package/src/cli/desktop-pane-shot.ts +30 -31
  24. package/src/cli/entry.ts +7 -0
  25. package/src/cli/export-source.ts +43 -0
  26. package/src/cli/freshness.ts +18 -3
  27. package/src/cli/glossary.ts +17 -0
  28. package/src/cli/history-rows.ts +174 -13
  29. package/src/cli/known-gaps.ts +14 -3
  30. package/src/cli/listing-arg.ts +20 -2
  31. package/src/cli/listing-suggestion.ts +159 -0
  32. package/src/cli/native-smoke.ts +13 -0
  33. package/src/cli/not-a-ticker.ts +13 -1
  34. package/src/cli/pane-functions/capabilities.ts +16 -8
  35. package/src/cli/pane-functions/dom.ts +28 -13
  36. package/src/cli/pane-functions/function-help.ts +51 -6
  37. package/src/cli/pane-functions/headless.ts +26 -7
  38. package/src/cli/pane-functions/index.ts +46 -3
  39. package/src/cli/pane-functions/report-notices.ts +6 -3
  40. package/src/cli/pane-functions/report.ts +16 -2
  41. package/src/cli/pane-functions/resolver.ts +7 -4
  42. package/src/cli/report-tables.ts +31 -7
  43. package/src/cli/result.ts +54 -11
  44. package/src/cli/symbol-rows.ts +18 -1
  45. package/src/components/chart/composite/axis-overlays.tsx +2 -0
  46. package/src/components/chart/composite/composite-chart.tsx +4 -21
  47. package/src/components/chart/composite/legend.tsx +10 -6
  48. package/src/components/chart/composite/price-axis-labels.tsx +1 -0
  49. package/src/components/command-bar/list/model.ts +2 -0
  50. package/src/components/command-bar/pane-templates/items.ts +1 -1
  51. package/src/components/command-bar/routes/ticker-search/actions.ts +1 -0
  52. package/src/components/command-bar/routes/ticker-search/results.ts +3 -1
  53. package/src/components/command-bar/workflow/ops.ts +8 -97
  54. package/src/components/command-bar/workflow/pane-reuse.ts +177 -0
  55. package/src/components/data-table/view.tsx +1 -0
  56. package/src/components/feed-data-table/stack-view.tsx +18 -42
  57. package/src/components/layout/missing-pane.tsx +2 -0
  58. package/src/components/layout/shell/index.tsx +1 -0
  59. package/src/components/layout/shell/pane/ask.ts +12 -7
  60. package/src/components/layout/shell/pane/capture.ts +8 -34
  61. package/src/components/markdown-text.tsx +37 -6
  62. package/src/components/reader/article-reader.tsx +91 -266
  63. package/src/components/reader/detail-reader.tsx +210 -0
  64. package/src/components/reader/reader-text.tsx +239 -0
  65. package/src/components/table-view-shared.tsx +8 -4
  66. package/src/components/ticker/badge/list.tsx +15 -4
  67. package/src/components/ui/choice-dialog.tsx +3 -0
  68. package/src/components/ui/data-table/types.ts +7 -0
  69. package/src/components/ui/detail-scroll-body.tsx +31 -4
  70. package/src/components/ui/display.tsx +46 -1
  71. package/src/components/ui/icon.tsx +11 -1
  72. package/src/components/ui/list-view.tsx +3 -1
  73. package/src/components/ui/query-bar-keys.tsx +2 -0
  74. package/src/components/ui/query-bar.tsx +9 -1
  75. package/src/components/ui/select-button.tsx +3 -0
  76. package/src/data/config/store/fold-panes.ts +202 -11
  77. package/src/data/config/store/migrations.ts +13 -1
  78. package/src/docs/snapshot.json +29 -0
  79. package/src/i18n/es.ts +14 -0
  80. package/src/i18n/ja.ts +14 -0
  81. package/src/i18n/ko.ts +14 -0
  82. package/src/i18n/zh-cn.ts +14 -0
  83. package/src/i18n/zh-tw.ts +14 -0
  84. package/src/layout/desk-keys.ts +7 -0
  85. package/src/layout/desks.ts +2 -2
  86. package/src/market-data/market/status.ts +67 -6
  87. package/src/market-data/quotes/contributions.ts +15 -0
  88. package/src/market-data/quotes/regular-session-valuation.ts +131 -0
  89. package/src/market-data/quotes/resolution.ts +4 -0
  90. package/src/market-data/snapshot-provider.ts +6 -3
  91. package/src/notifications/app-badge.ts +30 -0
  92. package/src/plugins/builtin/account-management/calendar-tab.tsx +1 -1
  93. package/src/plugins/builtin/account-management/form-components.tsx +53 -1
  94. package/src/plugins/builtin/account-management/keyboard.ts +5 -0
  95. package/src/plugins/builtin/account-management/model.ts +27 -0
  96. package/src/plugins/builtin/account-management/pane.tsx +62 -2
  97. package/src/plugins/builtin/alerts/cli.ts +6 -4
  98. package/src/plugins/builtin/analytics/index.tsx +1 -1
  99. package/src/plugins/builtin/awards/pane.tsx +29 -18
  100. package/src/plugins/builtin/backtest/pane.tsx +2 -1
  101. package/src/plugins/builtin/bond-calculator/index.tsx +3 -5
  102. package/src/plugins/builtin/buildout/detail/company.tsx +7 -4
  103. package/src/plugins/builtin/builtin-plugin-meta.ts +5 -4
  104. package/src/plugins/builtin/catalysts/model.ts +92 -10
  105. package/src/plugins/builtin/catalysts/pane.tsx +6 -4
  106. package/src/plugins/builtin/cds/index.tsx +21 -30
  107. package/src/plugins/builtin/changelog/index.tsx +11 -12
  108. package/src/plugins/builtin/chart-composer/headless-schema.ts +2 -0
  109. package/src/plugins/builtin/chart-composer/headless.ts +87 -5
  110. package/src/plugins/builtin/chart-composer/pane.tsx +3 -2
  111. package/src/plugins/builtin/chart-composer/session-history.ts +20 -5
  112. package/src/plugins/builtin/chat/command-bar.ts +6 -8
  113. package/src/plugins/builtin/chat/content/composer-runtime.ts +2 -2
  114. package/src/plugins/builtin/chat/content/composer.tsx +13 -4
  115. package/src/plugins/builtin/chat/content/footer.tsx +15 -5
  116. package/src/plugins/builtin/chat/content/handle-prompt.tsx +326 -0
  117. package/src/plugins/builtin/chat/content/index.tsx +113 -9
  118. package/src/plugins/builtin/chat/content/layout-metrics.ts +4 -0
  119. package/src/plugins/builtin/chat/content/open-targets.ts +20 -4
  120. package/src/plugins/builtin/chat/content/scroll.ts +6 -1
  121. package/src/plugins/builtin/chat/content/transcript.tsx +11 -2
  122. package/src/plugins/builtin/chat/ghost-user.ts +1 -1
  123. package/src/plugins/builtin/chat/handle-prompt.ts +128 -0
  124. package/src/plugins/builtin/chat/index.tsx +44 -21
  125. package/src/plugins/builtin/chat/layout.ts +7 -14
  126. package/src/plugins/builtin/chat/message/desktop.tsx +59 -11
  127. package/src/plugins/builtin/chat/message/header.tsx +69 -21
  128. package/src/plugins/builtin/chat/message/pane-chip.tsx +223 -0
  129. package/src/plugins/builtin/chat/message/person-icons.tsx +53 -0
  130. package/src/plugins/builtin/chat/message/profile-popover.tsx +73 -9
  131. package/src/plugins/builtin/chat/message/render-state.ts +15 -2
  132. package/src/plugins/builtin/chat/message/terminal.tsx +33 -8
  133. package/src/plugins/builtin/chat/message/types.ts +3 -0
  134. package/src/plugins/builtin/chat/pane-links/model.ts +149 -0
  135. package/src/plugins/builtin/chat/pane-links/open.ts +29 -0
  136. package/src/plugins/builtin/chat/pane-links/previews.ts +119 -0
  137. package/src/plugins/builtin/chat/pane-state.ts +5 -8
  138. package/src/plugins/builtin/chat/people.ts +202 -0
  139. package/src/plugins/builtin/chat/profile-popover.ts +18 -2
  140. package/src/plugins/builtin/chat/status-widget.tsx +3 -8
  141. package/src/plugins/builtin/chat/unread-badge.ts +65 -0
  142. package/src/plugins/builtin/chat/unread-inbox-pane.tsx +9 -6
  143. package/src/plugins/builtin/clinical-trials/client.ts +26 -1
  144. package/src/plugins/builtin/clinical-trials/index.tsx +10 -2
  145. package/src/plugins/builtin/clinical-trials/model.ts +269 -0
  146. package/src/plugins/builtin/clinical-trials/pane.tsx +247 -62
  147. package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +156 -0
  148. package/src/plugins/builtin/cloud/askg/attached-pane.tsx +3 -3
  149. package/src/plugins/builtin/cloud/askg/pane-attachment.ts +3 -3
  150. package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +1 -0
  151. package/src/plugins/builtin/comment-letters/pane.tsx +30 -11
  152. package/src/plugins/builtin/commodities-energy/index.tsx +179 -0
  153. package/src/plugins/builtin/commodity-balances/client.ts +208 -0
  154. package/src/plugins/builtin/commodity-balances/headless.ts +66 -0
  155. package/src/plugins/builtin/commodity-balances/index.tsx +32 -0
  156. package/src/plugins/builtin/commodity-balances/model.ts +261 -0
  157. package/src/plugins/builtin/commodity-balances/pane.tsx +227 -0
  158. package/src/plugins/builtin/composite-plugins.ts +26 -1
  159. package/src/plugins/builtin/credit-boards/index.tsx +21 -42
  160. package/src/plugins/builtin/credit-boards/model.ts +0 -2
  161. package/src/plugins/builtin/credit-conditions/index.tsx +10 -19
  162. package/src/plugins/builtin/credit-documents/index.tsx +27 -10
  163. package/src/plugins/builtin/debt-maturities/index.tsx +3 -17
  164. package/src/plugins/builtin/debug/index.tsx +2 -2
  165. package/src/plugins/builtin/dividend-yield/client.ts +3 -1
  166. package/src/plugins/builtin/dividend-yield/headless.ts +3 -1
  167. package/src/plugins/builtin/dividend-yield/pane.tsx +3 -1
  168. package/src/plugins/builtin/dividend-yield/provider-client.ts +3 -1
  169. package/src/plugins/builtin/docs/blocks.tsx +4 -3
  170. package/src/plugins/builtin/docs/pane.tsx +16 -8
  171. package/src/plugins/builtin/earnings/index.tsx +2 -0
  172. package/src/plugins/builtin/earnings-calls/pane.tsx +2 -12
  173. package/src/plugins/builtin/equity-screener/client.ts +1 -1
  174. package/src/plugins/builtin/equity-screener/headless.ts +36 -2
  175. package/src/plugins/builtin/equity-screener/model.ts +121 -68
  176. package/src/plugins/builtin/executives/pane.tsx +3 -1
  177. package/src/plugins/builtin/filing-events/index.tsx +20 -0
  178. package/src/plugins/builtin/fixed-income/index.tsx +46 -0
  179. package/src/plugins/builtin/fixed-income/model.ts +27 -0
  180. package/src/plugins/builtin/fixed-income/pane.tsx +162 -0
  181. package/src/plugins/builtin/fixed-income/shortcut.ts +52 -0
  182. package/src/plugins/builtin/futures/index.tsx +24 -7
  183. package/src/plugins/builtin/futures/series.ts +23 -2
  184. package/src/plugins/builtin/futures/use-front-returns.ts +3 -2
  185. package/src/plugins/builtin/futures-curve/pane.tsx +6 -4
  186. package/src/plugins/builtin/help/shortcut-table.tsx +4 -2
  187. package/src/plugins/builtin/insider/detail.tsx +20 -12
  188. package/src/plugins/builtin/insider/index.tsx +2 -2
  189. package/src/plugins/builtin/iv-history/index.tsx +3 -0
  190. package/src/plugins/builtin/iv-history/screen-pane.tsx +13 -5
  191. package/src/plugins/builtin/jodi/client.ts +220 -0
  192. package/src/plugins/builtin/jodi/headless.ts +63 -0
  193. package/src/plugins/builtin/jodi/index.tsx +32 -0
  194. package/src/plugins/builtin/jodi/model.ts +75 -0
  195. package/src/plugins/builtin/jodi/pane.tsx +141 -0
  196. package/src/plugins/builtin/market-heatmap/evidence.ts +13 -4
  197. package/src/plugins/builtin/market-heatmap/headless.ts +3 -3
  198. package/src/plugins/builtin/market-heatmap/index.tsx +41 -23
  199. package/src/plugins/builtin/market-heatmap/live.ts +2 -1
  200. package/src/plugins/builtin/market-heatmap/portfolio.ts +67 -21
  201. package/src/plugins/builtin/market-movers/table.ts +2 -2
  202. package/src/plugins/builtin/market-valuation/align.ts +10 -9
  203. package/src/plugins/builtin/mna/distress/model.ts +52 -1
  204. package/src/plugins/builtin/mna/distress/pane.tsx +17 -12
  205. package/src/plugins/builtin/mna/index.tsx +2 -0
  206. package/src/plugins/builtin/news/wire/index.tsx +2 -2
  207. package/src/plugins/builtin/news/wire/industry-pane.tsx +28 -14
  208. package/src/plugins/builtin/news/wire/letters/client.ts +108 -0
  209. package/src/plugins/builtin/news/wire/letters/model.ts +69 -0
  210. package/src/plugins/builtin/news/wire/letters/pane.tsx +155 -0
  211. package/src/plugins/builtin/news/wire/mutes.ts +2 -1
  212. package/src/plugins/builtin/news/wire/news/query-presets.ts +4 -1
  213. package/src/plugins/builtin/openfda/client.ts +123 -15
  214. package/src/plugins/builtin/openfda/model.ts +163 -0
  215. package/src/plugins/builtin/openfda/pane.tsx +109 -65
  216. package/src/plugins/builtin/openfda/types.ts +25 -0
  217. package/src/plugins/builtin/options/view.tsx +1 -1
  218. package/src/plugins/builtin/pe-band/model.ts +3 -2
  219. package/src/plugins/builtin/plugin-marketplace/pane.tsx +1 -1
  220. package/src/plugins/builtin/plugin-module.ts +3 -1
  221. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +1 -1
  222. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +19 -4
  223. package/src/plugins/builtin/portfolio-list/cli/render.ts +2 -2
  224. package/src/plugins/builtin/portfolio-list/cli/shared.ts +39 -0
  225. package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +18 -5
  226. package/src/plugins/builtin/portfolio-list/live-valuation.ts +25 -13
  227. package/src/plugins/builtin/power/evidence.ts +32 -3
  228. package/src/plugins/builtin/power/gas-client.ts +110 -0
  229. package/src/plugins/builtin/power/gas-model.ts +194 -0
  230. package/src/plugins/builtin/power/gas.tsx +145 -0
  231. package/src/plugins/builtin/power/headless.ts +4 -1
  232. package/src/plugins/builtin/power/index.tsx +19 -9
  233. package/src/plugins/builtin/power/pane.tsx +2 -3
  234. package/src/plugins/builtin/power/prices-headless.ts +75 -7
  235. package/src/plugins/builtin/power/prices.tsx +36 -13
  236. package/src/plugins/builtin/primary-dealers/client.ts +108 -0
  237. package/src/plugins/builtin/primary-dealers/headless.ts +53 -0
  238. package/src/plugins/builtin/primary-dealers/index.tsx +17 -0
  239. package/src/plugins/builtin/primary-dealers/model.ts +206 -0
  240. package/src/plugins/builtin/primary-dealers/pane.tsx +149 -0
  241. package/src/plugins/builtin/relative-rotation/index.tsx +3 -0
  242. package/src/plugins/builtin/research/analyst-model.ts +9 -5
  243. package/src/plugins/builtin/research/headless-schema.ts +2 -1
  244. package/src/plugins/builtin/research/relative-valuation-headless.ts +68 -32
  245. package/src/plugins/builtin/research/relative-valuation-model.ts +45 -24
  246. package/src/plugins/builtin/research/relative-valuation-pane.tsx +39 -4
  247. package/src/plugins/builtin/scanner/flow-pane.tsx +8 -1
  248. package/src/plugins/builtin/sec/filing-display.tsx +3 -36
  249. package/src/plugins/builtin/sec/index.tsx +131 -43
  250. package/src/plugins/builtin/sec/model.ts +148 -2
  251. package/src/plugins/builtin/sec/row-summaries.ts +105 -0
  252. package/src/plugins/builtin/sectors/index.tsx +2 -0
  253. package/src/plugins/builtin/sectors/sector-model.ts +2 -2
  254. package/src/plugins/builtin/series-inventory.ts +1 -0
  255. package/src/plugins/builtin/shared/locked-rows.tsx +1 -1
  256. package/src/plugins/builtin/shared/use-quote-board.ts +179 -43
  257. package/src/plugins/builtin/shared/wall-teaser.tsx +2 -2
  258. package/src/plugins/builtin/shared/zip-text.ts +46 -0
  259. package/src/plugins/builtin/short-interest/watch-model.ts +11 -2
  260. package/src/plugins/builtin/themes/model.ts +2 -2
  261. package/src/plugins/builtin/ticker-detail/financials/aggregation.ts +11 -0
  262. package/src/plugins/builtin/ticker-detail/financials/ratios.ts +81 -19
  263. package/src/plugins/builtin/ticker-detail/financials/tab.tsx +1 -1
  264. package/src/plugins/builtin/ticker-detail/headless.ts +2 -2
  265. package/src/plugins/builtin/ticker-detail/overview/model.ts +3 -1
  266. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +7 -5
  267. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +8 -6
  268. package/src/plugins/builtin/time-sales/headless.ts +22 -0
  269. package/src/plugins/builtin/trace-bonds/client.ts +196 -0
  270. package/src/plugins/builtin/trace-bonds/headless.ts +69 -0
  271. package/src/plugins/builtin/trace-bonds/index.tsx +33 -0
  272. package/src/plugins/builtin/trace-bonds/model.ts +115 -0
  273. package/src/plugins/builtin/trace-bonds/pane.tsx +191 -0
  274. package/src/plugins/builtin/treasury-auctions/index.tsx +33 -47
  275. package/src/plugins/builtin/treasury-daily/client.ts +96 -0
  276. package/src/plugins/builtin/treasury-daily/headless.ts +69 -0
  277. package/src/plugins/builtin/treasury-daily/index.tsx +17 -0
  278. package/src/plugins/builtin/treasury-daily/model.ts +69 -0
  279. package/src/plugins/builtin/treasury-daily/pane.tsx +140 -0
  280. package/src/plugins/builtin/world-venue-map/entity-detail.tsx +15 -2
  281. package/src/plugins/builtin/world-venue-map/map.tsx +1 -0
  282. package/src/plugins/builtin-editorial.ts +1 -1
  283. package/src/plugins/catalog-browser.ts +25 -2
  284. package/src/plugins/marketplace/model.ts +57 -21
  285. package/src/renderers/dom/build-assets.ts +14 -5
  286. package/src/renderers/dom/data-table/index.tsx +5 -3
  287. package/src/renderers/dom/desktop/controls.tsx +2 -1
  288. package/src/renderers/dom/desktop/icons.tsx +25 -0
  289. package/src/renderers/dom/desktop/list-view.tsx +1 -1
  290. package/src/renderers/dom/desktop/menu.tsx +1 -1
  291. package/src/renderers/dom/desktop/query-bar.tsx +3 -1
  292. package/src/renderers/dom/host/chart-surface.tsx +7 -2
  293. package/src/renderers/dom/host/resize-rows.ts +111 -0
  294. package/src/renderers/dom/host/sticky-scroll.ts +22 -3
  295. package/src/renderers/dom/host/style.ts +14 -0
  296. package/src/renderers/dom/host/surface-3d.tsx +2 -1
  297. package/src/renderers/dom/host/surface-fit.ts +10 -6
  298. package/src/renderers/dom/host/text.tsx +2 -2
  299. package/src/renderers/dom/styles.css +63 -6
  300. package/src/renderers/electrobun/bun/desktop/app-badge.ts +37 -0
  301. package/src/renderers/electrobun/bun/desktop/backend.ts +1 -0
  302. package/src/renderers/electrobun/bun/desktop/host-requests.ts +8 -0
  303. package/src/renderers/electrobun/bun/desktop/macos-dock-badge.ts +37 -0
  304. package/src/renderers/electrobun/bun/desktop/macos-dock-icon.ts +2 -40
  305. package/src/renderers/electrobun/bun/desktop/macos-objc.ts +41 -0
  306. package/src/renderers/electrobun/bun/tui-entry.ts +18 -1
  307. package/src/renderers/electrobun/shared/protocol.ts +2 -0
  308. package/src/renderers/electrobun/view/app-badge-host.ts +17 -0
  309. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +8 -3
  310. package/src/renderers/electrobun/view/main.tsx +2 -0
  311. package/src/renderers/electrobun/view/remote/asset-data-client.ts +5 -1
  312. package/src/series/validate.ts +1 -1
  313. package/src/shares/api.ts +5 -1
  314. package/src/shares/location.ts +31 -0
  315. package/src/shares/open.ts +26 -0
  316. package/src/shares/pane-image.ts +18 -6
  317. package/src/shares/personal-pane.ts +42 -54
  318. package/src/sources/gloomberb-cloud/index.ts +7 -2
  319. package/src/sources/gloomberb-cloud/normalizers.ts +5 -0
  320. package/src/sources/history-coverage.ts +22 -4
  321. package/src/sources/history-result.ts +3 -1
  322. package/src/sources/provider-errors.ts +2 -1
  323. package/src/sources/provider-router/batches.ts +18 -2
  324. package/src/sources/provider-router/index.ts +2 -1
  325. package/src/state/app/context/index.tsx +3 -2
  326. package/src/test-support/builtin-panes.ts +31 -0
  327. package/src/tickers/listing-venues.ts +22 -0
  328. package/src/tickers/search/index.ts +7 -2
  329. package/src/tickers/search/ranking.ts +19 -0
  330. package/src/time-series/chart-data.ts +2 -0
  331. package/src/time-series/parsed-history-cache.ts +41 -11
  332. package/src/time-series/resolve.ts +47 -15
  333. package/src/types/config.ts +1 -1
  334. package/src/types/data-provider.ts +13 -1
  335. package/src/types/financials.ts +13 -0
  336. package/src/types/headless.ts +22 -0
  337. package/src/types/plugin.ts +33 -6
  338. package/src/types/price-history.ts +2 -0
  339. package/src/ui/host.tsx +6 -0
  340. package/src/utils/crypto-pair.ts +1 -1
  341. package/src/utils/currency-pair.ts +66 -0
  342. package/src/utils/decimal-comma.ts +57 -0
  343. package/src/utils/guards.ts +25 -0
  344. package/src/utils/local-utc-offset.ts +37 -0
  345. package/src/utils/plugin-proxy-hosts.json +8 -1
  346. package/src/components/chart/composite/crosshair-readout.tsx +0 -150
  347. package/src/plugins/builtin/chat/message/ai-badge.tsx +0 -18
  348. package/src/plugins/builtin/credit-boards/pane.tsx +0 -30
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gloomberb",
3
- "version": "0.16.6",
3
+ "version": "0.17.0",
4
4
  "packageManager": "bun@1.3.11",
5
5
  "description": "Market research and portfolio tracker for the terminal",
6
6
  "module": "src/public/index.ts",
@@ -85,6 +85,7 @@
85
85
  "check:pane-chrome": "bun run scripts/pane-chrome/checks.ts",
86
86
  "check:pane-screenshot": "bun run scripts/pane-chrome/screenshot.ts",
87
87
  "check:pane-rest": "bun run scripts/pane-chrome/rest-diff.ts",
88
+ "check:pane-layout": "bun run scripts/pane-chrome/layout-sweep.ts",
88
89
  "benchmark:pane-drag:compare": "bun run scripts/pane-chrome/frame-budget.ts",
89
90
  "test:global-upgrade": "bun run scripts/smoke-global-upgrade.ts",
90
91
  "release": "./scripts/release.sh"
@@ -0,0 +1,47 @@
1
+ /**
2
+ * What a chat message opens in the terminal beside its text, as the server
3
+ * sends it: the news story a Gloom Wire headline names, the calendar a morning
4
+ * post reads from. Only the server writes it, from ids it already resolved.
5
+ * A kind this version does not know, or a malformed one, reads as absent and
6
+ * the message is plain text, as it is for a server that predates the field.
7
+ */
8
+ export type ChatPaneLink =
9
+ | { kind: "news-story"; storyId: string }
10
+ | { kind: "economic-calendar"; date: string }
11
+ | { kind: "earnings-calendar"; date: string }
12
+ | { kind: "ipo-calendar"; date: string }
13
+ | { kind: "energy-report"; report: "petroleum" | "gas"; weekEnding: string };
14
+
15
+ const STORY_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
16
+ const DATE = /^\d{4}-\d{2}-\d{2}$/;
17
+ const DATED_KINDS: ReadonlySet<string> = new Set(["economic-calendar", "earnings-calendar", "ipo-calendar"]);
18
+
19
+ function isCalendarDate(value: unknown): value is string {
20
+ if (typeof value !== "string" || !DATE.test(value)) return false;
21
+ const date = new Date(`${value}T00:00:00Z`);
22
+ return Number.isFinite(date.getTime()) && date.toISOString().startsWith(value);
23
+ }
24
+
25
+ export function parseChatPaneLink(value: unknown): ChatPaneLink | null {
26
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
27
+ const link = value as Record<string, unknown>;
28
+ const keys = Object.keys(link).sort().join(",");
29
+ if (link.kind === "news-story") {
30
+ return keys === "kind,storyId" && typeof link.storyId === "string" && STORY_ID.test(link.storyId)
31
+ ? { kind: "news-story", storyId: link.storyId }
32
+ : null;
33
+ }
34
+ if (typeof link.kind === "string" && DATED_KINDS.has(link.kind)) {
35
+ return keys === "date,kind" && isCalendarDate(link.date)
36
+ ? { kind: link.kind, date: link.date } as ChatPaneLink
37
+ : null;
38
+ }
39
+ if (link.kind === "energy-report") {
40
+ return keys === "kind,report,weekEnding"
41
+ && (link.report === "petroleum" || link.report === "gas")
42
+ && isCalendarDate(link.weekEnding)
43
+ ? { kind: "energy-report", report: link.report, weekEnding: link.weekEnding }
44
+ : null;
45
+ }
46
+ return null;
47
+ }
@@ -19,6 +19,7 @@ import type {
19
19
  ChatStateResponse,
20
20
  } from "./types";
21
21
  import type { CloudApiRequest } from "./request";
22
+ import { localUtcOffsetMinutes } from "../utils/local-utc-offset";
22
23
  type ChatNotificationListener = (notification: ChatNotification) => void;
23
24
  type ChatPresenceListener = (onlineCount: number) => void;
24
25
 
@@ -127,8 +128,15 @@ export class CloudChatApi {
127
128
  return this.request<{ onlineCount: number }>("/chat/presence");
128
129
  }
129
130
 
131
+ /**
132
+ * Channels, unread counts and pending notifications. It also reports this
133
+ * machine's current offset from UTC, so the card others open on you shows
134
+ * your local time; a server that predates it ignores the parameter.
135
+ */
130
136
  async getState(): Promise<ChatStateResponse> {
131
- const state = await this.request<ChatStateResponse>("/chat/state");
137
+ const offset = localUtcOffsetMinutes();
138
+ const query = offset === null ? "" : `?${new URLSearchParams({ utcOffset: String(offset) })}`;
139
+ const state = await this.request<ChatStateResponse>(`/chat/state${query}`);
132
140
  this.noteMessages((state.notifications ?? []).map((notification) => notification?.message));
133
141
  return normalizeChatState(state);
134
142
  }
@@ -23,8 +23,10 @@ import {
23
23
  type GoingConcernParams,
24
24
  type InsolvencyNoticesParams,
25
25
  } from "./distress";
26
+ import type { LettersParams } from "./letters";
26
27
  import type { IpoCalendarParams, IpoCalendarPayload } from "./ipo";
27
28
  import type { TickerChanges } from "../types/ticker-changes";
29
+ import type { OrganizationTickersResponse } from "./organization-tickers";
28
30
  import type { CryptoMarketsPayload } from "./crypto-markets";
29
31
  import type { CentralBankRatesPayload } from "./central-bank-rates";
30
32
  import type { EstimateRevisionsPayload } from "./estimate-revisions";
@@ -37,7 +39,7 @@ import type { SocialMentionDayPosts, SocialMentionsPayload, SocialMentionsRange
37
39
  import type { FuturesContractTermsPayload, FuturesCurveAsOfPayload, FuturesCurvePayload } from "./futures-curve";
38
40
  import type { CotBoardPayload, CotContractPayload, CotFamily, CotClass } from "./cot";
39
41
  import type { DoeBoardPayload } from "./doe";
40
- import type { CarbonBoard, CarbonHistory, EuropePowerBoard, EuropePowerHistory } from "./europe-energy";
42
+ import type { CarbonBoard, CarbonHistory, EuropePowerBoard, EuropePowerHistory, GasStorageBoard, GasStorageHistory, GasStorageSeasonal } from "./europe-energy";
41
43
  import type { GpuBoardPayload, GpuEventsPayload, GpuHistoryPayload, GpuHistoryQuery } from "./gpu";
42
44
  import type { CpiBoardPayload } from "./cpi";
43
45
  import type { TapeSnapshot } from "./tape";
@@ -388,6 +390,15 @@ export class CloudDataApi {
388
390
  );
389
391
  }
390
392
 
393
+ /** The listed company each organization name resolves to, if any: a trial's lead sponsors, a page at a time. */
394
+ async getCloudOrganizationTickers(names: readonly string[], signal?: AbortSignal): Promise<OrganizationTickersResponse> {
395
+ return this.request<OrganizationTickersResponse>("/market/organization-tickers", {
396
+ method: "POST",
397
+ body: JSON.stringify({ names }),
398
+ signal,
399
+ });
400
+ }
401
+
391
402
  async getCloudHistory(
392
403
  symbol: string,
393
404
  exchange: string,
@@ -620,6 +631,25 @@ export class CloudDataApi {
620
631
  return this.request<CarbonHistory>(`/cloud/europe-energy/carbon/history${query}`, { signal: AbortSignal.timeout(30_000) });
621
632
  }
622
633
 
634
+ /** European gas storage: the EU aggregate and each member state as of its latest gas day. */
635
+ async getCloudEuropeGas(): Promise<GasStorageBoard> {
636
+ return this.request<GasStorageBoard>("/cloud/europe-energy/gas", { signal: AbortSignal.timeout(30_000) });
637
+ }
638
+
639
+ /** One area's gas days from `from` (all kept without one), oldest first. */
640
+ async getCloudEuropeGasHistory(code: string, from?: string | null): Promise<GasStorageHistory> {
641
+ const query = from ? `?${new URLSearchParams({ from })}` : "";
642
+ return this.request<GasStorageHistory>(
643
+ `/cloud/europe-energy/gas/${encodeURIComponent(code)}/history${query}`,
644
+ { signal: AbortSignal.timeout(30_000) },
645
+ );
646
+ }
647
+
648
+ /** One area's latest year against last year and its five-year range, day by day. */
649
+ async getCloudEuropeGasSeasonal(code: string): Promise<GasStorageSeasonal> {
650
+ return this.request<GasStorageSeasonal>(`/cloud/europe-energy/gas/${encodeURIComponent(code)}/seasonal`, { signal: AbortSignal.timeout(30_000) });
651
+ }
652
+
623
653
  async getCloudGpuBoard(): Promise<GpuBoardPayload> {
624
654
  return this.request<GpuBoardPayload>("/cloud/gpu/board");
625
655
  }
@@ -727,6 +757,12 @@ export class CloudDataApi {
727
757
  return this.request<unknown>(`/public/insolvency-notices${distressQuery(params)}`, { signal: signalWithTimeout(options?.signal, 30_000) });
728
758
  }
729
759
 
760
+ async getPublicLetters(params: LettersParams = {}, options?: { signal?: AbortSignal }): Promise<unknown> {
761
+ const { kinds, ...rest } = params;
762
+ const query = distressQuery({ kind: kinds?.length ? kinds.join(",") : undefined, ...rest });
763
+ return this.request<unknown>(`/public/letters${query}`, { signal: signalWithTimeout(options?.signal, 30_000) });
764
+ }
765
+
730
766
  async getCloudIpoCalendar(params: IpoCalendarParams = {}, options?: { signal?: AbortSignal }): Promise<IpoCalendarPayload> {
731
767
  const query = new URLSearchParams();
732
768
  for (const [key, value] of Object.entries(params)) {
@@ -38,6 +38,7 @@ export const NUMERIC_FIELDS = [
38
38
  "return1YPercent",
39
39
  "fromHigh52WPercent",
40
40
  "beta",
41
+ "atrPercent",
41
42
  "priceToBook",
42
43
  "evToEbitda",
43
44
  "roePercent",
@@ -58,6 +59,8 @@ export const RESEARCH_FIELDS = [
58
59
  "return1WPercent", "return1MPercent", "return3MPercent", "returnYtdPercent", "return1YPercent", "fromHigh52WPercent",
59
60
  "beta", "priceToBook", "evToEbitda", "roePercent", "epsRevision30dPercent", "analystUpsidePercent", "ivRank", "ivToHv",
60
61
  ] as const;
62
+ /** Average true range as a percent of price; listed only when asked (`include=volatility`), and omitted by a server from before it. */
63
+ export const VOLATILITY_FIELDS = ["atrPercent"] as const;
61
64
  export const CATEGORY_FIELDS = [
62
65
  "symbol",
63
66
  "exchange",
@@ -104,3 +104,94 @@ export interface CarbonHistory {
104
104
  /** Each auction day's price, oldest first. */
105
105
  points: Array<{ date: string; value: number }>;
106
106
  }
107
+
108
+ /** Percent full, TWh in storage, GWh/day flows: as GIE AGSI+ publishes them. */
109
+ interface GasStorageUnits {
110
+ full: "%";
111
+ storage: "TWh";
112
+ flow: "GWh/day";
113
+ }
114
+
115
+ /** The platform's own flag for a gas day. */
116
+ type GasReadingStatus = "estimated" | "confirmed";
117
+
118
+ /** Fullness on the same calendar day in the five years before the gas day's own; fewer where history is short. */
119
+ interface GasFiveYear {
120
+ years: number[];
121
+ min: number;
122
+ max: number;
123
+ average: number;
124
+ /** Percentage points against the average. */
125
+ vsAverage: number;
126
+ /** 0 at the low, 100 at the high, beyond either outside the range; null under two years. */
127
+ position: number | null;
128
+ }
129
+
130
+ /** One area's latest gas day with a reading: the EU aggregate or a member state. */
131
+ export interface GasStorageRow {
132
+ /** EU, or ISO 3166 alpha-2. */
133
+ code: string;
134
+ name: string;
135
+ /** The gas day, dated by its start. */
136
+ gasDay: string | null;
137
+ status: GasReadingStatus | null;
138
+ /** Percent of working gas volume. */
139
+ full: number | null;
140
+ /** TWh. */
141
+ gasInStorage: number | null;
142
+ workingGasVolume: number | null;
143
+ /** GWh/day. */
144
+ injection: number | null;
145
+ withdrawal: number | null;
146
+ previousGasDay: string | null;
147
+ /** Percentage points against the gas day before. */
148
+ change: number | null;
149
+ yearAgo: { gasDay: string; full: number; gasInStorage: number | null; change: number } | null;
150
+ fiveYear: GasFiveYear | null;
151
+ /** Fullness at the end of each week of the year to `gasDay`, oldest first. */
152
+ trail: Array<{ date: string; value: number }>;
153
+ }
154
+
155
+ export interface GasStorageBoard {
156
+ generatedAt: string;
157
+ status: "available" | "unavailable";
158
+ units: GasStorageUnits;
159
+ source: { name: string; url: string };
160
+ /** When every area was last read from the platform. */
161
+ checkedAt: string | null;
162
+ gaps: string[];
163
+ /** The EU aggregate first, then the member states by name. */
164
+ rows: GasStorageRow[];
165
+ }
166
+
167
+ export interface GasStorageHistory {
168
+ code: string;
169
+ name: string;
170
+ units: GasStorageUnits;
171
+ source: { name: string; url: string };
172
+ /** Every gas day with a reading, oldest first; a missing figure is null, never zero. */
173
+ points: Array<{
174
+ date: string;
175
+ status: GasReadingStatus;
176
+ full: number | null;
177
+ gasInStorage: number | null;
178
+ workingGasVolume: number | null;
179
+ injection: number | null;
180
+ withdrawal: number | null;
181
+ }>;
182
+ }
183
+
184
+ /** The latest gas day's calendar year against last year and the five years before, on that year's calendar. */
185
+ export interface GasStorageSeasonal {
186
+ code: string;
187
+ name: string;
188
+ unit: "%";
189
+ source: { name: string; url: string };
190
+ year: number | null;
191
+ /** The band years holding any reading. */
192
+ years: number[];
193
+ current: Array<[string, number]>;
194
+ previous: Array<[string, number]>;
195
+ /** [date, min, average, max, years with a reading]. */
196
+ band: Array<[string, number | null, number | null, number | null, number]>;
197
+ }
@@ -40,6 +40,7 @@ import type {
40
40
 
41
41
  export { setCloudApiFetchTransport, setCloudApiUploadTransport } from "./request";
42
42
  export type { ChatAttachmentUpload } from "./chat";
43
+ export type { ChatPaneLink } from "./chat-pane-link";
43
44
  export { NoteConflictError } from "./notes";
44
45
  export { ThesisConflictError, ThesisGoalpostError } from "./theses";
45
46
  export { TeamRevisionConflictError } from "./views";
@@ -741,6 +742,7 @@ class GloomApiClient {
741
742
  getCloudAnalystResearch = this.data.getCloudAnalystResearch.bind(this.data);
742
743
  getCloudCorporateActions = this.data.getCloudCorporateActions.bind(this.data);
743
744
  getCloudTickerChanges = this.data.getCloudTickerChanges.bind(this.data);
745
+ getCloudOrganizationTickers = this.data.getCloudOrganizationTickers.bind(this.data);
744
746
  getCloudHistory = this.data.getCloudHistory.bind(this.data);
745
747
  getCloudExchangeRate = this.data.getCloudExchangeRate.bind(this.data);
746
748
  getCloudEconomicCalendar = this.data.getCloudEconomicCalendar.bind(this.data);
@@ -772,6 +774,7 @@ class GloomApiClient {
772
774
  getPublicGoingConcern = this.data.getPublicGoingConcern.bind(this.data);
773
775
  getPublicDistressDesignations = this.data.getPublicDistressDesignations.bind(this.data);
774
776
  getPublicInsolvencyNotices = this.data.getPublicInsolvencyNotices.bind(this.data);
777
+ getPublicLetters = this.data.getPublicLetters.bind(this.data);
775
778
  getCloudIpoCalendar = this.data.getCloudIpoCalendar.bind(this.data);
776
779
  getCloudShortVolume = this.data.getCloudShortVolume.bind(this.data);
777
780
  getCloudSocialMentions = this.data.getCloudSocialMentions.bind(this.data);
@@ -817,6 +820,9 @@ class GloomApiClient {
817
820
  getCloudEuropePowerHistory = this.data.getCloudEuropePowerHistory.bind(this.data);
818
821
  getCloudEuropeCarbon = this.data.getCloudEuropeCarbon.bind(this.data);
819
822
  getCloudEuropeCarbonHistory = this.data.getCloudEuropeCarbonHistory.bind(this.data);
823
+ getCloudEuropeGas = this.data.getCloudEuropeGas.bind(this.data);
824
+ getCloudEuropeGasHistory = this.data.getCloudEuropeGasHistory.bind(this.data);
825
+ getCloudEuropeGasSeasonal = this.data.getCloudEuropeGasSeasonal.bind(this.data);
820
826
  getCloudGpuBoard = this.data.getCloudGpuBoard.bind(this.data);
821
827
  getCloudGpuHistory = this.data.getCloudGpuHistory.bind(this.data);
822
828
  getCloudGpuEvents = this.data.getCloudGpuEvents.bind(this.data);
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Public fund and investor letters: a link-only list of each letter's title,
3
+ * who wrote it, when, what kind it is and where its publisher keeps it. The
4
+ * route is anonymous; the Letters code of Topic News parses the answer before
5
+ * drawing anything.
6
+ */
7
+ export type LetterKind = "shareholder_letter" | "memo" | "commentary" | "annual_report";
8
+
9
+ /**
10
+ * What a letter's date is: the date its publisher states, its EDGAR filing
11
+ * date, or the last change of its document file (for publishers whose pages
12
+ * give no date), which is not a publication date.
13
+ */
14
+ export type LetterDateBasis = "published" | "filed" | "file_modified";
15
+
16
+ export interface LettersParams {
17
+ /** Any of these kinds, sent as one comma-separated `kind`; every kind when absent. */
18
+ kinds?: readonly LetterKind[];
19
+ /** 1 to 200. */
20
+ limit?: number;
21
+ offset?: number;
22
+ }
23
+
24
+ export interface Letter {
25
+ id: string;
26
+ title: string;
27
+ entity: string;
28
+ kind: LetterKind;
29
+ /** `YYYY-MM-DD`; `date_basis` says what it is. */
30
+ date: string;
31
+ date_basis: LetterDateBasis;
32
+ /** The publisher's own page or file. */
33
+ url: string;
34
+ }
35
+
36
+ export interface LettersPayload {
37
+ items: Letter[];
38
+ hasMore: boolean;
39
+ limit: number;
40
+ offset: number;
41
+ }
@@ -9,6 +9,8 @@ import type {
9
9
  ChatReaction,
10
10
  ChatReactionUser,
11
11
  ChatStateResponse,
12
+ ChatUserBadge,
13
+ ChatUserSummary,
12
14
  CloudSavedSearch,
13
15
  CloudSearchHit,
14
16
  CloudSearchResponse,
@@ -17,6 +19,7 @@ import type {
17
19
  TeamNotification,
18
20
  } from "./types";
19
21
  import { normalizeTimestamp } from "../utils/timestamp";
22
+ import { parseChatPaneLink } from "./chat-pane-link";
20
23
 
21
24
  /**
22
25
  * Saved-search writes answer with either the record itself or `{ search }`.
@@ -82,17 +85,37 @@ export function normalizeChatAttachments(value: unknown): ChatAttachment[] {
82
85
  const CHAT_ACCOUNT_TYPES: readonly string[] = ["human", "managed_llm", "discord"] satisfies ChatAccountType[];
83
86
  const CHAT_MESSAGE_ORIGINS: readonly string[] = ["app", "discord"] satisfies ChatMessageOrigin[];
84
87
 
85
- /** A value a newer server adds that this client does not know reads as absent: a human, a message from the app. */
86
- function chatUserWithKnownAccountType(user: ChatMessage["user"]): ChatMessage["user"] {
87
- if (!user || user.accountType === undefined || CHAT_ACCOUNT_TYPES.includes(user.accountType)) return user;
88
- const { accountType: _unknown, ...known } = user;
89
- return known;
88
+ const CHAT_USER_BADGES: readonly string[] = ["team", "pro"] satisfies ChatUserBadge[];
89
+
90
+ /**
91
+ * A value a newer server adds that this client does not know reads as absent:
92
+ * a human, a message from the app, no badge. A clock or desks that are not
93
+ * what the server promises are dropped rather than shown wrong.
94
+ */
95
+ function normalizeChatUser(user: ChatUserSummary): ChatUserSummary {
96
+ if (!user || typeof user !== "object") return user;
97
+ const { accountType, badges, utcOffsetMinutes, desks, ...rest } = user;
98
+ const knownBadges = Array.isArray(badges)
99
+ ? CHAT_USER_BADGES.filter((badge) => badges.includes(badge as ChatUserBadge)) as ChatUserBadge[]
100
+ : [];
101
+ const knownDesks = Array.isArray(desks) ? desks.filter((desk): desk is string => typeof desk === "string" && !!desk) : [];
102
+ const offset = typeof utcOffsetMinutes === "number" && Number.isInteger(utcOffsetMinutes)
103
+ && utcOffsetMinutes >= -12 * 60 && utcOffsetMinutes <= 14 * 60
104
+ ? utcOffsetMinutes
105
+ : null;
106
+ return {
107
+ ...rest,
108
+ ...(accountType !== undefined && CHAT_ACCOUNT_TYPES.includes(accountType) ? { accountType } : {}),
109
+ ...(knownBadges.length > 0 ? { badges: knownBadges } : {}),
110
+ ...(offset !== null ? { utcOffsetMinutes: offset } : {}),
111
+ ...(knownDesks.length > 0 ? { desks: knownDesks } : {}),
112
+ };
90
113
  }
91
114
 
92
115
  export function normalizeChatMessage(message: ChatMessage): ChatMessage {
93
116
  const normalized: ChatMessage = {
94
117
  ...message,
95
- user: chatUserWithKnownAccountType(message.user),
118
+ user: normalizeChatUser(message.user),
96
119
  content: typeof message.content === "string" ? message.content : "",
97
120
  createdAt: normalizeTimestamp(message.createdAt),
98
121
  ...(message.editedAt
@@ -105,6 +128,11 @@ export function normalizeChatMessage(message: ChatMessage): ChatMessage {
105
128
  if (message.attachmentReview !== "pending" && message.attachmentReview !== "failed") {
106
129
  delete normalized.attachmentReview;
107
130
  }
131
+ if ("paneLink" in message) {
132
+ const paneLink = parseChatPaneLink(message.paneLink);
133
+ if (paneLink) normalized.paneLink = paneLink;
134
+ else delete normalized.paneLink;
135
+ }
108
136
  return normalized;
109
137
  }
110
138
 
@@ -171,6 +199,8 @@ export function normalizeChatChannel(
171
199
  ...channel,
172
200
  kind: channel.kind ?? fallbackKind,
173
201
  created_at: normalizeTimestamp(channel.created_at),
202
+ ...(channel.dmUser ? { dmUser: normalizeChatUser(channel.dmUser) } : {}),
203
+ ...(Array.isArray(channel.members) ? { members: channel.members.map(normalizeChatUser) } : {}),
174
204
  };
175
205
  }
176
206
 
@@ -0,0 +1,44 @@
1
+ /** Names per request: one page of a list, such as a clinical trial search page's lead sponsors. */
2
+ export const ORGANIZATION_TICKERS_BATCH_LIMIT = 50;
3
+
4
+ /**
5
+ * What Gloom Cloud resolved an organization name to: the listing of the
6
+ * company the name belongs to, or why there is none. The match is the
7
+ * server's, from reviewed and exact aliases only; the app never matches a
8
+ * name to a ticker itself.
9
+ */
10
+ export interface OrganizationTicker {
11
+ name: string;
12
+ /** Null when the name is unlinked, ambiguous, or resolved to a company without a listing. */
13
+ ticker: string | null;
14
+ exchange: string | null;
15
+ /** The company the name resolved to; null when unlinked. */
16
+ entityName: string | null;
17
+ resolution: "identifier" | "alias" | "unresolved";
18
+ /** Left unlinked because the name fits more than one company. */
19
+ ambiguous: boolean;
20
+ }
21
+
22
+ export interface OrganizationTickersResponse {
23
+ results: OrganizationTicker[];
24
+ }
25
+
26
+ function isResult(value: unknown): value is OrganizationTicker {
27
+ if (!value || typeof value !== "object") return false;
28
+ const row = value as Record<string, unknown>;
29
+ const text = (field: unknown) => field === null || typeof field === "string";
30
+ return typeof row.name === "string"
31
+ && text(row.ticker) && text(row.exchange) && text(row.entityName)
32
+ && (row.resolution === "identifier" || row.resolution === "alias" || row.resolution === "unresolved")
33
+ && typeof row.ambiguous === "boolean";
34
+ }
35
+
36
+ /** The answers in the order asked; throws when the server answered something else. */
37
+ export function validateOrganizationTickers(payload: unknown, names: readonly string[]): OrganizationTicker[] {
38
+ const results = (payload as Partial<OrganizationTickersResponse> | null)?.results;
39
+ if (!Array.isArray(results) || results.length !== names.length || !results.every(isResult)
40
+ || results.some((result, index) => result.name !== names[index])) {
41
+ throw new Error("The server returned invalid organization tickers.");
42
+ }
43
+ return results;
44
+ }
@@ -410,12 +410,14 @@ export class CloudApiRequestTransport {
410
410
  // Cloud outage in the connections pane. Nor must the map's probe for geo
411
411
  // layers, which a server without them answers with 404, or a ticker
412
412
  // change lookup: a note beside a quote, never one of the refused market
413
- // requests a lookup of many symbols waits out and retries.
413
+ // requests a lookup of many symbols waits out and retries. Nor the
414
+ // tickers beside a list of organization names, which say so in place.
414
415
  if (
415
416
  path.startsWith("/telemetry/")
416
417
  || path === "/assist/searches"
417
418
  || path === "/cloud/geo/layers"
418
419
  || path.startsWith("/market/ticker-changes?")
420
+ || path === "/market/organization-tickers"
419
421
  ) return request();
420
422
  return this.connectionHealth.track(
421
423
  GLOOM_CLOUD_HTTP_CONNECTION_ID,
@@ -9,6 +9,7 @@ import type {
9
9
  TickerFinancials,
10
10
  } from "../types/financials";
11
11
  import type { SyncSettings, SyncSnapshot } from "../sync/types";
12
+ import type { ChatPaneLink } from "./chat-pane-link";
12
13
 
13
14
  /**
14
15
  * What stands behind a chat account. `discord` is a person on the Gloom
@@ -20,6 +21,9 @@ export type ChatAccountType = "human" | "managed_llm" | "discord";
20
21
  /** Where a message was written. A message without one came from the app. */
21
22
  export type ChatMessageOrigin = "app" | "discord";
22
23
 
24
+ /** A badge an account earns in chat: `team` for Gloom staff, `pro` with Pro access. */
25
+ export type ChatUserBadge = "team" | "pro";
26
+
23
27
  export interface ChatUserSummary {
24
28
  id: string;
25
29
  /** A person, a managed assistant (Gloombot), or a Discord user bridged into public channels. */
@@ -32,6 +36,15 @@ export interface ChatUserSummary {
32
36
  profilePublic?: boolean;
33
37
  acceptUnknownDms?: boolean;
34
38
  portfolioAnalytics?: PublicPortfolioAnalytics | null;
39
+ /** Badges the account earns. A server that predates them, or a person with none, leaves this out. */
40
+ badges?: ChatUserBadge[];
41
+ /**
42
+ * The person's current offset from UTC in minutes, as their app last
43
+ * reported it: left out while they keep their local time hidden.
44
+ */
45
+ utcOffsetMinutes?: number;
46
+ /** The desks (equities, options, ...) the person chose to show; left out unless they did. */
47
+ desks?: string[];
35
48
  }
36
49
 
37
50
  /**
@@ -105,6 +118,8 @@ export interface ChatMessage {
105
118
  * message yet.
106
119
  */
107
120
  attachmentReview?: "pending" | "failed";
121
+ /** What the message opens in the terminal (a Gloom Wire story or calendar). Only the server sets it. */
122
+ paneLink?: ChatPaneLink;
108
123
  clientStatus?: "sending" | "failed";
109
124
  clientError?: string | null;
110
125
  /** The idempotency key a local send went out with, so a retry repeats it. */
@@ -208,6 +223,12 @@ export interface AccountProfile {
208
223
  acceptUnknownDms: boolean;
209
224
  chatEmailNotificationsEnabled: boolean;
210
225
  portfolioAnalytics?: PublicPortfolioAnalytics | null;
226
+ /** Chat shows your local time on your card. A server that predates the setting leaves it out. */
227
+ showLocalTime?: boolean;
228
+ /** The desks chat shows beside your name; empty shows none. */
229
+ chatDesks?: string[];
230
+ /** Your answer to "What do you trade?" at the first run: private, it pre-fills `chatDesks`. */
231
+ onboardingDesks?: string[];
211
232
  syncEnabled: boolean;
212
233
  weeklyRoundupEnabled: boolean;
213
234
  positionAlertsEnabled: boolean;
@@ -351,6 +372,9 @@ export type AccountProfileUpdate = Partial<{
351
372
  syncEnabled: boolean;
352
373
  weeklyRoundupEnabled: boolean;
353
374
  positionAlertsEnabled: boolean;
375
+ showLocalTime: boolean;
376
+ /** Null or empty shows no desks. */
377
+ chatDesks: string[] | null;
354
378
  }>;
355
379
 
356
380
  export interface CloudSyncSnapshotResponse {
@@ -1421,7 +1445,9 @@ export interface CloudHistoryCoverage {
1421
1445
  /** First bar date (YYYY-MM-DD) the source serves, such as a fund's inception. */
1422
1446
  firstAllowedBarDate?: string;
1423
1447
  inceptionDate?: string;
1424
- sourceUrl: string;
1448
+ /** Why nothing earlier is served, such as "old-shekel quotes before 1992 are not served". */
1449
+ note?: string;
1450
+ sourceUrl?: string;
1425
1451
  firstBarDate: string | null;
1426
1452
  lastBarDate: string | null;
1427
1453
  barCount: number;
@@ -13,7 +13,8 @@ import type { DesktopWindowBridge } from "../../types/desktop-window";
13
13
  import { requestAccountManagementTab } from "../../plugins/builtin/account-management/navigation";
14
14
  import { openTeamPane } from "../../plugins/builtin/cloud/team/pane-request";
15
15
  import { chatController } from "../../plugins/builtin/chat/controller";
16
- import { getShare } from "../../shares/api";
16
+ import { getShare, type ShareRecord } from "../../shares/api";
17
+ import { openShareLink, registerShareOpener } from "../../shares/open";
17
18
  import { openPaneShare } from "../../shares/pane";
18
19
  import { PluginOffError, notifyPluginOff } from "../../plugins/plugin-off";
19
20
  import { materializeMarketplaceLayout } from "../../shares/portable-layout";
@@ -514,15 +515,44 @@ async function handleOpenLayout(
514
515
  notifySuccess(options.pluginRegistry, `Layout "${entry.name}" added.`);
515
516
  }
516
517
 
517
- async function handleOpenShare(
518
- action: Extract<DesktopDeepLinkAction, { type: "open-share" }>,
518
+ /**
519
+ * Opens a pane share in this app: the web app's `?share=` hand-off, a
520
+ * gloomberb://share link and a share link in chat all come through here.
521
+ * With `fallback`, a share this app cannot read or that holds a snapshot
522
+ * goes there (its web page) instead of to an error.
523
+ */
524
+ async function openShareInApp(
525
+ id: string,
519
526
  pluginRegistry: PluginRegistry,
527
+ options: { retry: () => void; fallback?: () => void },
520
528
  ): Promise<void> {
521
- const share = await getShare(action.id, fetch, { trackView: false });
522
- if (!share) throw new Error("This share is unavailable or has expired.");
523
- if (share.kind !== "pane") throw new Error("This link contains a snapshot, not a live pane.");
524
- await openPaneShare(pluginRegistry, share.data);
525
- notifySuccess(pluginRegistry, action.message);
529
+ let share: ShareRecord | null;
530
+ try {
531
+ share = await getShare(id, fetch, { trackView: false });
532
+ } catch (error) {
533
+ if (options.fallback) options.fallback();
534
+ else notifyError(pluginRegistry, error instanceof Error ? error.message : "Could not open shared pane.");
535
+ return;
536
+ }
537
+ if (!share) {
538
+ notifyError(pluginRegistry, "This share is unavailable or has expired.");
539
+ return;
540
+ }
541
+ if (share.kind !== "pane") {
542
+ if (options.fallback) options.fallback();
543
+ else notifyError(pluginRegistry, "This link contains a snapshot, not a live pane.");
544
+ return;
545
+ }
546
+ try {
547
+ await openPaneShare(pluginRegistry, share.data);
548
+ notifySuccess(pluginRegistry, "Opened shared pane.");
549
+ } catch (error) {
550
+ if (error instanceof PluginOffError) {
551
+ notifyPluginOff(pluginRegistry, error, options.retry);
552
+ return;
553
+ }
554
+ notifyError(pluginRegistry, error instanceof Error ? error.message : "Could not open shared pane.");
555
+ }
526
556
  }
527
557
 
528
558
  export function handleDesktopDeepLink(rawUrl: string, options: DesktopDeepLinkHandlerOptions): void {
@@ -569,13 +599,7 @@ export function handleDesktopDeepLink(rawUrl: string, options: DesktopDeepLinkHa
569
599
  handleOpenNews(action, options.pluginRegistry);
570
600
  return;
571
601
  case "open-share":
572
- void handleOpenShare(action, options.pluginRegistry).catch((error) => {
573
- if (error instanceof PluginOffError) {
574
- notifyPluginOff(options.pluginRegistry, error, () => handleDesktopDeepLink(rawUrl, options));
575
- return;
576
- }
577
- notifyError(options.pluginRegistry, error instanceof Error ? error.message : "Could not open shared pane.");
578
- });
602
+ void openShareInApp(action.id, options.pluginRegistry, { retry: () => handleDesktopDeepLink(rawUrl, options) });
579
603
  return;
580
604
  case "open-layout":
581
605
  void handleOpenLayout(action, options).catch((error) => {
@@ -600,6 +624,15 @@ export function useDesktopDeepLinkRuntime({
600
624
  pluginRegistry: PluginRegistry;
601
625
  stateRef: { current: AppState };
602
626
  }) {
627
+ // Share links opened inside the app (chat's share chips) take the same path, in every renderer.
628
+ useEffect(() => {
629
+ if (!initialized) return;
630
+ return registerShareOpener((id, { fallback }) => openShareInApp(id, pluginRegistry, {
631
+ fallback,
632
+ retry: () => openShareLink(id, fallback),
633
+ }));
634
+ }, [initialized, pluginRegistry]);
635
+
603
636
  useEffect(() => {
604
637
  if (!initialized || desktopWindowKind === "detached" || !desktopDeepLinkBridge) return;
605
638
  return desktopDeepLinkBridge.subscribe((deeplink) => {