gloomberb 0.11.0 → 0.11.2

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 (392) hide show
  1. package/README.md +38 -15
  2. package/README.zh-CN.md +12 -12
  3. package/package.json +10 -3
  4. package/src/api-client/auth.ts +14 -10
  5. package/src/api-client/data.ts +54 -0
  6. package/src/api-client/index.test.ts +124 -0
  7. package/src/api-client/index.ts +139 -4
  8. package/src/api-client/paths.ts +89 -0
  9. package/src/api-client/request.ts +15 -2
  10. package/src/api-client/socket-health.test.ts +1 -0
  11. package/src/api-client/socket.ts +2 -1
  12. package/src/api-client/types.ts +163 -0
  13. package/src/app/pane-runtime/index.tsx +4 -1
  14. package/src/app/pane-runtime/plugin-bindings.test.ts +117 -0
  15. package/src/app/pane-runtime/plugin-bindings.ts +25 -4
  16. package/src/app/pane-runtime/ticker-inspector-runtime.ts +25 -14
  17. package/src/app/runtime/desktop-deeplink.test.ts +16 -0
  18. package/src/app/runtime/desktop-deeplink.ts +66 -2
  19. package/src/app/runtime/desktop-menu.ts +3 -0
  20. package/src/app/runtime/plugin-bindings.ts +30 -28
  21. package/src/app/runtime/update.ts +8 -6
  22. package/src/app.tsx +21 -7
  23. package/src/architecture/import-boundaries.test.ts +5 -1
  24. package/src/capabilities/factories.ts +4 -0
  25. package/src/cli/commands/automation.test.ts +9 -0
  26. package/src/cli/commands/automation.ts +22 -73
  27. package/src/cli/index.ts +10 -6
  28. package/src/cli/registry.ts +1 -1
  29. package/src/components/chart/composite/composite-chart.test.tsx +45 -2
  30. package/src/components/chart/composite/composite-chart.tsx +16 -12
  31. package/src/components/chart/composite/format.test.ts +34 -0
  32. package/src/components/chart/composite/format.ts +33 -10
  33. package/src/components/chart/composite/scene.ts +0 -21
  34. package/src/components/chart/composite/tools.ts +49 -0
  35. package/src/components/chart/core/axis-format.ts +13 -3
  36. package/src/components/chart/core/index.test.ts +7 -0
  37. package/src/components/chart/core/renderer.ts +3 -4
  38. package/src/components/command-bar/commands/direct/index.ts +4 -0
  39. package/src/components/command-bar/commands/registry.ts +8 -1
  40. package/src/components/command-bar/layout-items/current-layout.ts +1 -1
  41. package/src/components/command-bar/pane-templates/workflow.ts +4 -0
  42. package/src/components/command-bar/routes/root/shortcut-feedback.ts +3 -0
  43. package/src/components/command-bar/routes/root/shortcuts.test.ts +19 -0
  44. package/src/components/command-bar/routes/root/shortcuts.ts +3 -1
  45. package/src/components/command-bar/surface/index.test.tsx +6 -6
  46. package/src/components/command-bar/surface/index.tsx +4 -1
  47. package/src/components/command-bar/surface/pane.test.tsx +66 -2
  48. package/src/components/command-bar/view-model.test.ts +2 -1
  49. package/src/components/command-bar/workflow/ops.test.ts +54 -0
  50. package/src/components/command-bar/workflow/ops.ts +1 -1
  51. package/src/components/company-logo.test.ts +19 -0
  52. package/src/components/company-logo.tsx +60 -0
  53. package/src/components/feed-data-table/stack-view.tsx +7 -1
  54. package/src/components/index.ts +1 -0
  55. package/src/components/layout/detached-pane-shell.tsx +85 -15
  56. package/src/components/layout/header.tsx +5 -5
  57. package/src/components/layout/pane/title.ts +31 -3
  58. package/src/components/layout/shell/active-drag.ts +4 -11
  59. package/src/components/layout/shell/drag/index.ts +4 -20
  60. package/src/components/layout/shell/drag/runtime.ts +3 -4
  61. package/src/components/layout/shell/index.test.tsx +17 -3
  62. package/src/components/layout/shell/index.tsx +89 -31
  63. package/src/components/layout/shell/menu.test.ts +37 -1
  64. package/src/components/layout/shell/menu.ts +14 -0
  65. package/src/components/layout/shell/native/pointer-runtime.ts +22 -8
  66. package/src/components/layout/shell/pane/actions.ts +0 -5
  67. package/src/components/layout/shell/pane/management-shortcuts.ts +9 -4
  68. package/src/components/layout/shell/shortcuts.ts +6 -3
  69. package/src/components/layout/shell/ticker-link-menu.test.ts +82 -0
  70. package/src/components/layout/shell/ticker-link-menu.ts +78 -0
  71. package/src/components/layout/status-bar.test.tsx +62 -61
  72. package/src/components/layout/status-bar.tsx +53 -75
  73. package/src/components/onboarding/account-step/form-panel.tsx +4 -2
  74. package/src/components/onboarding/account-step/index.tsx +1 -1
  75. package/src/components/onboarding/account-step/signed-in-panel.tsx +1 -1
  76. package/src/components/onboarding/onboarding-wizard.test.tsx +44 -1
  77. package/src/components/onboarding/onboarding-wizard.tsx +13 -5
  78. package/src/components/onboarding/wizard-account.ts +4 -23
  79. package/src/components/table-view-shared.test.ts +11 -0
  80. package/src/components/table-view-shared.tsx +26 -0
  81. package/src/components/ui/data-table/index.tsx +19 -1
  82. package/src/components/ui/fields.tsx +5 -1
  83. package/src/core/state/app/layout-reducer.ts +36 -0
  84. package/src/core/state/app/layout.ts +10 -5
  85. package/src/core/state/app/state.ts +0 -13
  86. package/src/core/state/app/types.ts +6 -4
  87. package/src/data/config/chart-settings.ts +1 -1
  88. package/src/data/config/layout.ts +2 -0
  89. package/src/data/config/store/index.test.ts +27 -6
  90. package/src/data/config/store/normalize.ts +3 -5
  91. package/src/data/plugin-state-store.ts +11 -0
  92. package/src/data/sqlite/cache.test.ts +10 -0
  93. package/src/i18n/es.ts +13 -5
  94. package/src/i18n/ja.ts +13 -5
  95. package/src/i18n/ko.ts +13 -5
  96. package/src/i18n/zh-cn.ts +13 -5
  97. package/src/i18n/zh-tw.ts +13 -5
  98. package/src/layout-marketplace/api.test.ts +54 -0
  99. package/src/layout-marketplace/api.ts +65 -0
  100. package/src/layout-marketplace/gallery-desktop.test.tsx +262 -0
  101. package/src/layout-marketplace/gallery-desktop.tsx +375 -0
  102. package/src/layout-marketplace/gallery-terminal.tsx +376 -0
  103. package/src/layout-marketplace/gallery.test.tsx +84 -0
  104. package/src/layout-marketplace/gallery.tsx +323 -0
  105. package/src/layout-marketplace/geometry.test.ts +47 -0
  106. package/src/layout-marketplace/geometry.ts +80 -0
  107. package/src/layout-marketplace/mini-workspace.test.tsx +122 -0
  108. package/src/layout-marketplace/mini-workspace.tsx +428 -0
  109. package/src/layout-marketplace/model.test.ts +118 -0
  110. package/src/layout-marketplace/model.ts +171 -0
  111. package/src/layout-marketplace/name-dialog.tsx +60 -0
  112. package/src/layout-marketplace/pane-imagery.ts +85 -0
  113. package/src/layout-marketplace/pane.tsx +20 -0
  114. package/src/layout-marketplace/payload.test.ts +246 -0
  115. package/src/layout-marketplace/payload.ts +624 -0
  116. package/src/layout-marketplace/use-marketplace.ts +63 -0
  117. package/src/market-data/coordinator/index.test.ts +2 -2
  118. package/src/market-data/coordinator/index.ts +1 -1
  119. package/src/market-data/hooks.tsx +0 -41
  120. package/src/market-data/market/format.test.ts +7 -0
  121. package/src/market-data/market/freshness.ts +5 -2
  122. package/src/market-data/quotes/freshness.test.ts +16 -0
  123. package/src/news/aggregator.test.ts +92 -3
  124. package/src/news/aggregator.ts +98 -18
  125. package/src/news/hooks.test.tsx +2 -0
  126. package/src/news/hooks.ts +27 -1
  127. package/src/news/news-model.ts +5 -2
  128. package/src/news/types.ts +7 -0
  129. package/src/plugins/broker-sync/broker-sync.test.ts +58 -0
  130. package/src/plugins/broker-sync/normalize.ts +42 -5
  131. package/src/plugins/builtin/account-management/pane.tsx +5 -5
  132. package/src/plugins/builtin/ai/index.tsx +18 -2
  133. package/src/plugins/builtin/ai/screener/pane.test.tsx +47 -4
  134. package/src/plugins/builtin/ai/screener/pane.tsx +35 -12
  135. package/src/plugins/builtin/alerts/alert-engine.test.ts +53 -1
  136. package/src/plugins/builtin/alerts/alert-engine.ts +34 -1
  137. package/src/plugins/builtin/alerts/index.test.tsx +49 -6
  138. package/src/plugins/builtin/alerts/index.tsx +11 -6
  139. package/src/plugins/builtin/alerts/pane.tsx +51 -9
  140. package/src/plugins/builtin/alerts/quotes.ts +11 -3
  141. package/src/plugins/builtin/alerts/types.ts +1 -0
  142. package/src/plugins/builtin/analytics/index.tsx +11 -1
  143. package/src/plugins/builtin/analytics/pane-model.ts +6 -4
  144. package/src/plugins/builtin/broker-manager/index.tsx +3 -0
  145. package/src/plugins/builtin/buildout/model/index.ts +1 -1
  146. package/src/plugins/builtin/buildout/pane/index.tsx +17 -18
  147. package/src/plugins/builtin/cds/client.test.ts +83 -0
  148. package/src/plugins/builtin/cds/client.ts +67 -0
  149. package/src/plugins/builtin/cds/index.tsx +41 -0
  150. package/src/plugins/builtin/cds/model.test.ts +232 -0
  151. package/src/plugins/builtin/cds/model.ts +401 -0
  152. package/src/plugins/builtin/cds/pane.test.tsx +132 -0
  153. package/src/plugins/builtin/cds/pane.tsx +358 -0
  154. package/src/plugins/builtin/chart-composer/chart-spec.ts +31 -0
  155. package/src/plugins/builtin/chart-composer/index.tsx +88 -2
  156. package/src/plugins/builtin/chart-composer/pane-share.test.ts +71 -0
  157. package/src/plugins/builtin/chart-composer/pane.tsx +103 -15
  158. package/src/plugins/builtin/chat/content/index.tsx +4 -1
  159. package/src/plugins/builtin/chat/controller/channels.ts +7 -1
  160. package/src/plugins/builtin/chat/controller/index.ts +14 -9
  161. package/src/plugins/builtin/chat/controller/notifications.ts +13 -3
  162. package/src/plugins/builtin/chat/controller/persistence.ts +0 -3
  163. package/src/plugins/builtin/chat/controller/realtime.ts +3 -3
  164. package/src/plugins/builtin/chat/controller/send.ts +1 -3
  165. package/src/plugins/builtin/chat/controller/session-runtime.ts +18 -11
  166. package/src/plugins/builtin/chat/controller/state.ts +1 -3
  167. package/src/plugins/builtin/chat/controller/storage.ts +0 -2
  168. package/src/plugins/builtin/chat/controller/utils.ts +4 -6
  169. package/src/plugins/builtin/chat/controller/view.ts +2 -2
  170. package/src/plugins/builtin/chat/controller.test.ts +142 -14
  171. package/src/plugins/builtin/chat/message/desktop.test.tsx +88 -0
  172. package/src/plugins/builtin/chat/message/desktop.tsx +3 -1
  173. package/src/plugins/builtin/chat/message/profile-popover.test.tsx +1 -29
  174. package/src/plugins/builtin/chat/profile-popover.ts +53 -22
  175. package/src/plugins/builtin/chat/sidebar.test.tsx +81 -0
  176. package/src/plugins/builtin/chat/sidebar.tsx +63 -0
  177. package/src/plugins/builtin/chat/test-harness.tsx +29 -1
  178. package/src/plugins/builtin/cloud/auth-actions.tsx +14 -7
  179. package/src/plugins/builtin/cloud/auth-commands.ts +21 -51
  180. package/src/plugins/builtin/cloud/auth-dialog.tsx +296 -0
  181. package/src/{components/onboarding/account-step/model.test.ts → plugins/builtin/cloud/auth-model.test.ts} +1 -1
  182. package/src/{components/onboarding/account-step/model.ts → plugins/builtin/cloud/auth-model.ts} +37 -3
  183. package/src/plugins/builtin/cloud/browser.tsx +9 -0
  184. package/src/plugins/builtin/cloud/buildout-module.tsx +23 -0
  185. package/src/plugins/builtin/cloud/device-signin.ts +2 -8
  186. package/src/plugins/builtin/cloud/index.tsx +2 -0
  187. package/src/plugins/builtin/cloud/plugin.tsx +14 -24
  188. package/src/plugins/builtin/cloud/upgrade-command.ts +13 -6
  189. package/src/plugins/builtin/cloud-tweets/pane-share.test.ts +56 -0
  190. package/src/plugins/builtin/cloud-tweets/pane.tsx +20 -5
  191. package/src/plugins/builtin/cloud-tweets/registration.ts +50 -6
  192. package/src/plugins/builtin/composite-plugins.ts +3 -1
  193. package/src/plugins/builtin/congress-trades/detail.tsx +43 -1
  194. package/src/plugins/builtin/congress-trades/model.test.ts +85 -0
  195. package/src/plugins/builtin/congress-trades/model.ts +69 -1
  196. package/src/plugins/builtin/congress-trades/pane.tsx +51 -2
  197. package/src/plugins/builtin/connections/index.ts +3 -0
  198. package/src/plugins/builtin/dividend-yield/client.ts +18 -0
  199. package/src/plugins/builtin/dividend-yield/pane.tsx +7 -6
  200. package/src/plugins/builtin/earnings/index.tsx +5 -3
  201. package/src/plugins/builtin/earnings/model.test.ts +19 -1
  202. package/src/plugins/builtin/earnings/model.ts +17 -15
  203. package/src/plugins/builtin/econ/calendar-model.ts +0 -5
  204. package/src/plugins/builtin/econ/index.test.tsx +6 -1
  205. package/src/plugins/builtin/fear-greed/charts.tsx +12 -8
  206. package/src/plugins/builtin/help/index.tsx +2 -2
  207. package/src/plugins/builtin/holders/thirteenf-match.ts +1 -2
  208. package/src/plugins/builtin/insider/index.tsx +27 -11
  209. package/src/plugins/builtin/kelly-sizer/index.tsx +4 -1
  210. package/src/plugins/builtin/layout-manager/index.tsx +17 -4
  211. package/src/plugins/builtin/news/index.tsx +11 -6
  212. package/src/plugins/builtin/news/wire/breaking/notifications.test.ts +2 -0
  213. package/src/plugins/builtin/news/wire/breaking/pane.test.tsx +2 -0
  214. package/src/plugins/builtin/news/wire/breaking/pane.tsx +4 -1
  215. package/src/plugins/builtin/news/wire/feed-config.test.ts +15 -1
  216. package/src/plugins/builtin/news/wire/feed-config.ts +25 -5
  217. package/src/plugins/builtin/news/wire/index.tsx +3 -3
  218. package/src/plugins/builtin/news/wire/industry-pane.tsx +7 -2
  219. package/src/plugins/builtin/news/wire/news/footer.ts +34 -1
  220. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +4 -1
  221. package/src/plugins/builtin/news/wire/news/table.tsx +8 -2
  222. package/src/plugins/builtin/news/wire/rss/source.test.ts +12 -0
  223. package/src/plugins/builtin/news/wire/rss/source.ts +1 -1
  224. package/src/plugins/builtin/notes/files.ts +80 -0
  225. package/src/plugins/builtin/notes/index.tsx +3 -0
  226. package/src/plugins/builtin/notes/sync.test.ts +49 -0
  227. package/src/plugins/builtin/notes/sync.ts +98 -0
  228. package/src/plugins/builtin/options/index.tsx +1 -0
  229. package/src/plugins/builtin/options/view.test.tsx +25 -3
  230. package/src/plugins/builtin/options/view.tsx +1 -0
  231. package/src/plugins/builtin/portfolio-list/account-metrics.test.ts +22 -0
  232. package/src/plugins/builtin/portfolio-list/account-metrics.ts +12 -7
  233. package/src/plugins/builtin/portfolio-list/cli/render.ts +5 -3
  234. package/src/plugins/builtin/portfolio-list/column-values.ts +15 -4
  235. package/src/plugins/builtin/portfolio-list/header.tsx +13 -3
  236. package/src/plugins/builtin/portfolio-list/index.tsx +8 -0
  237. package/src/plugins/builtin/portfolio-list/metrics.test.ts +31 -1
  238. package/src/plugins/builtin/portfolio-list/pane/index.test.tsx +1 -0
  239. package/src/plugins/builtin/portfolio-list/pane/index.tsx +10 -7
  240. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +6 -1
  241. package/src/plugins/builtin/portfolio-list/position-metrics.ts +25 -6
  242. package/src/plugins/builtin/portfolio-list/summary/index.tsx +35 -21
  243. package/src/plugins/builtin/portfolio-list/summary/totals.ts +20 -6
  244. package/src/plugins/builtin/research/corporate-actions-pane.tsx +5 -12
  245. package/src/plugins/builtin/research/equity-diagnostic-pane.test.tsx +329 -0
  246. package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +582 -0
  247. package/src/plugins/builtin/research/index.test.ts +14 -0
  248. package/src/plugins/builtin/research/index.tsx +29 -0
  249. package/src/plugins/builtin/sec/index.tsx +24 -9
  250. package/src/plugins/builtin/sectors/index.tsx +14 -0
  251. package/src/plugins/builtin/shared/cloud-upgrade.ts +18 -11
  252. package/src/plugins/builtin/shared/public-pane.test.ts +12 -0
  253. package/src/plugins/builtin/shared/public-pane.ts +9 -0
  254. package/src/plugins/builtin/shared/public-share.ts +23 -0
  255. package/src/plugins/builtin/shared/ticker-source.ts +32 -0
  256. package/src/plugins/builtin/shared/ticker-surface.test.ts +27 -0
  257. package/src/plugins/builtin/shared/ticker-surface.ts +17 -0
  258. package/src/plugins/builtin/substack/api/cache.ts +7 -3
  259. package/src/plugins/builtin/substack/api/loaders.ts +36 -12
  260. package/src/plugins/builtin/substack/api/types.ts +2 -0
  261. package/src/plugins/builtin/substack/pane-state.ts +1 -1
  262. package/src/plugins/builtin/substack/pane.tsx +55 -12
  263. package/src/plugins/builtin/thirteenf/api.test.ts +1 -1
  264. package/src/plugins/builtin/thirteenf/api.ts +40 -18
  265. package/src/plugins/builtin/thirteenf/data.ts +15 -7
  266. package/src/plugins/builtin/thirteenf/pane.tsx +45 -10
  267. package/src/plugins/builtin/ticker-detail/index.tsx +10 -0
  268. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +12 -4
  269. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +8 -2
  270. package/src/plugins/builtin/ticker-detail/settings.ts +12 -0
  271. package/src/plugins/builtin/treasury-auctions/index.tsx +2 -0
  272. package/src/plugins/builtin/world-indices/index.tsx +2 -0
  273. package/src/plugins/catalog-browser.test.ts +78 -0
  274. package/src/plugins/catalog-browser.ts +136 -0
  275. package/src/plugins/gridlock-notification.ts +2 -1
  276. package/src/plugins/ibkr/flex/index.test.ts +19 -0
  277. package/src/plugins/ibkr/flex/index.ts +1 -1
  278. package/src/plugins/pane-manager/gridlock.ts +87 -3
  279. package/src/plugins/pane-manager.test.ts +30 -0
  280. package/src/plugins/pane-manager.ts +6 -1
  281. package/src/plugins/prediction-markets/chart.tsx +2 -2
  282. package/src/plugins/prediction-markets/controller/catalog.ts +77 -2
  283. package/src/plugins/prediction-markets/controller/data.ts +7 -1
  284. package/src/plugins/prediction-markets/controller/index.ts +9 -2
  285. package/src/plugins/prediction-markets/controller/keyboard.ts +10 -14
  286. package/src/plugins/prediction-markets/detail/overview.tsx +21 -0
  287. package/src/plugins/prediction-markets/detail/pane.tsx +43 -34
  288. package/src/plugins/prediction-markets/detail/trades.tsx +34 -9
  289. package/src/plugins/prediction-markets/detail.test.tsx +29 -2
  290. package/src/plugins/prediction-markets/index.tsx +8 -4
  291. package/src/plugins/prediction-markets/navigation.ts +0 -1
  292. package/src/plugins/prediction-markets/pane.test.tsx +77 -0
  293. package/src/plugins/prediction-markets/pane.tsx +12 -0
  294. package/src/plugins/prediction-markets/plugin.test.tsx +36 -0
  295. package/src/plugins/prediction-markets/services/kalshi/adapter.ts +49 -9
  296. package/src/plugins/prediction-markets/services/polymarket/adapter.ts +31 -0
  297. package/src/plugins/prediction-markets/test-helpers.tsx +9 -0
  298. package/src/plugins/registry/index.ts +2 -0
  299. package/src/plugins/ticker-navigation.test.ts +24 -0
  300. package/src/plugins/ticker-navigation.ts +40 -0
  301. package/src/remote/schema.ts +1 -1
  302. package/src/renderers/browser/app-services.ts +61 -0
  303. package/src/renderers/browser/cloud-transport.test.ts +68 -0
  304. package/src/renderers/browser/cloud-transport.ts +48 -0
  305. package/src/renderers/browser/config-host.ts +58 -0
  306. package/src/renderers/browser/deeplink-bridge.test.ts +41 -0
  307. package/src/renderers/browser/deeplink-bridge.ts +22 -0
  308. package/src/renderers/browser/error-boundary.tsx +23 -0
  309. package/src/renderers/browser/main.tsx +64 -0
  310. package/src/renderers/browser/persistence.ts +74 -0
  311. package/src/renderers/browser/storage.test.ts +66 -0
  312. package/src/renderers/browser/storage.ts +50 -0
  313. package/src/renderers/browser/ticker-repository.ts +59 -0
  314. package/src/renderers/browser/ui-host.tsx +40 -0
  315. package/src/renderers/cloudflare/worker.test.ts +136 -0
  316. package/src/renderers/cloudflare/worker.ts +116 -0
  317. package/src/renderers/electrobun/bun/application-menu/index.ts +3 -2
  318. package/src/renderers/electrobun/bun/desktop/initialization.ts +1 -0
  319. package/src/renderers/electrobun/bun/desktop/plugin-state.ts +7 -1
  320. package/src/renderers/electrobun/view/app-services.ts +14 -3
  321. package/src/renderers/electrobun/view/build-assets.ts +12 -10
  322. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -1
  323. package/src/renderers/electrobun/view/data-table/index.tsx +10 -1
  324. package/src/renderers/electrobun/view/desktop/controls.tsx +2 -0
  325. package/src/renderers/electrobun/view/dom-ui-host.tsx +143 -0
  326. package/src/renderers/electrobun/view/host/box.test.tsx +33 -0
  327. package/src/renderers/electrobun/view/host/box.tsx +11 -6
  328. package/src/renderers/electrobun/view/host/chart-surface.tsx +7 -1
  329. package/src/renderers/electrobun/view/host/input.tsx +11 -7
  330. package/src/renderers/electrobun/view/host/native.ts +0 -5
  331. package/src/renderers/electrobun/view/main.tsx +2 -0
  332. package/src/renderers/electrobun/view/remote/persistence.ts +5 -0
  333. package/src/renderers/electrobun/view/styles.css +107 -7
  334. package/src/renderers/electrobun/view/toast-host.test.tsx +78 -0
  335. package/src/renderers/electrobun/view/toast-host.tsx +69 -43
  336. package/src/renderers/electrobun/view/ui-host.tsx +6 -137
  337. package/src/renderers/opentui/start.tsx +2 -0
  338. package/src/renderers/share/layout-view.test.tsx +58 -0
  339. package/src/renderers/share/layout-view.tsx +133 -0
  340. package/src/renderers/share/main.tsx +102 -0
  341. package/src/renderers/share/styles.css +54 -0
  342. package/src/renderers/share/view.test.tsx +100 -0
  343. package/src/renderers/share/view.tsx +150 -0
  344. package/src/shares/api.test.ts +105 -0
  345. package/src/shares/api.ts +127 -0
  346. package/src/shares/chart-snapshot.test.ts +34 -0
  347. package/src/shares/chart-snapshot.ts +28 -0
  348. package/src/shares/location.test.ts +19 -0
  349. package/src/shares/location.ts +11 -0
  350. package/src/shares/pane.test.ts +110 -0
  351. package/src/shares/pane.ts +54 -0
  352. package/src/shares/payload.ts +177 -0
  353. package/src/sources/gloomberb-cloud/index.test.ts +61 -1
  354. package/src/sources/gloomberb-cloud/index.ts +86 -14
  355. package/src/sources/gloomberb-cloud/normalizers.test.ts +34 -0
  356. package/src/sources/gloomberb-cloud/normalizers.ts +5 -5
  357. package/src/sources/provider-router/history.test.ts +2 -2
  358. package/src/sources/provider-router/news.test.ts +34 -0
  359. package/src/sources/provider-router/news.ts +15 -1
  360. package/src/sources/sec-edgar.test.ts +59 -0
  361. package/src/sources/sec-edgar.ts +63 -23
  362. package/src/sources/yahoo-finance/mappers.ts +3 -0
  363. package/src/sources/yahoo-finance/snapshots.ts +10 -2
  364. package/src/sources/yahoo-finance/symbols.test.ts +10 -0
  365. package/src/sources/yahoo-finance/symbols.ts +13 -8
  366. package/src/sources/yahoo-finance.test.ts +1 -1
  367. package/src/sources/yahoo-finance.ts +6 -2
  368. package/src/state/app/bootstrap.ts +6 -7
  369. package/src/state/app/context/index.test.ts +41 -1
  370. package/src/state/app/context/index.test.tsx +34 -16
  371. package/src/state/selectors-ui.ts +0 -8
  372. package/src/sync/baseline.ts +32 -0
  373. package/src/sync/controller.test.ts +124 -1
  374. package/src/sync/controller.ts +54 -12
  375. package/src/sync/core-contributors.test.ts +44 -0
  376. package/src/sync/core-contributors.ts +59 -10
  377. package/src/sync/react.ts +22 -2
  378. package/src/sync/types.ts +12 -0
  379. package/src/theme/font-scale.ts +0 -11
  380. package/src/tickers/search/index.test.ts +27 -0
  381. package/src/tickers/search/upsert.ts +10 -0
  382. package/src/types/capability-route-source.ts +2 -1
  383. package/src/types/config.ts +35 -5
  384. package/src/types/desktop-menu.ts +1 -0
  385. package/src/types/plugin.ts +36 -0
  386. package/src/ui/context-menu.tsx +1 -1
  387. package/src/ui/host.tsx +4 -0
  388. package/src/ui/toast.tsx +2 -0
  389. package/src/utils/back-navigation.ts +0 -4
  390. package/src/utils/price-history.ts +11 -10
  391. package/src/utils/sec.test.ts +6 -0
  392. package/src/utils/sec.ts +1 -1
