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,60 @@
1
+ import { ImageSurface, Text, useUiCapabilities } from "../ui";
2
+ import { cloudLogoPath, type CloudLogoKind } from "../api-client/paths";
3
+ import { getCloudApiBaseUrl } from "../api-client/request";
4
+ import { resolveAssetDisplayKind } from "../market-data/market/format";
5
+ import { colors } from "../theme/colors";
6
+
7
+ export function resolveCompanyLogoSrc(input: {
8
+ symbol: string;
9
+ assetCategory?: string;
10
+ }): string | null {
11
+ const kind = logoKindForAsset(input.assetCategory);
12
+ if (!kind) return null;
13
+ const path = cloudLogoPath(kind, input.symbol);
14
+ return path ? `${getCloudApiBaseUrl()}${path}` : null;
15
+ }
16
+
17
+ export function CompanyLogo({
18
+ symbol,
19
+ assetCategory,
20
+ name,
21
+ width = 5,
22
+ height = 2,
23
+ }: {
24
+ symbol: string;
25
+ assetCategory?: string;
26
+ name?: string;
27
+ width?: number;
28
+ height?: number;
29
+ }) {
30
+ const { nativePaneChrome, cellWidthPx = 8, cellHeightPx = 18 } = useUiCapabilities();
31
+ const src = nativePaneChrome === true ? resolveCompanyLogoSrc({ symbol, assetCategory }) : null;
32
+ if (!src) return null;
33
+
34
+ return (
35
+ <ImageSurface
36
+ src={src}
37
+ alt={name || symbol}
38
+ width={width}
39
+ height={height}
40
+ marginRight={1}
41
+ flexShrink={0}
42
+ alignItems="center"
43
+ justifyContent="center"
44
+ objectFit="contain"
45
+ style={{
46
+ width: cellWidthPx * width,
47
+ height: cellHeightPx * height,
48
+ flexShrink: 0,
49
+ }}
50
+ >
51
+ <Text fg={colors.textDim}>{symbol.trim().charAt(0).toUpperCase() || "?"}</Text>
52
+ </ImageSurface>
53
+ );
54
+ }
55
+
56
+ function logoKindForAsset(assetCategory?: string): CloudLogoKind | null {
57
+ const kind = resolveAssetDisplayKind({ assetCategory });
58
+ if (kind === "cash" || kind === "contract") return null;
59
+ return kind === "crypto" ? "crypto" : "ticker";
60
+ }
@@ -1,6 +1,6 @@
1
1
  import { Box, ScrollBox, Text } from "../../ui";
2
2
  import { TextAttributes, type ScrollBoxRenderable } from "../../ui";
3
- import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react";
3
+ import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode, type RefObject } from "react";
4
4
  import { t } from "../../i18n";
5
5
  import { useAppLanguage } from "../../i18n/react";
6
6
  import { displayWidth, formatTimeAgo } from "../../utils/format";
@@ -64,6 +64,8 @@ interface FeedDataTableStackViewProps {
64
64
  isItemRead?: (item: FeedDataTableItem) => boolean;
65
65
  onOpenItem?: (item: FeedDataTableItem, index: number) => void;
66
66
  onOpenItemIdChange?: (itemId: string | null) => void;
67
+ scrollRef?: RefObject<ScrollBoxRenderable | null>;
68
+ onBodyScrollActivity?: () => void;
67
69
  }
68
70
 
