gloomberb 0.10.5 → 0.11.1

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 (660) hide show
  1. package/README.md +58 -9
  2. package/README.zh-CN.md +6 -6
  3. package/package.json +12 -3
  4. package/src/api-client/auth.ts +48 -10
  5. package/src/api-client/data.ts +72 -0
  6. package/src/api-client/index.test.ts +158 -0
  7. package/src/api-client/index.ts +115 -4
  8. package/src/api-client/paths.ts +73 -0
  9. package/src/api-client/request.test.ts +30 -0
  10. package/src/api-client/request.ts +58 -21
  11. package/src/api-client/socket-health.test.ts +80 -0
  12. package/src/api-client/socket.ts +110 -6
  13. package/src/api-client/types.ts +267 -0
  14. package/src/app/global-shortcuts.test.tsx +144 -9
  15. package/src/app/global-shortcuts.ts +33 -11
  16. package/src/app/runtime/broker-import.ts +39 -5
  17. package/src/app/runtime/startup.ts +7 -1
  18. package/src/app/runtime/update.ts +32 -5
  19. package/src/app.tsx +48 -24
  20. package/src/architecture/import-boundaries.test.ts +5 -1
  21. package/src/brokers/broker-ticker-sync.ts +3 -8
  22. package/src/brokers/sync-broker-instance.test.ts +59 -0
  23. package/src/brokers/sync-broker-instance.ts +37 -13
  24. package/src/capabilities/chart-series.ts +318 -0
  25. package/src/capabilities/factories.ts +50 -0
  26. package/src/capabilities/index.ts +1 -0
  27. package/src/capabilities/registry.test.ts +135 -0
  28. package/src/capabilities/registry.ts +31 -4
  29. package/src/capabilities/types.ts +52 -0
  30. package/src/cli/commands/overview.ts +2 -2
  31. package/src/cli/desktop-pane-shot.ts +2 -0
  32. package/src/cli/index.ts +10 -4
  33. package/src/cli/pane-functions/catalog.ts +4 -0
  34. package/src/cli/pane-functions/data.ts +6 -1
  35. package/src/cli/pane-functions/discovery.ts +76 -10
  36. package/src/cli/pane-functions/report.test.ts +76 -0
  37. package/src/cli/pane-functions/report.ts +7 -1
  38. package/src/cli/pane-functions/resolver.ts +4 -1
  39. package/src/cli/pane-functions/screenshot.test.ts +56 -3
  40. package/src/cli/pane-functions/screenshot.ts +68 -5
  41. package/src/cli/registry.ts +1 -1
  42. package/src/components/chart/composite/composite-chart.test.tsx +54 -0
  43. package/src/components/chart/composite/composite-chart.tsx +96 -18
  44. package/src/components/chart/composite/downsample.test.ts +136 -0
  45. package/src/components/chart/composite/downsample.ts +208 -0
  46. package/src/components/chart/composite/format.test.ts +34 -0
  47. package/src/components/chart/composite/format.ts +33 -10
  48. package/src/components/chart/composite/panel-series.test.ts +66 -0
  49. package/src/components/chart/composite/panel-series.ts +101 -0
  50. package/src/components/chart/core/axis-format.ts +13 -3
  51. package/src/components/chart/core/index.test.ts +7 -0
  52. package/src/components/chart/core/renderer.ts +3 -4
  53. package/src/components/chart/multi-line/renderer.ts +1 -0
  54. package/src/components/chart/native/kitty/index.test.ts +25 -3
  55. package/src/components/chart/native/kitty/support.ts +37 -3
  56. package/src/components/chart/native/use-chart-text-fallback.ts +19 -0
  57. package/src/components/chart/static/chart/surface.tsx +43 -12
  58. package/src/components/chart/static/multi-line-chart-surface.tsx +41 -5
  59. package/src/components/command-bar/commands/direct/index.ts +18 -0
  60. package/src/components/command-bar/commands/registry.ts +14 -0
  61. package/src/components/command-bar/routes/root/article-results.test.ts +83 -0
  62. package/src/components/command-bar/routes/root/article-results.ts +55 -0
  63. package/src/components/command-bar/routes/root/command-items.ts +3 -0
  64. package/src/components/command-bar/routes/root/results.ts +38 -11
  65. package/src/components/command-bar/routes/root/runtime.ts +6 -2
  66. package/src/components/command-bar/routes/ticker-search/actions.ts +1 -1
  67. package/src/components/command-bar/surface/assist.test.tsx +13 -6
  68. package/src/components/command-bar/surface/index.tsx +39 -1
  69. package/src/components/command-bar/view-model.test.ts +19 -0
  70. package/src/components/command-bar/view-model.ts +39 -3
  71. package/src/components/command-bar/workflow/collection-actions.test.ts +94 -0
  72. package/src/components/command-bar/workflow/collection-actions.ts +14 -3
  73. package/src/components/command-bar/workflow/ops.test.ts +136 -0
  74. package/src/components/command-bar/workflow/ops.ts +71 -1
  75. package/src/components/company-logo.test.ts +19 -0
  76. package/src/components/company-logo.tsx +60 -0
  77. package/src/components/feed-data-table/stack-view.tsx +7 -1
  78. package/src/components/index.ts +2 -1
  79. package/src/components/layout/detached-pane-shell.tsx +11 -5
  80. package/src/components/layout/header.test.tsx +35 -0
  81. package/src/components/layout/header.tsx +106 -14
  82. package/src/components/layout/pane/cycle-order.test.ts +0 -1
  83. package/src/components/layout/pane/footer/index.test.tsx +19 -19
  84. package/src/components/layout/pane/header.tsx +2 -13
  85. package/src/components/layout/shell/index.tsx +5 -0
  86. package/src/components/layout/status-bar.test.tsx +36 -1
  87. package/src/components/layout/status-bar.tsx +48 -2
  88. package/src/components/onboarding/account-step/chooser-panel.tsx +27 -50
  89. package/src/components/onboarding/account-step/form-panel.tsx +88 -30
  90. package/src/components/onboarding/account-step/index.tsx +11 -2
  91. package/src/components/onboarding/account-step/qr-panel.tsx +68 -0
  92. package/src/components/onboarding/account-step/signed-in-panel.tsx +5 -8
  93. package/src/components/onboarding/onboarding-frame.tsx +517 -0
  94. package/src/components/onboarding/onboarding-steps.tsx +0 -264
  95. package/src/components/onboarding/onboarding-wizard.test.tsx +665 -196
  96. package/src/components/onboarding/onboarding-wizard.tsx +857 -267
  97. package/src/components/onboarding/portfolio-step/broker-fields-panel.tsx +81 -51
  98. package/src/components/onboarding/portfolio-step/broker-setup-panel.tsx +14 -20
  99. package/src/components/onboarding/portfolio-step/broker-sync-panel.tsx +8 -11
  100. package/src/components/onboarding/portfolio-step/choose-panel.tsx +18 -22
  101. package/src/components/onboarding/portfolio-step/index.tsx +3 -0
  102. package/src/components/onboarding/portfolio-step/types.ts +3 -0
  103. package/src/components/onboarding/wizard-account.ts +41 -25
  104. package/src/components/onboarding/wizard-broker-sync.ts +45 -67
  105. package/src/components/onboarding/wizard-model.ts +18 -2
  106. package/src/components/table-view-shared.test.ts +11 -0
  107. package/src/components/table-view-shared.tsx +26 -0
  108. package/src/components/ui/data-table/index.tsx +19 -1
  109. package/src/components/ui/data-table/opentui/index.tsx +29 -9
  110. package/src/components/ui/data-table/types.ts +2 -0
  111. package/src/components/ui/external-link.tsx +4 -2
  112. package/src/components/ui/fields.tsx +5 -1
  113. package/src/components/ui/loading.tsx +1 -1
  114. package/src/components/ui/status.tsx +76 -4
  115. package/src/components/ui/table-layout.test.ts +24 -0
  116. package/src/components/ui/table-layout.ts +90 -23
  117. package/src/components/ui/tabs.tsx +86 -4
  118. package/src/core/app-services.ts +14 -3
  119. package/src/core/connection-health.test.ts +143 -0
  120. package/src/core/connection-health.ts +295 -0
  121. package/src/core/state/app/layout-reducer.ts +34 -0
  122. package/src/core/state/app/layout.ts +21 -0
  123. package/src/core/state/app/state.ts +10 -0
  124. package/src/core/state/app/types.ts +7 -1
  125. package/src/data/config/chart-settings.ts +11 -2
  126. package/src/data/config/store/index.test.ts +83 -9
  127. package/src/data/config/store/migrations.ts +16 -0
  128. package/src/data/config/store/normalize.ts +59 -1
  129. package/src/data/fred-series.ts +8 -2
  130. package/src/i18n/es.ts +135 -0
  131. package/src/i18n/ja.ts +135 -0
  132. package/src/i18n/ko.ts +135 -0
  133. package/src/i18n/zh-cn.ts +135 -0
  134. package/src/i18n/zh-tw.ts +135 -0
  135. package/src/market-data/coordinator/chart.ts +3 -2
  136. package/src/market-data/coordinator/index.test.ts +2 -2
  137. package/src/market-data/coordinator/index.ts +1 -1
  138. package/src/market-data/hooks.tsx +3 -2
  139. package/src/market-data/market/format.test.ts +7 -0
  140. package/src/market-data/market/freshness.ts +5 -2
  141. package/src/market-data/market/status.test.ts +12 -0
  142. package/src/market-data/market/status.ts +34 -17
  143. package/src/market-data/quotes/freshness.test.ts +16 -0
  144. package/src/news/aggregator.test.ts +131 -3
  145. package/src/news/aggregator.ts +163 -27
  146. package/src/news/hooks.test.tsx +2 -0
  147. package/src/news/hooks.ts +31 -1
  148. package/src/news/news-model.ts +5 -2
  149. package/src/news/types.ts +7 -0
  150. package/src/notifications/app-notifier.test.ts +28 -6
  151. package/src/notifications/app-notifier.ts +24 -5
  152. package/src/plugins/broker-sync/broker-sync.test.ts +159 -0
  153. package/src/plugins/broker-sync/native-loader.ts +10 -0
  154. package/src/plugins/broker-sync/normalize.ts +301 -0
  155. package/src/plugins/broker-sync/public.ts +122 -0
  156. package/src/plugins/broker-sync/robinhood-native.ts +367 -0
  157. package/src/plugins/broker-sync/robinhood.ts +114 -0
  158. package/src/plugins/broker-sync/simplefin-native.ts +201 -0
  159. package/src/plugins/broker-sync/simplefin.ts +70 -0
  160. package/src/plugins/builtin/account-management/footer.ts +8 -19
  161. package/src/plugins/builtin/account-management/pane.tsx +18 -13
  162. package/src/plugins/builtin/ai/pi/host.ts +6 -5
  163. package/src/plugins/builtin/ai/providers.ts +1 -1
  164. package/src/plugins/builtin/ai/screener/footer.ts +0 -7
  165. package/src/plugins/builtin/ai/screener/pane.test.tsx +2 -1
  166. package/src/plugins/builtin/ai/screener/pane.tsx +3 -1
  167. package/src/plugins/builtin/ai/screener/results-view.tsx +8 -0
  168. package/src/plugins/builtin/ai/screener/runner.ts +7 -3
  169. package/src/plugins/builtin/ai/workspace/pane.tsx +13 -2
  170. package/src/plugins/builtin/alerts/alert-engine.test.ts +38 -1
  171. package/src/plugins/builtin/alerts/alert-engine.ts +43 -1
  172. package/src/plugins/builtin/alerts/constants.ts +2 -1
  173. package/src/plugins/builtin/alerts/index.test.tsx +49 -6
  174. package/src/plugins/builtin/alerts/index.tsx +68 -36
  175. package/src/plugins/builtin/alerts/pane.tsx +64 -64
  176. package/src/plugins/builtin/alerts/quotes.ts +11 -3
  177. package/src/plugins/builtin/alerts/types.ts +1 -0
  178. package/src/plugins/builtin/analytics/index.tsx +32 -12
  179. package/src/plugins/builtin/analytics/pane-model.ts +54 -18
  180. package/src/plugins/builtin/analytics/portfolio-selection.ts +19 -0
  181. package/src/plugins/builtin/analytics/view.tsx +5 -3
  182. package/src/plugins/builtin/broker-manager/detail.tsx +7 -2
  183. package/src/plugins/builtin/broker-manager/footer.ts +4 -9
  184. package/src/plugins/builtin/broker-manager/index.tsx +1 -6
  185. package/src/plugins/builtin/buildout/detail/ui.tsx +15 -4
  186. package/src/plugins/builtin/buildout/model/index.ts +1 -1
  187. package/src/plugins/builtin/buildout/pane/header.tsx +1 -0
  188. package/src/plugins/builtin/buildout/pane/index.tsx +17 -18
  189. package/src/plugins/builtin/changelog/index.tsx +60 -45
  190. package/src/plugins/builtin/chart-composer/catalog-inventory.test.ts +97 -0
  191. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +366 -0
  192. package/src/plugins/builtin/chart-composer/chart-spec.ts +21 -1
  193. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +307 -0
  194. package/src/plugins/builtin/chart-composer/editor/controller.ts +1 -1
  195. package/src/plugins/builtin/chart-composer/index.tsx +45 -5
  196. package/src/plugins/builtin/chart-composer/pane.tsx +29 -89
  197. package/src/plugins/builtin/chart-composer/presets.test.ts +48 -0
  198. package/src/plugins/builtin/chart-composer/presets.ts +69 -3
  199. package/src/plugins/builtin/chart-composer/quick-add.tsx +8 -5
  200. package/src/plugins/builtin/chart-composer/semantic.ts +18 -14
  201. package/src/plugins/builtin/chart-composer/series-catalog.test.ts +39 -0
  202. package/src/plugins/builtin/chart-composer/series-catalog.ts +104 -3
  203. package/src/plugins/builtin/chart-composer/shortcuts.test.ts +10 -10
  204. package/src/plugins/builtin/chart-composer/shortcuts.ts +4 -12
  205. package/src/plugins/builtin/chart-composer/use-series-catalog.ts +230 -23
  206. package/src/plugins/builtin/chat/chat.test.tsx +22 -0
  207. package/src/plugins/builtin/chat/content/composer.tsx +6 -4
  208. package/src/plugins/builtin/chat/content/index.tsx +7 -0
  209. package/src/plugins/builtin/chat/content/snapshot.ts +9 -2
  210. package/src/plugins/builtin/chat/content/transcript.tsx +10 -0
  211. package/src/plugins/builtin/chat/controller/channel-actions.ts +11 -1
  212. package/src/plugins/builtin/chat/controller/channels.ts +20 -4
  213. package/src/plugins/builtin/chat/controller/index.ts +67 -15
  214. package/src/plugins/builtin/chat/controller/lifecycle.ts +3 -0
  215. package/src/plugins/builtin/chat/controller/message-loading.ts +5 -1
  216. package/src/plugins/builtin/chat/controller/messages.ts +3 -1
  217. package/src/plugins/builtin/chat/controller/notifications.ts +32 -11
  218. package/src/plugins/builtin/chat/controller/persistence.ts +0 -3
  219. package/src/plugins/builtin/chat/controller/realtime.ts +3 -3
  220. package/src/plugins/builtin/chat/controller/send.ts +1 -3
  221. package/src/plugins/builtin/chat/controller/session-runtime.ts +4 -9
  222. package/src/plugins/builtin/chat/controller/state.ts +8 -3
  223. package/src/plugins/builtin/chat/controller/storage.ts +0 -2
  224. package/src/plugins/builtin/chat/controller/view.ts +3 -2
  225. package/src/plugins/builtin/chat/controller.test.ts +195 -28
  226. package/src/plugins/builtin/chat/profile-popover.ts +1 -1
  227. package/src/plugins/builtin/cloud/auth-actions.tsx +14 -7
  228. package/src/plugins/builtin/cloud/auth-commands.ts +38 -46
  229. package/src/plugins/builtin/cloud/auth-dialog.tsx +296 -0
  230. package/src/{components/onboarding/account-step/model.test.ts → plugins/builtin/cloud/auth-model.test.ts} +1 -1
  231. package/src/{components/onboarding/account-step/model.ts → plugins/builtin/cloud/auth-model.ts} +37 -6
  232. package/src/plugins/builtin/cloud/browser.tsx +9 -0
  233. package/src/plugins/builtin/cloud/buildout-module.tsx +23 -0
  234. package/src/plugins/builtin/cloud/device-signin-dialog.tsx +243 -0
  235. package/src/plugins/builtin/cloud/device-signin.test.ts +217 -0
  236. package/src/plugins/builtin/cloud/device-signin.ts +234 -0
  237. package/src/plugins/builtin/cloud/index.tsx +2 -0
  238. package/src/plugins/builtin/cloud/plugin.tsx +10 -24
  239. package/src/plugins/builtin/cloud/upgrade-command.ts +13 -6
  240. package/src/plugins/builtin/cloud-tweets/registration.ts +6 -1
  241. package/src/plugins/builtin/cloud-tweets/table.tsx +57 -10
  242. package/src/plugins/builtin/composite-plugins.ts +39 -5
  243. package/src/plugins/builtin/congress-trades/detail.tsx +45 -5
  244. package/src/plugins/builtin/congress-trades/footer.ts +7 -16
  245. package/src/plugins/builtin/congress-trades/model.test.ts +85 -0
  246. package/src/plugins/builtin/congress-trades/model.ts +69 -1
  247. package/src/plugins/builtin/congress-trades/pane.tsx +63 -22
  248. package/src/plugins/builtin/connections/index.ts +53 -0
  249. package/src/plugins/builtin/connections/pane.test.tsx +79 -0
  250. package/src/plugins/builtin/connections/pane.tsx +258 -0
  251. package/src/plugins/builtin/correlation/index.test.tsx +2 -1
  252. package/src/plugins/builtin/correlation/index.tsx +58 -19
  253. package/src/plugins/builtin/correlation/matrix/model.ts +18 -7
  254. package/src/plugins/builtin/correlation/relationship/pane.tsx +43 -19
  255. package/src/plugins/builtin/correlation/settings.ts +1 -1
  256. package/src/plugins/builtin/credit-conditions/client.test.ts +80 -0
  257. package/src/plugins/builtin/credit-conditions/client.ts +105 -0
  258. package/src/plugins/builtin/credit-conditions/index.test.tsx +111 -0
  259. package/src/plugins/builtin/credit-conditions/index.tsx +227 -0
  260. package/src/plugins/builtin/credit-conditions/model.test.ts +43 -0
  261. package/src/plugins/builtin/credit-conditions/model.ts +64 -0
  262. package/src/plugins/builtin/debug/index.tsx +11 -6
  263. package/src/plugins/builtin/dividend-yield/client.test.ts +58 -0
  264. package/src/plugins/builtin/dividend-yield/client.ts +270 -0
  265. package/src/plugins/builtin/dividend-yield/index.tsx +61 -0
  266. package/src/plugins/builtin/dividend-yield/model.ts +83 -0
  267. package/src/plugins/builtin/dividend-yield/pane.tsx +306 -0
  268. package/src/plugins/builtin/dividend-yield/types.ts +22 -0
  269. package/src/plugins/builtin/earnings/data/cache.test.ts +10 -7
  270. package/src/plugins/builtin/earnings/data/cache.ts +23 -9
  271. package/src/plugins/builtin/earnings/index.tsx +59 -6
  272. package/src/plugins/builtin/earnings/model.test.ts +19 -1
  273. package/src/plugins/builtin/earnings/model.ts +17 -15
  274. package/src/plugins/builtin/econ/calendar-model.test.ts +37 -3
  275. package/src/plugins/builtin/econ/calendar-model.ts +32 -26
  276. package/src/plugins/builtin/econ/fred-series-map.ts +18 -0
  277. package/src/plugins/builtin/econ/index.test.tsx +110 -0
  278. package/src/plugins/builtin/econ/index.tsx +95 -52
  279. package/src/plugins/builtin/fear-greed/cache.test.ts +18 -3
  280. package/src/plugins/builtin/fear-greed/cache.ts +18 -6
  281. package/src/plugins/builtin/fear-greed/charts.tsx +9 -2
  282. package/src/plugins/builtin/fear-greed/format.ts +0 -9
  283. package/src/plugins/builtin/fear-greed/pane.tsx +21 -21
  284. package/src/plugins/builtin/futures/contracts.ts +100 -0
  285. package/src/plugins/builtin/futures/index.tsx +298 -0
  286. package/src/plugins/builtin/futures/model.test.ts +129 -0
  287. package/src/plugins/builtin/futures/model.ts +136 -0
  288. package/src/plugins/builtin/futures/pane.test.tsx +239 -0
  289. package/src/plugins/builtin/futures/table.test.ts +155 -0
  290. package/src/plugins/builtin/futures/table.tsx +241 -0
  291. package/src/plugins/builtin/fx-matrix/index.tsx +160 -134
  292. package/src/plugins/builtin/fx-matrix/pairs.ts +12 -1
  293. package/src/plugins/builtin/fx-matrix/pane.test.tsx +109 -0
  294. package/src/plugins/builtin/help/components.tsx +1 -30
  295. package/src/plugins/builtin/help/index.tsx +4 -24
  296. package/src/plugins/builtin/holders/pane.tsx +17 -12
  297. package/src/plugins/builtin/holders/thirteenf-match.ts +1 -2
  298. package/src/plugins/builtin/holders/treemap.tsx +4 -2
  299. package/src/plugins/builtin/insider/index.tsx +40 -14
  300. package/src/plugins/builtin/ipo-calendar/cache.ts +101 -0
  301. package/src/plugins/builtin/ipo-calendar/client.test.ts +31 -0
  302. package/src/plugins/builtin/ipo-calendar/client.ts +296 -0
  303. package/src/plugins/builtin/ipo-calendar/index.tsx +65 -0
  304. package/src/plugins/builtin/ipo-calendar/model.ts +145 -0
  305. package/src/plugins/builtin/ipo-calendar/pane.tsx +328 -0
  306. package/src/plugins/builtin/ipo-calendar/types.ts +19 -0
  307. package/src/plugins/builtin/kelly-sizer/index.test.tsx +3 -3
  308. package/src/plugins/builtin/kelly-sizer/model.ts +8 -1
  309. package/src/plugins/builtin/kelly-sizer/pane.tsx +33 -15
  310. package/src/plugins/builtin/kelly-sizer/view.tsx +12 -5
  311. package/src/plugins/builtin/market-halts/client.ts +114 -0
  312. package/src/plugins/builtin/market-halts/index.tsx +29 -0
  313. package/src/plugins/builtin/market-halts/model.test.ts +187 -0
  314. package/src/plugins/builtin/market-halts/model.ts +291 -0
  315. package/src/plugins/builtin/market-halts/pane.tsx +256 -0
  316. package/src/plugins/builtin/market-heatmap/data.test.ts +1 -0
  317. package/src/plugins/builtin/market-heatmap/data.ts +12 -3
  318. package/src/plugins/builtin/market-heatmap/index.tsx +131 -46
  319. package/src/plugins/builtin/market-movers/index.tsx +145 -63
  320. package/src/plugins/builtin/market-movers/model.ts +19 -3
  321. package/src/plugins/builtin/market-movers/screener.test.ts +108 -0
  322. package/src/plugins/builtin/market-movers/screener.ts +168 -10
  323. package/src/plugins/builtin/news/index.tsx +87 -104
  324. package/src/plugins/builtin/news/wire/article-search.test.ts +72 -0
  325. package/src/plugins/builtin/news/wire/article-search.ts +140 -0
  326. package/src/plugins/builtin/news/wire/breaking/notifications.test.ts +2 -0
  327. package/src/plugins/builtin/news/wire/breaking/pane.test.tsx +2 -0
  328. package/src/plugins/builtin/news/wire/breaking/pane.tsx +22 -9
  329. package/src/plugins/builtin/news/wire/categories.ts +35 -0
  330. package/src/plugins/builtin/news/wire/default-feeds.ts +47 -1
  331. package/src/plugins/builtin/news/wire/feed-config.test.ts +10 -5
  332. package/src/plugins/builtin/news/wire/index.tsx +42 -5
  333. package/src/plugins/builtin/news/wire/industry-pane.tsx +44 -20
  334. package/src/plugins/builtin/news/wire/news/detail-view.tsx +10 -5
  335. package/src/plugins/builtin/news/wire/news/footer.ts +35 -1
  336. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +19 -7
  337. package/src/plugins/builtin/news/wire/news/query-presets.ts +16 -2
  338. package/src/plugins/builtin/news/wire/news/table.test.tsx +54 -0
  339. package/src/plugins/builtin/news/wire/news/table.tsx +119 -19
  340. package/src/plugins/builtin/news/wire/rss/source.test.ts +12 -0
  341. package/src/plugins/builtin/news/wire/rss/source.ts +1 -1
  342. package/src/plugins/builtin/notes/files.test.ts +18 -0
  343. package/src/plugins/builtin/notes/files.ts +90 -4
  344. package/src/plugins/builtin/notes/index.tsx +7 -1
  345. package/src/plugins/builtin/notes/quick-notes-pane.test.tsx +182 -0
  346. package/src/plugins/builtin/notes/quick-notes-pane.tsx +74 -31
  347. package/src/plugins/builtin/notes/sync.test.ts +49 -0
  348. package/src/plugins/builtin/notes/sync.ts +98 -0
  349. package/src/plugins/builtin/notes/ticker-notes-tab.test.tsx +380 -0
  350. package/src/plugins/builtin/notes/ticker-notes-tab.tsx +97 -40
  351. package/src/plugins/builtin/options/calc-seed.test.ts +118 -0
  352. package/src/plugins/builtin/options/calc-seed.ts +52 -0
  353. package/src/plugins/builtin/options/footer.ts +4 -1
  354. package/src/plugins/builtin/options/index.tsx +22 -1
  355. package/src/plugins/builtin/options/live-quotes.ts +8 -0
  356. package/src/plugins/builtin/options/table.ts +3 -1
  357. package/src/plugins/builtin/options/view.test.tsx +5 -1
  358. package/src/plugins/builtin/options/view.tsx +112 -17
  359. package/src/plugins/builtin/options-calculator/index.tsx +35 -0
  360. package/src/plugins/builtin/options-calculator/model.test.ts +172 -0
  361. package/src/plugins/builtin/options-calculator/model.ts +273 -0
  362. package/src/plugins/builtin/options-calculator/pane.test.tsx +138 -0
  363. package/src/plugins/builtin/options-calculator/pane.tsx +218 -0
  364. package/src/plugins/builtin/plugin-module.test.ts +16 -2
  365. package/src/plugins/builtin/plugin-module.ts +5 -1
  366. package/src/plugins/builtin/polls/client.ts +57 -0
  367. package/src/plugins/builtin/polls/hooks.ts +20 -0
  368. package/src/plugins/builtin/polls/index.tsx +35 -0
  369. package/src/plugins/builtin/polls/normalize.test.ts +230 -0
  370. package/src/plugins/builtin/polls/normalize.ts +319 -0
  371. package/src/plugins/builtin/polls/pane.tsx +722 -0
  372. package/src/plugins/builtin/polls/types.ts +77 -0
  373. package/src/plugins/builtin/portfolio-list/account-metrics.test.ts +20 -0
  374. package/src/plugins/builtin/portfolio-list/account-metrics.ts +11 -6
  375. package/src/plugins/builtin/portfolio-list/header.tsx +15 -4
  376. package/src/plugins/builtin/portfolio-list/pane/index.test.tsx +1 -0
  377. package/src/plugins/builtin/portfolio-list/pane/index.tsx +7 -3
  378. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +6 -1
  379. package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +1 -1
  380. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -0
  381. package/src/plugins/builtin/portfolio-list/summary/index.tsx +16 -6
  382. package/src/plugins/builtin/research/analyst-pane.tsx +40 -67
  383. package/src/plugins/builtin/research/corporate-actions-pane.tsx +30 -31
  384. package/src/plugins/builtin/research/equity-diagnostic-pane.test.tsx +303 -0
  385. package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +485 -0
  386. package/src/plugins/builtin/research/index.test.ts +20 -25
  387. package/src/plugins/builtin/research/index.tsx +36 -5
  388. package/src/plugins/builtin/research/relative-valuation-pane.tsx +86 -26
  389. package/src/plugins/builtin/scanner/denied.tsx +31 -0
  390. package/src/plugins/builtin/scanner/feed.ts +94 -0
  391. package/src/plugins/builtin/scanner/flow-model.test.ts +85 -0
  392. package/src/plugins/builtin/scanner/flow-model.ts +133 -0
  393. package/src/plugins/builtin/scanner/flow-pane.tsx +197 -0
  394. package/src/plugins/builtin/scanner/hilo-bars.tsx +121 -0
  395. package/src/plugins/builtin/scanner/hilo-model.test.ts +44 -0
  396. package/src/plugins/builtin/scanner/hilo-model.ts +75 -0
  397. package/src/plugins/builtin/scanner/hilo-pane.tsx +165 -0
  398. package/src/plugins/builtin/scanner/index.ts +109 -0
  399. package/src/plugins/builtin/scanner/waiting.tsx +11 -0
  400. package/src/plugins/builtin/sec/index.tsx +33 -14
  401. package/src/plugins/builtin/sectors/index.tsx +119 -81
  402. package/src/plugins/builtin/sectors/move-bar.tsx +43 -0
  403. package/src/plugins/builtin/sectors/sector-data.ts +18 -0
  404. package/src/plugins/builtin/sectors/sector-model.ts +13 -15
  405. package/src/plugins/builtin/shared/auto-refresh.ts +51 -0
  406. package/src/plugins/builtin/shared/cloud-upgrade.ts +37 -6
  407. package/src/plugins/builtin/shared/pane-footer.ts +16 -3
  408. package/src/plugins/builtin/shared/public-share.ts +23 -0
  409. package/src/plugins/builtin/shared/screener-live-quotes.test.ts +91 -0
  410. package/src/plugins/builtin/shared/screener-live-quotes.ts +134 -0
  411. package/src/plugins/builtin/shared/table-pane.ts +1 -5
  412. package/src/plugins/builtin/shared/use-quote-board.test.tsx +211 -0
  413. package/src/plugins/builtin/shared/use-quote-board.ts +195 -0
  414. package/src/plugins/builtin/short-interest/client.ts +124 -0
  415. package/src/plugins/builtin/short-interest/index.tsx +62 -0
  416. package/src/plugins/builtin/short-interest/model.ts +113 -0
  417. package/src/plugins/builtin/short-interest/pane.tsx +252 -0
  418. package/src/plugins/builtin/short-interest/types.ts +9 -0
  419. package/src/plugins/builtin/substack/api/cache.ts +7 -3
  420. package/src/plugins/builtin/substack/api/loaders.ts +36 -12
  421. package/src/plugins/builtin/substack/api/types.ts +2 -0
  422. package/src/plugins/builtin/substack/article-detail.tsx +15 -1
  423. package/src/plugins/builtin/substack/pane-footer.ts +3 -22
  424. package/src/plugins/builtin/substack/pane-state.ts +1 -1
  425. package/src/plugins/builtin/substack/pane.tsx +56 -14
  426. package/src/plugins/builtin/thirteenf/api.test.ts +1 -1
  427. package/src/plugins/builtin/thirteenf/api.ts +44 -19
  428. package/src/plugins/builtin/thirteenf/data.ts +15 -7
  429. package/src/plugins/builtin/thirteenf/model.ts +2 -0
  430. package/src/plugins/builtin/thirteenf/pane.tsx +51 -12
  431. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +10 -4
  432. package/src/plugins/builtin/ticker-detail/data-panes/provider-search.tsx +67 -11
  433. package/src/plugins/builtin/ticker-detail/financials/tab.tsx +15 -8
  434. package/src/plugins/builtin/ticker-detail/index.tsx +5 -21
  435. package/src/plugins/builtin/ticker-detail/overview/components.tsx +72 -32
  436. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +12 -14
  437. package/src/plugins/builtin/ticker-detail/pane.tsx +4 -1
  438. package/src/plugins/builtin/ticker-detail/quote-monitor/card.test.tsx +70 -0
  439. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +42 -28
  440. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +96 -49
  441. package/src/plugins/builtin/ticker-detail/research-tabs.ts +32 -0
  442. package/src/plugins/builtin/ticker-detail/settings.ts +12 -0
  443. package/src/plugins/builtin/ticker-research-backend-plugin.ts +2 -9
  444. package/src/plugins/builtin/ticker-research-plugin.ts +6 -0
  445. package/src/plugins/builtin/treasury-auctions/cache.test.ts +173 -0
  446. package/src/plugins/builtin/treasury-auctions/cache.ts +104 -0
  447. package/src/plugins/builtin/treasury-auctions/client.test.ts +181 -0
  448. package/src/plugins/builtin/treasury-auctions/client.ts +151 -0
  449. package/src/plugins/builtin/treasury-auctions/index.tsx +90 -0
  450. package/src/plugins/builtin/treasury-auctions/model.test.ts +147 -0
  451. package/src/plugins/builtin/treasury-auctions/model.ts +188 -0
  452. package/src/plugins/builtin/treasury-auctions/pane.test.tsx +170 -0
  453. package/src/plugins/builtin/treasury-auctions/pane.tsx +417 -0
  454. package/src/plugins/builtin/treasury-auctions/types.ts +59 -0
  455. package/src/plugins/builtin/tv/pane.tsx +67 -7
  456. package/src/plugins/builtin/tv/youtube-embed.test.ts +84 -0
  457. package/src/plugins/builtin/tv/youtube-embed.ts +173 -0
  458. package/src/plugins/builtin/tv/youtube-stream.ts +27 -2
  459. package/src/plugins/builtin/volatility/client.ts +87 -0
  460. package/src/plugins/builtin/volatility/index.test.tsx +107 -0
  461. package/src/plugins/builtin/volatility/index.tsx +193 -0
  462. package/src/plugins/builtin/volatility/model.test.ts +66 -0
  463. package/src/plugins/builtin/volatility/model.ts +113 -0
  464. package/src/plugins/builtin/world-indices/footer.ts +43 -23
  465. package/src/plugins/builtin/world-indices/index.tsx +44 -93
  466. package/src/plugins/builtin/world-indices/indices.ts +11 -2
  467. package/src/plugins/builtin/world-indices/model.ts +17 -24
  468. package/src/plugins/builtin/world-indices/pane.test.tsx +116 -0
  469. package/src/plugins/builtin/world-indices/table.tsx +82 -37
  470. package/src/plugins/builtin/yield-curve/index.tsx +16 -7
  471. package/src/plugins/builtin/yield-curve/treasury-data.test.ts +25 -0
  472. package/src/plugins/builtin/yield-curve/treasury-data.ts +16 -0
  473. package/src/plugins/catalog-backend.ts +8 -0
  474. package/src/plugins/catalog-browser.test.ts +68 -0
  475. package/src/plugins/catalog-browser.ts +134 -0
  476. package/src/plugins/catalog-ui.ts +8 -0
  477. package/src/plugins/event-bus.ts +1 -0
  478. package/src/plugins/ibkr/flex/index.test.ts +19 -0
  479. package/src/plugins/ibkr/flex/index.ts +1 -1
  480. package/src/plugins/ibkr/trade/tab/footer.ts +9 -23
  481. package/src/plugins/ibkr/trade/tab/index.tsx +1 -5
  482. package/src/plugins/ibkr/trading/pane-view.tsx +149 -87
  483. package/src/plugins/ibkr/trading/pane.tsx +2 -2
  484. package/src/plugins/ownership.ts +3 -0
  485. package/src/plugins/pane-manager/floating.ts +8 -2
  486. package/src/plugins/pane-manager.test.ts +13 -0
  487. package/src/plugins/prediction-markets/backend-plugin.ts +2 -0
  488. package/src/plugins/prediction-markets/capability.test.ts +281 -0
  489. package/src/plugins/prediction-markets/capability.ts +171 -0
  490. package/src/plugins/prediction-markets/chart.tsx +18 -26
  491. package/src/plugins/prediction-markets/columns.ts +3 -2
  492. package/src/plugins/prediction-markets/controller/catalog.ts +92 -14
  493. package/src/plugins/prediction-markets/controller/data.ts +7 -1
  494. package/src/plugins/prediction-markets/controller/detail.ts +4 -0
  495. package/src/plugins/prediction-markets/controller/index.ts +4 -16
  496. package/src/plugins/prediction-markets/detail/book.tsx +3 -23
  497. package/src/plugins/prediction-markets/detail/overview.tsx +0 -18
  498. package/src/plugins/prediction-markets/detail/pane.tsx +36 -12
  499. package/src/plugins/prediction-markets/detail.test.tsx +1 -1
  500. package/src/plugins/prediction-markets/index.tsx +2 -0
  501. package/src/plugins/prediction-markets/metrics.ts +28 -18
  502. package/src/plugins/prediction-markets/navigation.ts +1 -0
  503. package/src/plugins/prediction-markets/pane.test.tsx +1 -1
  504. package/src/plugins/prediction-markets/pane.tsx +8 -1
  505. package/src/plugins/prediction-markets/services/fetch.ts +3 -2
  506. package/src/plugins/prediction-markets/services/kalshi/adapter.ts +128 -29
  507. package/src/plugins/prediction-markets/services/kalshi/normalize.ts +3 -1
  508. package/src/plugins/prediction-markets/services/kalshi/types.ts +14 -0
  509. package/src/plugins/prediction-markets/services/polymarket/adapter.ts +57 -14
  510. package/src/plugins/prediction-markets/services/polymarket/detail.ts +67 -18
  511. package/src/plugins/prediction-markets/test-helpers.tsx +0 -1
  512. package/src/plugins/prediction-markets/types.ts +3 -9
  513. package/src/plugins/registry/context.ts +6 -1
  514. package/src/plugins/registry/index.test.ts +2 -0
  515. package/src/plugins/registry/index.ts +53 -3
  516. package/src/plugins/runtime/context.tsx +4 -2
  517. package/src/plugins/runtime/index.tsx +9 -0
  518. package/src/remote/controller.test.ts +17 -4
  519. package/src/renderers/browser/app-services.ts +61 -0
  520. package/src/renderers/browser/cloud-transport.test.ts +32 -0
  521. package/src/renderers/browser/cloud-transport.ts +29 -0
  522. package/src/renderers/browser/config-host.ts +62 -0
  523. package/src/renderers/browser/error-boundary.tsx +23 -0
  524. package/src/renderers/browser/main.tsx +61 -0
  525. package/src/renderers/browser/persistence.ts +74 -0
  526. package/src/renderers/browser/storage.test.ts +66 -0
  527. package/src/renderers/browser/storage.ts +50 -0
  528. package/src/renderers/browser/ticker-repository.ts +59 -0
  529. package/src/renderers/browser/ui-host.tsx +40 -0
  530. package/src/renderers/cloudflare/worker.test.ts +133 -0
  531. package/src/renderers/cloudflare/worker.ts +115 -0
  532. package/src/renderers/electrobun/bun/core-capabilities.ts +2 -2
  533. package/src/renderers/electrobun/bun/desktop/capability-bridge.test.ts +64 -0
  534. package/src/renderers/electrobun/bun/desktop/capability-bridge.ts +41 -7
  535. package/src/renderers/electrobun/bun/desktop/host-requests.ts +5 -2
  536. package/src/renderers/electrobun/bun/desktop/http-fetch.test.ts +33 -0
  537. package/src/renderers/electrobun/bun/desktop/http-fetch.ts +53 -6
  538. package/src/renderers/electrobun/bun/desktop/initialization.test.ts +27 -0
  539. package/src/renderers/electrobun/bun/desktop/initialization.ts +6 -1
  540. package/src/renderers/electrobun/bun/index.ts +1 -0
  541. package/src/renderers/electrobun/shared/protocol.ts +2 -0
  542. package/src/renderers/electrobun/view/app-services.ts +39 -5
  543. package/src/renderers/electrobun/view/build-assets.ts +17 -10
  544. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +186 -65
  545. package/src/renderers/electrobun/view/cli-pane-shot-health.test.ts +25 -0
  546. package/src/renderers/electrobun/view/cli-pane-shot-health.ts +31 -0
  547. package/src/renderers/electrobun/view/data-table/index.tsx +19 -11
  548. package/src/renderers/electrobun/view/data-table/row.tsx +2 -0
  549. package/src/renderers/electrobun/view/desktop/controls.test.tsx +68 -0
  550. package/src/renderers/electrobun/view/desktop/controls.tsx +27 -1
  551. package/src/renderers/electrobun/view/dom-ui-host.tsx +143 -0
  552. package/src/renderers/electrobun/view/host/box.test.tsx +113 -0
  553. package/src/renderers/electrobun/view/host/box.tsx +11 -6
  554. package/src/renderers/electrobun/view/host/chart-surface.tsx +7 -1
  555. package/src/renderers/electrobun/view/host/input.tsx +11 -7
  556. package/src/renderers/electrobun/view/host/media-surface.tsx +70 -3
  557. package/src/renderers/electrobun/view/host/tabs.tsx +97 -5
  558. package/src/renderers/electrobun/view/host/text.tsx +5 -2
  559. package/src/renderers/electrobun/view/input-host.test.tsx +140 -0
  560. package/src/renderers/electrobun/view/input-host.tsx +4 -2
  561. package/src/renderers/electrobun/view/main.tsx +2 -0
  562. package/src/renderers/electrobun/view/native-stubs/backend-rpc.ts +19 -1
  563. package/src/renderers/electrobun/view/native-stubs/broker-sync-native-loader.ts +7 -0
  564. package/src/renderers/electrobun/view/native-stubs/chart/kitty-support.ts +10 -0
  565. package/src/renderers/electrobun/view/remote/capability-invoker.test.ts +46 -0
  566. package/src/renderers/electrobun/view/remote/capability-invoker.ts +34 -13
  567. package/src/renderers/electrobun/view/remote/connection-health-backend.ts +19 -0
  568. package/src/renderers/electrobun/view/remote/connection-health.test.ts +44 -0
  569. package/src/renderers/electrobun/view/remote/connection-health.ts +49 -0
  570. package/src/renderers/electrobun/view/styles.css +5 -0
  571. package/src/renderers/electrobun/view/ui-host.tsx +3 -137
  572. package/src/renderers/opentui/host.tsx +15 -1
  573. package/src/renderers/opentui/start.tsx +2 -0
  574. package/src/renderers/opentui/terminal-media.test.ts +102 -0
  575. package/src/renderers/opentui/terminal-media.ts +142 -0
  576. package/src/renderers/share/main.tsx +54 -0
  577. package/src/renderers/share/styles.css +19 -0
  578. package/src/renderers/share/view.test.tsx +22 -0
  579. package/src/renderers/share/view.tsx +79 -0
  580. package/src/shares/api.test.ts +56 -0
  581. package/src/shares/api.ts +103 -0
  582. package/src/shares/chart-snapshot.test.ts +34 -0
  583. package/src/shares/chart-snapshot.ts +28 -0
  584. package/src/shares/payload.ts +104 -0
  585. package/src/sources/gloomberb-cloud/index.test.ts +61 -1
  586. package/src/sources/gloomberb-cloud/index.ts +86 -14
  587. package/src/sources/gloomberb-cloud/normalizers.test.ts +34 -0
  588. package/src/sources/gloomberb-cloud/normalizers.ts +5 -5
  589. package/src/sources/provider-router/connection-health.test.ts +28 -0
  590. package/src/sources/provider-router/connection-health.ts +47 -0
  591. package/src/sources/provider-router/history.test.ts +55 -6
  592. package/src/sources/provider-router/history.ts +119 -14
  593. package/src/sources/provider-router/index.ts +12 -1
  594. package/src/sources/provider-router/news.test.ts +34 -0
  595. package/src/sources/provider-router/news.ts +15 -1
  596. package/src/sources/sec-edgar.test.ts +59 -0
  597. package/src/sources/sec-edgar.ts +63 -23
  598. package/src/sources/yahoo-finance/history.ts +2 -9
  599. package/src/sources/yahoo-finance/http.ts +1 -1
  600. package/src/sources/yahoo-finance/mappers.ts +3 -0
  601. package/src/sources/yahoo-finance/snapshots.ts +10 -2
  602. package/src/sources/yahoo-finance/symbols.test.ts +10 -0
  603. package/src/sources/yahoo-finance/symbols.ts +14 -9
  604. package/src/sources/yahoo-finance/types.ts +19 -0
  605. package/src/sources/yahoo-finance.test.ts +1 -1
  606. package/src/sources/yahoo-finance.ts +6 -2
  607. package/src/state/app/context/index.test.tsx +101 -0
  608. package/src/state/app/context/index.tsx +10 -0
  609. package/src/state/config-save-scheduler.ts +1 -6
  610. package/src/state/persist-scheduler.test.ts +31 -0
  611. package/src/state/persist-scheduler.ts +21 -8
  612. package/src/sync/auth-transition.test.ts +36 -0
  613. package/src/sync/auth-transition.ts +22 -0
  614. package/src/sync/baseline.ts +32 -0
  615. package/src/sync/controller.test.ts +67 -0
  616. package/src/sync/controller.ts +32 -1
  617. package/src/sync/core-contributors.test.ts +102 -1
  618. package/src/sync/core-contributors.ts +98 -12
  619. package/src/sync/react.ts +18 -2
  620. package/src/sync/types.ts +12 -0
  621. package/src/test-support/plugin-runtime.ts +3 -0
  622. package/src/theme/font-scale.test.ts +14 -0
  623. package/src/theme/font-scale.ts +69 -0
  624. package/src/tickers/search/index.test.ts +27 -0
  625. package/src/tickers/search/upsert.ts +10 -0
  626. package/src/time-series/alignment.ts +15 -11
  627. package/src/time-series/field-catalog.ts +10 -0
  628. package/src/time-series/fundamentals.ts +19 -11
  629. package/src/time-series/hooks.ts +8 -2
  630. package/src/time-series/parsed-history-cache.ts +35 -0
  631. package/src/time-series/resolution.ts +10 -0
  632. package/src/time-series/resolve.test.ts +332 -3
  633. package/src/time-series/resolve.ts +225 -25
  634. package/src/time-series/spec-studies.test.ts +23 -0
  635. package/src/time-series/spec.ts +21 -2
  636. package/src/time-series/transforms.test.ts +76 -1
  637. package/src/time-series/transforms.ts +39 -8
  638. package/src/time-series/types.ts +9 -2
  639. package/src/time-series/use-chart-resolution.test.tsx +105 -1
  640. package/src/time-series/use-chart-resolution.ts +67 -13
  641. package/src/types/capability-route-source.ts +2 -1
  642. package/src/types/config.ts +30 -27
  643. package/src/types/media.ts +2 -0
  644. package/src/types/plugin.ts +8 -1
  645. package/src/ui/host.tsx +8 -0
  646. package/src/ui/pointer-drag.ts +12 -0
  647. package/src/ui/qr.test.ts +79 -0
  648. package/src/ui/qr.ts +82 -0
  649. package/src/updater.test.ts +117 -16
  650. package/src/updater.ts +32 -5
  651. package/src/utils/exchanges.ts +1 -0
  652. package/src/utils/external-url.test.ts +32 -0
  653. package/src/utils/external-url.ts +23 -0
  654. package/src/utils/price-history.ts +11 -10
  655. package/src/utils/sec.test.ts +6 -0
  656. package/src/utils/sec.ts +1 -1
  657. package/src/utils/sort-values.ts +31 -0
  658. package/src/utils/zoned-date-time.ts +67 -0
  659. package/src/components/onboarding/wizard-keyboard.ts +0 -306
  660. package/src/plugins/builtin/chart-composer/date-window-dialog.tsx +0 -120
