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
@@ -422,21 +422,121 @@ body.gloom-dragging [data-gloom-role="chart-surface"] {
422
422
  right: 16px;
423
423
  bottom: 16px;
424
424
  display: flex;
425
+ width: min(380px, calc(100vw - 32px));
425
426
  flex-direction: column;
426
427
  gap: 8px;
428
+ pointer-events: none;
427
429
  z-index: 10000;
428
430
  }
429
431
  .gloom-toast {
430
- min-width: 260px;
431
- max-width: 420px;
432
- padding: 10px 12px;
433
- border: 1px solid;
434
- box-shadow: 0 12px 24px color-mix(in srgb, var(--gloom-bg) 28%, transparent);
432
+ --gloom-toast-accent: var(--gloom-border-focused);
433
+ display: grid;
434
+ grid-template-columns: 8px minmax(0, 1fr) auto;
435
+ gap: 10px;
436
+ align-items: start;
437
+ width: 100%;
438
+ padding: 11px 10px 11px 12px;
439
+ overflow: hidden;
440
+ border: 1px solid color-mix(in srgb, var(--gloom-toast-accent) 54%, var(--gloom-border));
441
+ border-radius: 7px;
442
+ background: color-mix(in srgb, var(--gloom-panel) 94%, var(--gloom-bg));
443
+ color: var(--gloom-text);
444
+ box-shadow: 0 14px 32px color-mix(in srgb, var(--gloom-bg) 42%, transparent);
445
+ pointer-events: auto;
446
+ }
447
+ .gloom-toast[data-type="success"] { --gloom-toast-accent: var(--gloom-positive); }
448
+ .gloom-toast[data-type="error"] { --gloom-toast-accent: var(--gloom-negative); }
449
+ .gloom-toast[data-actionable="true"] {
450
+ cursor: pointer;
451
+ transition: transform 120ms cubic-bezier(0.23, 1, 0.32, 1), border-color 120ms ease, background-color 120ms ease;
452
+ }
453
+ .gloom-toast[data-actionable="true"]:active { transform: scale(0.985); }
454
+ .gloom-toast-indicator {
455
+ width: 8px;
456
+ height: 8px;
457
+ margin-top: 5px;
458
+ border-radius: 50%;
459
+ background: var(--gloom-toast-accent);
460
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--gloom-toast-accent) 14%, transparent);
461
+ }
462
+ .gloom-toast-content { min-width: 0; }
463
+ .gloom-toast-heading {
464
+ display: flex;
465
+ min-width: 0;
466
+ margin-bottom: 2px;
467
+ align-items: baseline;
468
+ gap: 7px;
469
+ }
470
+ .gloom-toast-title {
471
+ overflow: hidden;
472
+ color: var(--gloom-text-bright);
473
+ font-weight: 700;
474
+ text-overflow: ellipsis;
475
+ white-space: nowrap;
476
+ }
477
+ .gloom-toast-subtitle {
478
+ overflow: hidden;
479
+ color: var(--gloom-text-dim);
480
+ font-size: 11px;
481
+ text-overflow: ellipsis;
482
+ white-space: nowrap;
483
+ }
484
+ .gloom-toast-body {
485
+ color: var(--gloom-text);
486
+ line-height: 1.45;
487
+ overflow-wrap: anywhere;
488
+ }
489
+ .gloom-toast-controls {
490
+ display: flex;
491
+ align-items: center;
492
+ gap: 4px;
493
+ }
494
+ .gloom-toast-action,
495
+ .gloom-toast-dismiss {
496
+ appearance: none;
497
+ border: 1px solid transparent;
498
+ background: transparent;
499
+ color: var(--gloom-text-dim);
435
500
  }