69
71
  function timestampValue(item: FeedDataTableItem): number {
@@ -148,6 +150,8 @@ export function FeedDataTableStackView({
148
150
  isItemRead,
149
151
  onOpenItem,
150
152
  onOpenItemIdChange,
153
+ scrollRef,
154
+ onBodyScrollActivity,
151
155
  }: FeedDataTableStackViewProps) {
152
156
  const language = useAppLanguage();
153
157
  const [sortPreference, setSortPreference] = useState<SortPreference>({
@@ -362,6 +366,8 @@ export function FeedDataTableStackView({
362
366
  emptyStateTitle={t(emptyStateTitle)}
363
367
  emptyStateHint={emptyStateHint}
364
368
  showHorizontalScrollbar={false}
369
+ scrollRef={scrollRef}
370
+ onBodyScrollActivity={onBodyScrollActivity}
365
371
  />
366
372
  );
367
373
  }
@@ -14,6 +14,7 @@ export { TickerListTableView } from "./ticker/list-table-view";
14
14
  export type { TickerListVisibleRange } from "./ticker/list-table-view";
15
15
  export { TickerBadgeList } from "./ticker/badge/list";
16
16
  export { InputSearchBar } from "./input-search-bar";
17
+ export { isTableScrollNearEnd, useTableLoadMore } from "./table-view-shared";
17
18
  export { DataTableView } from "./data-table/view";
18
19
  export type { DataTableKeyEvent, DataTableRootKeyContext } from "./data-table/view";
19
20
  export { DataTableStackView } from "./data-table/stack-view";
@@ -1,8 +1,8 @@
1
- import { Box, Span, Text, useRendererHost, useUiCapabilities } from "../../ui";
1
+ import { Box, Span, Text, useContextMenu, useRendererHost, useUiCapabilities } from "../../ui";
2
2
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
3
  import { t } from "../../i18n";
4
4
  import { useShortcut, useViewport } from "../../react/input";
5
- import { useAppDispatch, useAppSelector } from "../../state/app/context";
5
+ import { resolveTickerForPane, useAppDispatch, useAppSelector } from "../../state/app/context";
6
6
  import type { DesktopWindowBridge } from "../../types/desktop-window";
7
7
  import { findPaneInstance } from "../../types/config";
8
8
  import type { PluginRegistry } from "../../plugins/registry";
@@ -20,6 +20,13 @@ import {
20
20
  recordDoubleEscapeClose,
21
21
  resetDoubleEscapeClose,
22
22
  } from "../../utils/double-escape-close";
23
+ import { createShare, openLiveShareUrl } from "../../shares/api";
24
+ import { buildPaneSharePayload } from "../../shares/pane";
25
+ import type { ContextMenuItem } from "../../types/context-menu";
26
+ import {
27
+ PANE_MANAGEMENT_ACCELERATORS,
28
+ resolvePaneManagementShortcut,
29
+ } from "./shell/shortcuts";
23
30
 
24
31
  interface DetachedPaneShellProps {
25
32
  pluginRegistry: PluginRegistry;
@@ -35,6 +42,7 @@ export function DetachedPaneShell({ pluginRegistry, desktopWindowBridge }: Detac
35
42
  const colors = useThemeColors();
36
43
  const dispatch = useAppDispatch();
37
44
  const rendererHost = useRendererHost();
45
+ const { showContextMenu } = useContextMenu();
38
46
  const config = useAppSelector((state) => state.config);
39
47
  const paneState = useAppSelector((state) => state.paneState);
40
48
  const inputCaptured = useAppSelector((state) => state.inputCaptured);
@@ -43,19 +51,34 @@ export function DetachedPaneShell({ pluginRegistry, desktopWindowBridge }: Detac
43
51
  typeof document === "undefined" ? true : document.hasFocus()
44
52
  ));
45
53
  const { width, height } = useViewport();
46
- const { cellHeightPx = 18, nativePaneChrome, titleBarOverlay, windowControls } = useUiCapabilities();
47
- const showWindowControls = windowControls === "windows";
48
- const titlebarLeadingInset = titleBarOverlay ? getTitlebarLeadingInset() : 0;
54
+ const {
55
+ cellHeightPx = 18,
56
+ nativePaneChrome,
57
+ publicSharing,
58
+ titleBarOverlay,
59
+ nativeWindowChrome = titleBarOverlay,
60
+ windowControls,
61
+ } = useUiCapabilities();
62
+ const showWindowControls = nativeWindowChrome && windowControls === "windows";
63
+ const titlebarLeadingInset = titleBarOverlay && nativeWindowChrome ? getTitlebarLeadingInset() : 0;
49
64
  const instance = useAppSelector((state) => findPaneInstance(state.config.layout, desktopWindowBridge.paneId) ?? null);
50
65
  const paneDef = instance ? pluginRegistry.panes.get(instance.paneId) ?? null : null;
51
66
  const hasPaneSettings = !!instance && pluginRegistry.hasPaneSettings(instance.instanceId);
52
- const quickSettings = instance ? pluginRegistry.resolvePaneQuickSettings(instance.instanceId) : [];
53
67
  const titleState = useMemo(
54
- () => ({ config, paneState }) as Parameters<typeof getPaneDisplayTitle>[0],
68
+ () => ({ config, paneState }) as Parameters<typeof resolveTickerForPane>[0],
55
69
  [config, paneState],
56
70
  );
71
+ const sharePayload = useMemo(() => instance && publicSharing
72
+ ? buildPaneSharePayload(
73
+ pluginRegistry,
74
+ instance,
75
+ paneState[instance.instanceId] ?? {},
76
+ resolveTickerForPane(titleState, instance.instanceId),
77
+ )
78
+ : null, [instance, paneState, pluginRegistry, publicSharing, titleState]);
79
+ const quickSettings = instance ? pluginRegistry.resolvePaneQuickSettings(instance.instanceId) : [];
57
80
  const title = instance && paneDef
58
- ? getPaneDisplayTitle(titleState, instance, paneDef)
81
+ ? getPaneDisplayTitle(titleState, instance, paneDef, pluginRegistry.panes)
59
82
  : "Detached Pane";
60
83
  const focused = windowFocused;
61
84
 
@@ -112,13 +135,59 @@ export function DetachedPaneShell({ pluginRegistry, desktopWindowBridge }: Detac
112
135
 
113
136
  const startWindowDrag = useCallback(() => {
114
137
  focusPane();
115
- void rendererHost.startWindowDrag?.();
116
- }, [focusPane, rendererHost]);
138
+ if (nativeWindowChrome) void rendererHost.startWindowDrag?.();
139
+ }, [focusPane, nativeWindowChrome, rendererHost]);
140
+
141
+ const sharePane = useCallback(async () => {
142
+ if (!sharePayload) return;
143
+ try {
144
+ const { id } = await createShare(sharePayload);
145
+ await rendererHost.copyText(openLiveShareUrl(id));
146
+ pluginRegistry.notify({ body: "Share link copied to clipboard", type: "success" });
147
+ } catch (error) {
148
+ pluginRegistry.notify({
149
+ body: error instanceof Error ? error.message : "Could not share this pane.",
150
+ type: "error",
151
+ });
152
+ }
153
+ }, [pluginRegistry, rendererHost, sharePayload]);
117
154
 
118
- const openSettings = useCallback((event?: { stopPropagation?: () => void; preventDefault?: () => void }) => {
155
+ useShortcut((event) => {
156
+ if (resolvePaneManagementShortcut(event) !== "share" || !sharePayload) return;
157
+ if (inputCaptured && event.ctrl && !event.meta && !event.super) return;
158
+ event.preventDefault();
159
+ event.stopPropagation();
160
+ void sharePane();
161
+ });
162
+
163
+ const openActions = useCallback((event?: { stopPropagation?: () => void; preventDefault?: () => void }) => {
119
164
  stopMouse(event);
120
- pluginRegistry.openPaneSettingsFn(desktopWindowBridge.paneId);
121
- }, [desktopWindowBridge.paneId, pluginRegistry]);
165
+ focusPane();
166
+ const items: ContextMenuItem[] = [];
167
+ if (hasPaneSettings) {
168
+ items.push({
169
+ id: "settings",
170
+ label: "Settings",
171
+ onSelect: () => pluginRegistry.openPaneSettingsFn(desktopWindowBridge.paneId),
172
+ });
173
+ }
174
+ if (sharePayload) items.push({
175
+ id: "share-pane",
176
+ label: "Share Pane",
177
+ accelerator: PANE_MANAGEMENT_ACCELERATORS.share,
178
+ onSelect: sharePane,
179
+ });
180
+ void showContextMenu({
181
+ kind: "pane",
182
+ paneId: desktopWindowBridge.paneId,
183
+ paneType: instance?.paneId ?? "",
184
+ title,
185
+ floating: true,
186
+ }, items, event).then((shown) => {
187
+ if (!shown && hasPaneSettings) pluginRegistry.openPaneSettingsFn(desktopWindowBridge.paneId);
188
+ else if (!shown && sharePayload) void sharePane();
189
+ });
190
+ }, [desktopWindowBridge.paneId, focusPane, hasPaneSettings, instance?.paneId, pluginRegistry, sharePane, sharePayload, showContextMenu, title]);
122
191
  const toggleQuickSetting = useCallback((key: string, event?: { stopPropagation?: () => void; preventDefault?: () => void }) => {
123
192
  stopMouse(event);
124
193
  focusPane();
@@ -224,14 +293,15 @@ export function DetachedPaneShell({ pluginRegistry, desktopWindowBridge }: Detac
224
293
  </Box>
225
294
  ))}
226
295
  <Box flexGrow={1} minWidth={0} />
227
- {hasPaneSettings && (
296
+ {(hasPaneSettings || sharePayload) && (
228
297
  <Text
229
298
  fg={paneTitleText(focused, true, colors)}
230
299
  selectable={false}
231
300
  className="electrobun-webkit-app-region-no-drag"
232
301
  data-gloom-role="pane-action"
233
302
  data-gloom-interactive="true"
234
- onMouseDown={openSettings}
303
+ aria-label="Pane actions"
304
+ onMouseDown={openActions}
235
305
  >
236
306
  {" ... "}
237
307
  </Text>
@@ -164,9 +164,9 @@ export function Header({
164
164
  const rendererHost = useRendererHost();
165
165
  const baseCurrency = useAppSelector(selectBaseCurrency);
166
166
  const appActive = useAppActive();
167
- const { titleBarOverlay, windowControls } = useUiCapabilities();
168
- const showWindowControls = windowControls === "windows";
169
- const titlebarLeadingInset = titleBarOverlay ? getTitlebarLeadingInset() : 0;
167
+ const { titleBarOverlay, nativeWindowChrome = titleBarOverlay, windowControls } = useUiCapabilities();
168
+ const showWindowControls = nativeWindowChrome && windowControls === "windows";
169
+ const titlebarLeadingInset = titleBarOverlay && nativeWindowChrome ? getTitlebarLeadingInset() : 0;
170
170
  const spyQuoteEntry = useQuoteEntry("SPY", null);
171
171
  const spyQuote = useResolvedEntryValue(spyQuoteEntry);
172
172
  const mktState = spyQuote?.marketState;
@@ -198,9 +198,9 @@ export function Header({
198
198
  : "SPY —";
199
199
 
200
200
  const startWindowDrag = useCallback(() => {
201
- if (!titleBarOverlay) return;
201
+ if (!titleBarOverlay || !nativeWindowChrome) return;
202
202
  void rendererHost.startWindowDrag?.();
203
- }, [rendererHost, titleBarOverlay]);
203
+ }, [nativeWindowChrome, rendererHost, titleBarOverlay]);
204
204
 
205
205
  // Market status
206
206
  const mktCountdown = mktState ? marketStateCountdown(mktState, now) : null;
@@ -1,9 +1,16 @@
1
1
  import { resolveCollectionForPane, resolveTickerForPane, type AppState } from "../../../state/app/context";
2
- import { t } from "../../../i18n";
3
- import { TICKER_RESEARCH_PANE_ID, type PaneInstanceConfig } from "../../../types/config";
2
+ import { t, tf } from "../../../i18n";
3
+ import {
4
+ findPaneInstance,
5
+ TICKER_RESEARCH_PANE_ID,
6
+ type PaneInstanceConfig,
7
+ } from "../../../types/config";
4
8
  import type { PaneDef } from "../../../types/plugin";
5
9
 
6
- export function getPaneDisplayTitle(
10
+ /** Single-cell link glyph: emoji link icons render double-width in terminals. */
11
+ const LINK_GLYPH = "\u29c9";
12
+
13
+ function getBasePaneDisplayTitle(
7
14
  state: Pick<AppState, "config" | "paneState">,
8
15
  instance: PaneInstanceConfig,
9
16
  paneDef: PaneDef,
@@ -38,6 +45,27 @@ export function getPaneDisplayTitle(
38
45
  ?? t(paneDef.name);
39
46
  }
40
47
 
48
+ // A source pane owns the cursor symbol; echoing it in its own title would just repeat the row.
49
+ if (paneDef.tickerSource) return t(paneDef.name);
50
+
41
51
  const ticker = resolveTickerForPane(state as AppState, instance.instanceId);
42
52
  return ticker ? `${t(paneDef.name)}: ${ticker}` : t(paneDef.name);
43
53
  }
54
+
55
+ export function getPaneDisplayTitle(
56
+ state: Pick<AppState, "config" | "paneState">,
57
+ instance: PaneInstanceConfig,
58
+ paneDef: PaneDef,
59
+ panes?: ReadonlyMap<string, PaneDef>,
60
+ ): string {
61
+ const title = getBasePaneDisplayTitle(state, instance, paneDef);
62
+ if (instance.paneId !== TICKER_RESEARCH_PANE_ID || instance.binding?.kind !== "follow" || !panes) {
63
+ return title;
64
+ }
65
+
66
+ const source = findPaneInstance(state.config.layout, instance.binding.sourceInstanceId);
67
+ const sourceDef = source ? panes.get(source.paneId) : null;
68
+ if (!source || !sourceDef) return title;
69
+ const sourceTitle = getBasePaneDisplayTitle(state, source, sourceDef);
70
+ return `${title} ${LINK_GLYPH} ${tf("Linked to {source}", { source: sourceTitle })}`;
71
+ }
@@ -1,4 +1,4 @@
1
- import { useCallback, type Dispatch } from "react";
1
+ import { useCallback } from "react";
2
2
  import {
3
3
  floatAtRect,
4
4
  getRememberedFloatingRect,
@@ -10,7 +10,6 @@ import {
10
10
  type LayoutBounds,
11
11
  type ResolvedPane,
12
12
  } from "../../../plugins/pane-manager";
13
- import type { AppAction } from "../../../state/app/context";
14
13
  import type { LayoutConfig } from "../../../types/config";
15
14
  import {
16
15
  finalizePaneDragRelease,
@@ -32,7 +31,6 @@ interface UseShellActiveDragOptions {
32
31
  appHeaderHeight: number;
33
32
  bounds: LayoutBounds;
34
33
  contentHeight: number;
35
- dispatch: Dispatch<AppAction>;
36
34
  dockGeometryOptions: DockGeometryOptions;
37
35
  dockLeafLayouts: DockLeafLayout[];
38
36
  dragRuntime: ShellDragRuntimeState;
@@ -52,7 +50,6 @@ export function useShellActiveDrag({
52
50
  appHeaderHeight,
53
51
  bounds,
54
52
  contentHeight,
55
- dispatch,
56
53
  dockGeometryOptions,
57
54
  dockLeafLayouts,
58
55
  dragRuntime,
@@ -168,16 +165,13 @@ export function useShellActiveDrag({
168
165
  ? getRememberedFloatingRect(baseLayout, drag.paneId, width, contentHeight, pane?.def)
169
166
  : drag.origRect;
170
167
  const releaseRect = resolvePaneDragFloatingRect(drag, baseRect, preciseX, preciseShellY, width, contentHeight);
171
- const releaseResult = finalizePaneDragRelease(baseLayout, drag.paneId, releaseRect, dockPreviewRef.current);
168
+ const nextLayout = finalizePaneDragRelease(baseLayout, drag.paneId, releaseRect, dockPreviewRef.current);
172
169
  if (windowMode) {
173
- updateWindowModePreviewLayout(releaseResult.nextLayout, drag.paneId);
170
+ updateWindowModePreviewLayout(nextLayout, drag.paneId);
174
171
  } else {
175
- persistLayout(releaseResult.nextLayout);
172
+ persistLayout(nextLayout);
176
173
  }
177
174
  focusPane(drag.paneId);
178
- if (!windowMode && releaseResult.shouldShowGridlockTip) {
179
- dispatch({ type: "SHOW_GRIDLOCK_TIP" });
180
- }
181
175
  updateDockPreview(null);
182
176
  setDragCursor(null);
183
177
  updateDragFloatingRect(null);
@@ -201,7 +195,6 @@ export function useShellActiveDrag({
201
195
  appHeaderHeight,
202
196
  bounds,
203
197
  contentHeight,
204
- dispatch,
205
198
  dockGeometryOptions,
206
199
  dividerPreviewRef,
207
200
  dockLeafLayouts,
@@ -46,11 +46,6 @@ export type DragPreview =
46
46
  rect: FloatingRect;
47
47
  };
48
48
 
49
- export interface PaneDragReleaseResult {
50
- nextLayout: LayoutConfig;
51
- shouldShowGridlockTip: boolean;
52
- }
53
-
54
49
  export interface PaneDragRectState {
55
50
  mode: "docked" | "floating";
56
51
  startX: number;
@@ -272,25 +267,14 @@ export function finalizePaneDragRelease(
272
267
  paneId: string,
273
268
  previewRect: FloatingRect,
274
269
  dockPreview: DragPreview | null,
275
- ): PaneDragReleaseResult {
270
+ ): LayoutConfig {
276
271
  if (dockPreview?.kind === "dock") {
277
- return {
278
- nextLayout: applyDrop(layout, paneId, dockPreview.target),
279
- shouldShowGridlockTip: false,
280
- };
272
+ return applyDrop(layout, paneId, dockPreview.target);
281
273
  }
282
-
283
274
  if (dockPreview?.kind === "snap") {
284
- return {
285
- nextLayout: floatAtRect(layout, paneId, dockPreview.rect),
286
- shouldShowGridlockTip: true,
287
- };
275
+ return floatAtRect(layout, paneId, dockPreview.rect);
288
276
  }
289
-
290
- return {
291
- nextLayout: floatAtRect(layout, paneId, previewRect),
292
- shouldShowGridlockTip: false,
293
- };
277
+ return floatAtRect(layout, paneId, previewRect);
294
278
  }
295
279
 
296
280
  export function makeSnapGuides(width: number, height: number): SnapGuide[] {
@@ -15,7 +15,6 @@ import {
15
15
  type LayoutBounds,
16
16
  type ResolvedPane,
17
17
  } from "../../../../plugins/pane-manager";
18
- import type { AppAction } from "../../../../state/app/context";
19
18
  import type { LayoutConfig } from "../../../../types/config";
20
19
  import {
21
20
  constrainFloatingRectToBounds,
@@ -165,7 +164,6 @@ interface UseShellPointerRuntimeOptions {
165
164
  bounds: LayoutBounds;
166
165
  closePaneMenu: () => void;
167
166
  contentHeight: number;
168
- dispatch: Dispatch<AppAction>;
169
167
  dockGeometryOptions: DockGeometryOptions;
170
168
  dockDividerLayouts: DockDividerLayout[];
171
169
  dockLeafLayouts: DockLeafLayout[];
@@ -193,6 +191,7 @@ interface UseShellPointerRuntimeOptions {
193
191
  visibleLayout: LayoutConfig;
194
192
  width: number;
195
193
  windowMode: WindowEditState | null;
194
+ commandBarOpen: boolean;
196
195
  }
197
196
 
198
197
  export function useShellPointerRuntime({
@@ -200,7 +199,6 @@ export function useShellPointerRuntime({
200
199
  bounds,
201
200
  closePaneMenu,
202
201
  contentHeight,
203
- dispatch,
204
202
  dockGeometryOptions,
205
203
  dockDividerLayouts,
206
204
  dockLeafLayouts,
@@ -224,12 +222,12 @@ export function useShellPointerRuntime({
224
222
  visibleLayout,
225
223
  width,
226
224
  windowMode,
225
+ commandBarOpen,
227
226
  }: UseShellPointerRuntimeOptions) {
228
227
  const handleActiveDrag = useShellActiveDrag({
229
228
  appHeaderHeight,
230
229
  bounds,
231
230
  contentHeight,
232
- dispatch,
233
231
  dockGeometryOptions,
234
232
  dockLeafLayouts,
235
233
  focusPane,
@@ -282,6 +280,7 @@ export function useShellPointerRuntime({
282
280
  setMenuState,
283
281
  transientFocusActive,
284
282
  windowMode,
283
+ commandBarOpen,
285
284
  });
286
285
 
287
286
  return {
@@ -62,6 +62,7 @@ function createShellPluginRegistry(options?: {
62
62
  hasPaneSettings: (paneId: string) => paneId === "portfolio-list:main",
63
63
  openPaneSettingsFn: () => {},
64
64
  openCommandBar: () => {},
65
+ showPane: () => {},
65
66
  openWindowMode: () => {},
66
67
  openWindowModeFn: () => {},
67
68
  updateLayoutFn: () => {},
@@ -356,7 +357,8 @@ describe("Shell", () => {
356
357
  expect(resolvePaneManagementShortcut({ ...base, name: "D", key: "D" })).toBe("toggle-floating");
357
358
  expect(resolvePaneManagementShortcut({ ...base, name: "o", key: "o" })).toBe("pop-out");
358
359
  expect(resolvePaneManagementShortcut({ ...base, name: "c", key: "c" })).toBe("copy-screenshot");
359
- expect(resolvePaneManagementShortcut({ ...base, name: "l", key: "l" })).toBe("layout-actions");
360
+ expect(resolvePaneManagementShortcut({ ...base, name: "s", key: "s" })).toBe("share");
361
+ expect(resolvePaneManagementShortcut({ ...base, name: "l", key: "l" })).toBe("layout-gallery");
360
362
  expect(resolvePaneManagementShortcut({ ...base, name: "f", key: "f" })).toBe("toggle-fullscreen");
361
363
  expect(resolvePaneManagementShortcut({ ...base, name: "g", key: "g" })).toBe("gridlock-all");
362
364
  expect(resolvePaneManagementShortcut({ ...base, name: "m", key: "m" })).toBe("window-mode");
@@ -366,6 +368,18 @@ describe("Shell", () => {
366
368
  expect(resolvePaneManagementShortcut({ ...base, name: "d", key: "d", meta: false, super: false })).toBeNull();
367
369
  });
368
370
 
371
+ test("opens the layout browser from the primary Shift-L shortcut", async () => {
372
+ const config = createDefaultConfig("/tmp/gloomberb-shell-layout-browser-shortcut-test");
373
+ const opened: string[] = [];
374
+ const registry = createShellPluginRegistry();
375
+ registry.showPane = (paneId) => opened.push(paneId);
376
+ await renderShellForWindowModeTest(createInitialState(config), { registry });
377
+
378
+ await emitKeypress({ name: "l", ctrl: true, shift: true });
379
+
380
+ expect(opened).toEqual(["layout-marketplace"]);
381
+ });
382
+
369
383
  test("toggles the focused pane fullscreen without persisting layout", async () => {
370
384
  const config = createDefaultConfig("/tmp/gloomberb-shell-fullscreen-shortcut-test");
371
385
  const mainPane = requireLayoutInstance(config, "portfolio-list:main");
@@ -478,8 +492,8 @@ describe("Shell", () => {
478
492
 
479
493
  const frame = testSetup!.captureCharFrame();
480
494
  const rows = frame.split("\n");
481
- expect(rows[2]?.indexOf("┌─:: Main Portfolio") ?? -1).toBeLessThan(0);
482
- expect(rows[5]?.indexOf("┌─:: Main Portfolio")).toBeGreaterThanOrEqual(14);
495
+ expect(rows[2]?.indexOf(":: Main Portfolio") ?? -1).toBeLessThan(0);
496
+ expect(rows[5]?.indexOf(":: Main Portfolio")).toBeGreaterThanOrEqual(14);
483
497
 
484
498
  await act(async () => {
485
499
  await testSetup!.mockMouse.release(16, 6);