@@ -104,10 +104,53 @@ afterEach(() => {
104
104
  setCloudApiFetchTransport(null);
105
105
  apiClient.setSessionToken(null);
106
106
  apiClient.setWebSocketToken(null);
107
+ apiClient.setCookieSessionMode(false);
107
108
  jest.useRealTimers();
108
109
  });
109
110
 
110
111
  describe("apiClient auth cookies", () => {
112
+ test("accepts a browser-managed api.gloom.sh cookie without exposing its value", async () => {
113
+ apiClient.setCookieSessionMode(true);
114
+ setCloudApiFetchTransport(mockFetch(() => createResponse({ user: verifiedUser })));
115
+
116
+ await expect(apiClient.signIn("test@example.com", "password")).resolves.toEqual(verifiedUser);
117
+ expect(apiClient.getSessionToken()).toBeNull();
118
+ expect(apiClient.isVerified()).toBe(true);
119
+ });
120
+
121
+ test("reports signed-in from the restored user when the cookie hides the raw token", async () => {
122
+ apiClient.setCookieSessionMode(true);
123
+ setCloudApiFetchTransport(mockFetch(() => createResponse({ user: verifiedUser })));
124
+
125
+ expect(apiClient.isSignedIn()).toBe(false);
126
+ await apiClient.signIn("test@example.com", "password");
127
+ expect(apiClient.getSessionToken()).toBeNull();
128
+ expect(apiClient.isSignedIn()).toBe(true);
129
+ });
130
+
131
+ test("coalesces anonymous browser session checks and remembers the result", async () => {
132
+ apiClient.setCookieSessionMode(true);
133
+ let requests = 0;
134
+ let finishRequest!: () => void;
135
+ setCloudApiFetchTransport(async () => {
136
+ requests += 1;
137
+ await new Promise<void>((resolve) => { finishRequest = resolve; });
138
+ return createResponse({ user: null });
139
+ });
140
+
141
+ const checks = [
142
+ apiClient.ensureVerifiedSession(),
143
+ apiClient.ensureVerifiedSession(),
144
+ apiClient.ensureVerifiedSession(),
145
+ ];
146
+ expect(requests).toBe(1);
147
+ finishRequest();
148
+
149
+ await expect(Promise.all(checks)).resolves.toEqual([null, null, null]);
150
+ await expect(apiClient.ensureVerifiedSession()).resolves.toBeNull();
151
+ expect(requests).toBe(1);
152
+ });
153
+
111
154
  test("captures secure session cookies after login and reuses them on session refresh", async () => {
112
155
  const seenCookies: Array<string | null> = [];
113
156
 
@@ -185,6 +228,28 @@ describe("apiClient auth cookies", () => {
185
228
  ]);
186
229
  });
187
230
 
231
+ test("creates a browser handoff with the captured session instead of exposing it in the URL", async () => {
232
+ let requestedUrl = "";
233
+ let requestedCookie: string | null = null;
234
+ apiClient.setSessionToken("desktop-session-token");
235
+ globalThis.fetch = mockFetch(async (input: Request | string | URL, init?: RequestInit) => {
236
+ requestedUrl = String(input);
237
+ requestedCookie = new Headers(init?.headers).get("Cookie");
238
+ return createResponse({
239
+ url: "https://api.gloom.sh/cloud/auth/browser-handoff?token=opaque-one-time-token",
240
+ });
241
+ });
242
+
243
+ const handoff = await apiClient.createBrowserHandoff();
244
+
245
+ expect(new URL(requestedUrl).pathname).toBe("/cloud/auth/browser-handoff");
246
+ expect(requestedCookie).toBe(
247
+ "__Secure-gloomberb.session_token=desktop-session-token; gloomberb.session_token=desktop-session-token",
248
+ );
249
+ expect(handoff.url).toContain("token=opaque-one-time-token");
250
+ expect(handoff.url).not.toContain("desktop-session-token");
251
+ });
252
+
188
253
  test("keeps cached identity when session refresh is rejected without a hard account-missing response", async () => {
189
254
  apiClient.setSessionToken("persisted-token.value");
190
255
  apiClient.restoreCachedUser(verifiedUser);
@@ -583,6 +648,65 @@ describe("apiClient quote socket", () => {
583
648
  });
584
649
  });
585
650
 
651
+ describe("apiClient scanner subscriptions", () => {
652
+ test("subscribes once for many panes, fans out, replays the snapshot, and unsubscribes last", () => {
653
+ const sockets = installTestWebSocket();
654
+ const first: unknown[] = [];
655
+ const second: unknown[] = [];
656
+
657
+ const unsubscribeFirst = apiClient.subscribeScanner("hilo", (event) => first.push(event));
658
+ const socket = sockets[0]!;
659
+ socket.open();
660
+ expect(socket.sent).toContainEqual({ type: "scanner.subscribe", scanner: "hilo" });
661
+
662
+ const payload = {
663
+ status: "live",
664
+ asOf: 1,
665
+ windows: { s30: { highs: 1, lows: 0 }, m1: { highs: 2, lows: 1 }, m5: { highs: 3, lows: 2 } },
666
+ highs: [],
667
+ lows: [],
668
+ };
669
+ socket.receive({ type: "scanner.hilo", ...payload });
670
+
671
+ // A second pane must not open a second upstream subscription, and must not
672
+ // wait a tick for its first frame.
673
+ const subscribeCount = () => socket.sent.filter((message: any) => message.type === "scanner.subscribe").length;
674
+ const before = subscribeCount();
675
+ const unsubscribeSecond = apiClient.subscribeScanner("hilo", (event) => second.push(event));
676
+ expect(subscribeCount()).toBe(before);
677
+ expect(second).toEqual([{ type: "data", payload }]);
678
+
679
+ socket.receive({ type: "scanner.hilo", ...payload, asOf: 2 });
680
+ expect(first).toHaveLength(2);
681
+ expect(second).toHaveLength(2);
682
+
683
+ unsubscribeFirst();
684
+ expect(socket.sent).not.toContainEqual({ type: "scanner.unsubscribe", scanner: "hilo" });
685
+ unsubscribeSecond();
686
+ expect(socket.sent).toContainEqual({ type: "scanner.unsubscribe", scanner: "hilo" });
687
+ });
688
+
689
+ test("replays scanner subscriptions after a reconnect and surfaces denials", () => {
690
+ const sockets = installTestWebSocket();
691
+ const seen: unknown[] = [];
692
+ const unsubscribe = apiClient.subscribeScanner("flow", (event) => seen.push(event));
693
+
694
+ const socket = sockets[0]!;
695
+ socket.open();
696
+ socket.receive({ type: "scanner.denied", scanner: "flow", reason: "pro_required" });
697
+ expect(seen).toEqual([{ type: "denied", reason: "pro_required" }]);
698
+
699
+ jest.useFakeTimers();
700
+ socket.closeWith({ code: 1006, reason: "network" });
701
+ jest.runAllTimers();
702
+ const reconnected = sockets[1]!;
703
+ reconnected.open();
704
+ expect(reconnected.sent).toContainEqual({ type: "scanner.subscribe", scanner: "flow" });
705
+
706
+ unsubscribe();
707
+ });
708
+ });
709
+
586
710
  describe("apiClient chat timestamps", () => {
587
711
  test("sends the before cursor when loading older chat messages", async () => {
588
712
  let requestedUrl = "";
@@ -958,3 +1082,37 @@ describe("apiClient cloud news", () => {
958
1082
  expect(story.id).toBe("story-1");
959
1083
  });
960
1084
  });
1085
+
1086
+ describe("apiClient equity diagnostic", () => {
1087
+ test("posts the symbol, exchange, and cache mode to the research route", async () => {
1088
+ let seenUrl = "";
1089
+ let seenInit: RequestInit | undefined;
1090
+ globalThis.fetch = mockFetch(async (input: Request | string | URL, init?: RequestInit) => {
1091
+ seenUrl = String(input);
1092
+ seenInit = init;
1093
+ return createResponse({ symbol: "AAPL", status: "complete" });
1094
+ });
1095
+
1096
+ await apiClient.getCloudEquityDiagnostic(" aapl ", "NASDAQ", "refresh");
1097
+
1098
+ expect(new URL(seenUrl).pathname).toBe("/research/equity-diagnostic");
1099
+ expect(seenInit?.method).toBe("POST");
1100
+ expect(JSON.parse(String(seenInit?.body))).toEqual({
1101
+ symbol: "AAPL",
1102
+ exchange: "NASDAQ",
1103
+ mode: "refresh",
1104
+ });
1105
+ });
1106
+
1107
+ test("omits an unknown exchange and defaults to the cached answer", async () => {
1108
+ let seenInit: RequestInit | undefined;
1109
+ globalThis.fetch = mockFetch(async (_input: Request | string | URL, init?: RequestInit) => {
1110
+ seenInit = init;
1111
+ return createResponse({ symbol: "AAPL", status: "complete" });
1112
+ });
1113
+
1114
+ await apiClient.getCloudEquityDiagnostic("AAPL");
1115
+
1116
+ expect(JSON.parse(String(seenInit?.body))).toEqual({ symbol: "AAPL", mode: "cache-first" });
1117
+ });
1118
+ });
@@ -8,6 +8,8 @@ import { CloudApiSocket } from "./socket";
8
8
  import type {
9
9
  CloudCongressHouseParams,
10
10
  CloudFredSeriesParams,
11
+ CloudSecFilingParams,
12
+ CloudSecFilingsParams,
11
13
  CloudHistoryParams,
12
14
  CloudNewsParams,
13
15
  CloudTickerTweetsParams,
@@ -35,23 +37,36 @@ import type {
35
37
  CloudFundamentals,
36
38
  CloudHoldersPayload,
37
39
  CloudAnalystResearchPayload,
40
+ CloudShortInterestPayload,
41
+ CloudBrowserHandoffResponse,
38
42
  CloudCorporateActionsPayload,
39
43
  CloudPricePointPayload,
40
44
  CloudEconEventPayload,
45
+ CloudEquityDiagnosticMode,
46
+ CloudEquityDiagnosticResult,
41
47
  CloudFredSeriesPayload,
42
48
  CloudYieldPointPayload,
43
49
  CloudCongressHousePayload,
44
50
  CloudNewsPayload,
51
+ CloudSecContentResponse,
52
+ CloudSecDocumentsResponse,
53
+ CloudSecFilingsResponse,
45
54
  CloudNewsListResponse,
46
55
  CloudTweetSearchResponse,
47
56
  CloudMarketResponse,
48
57
  CloudMarketBatchTarget,
49
58
  CloudMarketBatchPayload,
59
+ CloudMarketScreenerCategory,
60
+ CloudMarketScreenerPayload,
50
61
  CloudVerificationResponse,
62
+ DeviceAuthStartResponse,
63
+ DeviceAuthTokenResponse,
51
64
  CloudRoundupPreviewResponse,
52
65
  CloudSyncPushResponse,
53
66
  CloudSyncSnapshotResponse,
54
67
  QuoteStreamTarget,
68
+ ScannerFeedEvent,
69
+ ScannerKind,
55
70
  } from "./types";
56
71
  import type { SyncSettings, SyncSnapshot } from "../sync/types";
57
72
 
@@ -65,6 +80,8 @@ const ASSIST_REQUEST_TIMEOUT_MS = 6_000;
65
80
 
66
81
  class GloomApiClient {
67
82
  private currentUser: AuthUser | null = null;
83
+ private sessionChecked = false;
84
+ private sessionRequest: Promise<AuthUser | null> | null = null;
68
85
  private readonly currentUserListeners = new Set<() => void>();
69
86
  private readonly transport = new CloudApiRequestTransport();
70
87
  private readonly auth: CloudAuthApi;
@@ -76,6 +93,7 @@ class GloomApiClient {
76
93
  this.auth = new CloudAuthApi({
77
94
  getCurrentUser: () => this.currentUser,
78
95
  getSessionToken: () => this.transport.getSessionToken(),
96
+ hasSessionCredential: () => this.transport.hasSessionCredential(),
79
97
  request: (path, options) => this.request(path, options),
80
98
  requireCapturedSession: (message) => this.requireCapturedSession(message),
81
99
  setCurrentUser: (user) => this.setCurrentUser(user),
@@ -85,6 +103,7 @@ class GloomApiClient {
85
103
  this.socket = new CloudApiSocket({
86
104
  getBaseUrl: () => this.transport.baseUrl,
87
105
  getSocketAuthToken: () => this.getSocketAuthToken(),
106
+ hasSessionCredential: () => this.transport.hasSessionCredential(),
88
107
  hasVerifiedUser: () => this.currentUser?.emailVerified === true,
89
108
  isUsingWebSocketToken: () => !!this.transport.getWebSocketToken(),
90
109
  clearWebSocketTokenForFallback: () => this.transport.clearWebSocketTokenForFallback(),
@@ -115,8 +134,14 @@ class GloomApiClient {
115
134
  return this.transport.getWebSocketToken();
116
135
  }
117
136
 
137
+ setCookieSessionMode(enabled: boolean): void {
138
+ this.sessionChecked = false;
139
+ this.transport.setCookieSessionMode(enabled);
140
+ }
141
+
118
142
  setSessionToken(token: string | null): void {
119
143
  const changed = this.transport.getSessionToken() !== token;
144
+ this.sessionChecked = false;
120
145
  this.transport.setSessionToken(token);
121
146
  if (!token) {
122
147
  this.currentUser = null;
@@ -135,6 +160,15 @@ class GloomApiClient {
135
160
  return this.currentUser;
136
161
  }
137
162
 
163
+ /**
164
+ * Whether a signed-in session exists on this surface. Browser builds keep the
165
+ * session in an HttpOnly cookie, so the raw token is deliberately null there
166
+ * and the restored user is the only signal.
167
+ */
168
+ isSignedIn(): boolean {
169
+ return !!this.transport.getSessionToken() || !!this.currentUser;
170
+ }
171
+
138
172
  /** Notifies when the signed-in user changes, including plan and trial entitlement. */
139
173
  subscribeCurrentUser(listener: () => void): () => void {
140
174
  this.currentUserListeners.add(listener);
@@ -148,7 +182,7 @@ class GloomApiClient {
148
182
  }
149
183
 
150
184
  isVerified(): boolean {
151
- return !!this.transport.getSessionToken() && !!this.currentUser?.emailVerified;
185
+ return this.transport.hasSessionCredential() && !!this.currentUser?.emailVerified;
152
186
  }
153
187
 
154
188
  private setCurrentUser(user: AuthUser | null): void {
@@ -179,7 +213,7 @@ class GloomApiClient {
179
213
  }
180
214
 
181
215
  private requireCapturedSession(message: string): void {
182
- if (this.transport.getSessionToken()) return;
216
+ if (this.transport.hasSessionCredential()) return;
183
217
  this.transport.setWebSocketToken(null);
184
218
  this.setCurrentUser(null);
185
219
  throw new Error(message);
@@ -194,7 +228,9 @@ class GloomApiClient {
194
228
  }
195
229
 
196
230
  async ensureVerifiedSession(): Promise<AuthUser | null> {
197
- return this.auth.ensureVerifiedSession();
231
+ if (!this.transport.hasSessionCredential()) return null;
232
+ if (!this.currentUser && !this.sessionChecked) await this.getSession();
233
+ return this.currentUser?.emailVerified ? this.currentUser : null;
198
234
  }
199
235
 
200
236
  async signUp(email: string, username: string, name: string, password: string): Promise<AuthUser> {
@@ -205,18 +241,52 @@ class GloomApiClient {
205
241
  return this.auth.signIn(email, password);
206
242
  }
207
243
 
244
+ async startDeviceSignIn(body: { clientName?: string; clientPlatform?: string }): Promise<DeviceAuthStartResponse> {
245
+ return this.auth.startDeviceSignIn(body);
246
+ }
247
+
248
+ async pollDeviceSignIn(deviceCode: string): Promise<DeviceAuthTokenResponse> {
249
+ return this.auth.pollDeviceSignIn(deviceCode);
250
+ }
251
+
208
252
  async signOut(): Promise<void> {
209
253
  return this.auth.signOut();
210
254
  }
211
255
 
212
256
  async getSession(): Promise<AuthUser | null> {
213
- return this.auth.getSession();
257
+ if (this.sessionRequest) return this.sessionRequest;
258
+ this.sessionRequest = this.auth.getSession();
259
+ try {
260
+ const user = await this.sessionRequest;
261
+ this.sessionChecked = true;
262
+ return user;
263
+ } finally {
264
+ this.sessionRequest = null;
265
+ }
214
266
  }
215
267
 
216
268
  async sendVerification(): Promise<CloudVerificationResponse> {
217
269
  return this.auth.sendVerification();
218
270
  }
219
271
 
272
+ async requestPasswordReset(email: string): Promise<void> {
273
+ return this.auth.requestPasswordReset(email);
274
+ }
275
+
276
+ async createBrowserHandoff(): Promise<CloudBrowserHandoffResponse> {
277
+ return this.auth.createBrowserHandoff();
278
+ }
279
+
280
+ /** Creates a Stripe checkout session for Cloud Pro; the URL opens in a browser. */
281
+ async createCloudCheckout(): Promise<{ url: string }> {
282
+ return this.request<{ url: string }>("/stripe/checkout", { method: "POST", body: JSON.stringify({}) });
283
+ }
284
+
285
+ /** Stripe billing portal for an account that already has a subscription. */
286
+ async createBillingPortal(): Promise<{ url: string }> {
287
+ return this.request<{ url: string }>("/stripe/portal", { method: "POST", body: JSON.stringify({}) });
288
+ }
289
+
220
290
  async getAccountProfile(): Promise<AccountProfile> {
221
291
  return this.auth.getAccountProfile();
222
292
  }
@@ -394,6 +464,11 @@ class GloomApiClient {
394
464
  return this.socket.subscribeQuotes(targets, onQuote);
395
465
  }
396
466
 
467
+ /** Subscribes to a shared scanner feed; all panes of one kind share one upstream subscription. */
468
+ subscribeScanner(scanner: ScannerKind, listener: (event: ScannerFeedEvent) => void): () => void {
469
+ return this.socket.subscribeScanner(scanner, listener);
470
+ }
471
+
397
472
  dispose(): void {
398
473
  this.socket.dispose();
399
474
  }
@@ -413,6 +488,14 @@ class GloomApiClient {
413
488
  return this.data.getCloudQuotesBatch(targets, mode);
414
489
  }
415
490
 
491
+ async getCloudMarketScreener(
492
+ category: CloudMarketScreenerCategory,
493
+ count = 25,
494
+ mode: "cache-first" | "refresh" = "cache-first",
495
+ ): Promise<CloudMarketResponse<CloudMarketScreenerPayload>> {
496
+ return this.data.getCloudMarketScreener(category, count, mode);
497
+ }
498
+
416
499
  async getCloudOptionsChain(
417
500
  symbol: string,
418
501
  exchange?: string,
@@ -444,6 +527,10 @@ class GloomApiClient {
444
527
  return this.data.getCloudHolders(symbol, exchange);
445
528
  }
446
529
 
530
+ async getCloudShortInterest(symbol: string, years?: number): Promise<CloudMarketResponse<CloudShortInterestPayload>> {
531
+ return this.data.getCloudShortInterest(symbol, years);
532
+ }
533
+
447
534
  async getCloudAnalystResearch(symbol: string, exchange?: string): Promise<CloudMarketResponse<CloudAnalystResearchPayload>> {
448
535
  return this.data.getCloudAnalystResearch(symbol, exchange);
449
536
  }
@@ -476,6 +563,14 @@ class GloomApiClient {
476
563
  return this.data.getCloudEconomicCalendar();
477
564
  }
478
565
 
566
+ async getCloudEquityDiagnostic(
567
+ symbol: string,
568
+ exchange?: string,
569
+ mode: CloudEquityDiagnosticMode = "cache-first",
570
+ ): Promise<CloudEquityDiagnosticResult> {
571
+ return this.data.getCloudEquityDiagnostic(symbol, exchange, mode);
572
+ }
573
+
479
574
  async getCloudFredSeries(
480
575
  seriesId: string,
481
576
  params: CloudFredSeriesParams = {},
@@ -491,6 +586,22 @@ class GloomApiClient {
491
586
  return this.data.getCloudCongressHouse(params);
492
587
  }
493
588
 
589
+ async getCloudSecFilings(params: CloudSecFilingsParams): Promise<CloudSecFilingsResponse> {
590
+ return this.data.getCloudSecFilings(params);
591
+ }
592
+
593
+ async getCloudSecFilingDocuments(params: CloudSecFilingParams): Promise<CloudSecDocumentsResponse> {
594
+ return this.data.getCloudSecFilingDocuments(params);
595
+ }
596
+
597
+ async getCloudSecFilingContent(params: CloudSecFilingParams): Promise<CloudSecContentResponse> {
598
+ return this.data.getCloudSecFilingContent(params);
599
+ }
600
+
601
+ async getCloudSec13F(path: string, params: Record<string, string | number | undefined> = {}): Promise<unknown> {
602
+ return this.data.getCloudSec13F(path, params);
603
+ }
604
+
494
605
  async getCloudNews(params: CloudNewsParams = {}): Promise<CloudNewsListResponse> {
495
606
  return this.data.getCloudNews(params);
496
607
  }
@@ -19,7 +19,9 @@ export type CloudFredSeriesParams = {
19
19
  export type CloudCongressHouseParams = {
20
20
  year?: number;
21
21
  limit?: number;
22
+ offset?: number;
22
23
  filingLimit?: number;
24
+ filingOffset?: number;
23
25
  member?: string;
24
26
  ticker?: string;
25
27
  refresh?: boolean;
@@ -60,6 +62,23 @@ export type CloudTweetSearchParams = {
60
62
  hours?: number;
61
63
  };
62
64
 
65
+ const LOGO_SYMBOL_RE = /^[A-Z0-9][A-Z0-9._^:-]{0,23}$/;
66
+ const CRYPTO_QUOTE = /[-/](USD|USDT|USDC|EUR|GBP|BTC)$/;
67
+
68
+ export type CloudLogoKind = "ticker" | "crypto";
69
+
70
+ export function normalizeCloudLogoSymbol(kind: CloudLogoKind, symbol: string): string | null {
71
+ let normalized = symbol.trim().toUpperCase();
72
+ if (kind === "crypto") normalized = normalized.replace(CRYPTO_QUOTE, "");
73
+ return LOGO_SYMBOL_RE.test(normalized) ? normalized : null;
74
+ }
75
+
76
+ export function cloudLogoPath(kind: CloudLogoKind, symbol: string): string | null {
77
+ const normalized = normalizeCloudLogoSymbol(kind, symbol);
78
+ if (!normalized) return null;
79
+ return `/cloud/logos/${kind}/${encodeURIComponent(normalized)}`;
80
+ }
81
+
63
82
  function appendQuery(path: string, search: URLSearchParams): string {
64
83
  const query = search.toString();
65
84
  return `${path}${query ? `?${query}` : ""}`;
@@ -118,17 +137,71 @@ export function cloudFredSeriesPath(seriesId: string, params: CloudFredSeriesPar
118
137
  return appendQuery(`/cloud/econ/series/${encodeURIComponent(seriesId)}`, search);
119
138
  }
120
139
 
140
+ export type CloudSecFilingsParams = {
141
+ ticker: string;
142
+ limit?: number;
143
+ offset?: number;
144
+ };
145
+
146
+ export type CloudSecFilingParams = {
147
+ cik?: string;
148
+ accession?: string;
149
+ form?: string;
150
+ primaryDocument?: string;
151
+ primaryDocumentUrl?: string;
152
+ filingUrl?: string;
153
+ };
154
+
121
155
  export function cloudCongressHousePath(params: CloudCongressHouseParams = {}): string {
122
156
  const search = new URLSearchParams();
123
157
  if (params.year != null) search.set("year", String(params.year));
124
158
  if (params.limit != null) search.set("limit", String(params.limit));
159
+ if (params.offset != null) search.set("offset", String(params.offset));
125
160
  if (params.filingLimit != null) search.set("filingLimit", String(params.filingLimit));
161
+ if (params.filingOffset != null) search.set("filingOffset", String(params.filingOffset));
126
162
  if (params.member) search.set("member", params.member);
127
163
  if (params.ticker) search.set("ticker", params.ticker);
128
164
  if (params.refresh != null) search.set("refresh", String(params.refresh));
129
165
  return appendQuery("/cloud/congress/house", search);
130
166
  }
131
167
 
168
+ export function cloudSecFilingsPath(params: CloudSecFilingsParams): string {
169
+ const search = new URLSearchParams({ ticker: params.ticker });
170
+ if (params.limit != null) search.set("limit", String(params.limit));
171
+ if (params.offset != null) search.set("offset", String(params.offset));
172
+ return appendQuery("/cloud/sec/filings", search);
173
+ }
174
+
175
+ export function cloudSecFilingDocumentsPath(params: CloudSecFilingParams): string {
176
+ const search = new URLSearchParams();
177
+ if (params.cik) search.set("cik", params.cik);
178
+ if (params.accession) search.set("accession", params.accession);
179
+ if (params.form) search.set("form", params.form);
180
+ if (params.primaryDocument) search.set("primaryDocument", params.primaryDocument);
181
+ if (params.filingUrl) search.set("filingUrl", params.filingUrl);
182
+ return appendQuery("/cloud/sec/filing/documents", search);
183
+ }
184
+
185
+ export function cloudSecFilingContentPath(params: CloudSecFilingParams): string {
186
+ const search = new URLSearchParams();
187
+ if (params.cik) search.set("cik", params.cik);
188
+ if (params.accession) search.set("accession", params.accession);
189
+ if (params.form) search.set("form", params.form);
190
+ if (params.primaryDocument) search.set("primaryDocument", params.primaryDocument);
191
+ if (params.primaryDocumentUrl) search.set("primaryDocumentUrl", params.primaryDocumentUrl);
192
+ if (params.filingUrl) search.set("filingUrl", params.filingUrl);
193
+ return appendQuery("/cloud/sec/filing/content", search);
194
+ }
195
+
196
+ export function cloudSec13FPath(path: string, params: Record<string, string | number | undefined> = {}): string {
197
+ const search = new URLSearchParams();
198
+ for (const [key, value] of Object.entries(params)) {
199
+ if (value !== undefined) search.set(key, String(value));
200
+ }
201
+ const normalized = path.startsWith("/") ? path.slice(1) : path;
202
+ return appendQuery(`/cloud/sec/13f/${normalized}`, search);
203
+ }
204
+
132
205
  export function cloudNewsPath(params: CloudNewsParams = {}): string {
133
206
  const search = new URLSearchParams();
134
207
  if (params.feed) search.set("feed", params.feed);
@@ -1,4 +1,8 @@
1
1
  import { describe, expect, test } from "bun:test";
2
+ import {
3
+ ConnectionHealthRegistry,
4
+ registerGloomCloudConnectionSources,
5
+ } from "../core/connection-health";
2
6
  import { CloudApiRequestTransport } from "./request";
3
7
 
4
8
  function responseWithBody(body: () => Promise<string>): Response {
@@ -10,6 +14,32 @@ function responseWithBody(body: () => Promise<string>): Response {
10
14
  } as Response;
11
15
  }
12
16
 
17
+ describe("CloudApiRequestTransport connection reporting", () => {
18
+ test("reports real FRED requests to both Gloom and FRED sources", async () => {
19
+ const health = new ConnectionHealthRegistry();
20
+ const dispose = registerGloomCloudConnectionSources(health);
21
+ const transport = new CloudApiRequestTransport({
22
+ connectionHealth: health,
23
+ fetchTransport: async () => responseWithBody(async () => JSON.stringify({ observations: [], info: null })),
24
+ });
25
+
26
+ await transport.request("/cloud/econ/series/VIXCLS?limit=120&sortOrder=desc");
27
+
28
+ expect(health.getSnapshot().sources.map((source) => ({
29
+ id: source.id,
30
+ status: source.status,
31
+ operation: source.lastOperation,
32
+ }))).toEqual([
33
+ { id: "gloom-cloud-http", status: "connected", operation: "GET /cloud/econ/series/VIXCLS" },
34
+ { id: "gloom-cloud-socket", status: "idle", operation: null },
35
+ { id: "gloom-cloud-fred", status: "connected", operation: "GET /cloud/econ/series/VIXCLS" },
36
+ ]);
37
+
38
+ dispose();
39
+ expect(health.getSnapshot().sources).toEqual([]);
40
+ });
41
+ });
42
+
13
43
  describe("CloudApiRequestTransport market deadlines", () => {
14
44
  test("aborts when response headers never arrive", async () => {
15
45
  let signal: AbortSignal | null | undefined;