436
501
  .gloom-toast-action {
437
- margin-top: 8px;
438
- padding: 4px 8px;
502
+ padding: 3px 7px;
503
+ border-color: color-mix(in srgb, var(--gloom-toast-accent) 42%, var(--gloom-border));
504
+ border-radius: 4px;
505
+ color: var(--gloom-text-bright);
506
+ }
507
+ .gloom-toast-dismiss {
508
+ display: grid;
509
+ width: 22px;
510
+ height: 22px;
511
+ padding: 0;
439
512
  border-radius: 4px;
513
+ place-items: center;
514
+ }
515
+ .gloom-toast-dismiss svg {
516
+ fill: none;
517
+ stroke: currentColor;
518
+ stroke-linecap: round;
519
+ stroke-width: 1.5;
520
+ }
521
+ .gloom-toast-action:focus-visible,
522
+ .gloom-toast-dismiss:focus-visible {
523
+ outline: 1px solid var(--gloom-border-focused);
524
+ outline-offset: 1px;
525
+ }
526
+ @media (hover: hover) and (pointer: fine) {
527
+ .gloom-toast[data-actionable="true"]:hover {
528
+ border-color: var(--gloom-toast-accent);
529
+ background: color-mix(in srgb, var(--gloom-panel) 88%, var(--gloom-toast-accent));
530
+ }
531
+ .gloom-toast-action:hover,
532
+ .gloom-toast-dismiss:hover {
533
+ background: color-mix(in srgb, var(--gloom-text) 10%, transparent);
534
+ color: var(--gloom-text-bright);
535
+ }
536
+ }
537
+ @media (prefers-reduced-motion: reduce) {
538
+ .gloom-toast[data-actionable="true"] { transition: none; }
539
+ .gloom-toast[data-actionable="true"]:active { transform: none; }
440
540
  }