@@ -1,9 +1,12 @@
1
1
  /**
2
- * Logic for the onboarding account step, kept out of the React hook so the
3
- * transitions that gate a network call (validation, retry classification,
4
- * username derivation) can be exercised without rendering the wizard.
2
+ * Shared email/password auth logic for every sign-in surface (onboarding,
3
+ * auth dialog), kept out of React so the transitions that gate a network call
4
+ * (validation, retry classification, username derivation) can be exercised
5
+ * without rendering.
5
6
  */
7
+ import { apiClient, type AuthUser } from "../../../api-client";
6
8
  import { t } from "../../../i18n";
9
+ import { chatController } from "../chat/controller";
7
10
 
8
11
  export type AccountSub = "choose" | "signup" | "login" | "qr" | "signed-in";
9
12
 
@@ -100,6 +103,37 @@ export function classifyAccountError(error: unknown, mode: AccountMode): Account
100
103
  };
101
104
  }
102
105
 
106
+ /**
107
+ * Runs the sign-up or sign-in sequence every surface shares: sign-up derives a
108
+ * username the user never typed (retrying once when the derivation collides),
109
+ * unverified accounts get a verification email, and the captured session is
110
+ * handed to the chat controller so it reaches plugin persistence.
111
+ */
112
+ export async function performEmailAuth(mode: AccountMode, email: string, password: string): Promise<AuthUser> {
113
+ let user: AuthUser;
114
+ if (mode === "signup") {
115
+ try {
116
+ const username = deriveUsernameFromEmail(email);
117
+ user = await apiClient.signUp(email, username, username, password);
118
+ } catch (error) {
119
+ if (!isUsernameConflictError(error)) throw error;
120
+ const retryUsername = deriveUsernameFromEmail(email, 1);
121
+ user = await apiClient.signUp(email, retryUsername, retryUsername, password);
122
+ }
123
+ } else {
124
+ user = await apiClient.signIn(email, password);
125
+ }
126
+ if (!user.emailVerified) {
127
+ await apiClient.sendVerification().catch(() => {});
128
+ }
129
+ const sessionToken = apiClient.getSessionToken();
130
+ if (sessionToken) {
131
+ chatController.adoptSession(sessionToken, user);
132
+ }
133
+ await chatController.refreshSession().catch(() => {});
134
+ return user;
135
+ }
136
+
103
137
  export interface AccountFormState {
104
138
  mode: AccountMode;
105
139
  email: string;
@@ -0,0 +1,9 @@
1
+ import { ChatContent } from "../chat/content";
2
+ import { createChatPane } from "../chat/pane";
3
+ import { ChatStatusWidget } from "../chat/status-widget";
4
+ import { createGloomberbCloudPlugin } from "./plugin";
5
+
6
+ export const browserGloomberbCloudPlugin = createGloomberbCloudPlugin({
7
+ ChatPane: createChatPane(ChatContent),
8
+ ChatStatusWidget,
9
+ });
@@ -0,0 +1,23 @@
1
+ import { BuildoutPane } from "../buildout/pane";
2
+ import type { PluginModule } from "../plugin-module";
3
+
4
+ export const buildoutModule: PluginModule = {
5
+ panes: [{
6
+ id: "buildout",
7
+ name: "TheBuildout",
8
+ icon: "T",
9
+ component: BuildoutPane,
10
+ defaultPosition: "right",
11
+ defaultMode: "floating",
12
+ defaultFloatingSize: { width: 110, height: 34 },
13
+ }],
14
+ paneTemplates: [{
15
+ id: "buildout-pane",
16
+ paneId: "buildout",
17
+ label: "TheBuildout",
18
+ description: "Open TheBuildout infrastructure intelligence.",
19
+ keywords: ["tbo", "buildout", "thebuildout", "infrastructure", "sites", "intel"],
20
+ shortcut: { prefix: "TBO" },
21
+ createInstance: () => ({ placement: "floating" }),
22
+ }],
23
+ };
@@ -4,7 +4,6 @@
4
4
  * adopted through the same persistence and API-client path boot restoration
5
5
  * uses, so the rest of the app sees a normal signed-in session.
6
6
  */
7
- import { hostname } from "os";
8
7
  import {
9
8
  apiClient,
10
9
  type AuthUser,
@@ -53,13 +52,8 @@ const START_RETRY_MAX_MS = 30_000;
53
52
  const POLL_BACKOFF_FACTOR = 2;
54
53
 
55
54
  function defaultClientName(): string {
56
- try {
57
- const name = hostname().trim();
58
- if (name && name !== "localhost") return name;
59
- } catch {
60
- // Browser-bundled builds have no hostname; fall through to the generic name.
61
- }
62
- return "Gloomberb TUI";
55
+ const name = typeof process !== "undefined" ? process.env.HOSTNAME?.trim() : "";
56
+ return name && name !== "localhost" ? name : "Gloomberb";
63
57
  }
64
58
 
65
59
  function defaultClientPlatform(): string | undefined {
@@ -1,4 +1,5 @@
1
1
  import { ChatContent } from "../chat/content";
2
+ import { buildoutModule } from "./buildout-module";
2
3
  import { createChatPane } from "../chat/pane";
3
4
  import { ChatStatusWidget } from "../chat/status-widget";
4
5
  import { createGloomberbCloudPlugin } from "./plugin";
@@ -8,4 +9,5 @@ const ChatPane = createChatPane(ChatContent);
8
9
  export const gloomberbCloudPlugin = createGloomberbCloudPlugin({
9
10
  ChatPane,
10
11
  ChatStatusWidget,
12
+ extraModules: [buildoutModule],
11
13
  });
@@ -3,7 +3,6 @@ import type { GloomPlugin, PaneProps } from "../../../types/plugin";
3
3
  import { apiClient } from "../../../api-client";
4
4
  import { createGloomberbCloudCapabilities, createGloomberbCloudProvider } from "../../../sources/gloomberb-cloud";
5
5
  import { AccountManagementPane } from "../account-management/pane";
6
- import { BuildoutPane } from "../buildout/pane";
7
6
  import { chatController } from "../chat/controller";
8
7
  import {
9
8
  buildDmCommandResults,
@@ -23,10 +22,12 @@ import { composeBuiltinPlugin, type PluginModule } from "../plugin-module";
23
22
  import { registerCloudAuthCommands } from "./auth-commands";
24
23
  import { registerCloudUpgradeCommand } from "./upgrade-command";
25
24
  import { CloudUpgradeStatusWidget } from "./upgrade-status-widget";
25
+ import { createPublicPaneShare } from "../shared/public-pane";
26
26
 
27
27
  interface GloomberbCloudPluginComponents {
28
28
  ChatPane: (props: PaneProps) => ReactNode;
29
29
  ChatStatusWidget: ComponentType;
30
+ extraModules?: readonly PluginModule[];
30
31
  }
31
32
 
32
33
  function createCloudDataModule(): PluginModule {
@@ -59,6 +60,9 @@ function createChatModule(
59
60
  defaultPosition: "right",
60
61
  defaultMode: "floating",
61
62
  defaultFloatingSize: { width: 80, height: 30 },
63
+ portableShare: {
64
+ private: { title: true, params: true, settings: true, state: true },
65
+ },
62
66
  }],
63
67
  paneTemplates: [{
64
68
  id: "new-chat-pane",
@@ -96,7 +100,9 @@ function createChatModule(
96
100
  },
97
101
  setup(ctx) {
98
102
  chatController.attachPersistence(ctx.persistence, ctx.resume);
99
- chatController.setNotifier(ctx.notify);
103
+ chatController.setNotifier(ctx.notify, (channelId, messageId) => {
104
+ ctx.createPaneFromTemplate("new-chat-pane", { arg: channelId, values: { messageId } });
105
+ });
100
106
  ctx.registerCommand({
101
107
  id: "direct-message",
102
108
  label: "DM",
@@ -135,6 +141,9 @@ const accountModule: PluginModule = {
135
141
  defaultPosition: "right",
136
142
  defaultMode: "floating",
137
143
  defaultFloatingSize: { width: 72, height: 36 },
144
+ portableShare: {
145
+ private: { title: true, params: true, settings: true, state: true },
146
+ },
138
147
  }],
139
148
  paneTemplates: [{
140
149
  id: "account-management-pane",
@@ -154,27 +163,6 @@ const accountModule: PluginModule = {
154
163
  },
155
164
  };
156
165
 
157
- const buildoutModule: PluginModule = {
158
- panes: [{
159
- id: "buildout",
160
- name: "TheBuildout",
161
- icon: "T",
162
- component: BuildoutPane,
163
- defaultPosition: "right",
164
- defaultMode: "floating",
165
- defaultFloatingSize: { width: 110, height: 34 },
166
- }],
167
- paneTemplates: [{
168
- id: "buildout-pane",
169
- paneId: "buildout",
170
- label: "TheBuildout",
171
- description: "Open TheBuildout infrastructure intelligence.",
172
- keywords: ["tbo", "buildout", "thebuildout", "infrastructure", "sites", "intel"],
173
- shortcut: { prefix: "TBO" },
174
- createInstance: () => ({ placement: "floating" }),
175
- }],
176
- };
177
-
178
166
  const congressTradesModule: PluginModule = {
179
167
  panes: [{
180
168
  id: CONGRESS_TRADES_PANE_ID,
@@ -193,6 +181,7 @@ const congressTradesModule: PluginModule = {
193
181
  keywords: ["congress", "house", "trades", "ptr", "stock", "disclosures"],
194
182
  shortcut: { prefix: "CG" },
195
183
  createInstance: () => ({ placement: "floating" }),
184
+ publicShare: createPublicPaneShare("Congress Trades"),
196
185
  }],
197
186
  };
198
187
 
@@ -203,6 +192,7 @@ const twitterModule: PluginModule = {
203
192
  export function createGloomberbCloudPlugin({
204
193
  ChatPane,
205
194
  ChatStatusWidget,
195
+ extraModules = [],
206
196
  }: GloomberbCloudPluginComponents): GloomPlugin {
207
197
  return composeBuiltinPlugin({
208
198
  id: "gloomberb-cloud",
@@ -215,7 +205,7 @@ export function createGloomberbCloudPlugin({
215
205
  createCloudDataModule(),
216
206
  createChatModule(ChatPane, ChatStatusWidget),
217
207
  accountModule,
218
- buildoutModule,
208
+ ...extraModules,
219
209
  congressTradesModule,
220
210
  twitterModule,
221
211
  ],
@@ -3,6 +3,7 @@ import type { GloomPluginContext } from "../../../types/plugin";
3
3
  import { requestAccountManagementTab } from "../account-management/navigation";
4
4
  import { openCloudUpgradeUrl } from "../shared/cloud-upgrade";
5
5
  import { resolvePlanAccess } from "../shared/plan-access";
6
+ import { requestAuthDialog } from "./auth-dialog";
6
7
 
7
8
  export function registerCloudUpgradeCommand(ctx: GloomPluginContext): void {
8
9
  ctx.registerCommand({
@@ -15,14 +16,20 @@ export function registerCloudUpgradeCommand(ctx: GloomPluginContext): void {
15
16
  // Paying subscribers have nothing to upgrade to; trial accounts still do.
16
17
  hidden: () => resolvePlanAccess(apiClient.getCurrentUser()).isPayingPro,
17
18
  execute: () => {
18
- if (!apiClient.getSessionToken()) {
19
- ctx.openCommandBar("Sign Up");
19
+ const openUpgrade = () => {
20
+ if (openCloudUpgradeUrl()) return;
21
+ // No renderer-backed opener yet: fall back to the in-app billing surface.
22
+ requestAccountManagementTab("pro");
23
+ ctx.showPane("account-management");
24
+ };
25
+ if (!apiClient.isSignedIn()) {
26
+ // Upgrading needs an account first; continue to checkout once signed in.
27
+ if (!requestAuthDialog({ mode: "signup", onSignedIn: openUpgrade })) {
28
+ ctx.openCommandBar("Sign Up");
29
+ }
20
30
  return;
21
31
  }
22
- if (openCloudUpgradeUrl()) return;
23
- // No renderer-backed opener yet: fall back to the in-app billing surface.
24
- requestAccountManagementTab("pro");
25
- ctx.showPane("account-management");
32
+ openUpgrade();
26
33
  },
27
34
  });
28
35
  }
@@ -0,0 +1,56 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import type {
3
+ GloomPluginContext,
4
+ PaneTemplateContext,
5
+ PaneTemplateDef,
6
+ } from "../../../types/plugin";
7
+ import { createFeed } from "./model";
8
+ import { registerTwitterFeedFeature } from "./registration";
9
+
10
+ const context: PaneTemplateContext = {
11
+ config: {} as PaneTemplateContext["config"],
12
+ layout: { dockRoot: null, instances: [], floating: [], detached: [] },
13
+ focusedPaneId: null,
14
+ activeTicker: null,
15
+ activeCollectionId: null,
16
+ };
17
+
18
+ function twitterTemplate(): PaneTemplateDef {
19
+ let template: PaneTemplateDef | null = null;
20
+ registerTwitterFeedFeature({
21
+ registerTickerResearchTab() {},
22
+ registerPane() {},
23
+ registerCommand() {},
24
+ registerPaneTemplate(value) { template = value; },
25
+ } as unknown as GloomPluginContext);
26
+ if (!template) throw new Error("X Feed template was not registered");
27
+ return template;
28
+ }
29
+
30
+ describe("X Feed pane sharing", () => {
31
+ test("restores the active advanced-search query", async () => {
32
+ const template = twitterTemplate();
33
+ const first = createFeed("$SPY", "Latest");
34
+ const active = createFeed("$NVDA OR $AMD", "Top");
35
+ const shared = template.publicShare?.serialize({
36
+ pane: {
37
+ instanceId: "twitter-feed:private-query-id",
38
+ paneId: "twitter-feed",
39
+ params: { query: "old", queryType: "Latest" },
40
+ },
41
+ paneState: {
42
+ pluginState: {
43
+ "gloomberb-cloud": {
44
+ feeds: { feeds: [first, active] },
45
+ activeFeedId: active.id,
46
+ },
47
+ },
48
+ },
49
+ });
50
+
51
+ expect(shared?.data).toEqual({ query: "$NVDA OR $AMD", queryType: "Top" });
52
+ const options = template.publicShare?.restore(shared!.data);
53
+ const instance = await template.createInstance?.(context, options ?? undefined);
54
+ expect(instance?.params).toEqual({ query: "$NVDA OR $AMD", queryType: "Top" });
55
+ });
56
+ });
@@ -60,11 +60,16 @@ export function TwitterTickerTab({ focused, width, height }: TickerResearchTabPr
60
60
  export function TwitterFeedPane({ focused, width, height }: PaneProps) {
61
61
  const paneId = usePaneInstanceId();
62
62
  const paneInstance = usePaneInstance();
63
- const [persistedState, setPersistedState] = usePluginState<PersistedTwitterFeedState>(
63
+ const [legacyPersistedState, setLegacyPersistedState] = usePluginState<PersistedTwitterFeedState>(
64
64
  `twitter-feed:${paneId}`,
65
65
  EMPTY_FEED_STATE,
66
66
  { schemaVersion: TWEET_SEARCH_SCHEMA_VERSION },
67
67
  );
68
+ const [panePersistedState, setPanePersistedState] = usePluginPaneState<PersistedTwitterFeedState | null>(
69
+ "feeds",
70
+ null,
71
+ );
72
+ const persistedState = panePersistedState ?? legacyPersistedState;
68
73
  const [launchRequest, setLaunchRequest] = usePluginState<TwitterFeedLaunchRequest | null>(
69
74
  TWITTER_FEED_LAUNCH_STATE_KEY,
70
75
  null,
@@ -77,6 +82,14 @@ export function TwitterFeedPane({ focused, width, height }: PaneProps) {
77
82
  const initializedRef = useRef(false);
78
83
  const feeds = useMemo(() => normalizeFeeds(persistedState), [persistedState]);
79
84
 
85
+ useEffect(() => {
86
+ if (panePersistedState !== null) return;
87
+ const legacyFeeds = normalizeFeeds(legacyPersistedState);
88
+ if (legacyFeeds.length === 0) return;
89
+ setPanePersistedState({ feeds: legacyFeeds });
90
+ setLegacyPersistedState(EMPTY_FEED_STATE);
91
+ }, [legacyPersistedState, panePersistedState, setLegacyPersistedState, setPanePersistedState]);
92
+
80
93
  const focusSearch = useCallback(() => {
81
94
  setSearchFocused(true);
82
95
  setSearchFocusToken((current) => current + 1);
@@ -87,8 +100,10 @@ export function TwitterFeedPane({ focused, width, height }: PaneProps) {
87
100
  }, []);
88
101
 
89
102
  const updateFeeds = useCallback((updater: (feeds: TwitterFeed[]) => TwitterFeed[]) => {
90
- setPersistedState((current) => ({ feeds: updater(normalizeFeeds(current)) }));
91
- }, [setPersistedState]);
103
+ setPanePersistedState((current) => ({
104
+ feeds: updater(normalizeFeeds(current ?? persistedState)),
105
+ }));
106
+ }, [persistedState, setPanePersistedState]);
92
107
 
93
108
  const addFeed = useCallback((query = "", queryType: CloudTweetQueryType = "Latest") => {
94
109
  const feed = createFeed(query, queryType);
@@ -146,10 +161,10 @@ export function TwitterFeedPane({ focused, width, height }: PaneProps) {
146
161
  const seedQuery = typeof paneInstance?.params?.query === "string" ? paneInstance.params.query : "";
147
162
  const seedType = paneInstance?.params?.queryType === "Top" ? "Top" : "Latest";
148
163
  const feed = createFeed(seedQuery, seedType);
149
- setPersistedState({ feeds: [feed] });
164
+ setPanePersistedState({ feeds: [feed] });
150
165
  setActiveFeedId(feed.id);
151
166
  if (!seedQuery.trim()) focusSearch();
152
- }, [feeds.length, focusSearch, paneInstance?.params?.query, paneInstance?.params?.queryType, setActiveFeedId, setPersistedState]);
167
+ }, [feeds.length, focusSearch, paneInstance?.params?.query, paneInstance?.params?.queryType, setActiveFeedId, setPanePersistedState]);
153
168
 
154
169
  useEffect(() => {
155
170
  if (!launchRequest) return;
@@ -1,8 +1,10 @@
1
1
  import type { GloomPluginContext } from "../../../types/plugin";
2
+ import { canonicalExchange } from "../../../utils/exchanges";
2
3
  import {
3
4
  TWITTER_FEED_LAUNCH_SCHEMA_VERSION,
4
5
  TWITTER_FEED_LAUNCH_STATE_KEY,
5
6
  TWITTER_FEED_PANE_ID,
7
+ normalizeFeeds,
6
8
  type TwitterFeedLaunchRequest,
7
9
  } from "./model";
8
10
  import {
@@ -10,13 +12,21 @@ import {
10
12
  TwitterTickerTab,
11
13
  } from "./pane";
12
14
 
15
+ function record(value: unknown): value is Record<string, unknown> {
16
+ return value !== null && typeof value === "object" && !Array.isArray(value);
17
+ }
18
+
13
19
  export function registerTwitterFeedFeature(ctx: GloomPluginContext): void {
14
20
  ctx.registerTickerResearchTab({
15
21
  id: "ticker-tweets",
16
22
  name: "Tweets",
17
23
  order: 38,
18
24
  component: TwitterTickerTab,
19
- isVisible: ({ ticker }) => !!ticker,
25
+ isVisible: ({ ticker }) => {
26
+ if (!ticker) return false;
27
+ const exchange = canonicalExchange(ticker.metadata.exchange);
28
+ return !exchange || ["NASDAQ", "NYSE", "AMEX", "ARCA", "BATS", "OTC", "PINK"].includes(exchange);
29
+ },
20
30
  });
21
31
 
22
32
  ctx.registerPane({
@@ -36,16 +46,50 @@ export function registerTwitterFeedFeature(ctx: GloomPluginContext): void {
36
46
  description: "Open an X advanced-search feed.",
37
47
  keywords: ["twitter", "x", "tweet", "tweets", "feed", "social"],
38
48
  createInstance: (_context, options) => {
39
- const query = options?.values?.query?.trim() || options?.arg?.trim() || "";
49
+ const shared = record(options?.shareData) ? options.shareData : null;
50
+ const query = typeof shared?.query === "string"
51
+ ? shared.query.trim()
52
+ : options?.values?.query?.trim() || options?.arg?.trim() || "";
53
+ const queryType = shared?.queryType === "Top" || options?.values?.queryType === "Top"
54
+ ? "Top"
55
+ : "Latest";
40
56
  return {
41
57
  title: "X Feed",
42
58
  placement: "floating",
43
- params: {
44
- query,
45
- queryType: options?.values?.queryType === "Top" ? "Top" : "Latest",
46
- },
59
+ params: { query, queryType },
47
60
  };
48
61
  },
62
+ publicShare: {
63
+ serialize: ({ pane, paneState }) => {
64
+ const pluginState = record(paneState.pluginState)
65
+ ? paneState.pluginState["gloomberb-cloud"]
66
+ : null;
67
+ const feedsState = record(pluginState) ? pluginState.feeds : null;
68
+ const feeds = normalizeFeeds(feedsState);
69
+ const activeFeedId = record(pluginState) && typeof pluginState.activeFeedId === "string"
70
+ ? pluginState.activeFeedId
71
+ : null;
72
+ const active = feeds.find((feed) => feed.id === activeFeedId) ?? feeds[0];
73
+ const query = active?.query.trim()
74
+ || (typeof pane.params?.query === "string" ? pane.params.query.trim() : "");
75
+ if (!query) return null;
76
+ return {
77
+ title: pane.title?.trim() || "X Feed",
78
+ data: {
79
+ query,
80
+ queryType: active?.queryType === "Top" || pane.params?.queryType === "Top" ? "Top" : "Latest",
81
+ },
82
+ };
83
+ },
84
+ restore: (data) => (
85
+ Object.keys(data).every((key) => key === "query" || key === "queryType")
86
+ && typeof data.query === "string"
87
+ && data.query.trim().length > 0
88
+ && (data.queryType === "Latest" || data.queryType === "Top")
89
+ ? { shareData: { query: data.query.trim(), queryType: data.queryType } }
90
+ : null
91
+ ),
92
+ },
49
93
  });
50
94
 
51
95
  ctx.registerCommand({
@@ -7,6 +7,7 @@ import { brokerManagerModule } from "./broker-manager";
7
7
  import { changelogModule } from "./changelog";
8
8
  import { connectionsModule } from "./connections";
9
9
  import { correlationModule } from "./correlation";
10
+ import { cdsModule } from "./cds";
10
11
  import { creditConditionsModule } from "./credit-conditions";
11
12
  import { economicCalendarModule } from "./econ";
12
13
  import { earningsModule } from "./earnings";
@@ -89,7 +90,7 @@ export const macroPlugin = composeBuiltinPlugin({
89
90
  id: "macro",
90
91
  name: "Macro",
91
92
  version: "1.0.0",
92
- description: "Economic calendar, rates, volatility, credit spreads, Treasury auctions, earnings, IPOs, and live financial TV.",
93
+ description: "Economic calendar, rates, volatility, credit spreads, single-name CDS, Treasury auctions, earnings, IPOs, and live financial TV.",
93
94
  toggleable: true,
94
95
  modules: [
95
96
  macroSharedResourcesModule,
@@ -97,6 +98,7 @@ export const macroPlugin = composeBuiltinPlugin({
97
98
  yieldCurveModule,
98
99
  volatilityModule,
99
100
  creditConditionsModule,
101
+ cdsModule,
100
102
  treasuryAuctionsModule,
101
103
  earningsModule,
102
104
  ipoCalendarModule,
@@ -1,8 +1,9 @@
1
1
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
- import { Box, ScrollBox, Text, TextAttributes, useRendererHost } from "../../../ui";
2
+ import { Box, ScrollBox, Text, TextAttributes, useRendererHost, type ScrollBoxRenderable } from "../../../ui";
3
3
  import {
4
4
  DataTableView,
5
5
  usePaneFooter,
6
+ useTableLoadMore,
6
7
  type DataTableKeyEvent,
7
8
  } from "../../../components";
8
9
  import { useInlineTickerOpener } from "../../../state/hooks/inline-tickers";
@@ -19,6 +20,9 @@ import {
19
20
  CONGRESS_MEMBER_FILING_LIMIT,
20
21
  CONGRESS_MEMBER_TRADE_LIMIT,
21
22
  CONGRESS_TRADES_PANE_ID,
23
+ canLoadMoreCongress,
24
+ mergeCongressPages,
25
+ nextCongressPage,
22
26
  buildMemberTradeColumns,
23
27
  formatAmountRange,
24
28
  formatLag,
@@ -121,6 +125,8 @@ export function MemberTradesDetail({
121
125
  direction: "desc",
122
126
  });
123
127
  const fetchGenRef = useRef(0);
128
+ const [loadingMore, setLoadingMore] = useState(false);
129
+ const tradeScrollRef = useRef<ScrollBoxRenderable | null>(null);
124
130
 
125
131
  const load = useCallback((refresh = false) => {
126
132
  fetchGenRef.current += 1;
@@ -150,6 +156,40 @@ export function MemberTradesDetail({
150
156
  });
151
157
  }, [filingLimit, member.memberName, member.stateDistrict]);
152
158
 
159
+ const loadMore = useCallback(() => {
160
+ if (!detailPayload || loadingMore || status !== "loaded") return;
161
+ const nextRequest = nextCongressPage(detailPayload);
162
+ if (!nextRequest) return;
163
+ const gen = fetchGenRef.current;
164
+ setLoadingMore(true);
165
+ apiClient.getCloudCongressHouse({
166
+ ...nextRequest,
167
+ member: member.memberName,
168
+ limit: CONGRESS_MEMBER_TRADE_LIMIT,
169
+ filingLimit: Math.max(CONGRESS_MEMBER_FILING_LIMIT, filingLimit),
170
+ })
171
+ .then((payload) => {
172
+ if (fetchGenRef.current !== gen) return;
173
+ const merged = detailPayload ? mergeCongressPages(detailPayload, payload) : payload;
174
+ const exactMemberTrades = merged.trades.filter((trade) => (
175
+ trade.memberName === member.memberName
176
+ && trade.stateDistrict === member.stateDistrict
177
+ ));
178
+ setDetailPayload(merged);
179
+ setTrades(exactMemberTrades.length > 0 ? exactMemberTrades : merged.trades);
180
+ })
181
+ .finally(() => {
182
+ if (fetchGenRef.current !== gen) return;
183
+ setLoadingMore(false);
184
+ });
185
+ }, [detailPayload, filingLimit, loadingMore, member.memberName, member.stateDistrict, status]);
186
+
187
+ const onTradeScroll = useTableLoadMore(
188
+ tradeScrollRef,
189
+ !!detailPayload && status === "loaded" && !loadingMore && canLoadMoreCongress(detailPayload),
190
+ loadMore,
191
+ );
192
+
153
193
  useEffect(() => {
154
194
  fetchGenRef.current += 1;
155
195
  setTrades(initialTrades);
@@ -282,6 +322,8 @@ export function MemberTradesDetail({
282
322
  renderCell={renderCongressTradeCell}
283
323
  emptyStateTitle={emptyTitle}
284
324
  showHorizontalScrollbar={false}
325
+ scrollRef={tradeScrollRef}
326
+ onBodyScrollActivity={onTradeScroll}
285
327
  />
286
328
  </Box>
287
329
  );
@@ -0,0 +1,85 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import type { CloudCongressHousePayload } from "../../../api-client";
3
+ import { canLoadMoreCongress, congressPageAfterEmpty, mergeCongressPages, nextCongressPage } from "./model";
4
+
5
+ function payload(overrides: Partial<CloudCongressHousePayload> = {}): CloudCongressHousePayload {
6
+ return {
7
+ asOf: "2026-01-01T00:00:00.000Z",
8
+ chamber: "house",
9
+ source: "house-clerk",
10
+ year: 2026,
11
+ indexUpdatedAt: null,
12
+ filingsScanned: 20,
13
+ filingCount: 80,
14
+ filingOffset: 0,
15
+ hasMore: false,
16
+ hasMoreFilings: true,
17
+ nextOffset: 20,
18
+ nextFilingOffset: 20,
19
+ trades: [],
20
+ members: [],
21
+ ...overrides,
22
+ };
23
+ }
24
+
25
+ describe("congress paging", () => {
26
+ test("walks remaining trades, then more filings, then the previous year", () => {
27
+ expect(nextCongressPage(payload({ hasMore: true, nextOffset: 40 }))).toEqual({
28
+ year: 2026,
29
+ offset: 40,
30
+ filingOffset: 0,
31
+ });
32
+ expect(nextCongressPage(payload({ hasMore: false, hasMoreFilings: true, nextFilingOffset: 20 }))).toEqual({
33
+ year: 2026,
34
+ offset: 0,
35
+ filingOffset: 20,
36
+ });
37
+ expect(nextCongressPage(payload({ hasMore: false, hasMoreFilings: false, year: 2026 }))).toEqual({
38
+ year: 2025,
39
+ offset: 0,
40
+ filingOffset: 0,
41
+ });
42
+ expect(nextCongressPage(payload({
43
+ hasMore: false,
44
+ hasMoreFilings: undefined,
45
+ filingOffset: 0,
46
+ filingsScanned: 20,
47
+ filingCount: 80,
48
+ }))).toEqual({
49
+ year: 2026,
50
+ offset: 0,
51
+ filingOffset: 20,
52
+ });
53
+ expect(canLoadMoreCongress(payload({ hasMore: false, hasMoreFilings: false, year: 2008 }))).toBe(false);
54
+ expect(nextCongressPage(congressPageAfterEmpty(payload({
55
+ hasMore: true,
56
+ year: 2026,
57
+ filingsScanned: 20,
58
+ filingCount: 80,
59
+ })))).toEqual({
60
+ year: 2025,
61
+ offset: 0,
62
+ filingOffset: 0,
63
+ });
64
+ });
65
+
66
+ test("appends unique trades and members from the next page", () => {
67
+ const merged = mergeCongressPages(
68
+ payload({
69
+ trades: [{ id: "t1" } as CloudCongressHousePayload["trades"][number]],
70
+ members: [{ id: "m1" } as CloudCongressHousePayload["members"][number]],
71
+ }),
72
+ payload({
73
+ year: 2025,
74
+ trades: [
75
+ { id: "t1" } as CloudCongressHousePayload["trades"][number],
76
+ { id: "t2" } as CloudCongressHousePayload["trades"][number],
77
+ ],
78
+ members: [{ id: "m2" } as CloudCongressHousePayload["members"][number]],
79
+ }),
80
+ );
81
+ expect(merged.year).toBe(2025);
82
+ expect(merged.trades.map((trade) => trade.id)).toEqual(["t1", "t2"]);
83
+ expect(merged.members.map((member) => member.id)).toEqual(["m1", "m2"]);
84
+ });
85
+ });