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
@@ -0,0 +1,401 @@
1
+ import type { CloudCdsTradePayload } from "../../../api-client";
2
+ import type { DataTableColumn } from "../../../components";
3
+ import type { TickerRecord } from "../../../types/ticker";
4
+ import { formatCompact } from "../../../utils/format";
5
+ import { compareSortValues, type SortPreference } from "../../../utils/sort-values";
6
+
7
+ export const CDS_PANE_ID = "cds";
8
+
9
+ /**
10
+ * Raw DTCC values are decimals: `Fixed rate-Leg 1 = 0.01` is a 100bp coupon and
11
+ * `Spread-Leg 1 = 0.00256` under notation code "3" is 25.6bp. Only a report that
12
+ * explicitly labels itself basis points or percent is read any other way. Both
13
+ * conversions happen here and nowhere else.
14
+ */
15
+ const PERCENT_TO_BP = 100;
16
+ const DECIMAL_TO_BP = 10_000;
17
+
18
+ export interface CdsTrade {
19
+ id: string;
20
+ /** Reported spelling, kept verbatim so a display name can be chosen later. */
21
+ issuer: string;
22
+ /** Alias-collapsed grouping key. Internal only; never shown or sent. */
23
+ issuerKey: string;
24
+ /** Execution time when the report carried a usable one, else event time. */
25
+ eventAt: number;
26
+ maturity: string | null;
27
+ notional: number | null;
28
+ notionalCapped: boolean;
29
+ currency: string | null;
30
+ couponBp: number | null;
31
+ /** Only what the report carried. Never derived from upfront or coupon. */
32
+ spreadBp: number | null;
33
+ upfront: number | null;
34
+ upfrontCurrency: string | null;
35
+ }
36
+
37
+ export interface CdsIssuerSummary {
38
+ /** Grouping key, and the row's selection id. */
39
+ key: string;
40
+ /** Best spelling among the aliases in this group. */
41
+ issuer: string;
42
+ trades: number;
43
+ lastTradeAt: number;
44
+ latestSpreadBp: number | null;
45
+ }
46
+
47
+ /**
48
+ * Legal-name noise the tape spells inconsistently: "Oracle Corporation",
49
+ * "ORACLE CORPORATION", and "Oracle Cop" are one issuer. Only suffixes and
50
+ * articles belong here. Words that distinguish real entities, such as holdings,
51
+ * group, or a year, must keep their own row.
52
+ */
53
+ const LEGAL_NAME_NOISE = new Set([
54
+ "the", "co", "company", "corp", "corporation", "cop",
55
+ "inc", "incorporated", "limited", "ltd", "plc",
56
+ ]);
57
+
58
+ /**
59
+ * Obligation vocabulary the tape writes into `UPI Underlier Name` when it has
60
+ * no reference entity: seniority, instrument type, format, rule reference, and
61
+ * currency. A name built only from these describes a bond, not an issuer.
62
+ */
63
+ const OBLIGATION_NOISE = new Set([
64
+ "sr", "senior", "sub", "subordinated", "jr", "junior",
65
+ "nt", "nts", "note", "notes", "bd", "bds", "bond", "bonds",
66
+ "gtd", "guaranteed", "global", "medium", "term", "mtn", "emtn",
67
+ "144a", "regs", "s", "call",
68
+ "eur", "usd", "gbp", "jpy", "chf", "cad", "aud",
69
+ ]);
70
+
71
+ /** Whole-string sentinels the feed sends instead of leaving the field empty. */
72
+ const NAME_PLACEHOLDERS = new Set([
73
+ "no name obtainable", "name not available", "not available", "unknown", "n a",
74
+ ]);
75
+
76
+ /** Case, accents, and punctuation folded away into comparable tokens. */
77
+ function nameTokens(name: string): string[] {
78
+ return name
79
+ .normalize("NFKD")
80
+ .replace(/[\u0300-\u036f]/g, "")
81
+ .toLowerCase()
82
+ .replace(/[^\p{Letter}\p{Number}]+/gu, " ")
83
+ .trim()
84
+ .split(" ")
85
+ .filter(Boolean);
86
+ }
87
+
88
+ /**
89
+ * Whether a `UPI Underlier Name` names a company at all. "SR GTD NT 144A" and
90
+ * "MEDIUM TERM NOTES EUR 2.3750 S.10/CALL" are obligation descriptions that
91
+ * would otherwise become fake issuers and outrank real ones, while
92
+ * "Tencent Holdings Limited" and "DT.BANK MTN 17/20" carry a real entity.
93
+ */
94
+ function identifiesIssuer(name: string): boolean {
95
+ const tokens = nameTokens(name);
96
+ const folded = tokens.join(" ");
97
+ if (
98
+ tokens.length === 0
99
+ || NAME_PLACEHOLDERS.has(folded)
100
+ || folded.startsWith("medium term note")
101
+ ) return false;
102
+ return tokens.some((token) => (
103
+ !OBLIGATION_NOISE.has(token)
104
+ && !LEGAL_NAME_NOISE.has(token)
105
+ && !/^\d+$/.test(token)
106
+ ));
107
+ }
108
+
109
+ /** Case, accents, and punctuation folded away, then legal-name noise dropped. */
110
+ export function issuerGroupKey(name: string): string {
111
+ const tokens = nameTokens(name);
112
+ const meaningful = tokens.filter((token) => !LEGAL_NAME_NOISE.has(token));
113
+ // A name made only of noise ("The Company") keeps its tokens rather than
114
+ // collapsing every such issuer into one empty-key row.
115
+ return (meaningful.length > 0 ? meaningful : tokens).join(" ") || name.trim().toLowerCase();
116
+ }
117
+
118
+ /** Mixed case reads as a real name; all-caps is the tape shouting. */
119
+ function isMixedCase(name: string): boolean {
120
+ return name !== name.toUpperCase();
121
+ }
122
+
123
+ /**
124
+ * Picks the spelling a human would write. Mixed case wins over all-caps first,
125
+ * then the longer name, so a full legal name beats an abbreviated or inverted
126
+ * one ("Oracle Corporation" over "Oracle Cop", "The Boeing Company" over
127
+ * "Boeing Co/The"). Ties break lexically so refreshes do not reshuffle.
128
+ */
129
+ function preferredIssuerName(current: string, candidate: string): string {
130
+ const currentMixed = isMixedCase(current);
131
+ const candidateMixed = isMixedCase(candidate);
132
+ if (currentMixed !== candidateMixed) return candidateMixed ? candidate : current;
133
+ if (current.length !== candidate.length) return candidate.length > current.length ? candidate : current;
134
+ return candidate.localeCompare(current) < 0 ? candidate : current;
135
+ }
136
+
137
+ export function spreadToBasisPoints(value: number | null, notation: string | null): number | null {
138
+ if (value == null || !Number.isFinite(value)) return null;
139
+ const unit = (notation ?? "").trim().toLowerCase();
140
+ if (unit.startsWith("bp") || unit.includes("basis")) return value;
141
+ if (unit.includes("percent") || unit === "%") return value * PERCENT_TO_BP;
142
+ // Notation code "3", textual "decimal", and an unlabelled raw value are all decimals.
143
+ return value * DECIMAL_TO_BP;
144
+ }
145
+
146
+ function couponToBasisPoints(fixedRate: number | null): number | null {
147
+ if (fixedRate == null || !Number.isFinite(fixedRate)) return null;
148
+ return fixedRate * DECIMAL_TO_BP;
149
+ }
150
+
151
+ /**
152
+ * When the trade was struck, not when the tape carried it. A lifecycle
153
+ * correction is disseminated long after the fact, so using the event time would
154
+ * date an old trade as new.
155
+ */
156
+ function tapeTime(trade: CloudCdsTradePayload): number | null {
157
+ const executed = trade.executionTimestamp ? Date.parse(trade.executionTimestamp) : Number.NaN;
158
+ if (Number.isFinite(executed)) return executed;
159
+ const event = Date.parse(trade.eventTimestamp);
160
+ return Number.isFinite(event) ? event : null;
161
+ }
162
+
163
+ /**
164
+ * A reported issuer name is always trusted, even when the UPI underlier beside
165
+ * it is generic. Otherwise the UPI name is used only when it names a company.
166
+ * There is no third fallback: underlier IDs are opaque instrument codes, so
167
+ * showing one would just be a different kind of fake issuer.
168
+ */
169
+ function issuerOf(trade: CloudCdsTradePayload): string | null {
170
+ const reported = trade.issuerName?.trim();
171
+ if (reported) return reported;
172
+ const underlier = trade.upiUnderlierName?.trim();
173
+ return underlier && identifiesIssuer(underlier) ? underlier : null;
174
+ }
175
+
176
+ export function normalizeCdsTrades(trades: readonly CloudCdsTradePayload[]): CdsTrade[] {
177
+ const rows: CdsTrade[] = [];
178
+ for (const trade of trades) {
179
+ const eventAt = tapeTime(trade);
180
+ if (eventAt == null) continue;
181
+ const issuer = issuerOf(trade);
182
+ // Nothing names the reference entity, so the row would invent one.
183
+ if (!issuer) continue;
184
+ rows.push({
185
+ id: trade.disseminationId,
186
+ issuer,
187
+ issuerKey: issuerGroupKey(issuer),
188
+ eventAt,
189
+ maturity: trade.maturityDate ?? trade.expirationDate,
190
+ notional: trade.notionalAmount,
191
+ notionalCapped: trade.notionalCapped,
192
+ currency: trade.notionalCurrency,
193
+ couponBp: couponToBasisPoints(trade.fixedRate),
194
+ spreadBp: spreadToBasisPoints(trade.reportedSpread, trade.spreadNotation),
195
+ upfront: trade.upfrontAmount,
196
+ upfrontCurrency: trade.upfrontCurrency,
197
+ });
198
+ }
199
+ return rows;
200
+ }
201
+
202
+ /**
203
+ * Most-active issuers, with the spread of each issuer's newest report that
204
+ * actually carried one. A quoted issuer whose last print omitted the spread
205
+ * keeps its last known level instead of falling back to "--".
206
+ */
207
+ export function summarizeIssuers(trades: readonly CdsTrade[]): CdsIssuerSummary[] {
208
+ const byIssuer = new Map<string, CdsIssuerSummary>();
209
+ const spreadAt = new Map<string, number>();
210
+ for (const trade of trades) {
211
+ const current = byIssuer.get(trade.issuerKey);
212
+ if (!current) {
213
+ byIssuer.set(trade.issuerKey, {
214
+ key: trade.issuerKey,
215
+ issuer: trade.issuer,
216
+ trades: 1,
217
+ lastTradeAt: trade.eventAt,
218
+ latestSpreadBp: trade.spreadBp,
219
+ });
220
+ if (trade.spreadBp != null) spreadAt.set(trade.issuerKey, trade.eventAt);
221
+ continue;
222
+ }
223
+ current.trades += 1;
224
+ current.issuer = preferredIssuerName(current.issuer, trade.issuer);
225
+ if (trade.eventAt > current.lastTradeAt) current.lastTradeAt = trade.eventAt;
226
+ if (trade.spreadBp != null && trade.eventAt >= (spreadAt.get(trade.issuerKey) ?? -Infinity)) {
227
+ current.latestSpreadBp = trade.spreadBp;
228
+ spreadAt.set(trade.issuerKey, trade.eventAt);
229
+ }
230
+ }
231
+ return [...byIssuer.values()];
232
+ }
233
+
234
+ export function tradesForIssuer(trades: readonly CdsTrade[], issuerKey: string): CdsTrade[] {
235
+ return trades.filter((trade) => trade.issuerKey === issuerKey);
236
+ }
237
+
238
+ /**
239
+ * What the pane asks for. A tracked ticker already carries its company name; an
240
+ * untracked one yields the bare symbol, which the loader expands through
241
+ * instrument search before it reaches the backend.
242
+ */
243
+ export function resolveIssuerQuery(
244
+ symbol: string | null,
245
+ ticker: TickerRecord | null,
246
+ ): string | null {
247
+ return ticker?.metadata.name?.trim() || symbol?.trim().toUpperCase() || null;
248
+ }
249
+
250
+ export function formatBp(value: number | null): string {
251
+ if (value == null) return "--";
252
+ return `${Math.abs(value) >= 100 ? Math.round(value) : Math.round(value * 10) / 10}bp`;
253
+ }
254
+
255
+ export function formatNotional(trade: Pick<CdsTrade, "notional" | "notionalCapped">): string {
256
+ if (trade.notional == null) return "--";
257
+ return `${formatCompact(trade.notional)}${trade.notionalCapped ? "+" : ""}`;
258
+ }
259
+
260
+ export function formatUpfront(trade: Pick<CdsTrade, "upfront" | "upfrontCurrency">): string {
261
+ if (trade.upfront == null) return "--";
262
+ const sign = trade.upfront > 0 ? "+" : "";
263
+ return `${sign}${formatCompact(trade.upfront)}${trade.upfrontCurrency ? ` ${trade.upfrontCurrency}` : ""}`;
264
+ }
265
+
266
+ function pad(value: number): string {
267
+ return String(value).padStart(2, "0");
268
+ }
269
+
270
+ /** UTC, because DTCC disseminates in UTC and a guessed local zone misdates prints. */
271
+ export function formatEventTime(eventAt: number): string {
272
+ const date = new Date(eventAt);
273
+ return `${pad(date.getUTCMonth() + 1)}/${pad(date.getUTCDate())} ${pad(date.getUTCHours())}:${pad(date.getUTCMinutes())}`;
274
+ }
275
+
276
+ export function formatAsOf(asOf: string | null): string | null {
277
+ if (!asOf) return null;
278
+ const parsed = Date.parse(asOf);
279
+ return Number.isFinite(parsed) ? `${formatEventTime(parsed)}Z` : asOf;
280
+ }
281
+
282
+ export function formatMaturity(maturity: string | null): string {
283
+ return maturity ?? "--";
284
+ }
285
+
286
+ export type IssuerColumnId = "issuer" | "trades" | "last" | "spread";
287
+ export type IssuerColumn = DataTableColumn & { id: IssuerColumnId };
288
+ export type IssuerSortPreference = SortPreference<IssuerColumnId>;
289
+
290
+ export const ISSUER_SORT_COLUMN_IDS: readonly IssuerColumnId[] = ["issuer", "trades", "last", "spread"];
291
+ /** "Most active" is the reason the market-wide view exists. */
292
+ export const DEFAULT_ISSUER_SORT: IssuerSortPreference = { columnId: "trades", direction: "desc" };
293
+
294
+ export function buildIssuerColumns(width: number): IssuerColumn[] {
295
+ const issuerWidth = Math.max(16, width - 35);
296
+ return [
297
+ { id: "issuer", label: "ISSUER", width: issuerWidth, align: "left" },
298
+ { id: "trades", label: "TRADES", width: 7, align: "right" },
299
+ { id: "last", label: "LAST UTC", width: 12, align: "left" },
300
+ { id: "spread", label: "SPREAD", width: 10, align: "right" },
301
+ ];
302
+ }
303
+
304
+ function issuerSortValue(columnId: IssuerColumnId, row: CdsIssuerSummary): string | number | null {
305
+ switch (columnId) {
306
+ case "issuer":
307
+ return row.issuer;
308
+ case "trades":
309
+ return row.trades;
310
+ case "last":
311
+ return row.lastTradeAt;
312
+ case "spread":
313
+ return row.latestSpreadBp;
314
+ }
315
+ }
316
+
317
+ export function sortIssuers(
318
+ rows: readonly CdsIssuerSummary[],
319
+ sort: IssuerSortPreference,
320
+ ): CdsIssuerSummary[] {
321
+ const columnId = sort.columnId;
322
+ if (!columnId) return [...rows];
323
+ return [...rows].sort((left, right) => {
324
+ const compared = compareSortValues(
325
+ issuerSortValue(columnId, left),
326
+ issuerSortValue(columnId, right),
327
+ sort.direction,
328
+ );
329
+ // Stable secondary key so equal counts do not reshuffle between refreshes.
330
+ return compared !== 0 ? compared : left.issuer.localeCompare(right.issuer);
331
+ });
332
+ }
333
+
334
+ export type TradeColumnId = "time" | "maturity" | "notional" | "currency" | "coupon" | "spread" | "upfront";
335
+ export type TradeColumn = DataTableColumn & { id: TradeColumnId };
336
+ export type TradeSortPreference = SortPreference<TradeColumnId>;
337
+
338
+ export const TRADE_SORT_COLUMN_IDS: readonly TradeColumnId[] = [
339
+ "time",
340
+ "maturity",
341
+ "notional",
342
+ "coupon",
343
+ "spread",
344
+ "upfront",
345
+ ];
346
+ export const DEFAULT_TRADE_SORT: TradeSortPreference = { columnId: "time", direction: "desc" };
347
+
348
+ export function buildTradeColumns(width: number): TradeColumn[] {
349
+ const maturityWidth = Math.max(10, Math.min(12, width - 55));
350
+ return [
351
+ { id: "time", label: "TIME UTC", width: 12, align: "left" },
352
+ { id: "maturity", label: "MATURITY", width: maturityWidth, align: "left" },
353
+ { id: "notional", label: "NOTIONAL", width: 11, align: "right" },
354
+ { id: "currency", label: "CCY", width: 5, align: "left" },
355
+ { id: "coupon", label: "COUPON", width: 9, align: "right" },
356
+ { id: "spread", label: "SPREAD", width: 10, align: "right" },
357
+ { id: "upfront", label: "UPFRONT", width: 13, align: "right" },
358
+ ];
359
+ }
360
+
361
+ function tradeSortValue(columnId: TradeColumnId, row: CdsTrade): string | number | null {
362
+ switch (columnId) {
363
+ case "time":
364
+ return row.eventAt;
365
+ case "maturity":
366
+ return row.maturity;
367
+ case "notional":
368
+ return row.notional;
369
+ case "currency":
370
+ return row.currency;
371
+ case "coupon":
372
+ return row.couponBp;
373
+ case "spread":
374
+ return row.spreadBp;
375
+ case "upfront":
376
+ return row.upfront;
377
+ }
378
+ }
379
+
380
+ export function sortTrades(rows: readonly CdsTrade[], sort: TradeSortPreference): CdsTrade[] {
381
+ const columnId = sort.columnId;
382
+ if (!columnId) return [...rows];
383
+ return [...rows].sort((left, right) => {
384
+ const compared = compareSortValues(
385
+ tradeSortValue(columnId, left),
386
+ tradeSortValue(columnId, right),
387
+ sort.direction,
388
+ );
389
+ return compared !== 0 ? compared : right.eventAt - left.eventAt;
390
+ });
391
+ }
392
+
393
+ export function nextSort<Id extends string>(
394
+ current: SortPreference<Id>,
395
+ columnId: Id,
396
+ fallback: SortPreference<Id>,
397
+ ): SortPreference<Id> {
398
+ if (current.columnId !== columnId) return { columnId, direction: "asc" };
399
+ if (current.direction === "asc") return { columnId, direction: "desc" };
400
+ return fallback;
401
+ }
@@ -0,0 +1,132 @@
1
+ import { afterEach, describe, expect, test } from "bun:test";
2
+ import { act } from "react";
3
+ import { PaneFooterProvider } from "../../../components/layout/pane/footer";
4
+ import { testRender } from "../../../renderers/opentui/test-utils";
5
+ import { AppContext, createInitialState, PaneInstanceProvider } from "../../../state/app/context";
6
+ import { createDefaultConfig } from "../../../types/config";
7
+ import type { CdsActivity } from "./client";
8
+ import { normalizeCdsTrades } from "./model";
9
+ import { CdsPane } from "./pane";
10
+
11
+ let setup: Awaited<ReturnType<typeof testRender>> | undefined;
12
+
13
+ const ACTIVITY: CdsActivity = {
14
+ source: "DTCC PPD",
15
+ asOf: "2026-08-25T15:00:00Z",
16
+ // Market-wide: nothing was resolved because nothing was asked for.
17
+ issuer: null,
18
+ trades: normalizeCdsTrades([
19
+ trade("1", "Oracle Corporation", "2026-08-25T10:00:00Z", { reportedSpread: 0.009, spreadNotation: "3" }),
20
+ trade("2", "Oracle Corporation", "2026-08-25T12:00:00Z", { reportedSpread: null }),
21
+ trade("3", "Ford Motor Company", "2026-08-25T11:00:00Z", { reportedSpread: 0.025, spreadNotation: "3" }),
22
+ ]),
23
+ };
24
+
25
+ function trade(
26
+ id: string,
27
+ issuerName: string,
28
+ executionTimestamp: string,
29
+ overrides: { reportedSpread: number | null; spreadNotation?: string | null },
30
+ ) {
31
+ return {
32
+ disseminationId: id,
33
+ originalDisseminationId: null,
34
+ actionType: "NEWT",
35
+ eventTimestamp: executionTimestamp,
36
+ executionTimestamp,
37
+ effectiveDate: null,
38
+ expirationDate: null,
39
+ maturityDate: "2031-06-20",
40
+ issuerName,
41
+ underlierId: null,
42
+ underlierIdSource: null,
43
+ upi: null,
44
+ upiFisn: null,
45
+ upiUnderlierName: null,
46
+ notionalAmount: 5_000_000,
47
+ notionalCapped: true,
48
+ notionalCurrency: "USD",
49
+ // Raw DTCC decimal: 0.01 renders as a 100bp coupon.
50
+ fixedRate: 0.01,
51
+ reportedSpread: overrides.reportedSpread,
52
+ spreadNotation: overrides.spreadNotation ?? null,
53
+ upfrontAmount: null,
54
+ upfrontCurrency: null,
55
+ };
56
+ }
57
+
58
+ const loadActivity = async () => ACTIVITY;
59
+
60
+ async function settle() {
61
+ for (let index = 0; index < 6; index += 1) {
62
+ await act(async () => {
63
+ await Promise.resolve();
64
+ await setup!.renderOnce();
65
+ });
66
+ }
67
+ }
68
+
69
+ async function renderPane() {
70
+ const state = createInitialState(createDefaultConfig("/tmp/gloomberb-cds-test"));
71
+ await act(async () => {
72
+ setup = await testRender(
73
+ <AppContext value={{ state, dispatch: () => {} }}>
74
+ <PaneInstanceProvider paneId="cds:market">
75
+ <PaneFooterProvider>
76
+ {() => (
77
+ <CdsPane
78
+ paneId="cds:market"
79
+ paneType="cds"
80
+ focused
81
+ width={92}
82
+ height={16}
83
+ loadActivity={loadActivity}
84
+ />
85
+ )}
86
+ </PaneFooterProvider>
87
+ </PaneInstanceProvider>
88
+ </AppContext>,
89
+ { width: 92, height: 16 },
90
+ );
91
+ });
92
+ await settle();
93
+ }
94
+
95
+ afterEach(async () => {
96
+ if (setup) {
97
+ await act(async () => setup?.renderer.destroy());
98
+ setup = undefined;
99
+ }
100
+ });
101
+
102
+ describe("CdsPane", () => {
103
+ test("groups market activity by issuer, keeping the newest available spread", async () => {
104
+ await renderPane();
105
+ const frame = setup!.captureCharFrame();
106
+
107
+ // Most active first, and the 250bp report stays 250bp while the percent one becomes 90bp.
108
+ const oracle = frame.indexOf("Oracle Corporation");
109
+ const ford = frame.indexOf("Ford Motor Company");
110
+ expect(oracle).toBeGreaterThanOrEqual(0);
111
+ expect(ford).toBeGreaterThan(oracle);
112
+ expect(frame).toContain("90bp");
113
+ expect(frame).toContain("250bp");
114
+ });
115
+
116
+ test("opens the selected issuer's trades and shows -- for an unreported spread", async () => {
117
+ await renderPane();
118
+ await act(async () => {
119
+ setup!.mockInput.pressEnter();
120
+ await setup!.renderOnce();
121
+ });
122
+ await settle();
123
+
124
+ const frame = setup!.captureCharFrame();
125
+ expect(frame).toContain("NOTIONAL");
126
+ // Capped notional keeps its "+", and the coupon is shown instead of an implied spread.
127
+ expect(frame).toContain("5M+");
128
+ expect(frame).toContain("100bp");
129
+ expect(frame).toContain("--");
130
+ expect(frame).not.toContain("Ford Motor Company");
131
+ });
132
+ });