441
541
  .gloom-dialog-backdrop {
442
542
  position: fixed;
@@ -0,0 +1,78 @@
1
+ /** @jsxImportSource react */
2
+ import { Window } from "happy-dom";
3
+
4
+ const testWindow = new Window({ url: "http://localhost" });
5
+ Object.assign(globalThis, {
6
+ IS_REACT_ACT_ENVIRONMENT: true,
7
+ window: testWindow,
8
+ document: testWindow.document,
9
+ navigator: testWindow.navigator,
10
+ HTMLElement: testWindow.HTMLElement,
11
+ MouseEvent: testWindow.MouseEvent,
12
+ });
13
+
14
+ import { expect, test } from "bun:test";
15
+ import { act } from "react";
16
+ import { createRoot } from "react-dom/client";
17
+ import { useToastHost, type ToastHost } from "../../../ui/toast";
18
+ import { WebToastHostProvider } from "./toast-host";
19
+
20
+ let toastHost: ToastHost | null = null;
21
+
22
+ function ToastViewport() {
23
+ const host = useToastHost();
24
+ toastHost = host;
25
+ const Viewport = host.Viewport;
26
+ return <Viewport />;
27
+ }
28
+
29
+ test("DOM notifications show context and open from the whole card", async () => {
30
+ const container = testWindow.document.createElement("div");
31
+ testWindow.document.body.appendChild(container);
32
+ const root = createRoot(container as unknown as HTMLElement);
33
+ let opened = 0;
34
+
35
+ await act(async () => {
36
+ root.render(
37
+ <WebToastHostProvider>
38
+ <ToastViewport />
39
+ </WebToastHostProvider>,
40
+ );
41
+ });
42
+ await act(async () => {
43
+ toastHost?.info("@bob mentioned you", {
44
+ title: "Gloomberb chat",
45
+ subtitle: "#everyone",
46
+ duration: 0,
47
+ action: { label: "Open", onClick: () => opened++ },
48
+ });
49
+ });
50
+
51
+ const toast = container.querySelector(".gloom-toast") as unknown as HTMLElement;
52
+ expect(toast.textContent).toContain("Gloomberb chat");
53
+ expect(toast.textContent).toContain("#everyone");
54
+ expect(toast.getAttribute("data-actionable")).toBe("true");
55
+
56
+ await act(async () => {
57
+ toast.dispatchEvent(new MouseEvent("click", { bubbles: true }));
58
+ });
59
+ expect(opened).toBe(1);
60
+ expect(container.querySelector(".gloom-toast")).toBeNull();
61
+
62
+ await act(async () => {
63
+ toastHost?.info("Another message", {
64
+ duration: 0,
65
+ action: { label: "Open", onClick: () => opened++ },
66
+ });
67
+ });
68
+ const dismiss = container.querySelector(".gloom-toast-dismiss") as unknown as HTMLElement;
69
+ await act(async () => {
70
+ dismiss.dispatchEvent(new MouseEvent("click", { bubbles: true }));
71
+ });
72
+ expect(opened).toBe(1);
73
+ expect(container.querySelector(".gloom-toast")).toBeNull();
74
+
75
+ await act(async () => root.unmount());
76
+ container.remove();
77
+ toastHost = null;
78
+ });
@@ -1,6 +1,5 @@
1
1
  /** @jsxImportSource react */
2
- import { useCallback, useMemo, useState, type CSSProperties, type ReactNode } from "react";
3
- import { colors } from "../../../theme/colors";
2
+ import { useCallback, useMemo, useState, type ReactNode } from "react";
4
3
  import { ToastHostProvider, type ToastHost, type ToastOptions } from "../../../ui/toast";
5
4
 
6
5
  type WebToastType = "info" | "success" | "error";
@@ -9,33 +8,13 @@ interface ToastEntry {
9
8
  id: number;
10
9
  body: string;
11
10
  type: WebToastType;
11
+ title?: string;
12
+ subtitle?: string;
12
13
  action?: ToastOptions["action"];
13
14
  }
14
15
 
15
16
  let nextToastId = 1;
16
17
 
17
- function webToastBorderColor(type: WebToastType): string {
18
- if (type === "success") return colors.positive;
19
- if (type === "error") return colors.negative;
20
- return colors.borderFocused;
21
- }
22
-
23
- function getToastStyle(type: WebToastType): CSSProperties {
24
- return {
25
- background: colors.panel,
26
- borderColor: webToastBorderColor(type),
27
- color: colors.text,
28
- };
29
- }
30
-
31
- function getToastActionStyle(): CSSProperties {
32
- return {
33
- background: colors.selected,
34
- border: `1px solid ${colors.borderFocused}`,
35
- color: colors.selectedText,
36
- };
37
- }
38
-
39
18
  export function WebToastHostProvider({ children }: { children: ReactNode }) {
40
19
  const [toasts, setToasts] = useState<ToastEntry[]>([]);
41
20
 
@@ -45,7 +24,14 @@ export function WebToastHostProvider({ children }: { children: ReactNode }) {
45
24
 
46
25
  const push = useCallback((type: ToastEntry["type"], body: string, options?: ToastOptions) => {
47
26
  const id = nextToastId++;
48
- setToasts((current) => [...current, { id, type, body, action: options?.action }].slice(-6));
27
+ setToasts((current) => [...current, {
28
+ id,
29
+ type,
30
+ body,
31
+ title: options?.title,
32
+ subtitle: options?.subtitle,
33
+ action: options?.action,
34
+ }]);
49
35
  if (options?.duration !== 0) {
50
36
  setTimeout(() => dismiss(id), options?.duration ?? 4500);
51
37
  }
@@ -55,24 +41,64 @@ export function WebToastHostProvider({ children }: { children: ReactNode }) {
55
41
  const host = useMemo<ToastHost>(() => ({
56
42
  Viewport() {
57
43
  return (
58
- <div className="gloom-toast-viewport">
59
- {toasts.map((toast) => (
60
- <div key={toast.id} className="gloom-toast" style={getToastStyle(toast.type)}>
61
- <div>{toast.body}</div>
62
- {toast.action && (
63
- <button
64
- className="gloom-toast-action"
65
- style={getToastActionStyle()}
66
- onClick={() => {
67
- toast.action?.onClick();
68
- dismiss(toast.id);
69
- }}
70
- >
71
- {toast.action.label}
72
- </button>
73
- )}
74
- </div>
75
- ))}
44
+ <div className="gloom-toast-viewport" aria-label="Notifications" aria-live="polite">
45
+ {toasts.map((toast) => {
46
+ const activate = () => {
47
+ if (!toast.action) return;
48
+ try {
49
+ toast.action.onClick();
50
+ } finally {
51
+ dismiss(toast.id);
52
+ }
53
+ };
54
+ return (
55
+ <div
56
+ key={toast.id}
57
+ className="gloom-toast"
58
+ data-type={toast.type}
59
+ data-actionable={toast.action ? "true" : "false"}
60
+ onClick={toast.action ? activate : undefined}
61
+ >
62
+ <span className="gloom-toast-indicator" aria-hidden="true" />
63
+ <div className="gloom-toast-content">
64
+ {(toast.title || toast.subtitle) && (
65
+ <div className="gloom-toast-heading">
66
+ {toast.title && <span className="gloom-toast-title">{toast.title}</span>}
67
+ {toast.subtitle && <span className="gloom-toast-subtitle">{toast.subtitle}</span>}
68
+ </div>
69
+ )}
70
+ <div className="gloom-toast-body">{toast.body}</div>
71
+ </div>
72
+ <div className="gloom-toast-controls">
73
+ {toast.action && (
74
+ <button
75
+ type="button"
76
+ className="gloom-toast-action"
77
+ onClick={(event) => {
78
+ event.stopPropagation();
79
+ activate();
80
+ }}
81
+ >
82
+ {toast.action.label}
83
+ </button>
84
+ )}
85
+ <button
86
+ type="button"
87
+ className="gloom-toast-dismiss"
88
+ aria-label="Dismiss notification"
89
+ onClick={(event) => {
90
+ event.stopPropagation();
91
+ dismiss(toast.id);
92
+ }}
93
+ >
94
+ <svg viewBox="0 0 12 12" width="12" height="12" aria-hidden="true">
95
+ <path d="M2.25 2.25 9.75 9.75M9.75 2.25 2.25 9.75" />
96
+ </svg>
97
+ </button>
98
+ </div>
99
+ </div>
100
+ );
101
+ })}
76
102
  </div>
77
103
  );
78
104
  },
@@ -1,149 +1,18 @@
1
1
  /** @jsxImportSource react */
2
- import { useEffect, useState, type CSSProperties, type ReactNode } from "react";
3
2
  import type { RendererHost, UiHost } from "../../../ui/host";
4
- import { WEB_CELL_HEIGHT, WEB_CELL_WIDTH } from "./input-host";
5
3
  import { backendRequest } from "./backend-rpc";
6
- import { WebDataTable } from "./data-table";
7
- import {
8
- WebButton,
9
- WebCheckbox,
10
- WebDialogFrame,
11
- WebListView,
12
- WebMessageComposer,
13
- WebPageStackView,
14
- WebSegmentedControl,
15
- WebTextField,
16
- } from "./desktop/controls";
17
- import { WebPopover } from "./desktop/popover";
18
- import { WebBox } from "./host/box";
19
- import { WebChartSurface } from "./host/chart-surface";
20
- import { WebInput, WebTextarea } from "./host/input";
21
- import { WebMediaSurface } from "./host/media-surface";
22
4
  import {
23
5
  NATIVE_CONTEXT_MENU_SUPPORTED,
24
6
  showDesktopContextMenu,
25
7
  startElectrobunWindowDrag,
26
8
  } from "./host/native";
27
- import { WebScrollBox } from "./host/scroll-box";
28
- import { cleanDomProps, commonStyle } from "./host/style";
29
- import { WebAsciiText, WebSpan, WebStrong, WebText, WebUnderline } from "./host/text";
30
- import { WebTabs } from "./host/tabs";
31
-
32
- function currentDesktopPlatform(): string {
33
- const navigatorWithUserAgentData = navigator as Navigator & {
34
- userAgentData?: { platform?: string };
35
- };
36
- return [
37
- navigatorWithUserAgentData.userAgentData?.platform,
38
- navigator.platform,
39
- navigator.userAgent,
40
- ].filter((value): value is string => Boolean(value)).join(" ");
41
- }
42
-
43
- const DESKTOP_PLATFORM = currentDesktopPlatform();
44
- const USES_WINDOWS_CONTROLS = !/(darwin|ipad|iphone|linux|mac)/i.test(DESKTOP_PLATFORM);
45
- const NON_WINDOWS_DESKTOP_PLATFORMS = /^(darwin|linux|freebsd|openbsd|aix|sunos)$/i;
46
-
47
- function usesWindowsWindowControls(desktopPlatform?: string): boolean {
48
- const platform = desktopPlatform?.trim();
49
- if (!platform) {
50
- return USES_WINDOWS_CONTROLS;
51
- }
52
- if (/^win/i.test(platform)) {
53
- return true;
54
- }
55
- if (NON_WINDOWS_DESKTOP_PLATFORMS.test(platform)) {
56
- return false;
57
- }
58
- return USES_WINDOWS_CONTROLS;
59
- }
9
+ import { createDomUiHost } from "./dom-ui-host";
60
10
 
61
11
  export function createWebUiHost(desktopPlatform?: string): UiHost {
62
- const usesWindowsControls = usesWindowsWindowControls(desktopPlatform);
63
-
64
- return {
65
- kind: "desktop-web",
66
- capabilities: {
67
- nativePaneChrome: true,
68
- titleBarOverlay: true,
69
- precisePointer: true,
70
- fractionalViewport: true,
71
- cellWidthPx: WEB_CELL_WIDTH,
72
- cellHeightPx: WEB_CELL_HEIGHT,
73
- pixelRatio: Math.min(window.devicePixelRatio || 1, 2),
74
- canvasCharts: true,
75
- nativeContextMenu: NATIVE_CONTEXT_MENU_SUPPORTED,
76
- windowControls: usesWindowsControls ? "windows" : undefined,
77
- },
78
- Box: WebBox,
79
- Text: WebText,
80
- Span: WebSpan,
81
- Strong: WebStrong,
82
- Underline: WebUnderline,
83
- ScrollBox: WebScrollBox,
84
- Input: WebInput,
85
- Textarea: WebTextarea,
86
- Button: WebButton,
87
- Checkbox: WebCheckbox,
88
- Popover: WebPopover,
89
- TextField: WebTextField,
90
- MessageComposer: WebMessageComposer,
91
- ListView: WebListView,
92
- SegmentedControl: WebSegmentedControl,
93
- DialogFrame: WebDialogFrame,
94
- PageStackView: WebPageStackView,
95
- DataTable: WebDataTable,
96
- Tabs: WebTabs,
97
- ChartSurface: WebChartSurface,
98
- ImageSurface: ({ children, src, alt = "", objectFit = "contain", ...props }) => {
99
- const imageSrc = typeof src === "string" ? src.trim() : "";
100
- const [failed, setFailed] = useState(false);
101
- useEffect(() => setFailed(false), [imageSrc]);
102
- const baseStyle = commonStyle(props);
103
- return (
104
- <div
105
- {...cleanDomProps(props)}
106
- style={{
107
- ...baseStyle,
108
- overflow: baseStyle.overflow ?? "hidden",
109
- ...(props.style as CSSProperties | undefined),
110
- }}
111
- >
112
- {imageSrc && !failed ? (
113
- <img
114
- src={imageSrc}
115
- alt={alt}
116
- draggable={false}
117
- onError={() => setFailed(true)}
118
- style={{
119
- width: "100%",
120
- height: "100%",
121
- display: "block",
122
- objectFit,
123
- }}
124
- />
125
- ) : children as ReactNode}
126
- </div>
127
- );
128
- },
129
- MediaSurface: WebMediaSurface,
130
- SpinnerMark: ({ color, ...props }) => (
131
- <span
132
- {...cleanDomProps(props)}
133
- aria-hidden="true"
134
- style={{
135
- color,
136
- display: "inline-block",
137
- width: "1ch",
138
- animation: "gloom-spin 0.9s steps(8) infinite",
139
- ...(props.style as CSSProperties | undefined),
140
- }}
141
- >
142
- *
143
- </span>
144
- ),
145
- AsciiText: (props) => <WebAsciiText {...props} desktopPlatform={desktopPlatform} />,
146
- };
12
+ return createDomUiHost(desktopPlatform, {
13
+ nativeContextMenu: NATIVE_CONTEXT_MENU_SUPPORTED,
14
+ publicSharing: true,
15
+ });
147
16
  }
148
17
 
149
18
  export const webUiHost: UiHost = createWebUiHost();
@@ -189,7 +58,7 @@ export const webRendererHost: RendererHost = {
189
58
  try {
190
59
  return await navigator.clipboard.readText();
191
60
  } catch {
192
- return await backendRequest("host.readText");
61
+ return backendRequest("host.readText");
193
62
  }
194
63
  },
195
64
  notify(notification) {
@@ -6,6 +6,7 @@ import { getDataDir, initDataDir, setConfigStoreHost } from "../../data/config/s
6
6
  import { applyLanguageFromConfig } from "../../i18n";
7
7
  import * as nodeConfigStoreHost from "../../data/config/store/node";
8
8
  import { loadExternalPlugins } from "../../plugins/loader";
9
+ import { getLoadablePlugins } from "../../plugins/catalog";
9
10
  import { OpenTuiInputHostProvider } from "./input-host";
10
11
  import { debugLog } from "../../utils/debug-log";
11
12
  import { UiHostProvider } from "../../ui/host";
@@ -143,6 +144,7 @@ export async function startOpenTuiApp(options: StartOpenTuiAppOptions = {}): Pro
143
144
  config={config}
144
145
  servicesFactory={createAppServices}
145
146
  externalPlugins={externalPlugins}
147
+ plugins={getLoadablePlugins(externalPlugins)}
146
148
  cliLaunchRequest={cliLaunchRequest}
147
149
  remoteControlAdapter={remoteControlAdapter}
148
150
  />
@@ -0,0 +1,58 @@
1
+ import { expect, test } from "bun:test";
2
+ import { renderToStaticMarkup } from "react-dom/server";
3
+ import type { LayoutMarketplaceEntry } from "../../layout-marketplace/payload";
4
+ import { LayoutShareView } from "./layout-view";
5
+
6
+ const entry: LayoutMarketplaceEntry = {
7
+ id: "0123456789abcdef0123456789abcdef",
8
+ name: "Social <script>alert(1)</script> Desk",
9
+ schemaVersion: 2,
10
+ sourceConfigVersion: 21,
11
+ layout: {
12
+ dockRoot: {
13
+ kind: "split",
14
+ axis: "horizontal",
15
+ ratio: 0.6,
16
+ first: { kind: "pane", instanceId: "p1" },
17
+ second: { kind: "pane", instanceId: "p2" },
18
+ },
19
+ instances: [
20
+ {
21
+ instanceId: "p1",
22
+ paneId: "twitter-feed",
23
+ params: { query: "$NVDA <img src=x onerror=alert(1)>" },
24
+ },
25
+ {
26
+ instanceId: "p2",
27
+ paneId: "chart-composer",
28
+ binding: { kind: "fixed", symbol: "NVDA" },
29
+ },
30
+ ],
31
+ floating: [],
32
+ detached: [],
33
+ },
34
+ paneState: {},
35
+ author: { username: "analyst", displayName: "Analyst" },
36
+ publishedAt: "2026-08-27T00:00:00.000Z",
37
+ };
38
+
39
+ test("shared layout renders a safe workspace preview and live-copy CTA", () => {
40
+ const html = renderToStaticMarkup(
41
+ <LayoutShareView
42
+ entry={entry}
43
+ openLiveUrl="https://term.gloom.sh/?layout=0123456789abcdef0123456789abcdef"
44
+ />,
45
+ );
46
+
47
+ expect(html).toContain("Shared via Gloomberb");
48
+ expect(html).toContain("X Feed");
49
+ expect(html).toContain("Chart");
50
+ expect(html).toContain("$NVDA");
51
+ expect(html).toContain("Use this layout");
52
+ expect(html).toContain("independent, editable copy");
53
+ expect(html).toContain('href="https://term.gloom.sh/?layout=0123456789abcdef0123456789abcdef"');
54
+ expect(html).not.toContain("<script>");
55
+ expect(html).not.toContain("<img");
56
+ expect(html).toContain("&lt;script&gt;");
57
+ expect(html).toContain("&lt;img");
58
+ });