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
@@ -21,6 +21,7 @@ import {
21
21
  syncConfigActiveLayoutState,
22
22
  useAppDispatch,
23
23
  useAppSelector,
24
+ useAppStateRef,
24
25
  } from "../../../state/app/context";
25
26
  import {
26
27
  selectCommandBarOpen,
@@ -37,6 +38,7 @@ import {
37
38
  menuForPane,
38
39
  menuItemsForFallback,
39
40
  } from "./menu";
41
+ import { tickerLinkMenuItems } from "./ticker-link-menu";
40
42
  import {
41
43
  makeSnapGuides,
42
44
  resolveExternalDockPreview,
@@ -58,6 +60,7 @@ import {
58
60
  useShellResolvedPanes,
59
61
  useShellVisibleLayout,
60
62
  } from "./layout-state";
63
+ import { AuthDialogHost } from "../../../plugins/builtin/cloud/auth-dialog";
61
64
  import { DeviceSignInDialogHost } from "../../../plugins/builtin/cloud/device-signin-dialog";
62
65
  import { useShellPaneActions } from "./pane/actions";
63
66
  import { resolvePaneFocusSourceLayout } from "./fullscreen";
@@ -66,6 +69,9 @@ import {
66
69
  resolveShellCursorOcclusionRects,
67
70
  useShellCursorOcclusionGuard,
68
71
  } from "./cursor-occlusion";
72
+ import { createShare, openLiveShareUrl } from "../../../shares/api";
73
+ import { buildPaneSharePayload } from "../../../shares/pane";
74
+ import type { SharePayload } from "../../../shares/payload";
69
75
 
70
76
  export { resolveAppHeaderHeightCells } from "./chrome";
71
77
  export { buildNativeWindowState } from "./native/window-state";
@@ -99,13 +105,14 @@ export function Shell({
99
105
  const previousFocusedPaneId = useAppSelector((state) => state.previousFocusedPaneId);
100
106
  const activePanel = useAppSelector((state) => state.activePanel);
101
107
  const commandBarOpen = useAppSelector(selectCommandBarOpen);
108
+ const stateRef = useAppStateRef();
102
109
  const inputCaptured = useAppSelector((state) => state.inputCaptured);
103
110
  const statusBarVisible = useAppSelector(selectStatusBarVisible);
104
111
  const rendererHost = useRendererHost();
105
112
  const { setTransientLayout } = useTransientLayout();
106
113
  const uiKind = useUiHost().kind;
107
114
  const shortcutDisplayMode = getShortcutDisplayMode(uiKind);
108
- const { nativePaneChrome = false, nativeContextMenu, precisePointer, titleBarOverlay, cellHeightPx } = useUiCapabilities();
115
+ const { nativePaneChrome = false, nativeContextMenu, precisePointer, publicSharing, titleBarOverlay, cellHeightPx } = useUiCapabilities();
109
116
  const { showContextMenu } = useContextMenu();
110
117
  const { width, height } = useViewport();
111
118
  const shellRef = useRef<BoxRenderable | null>(null);
@@ -118,8 +125,9 @@ export function Shell({
118
125
  const dialogOpen = useDialogState((dialog) => dialog.isOpen);
119
126
  const [hoveredPaneId, setHoveredPaneId] = useState<string | null>(null);
120
127
  const setHoveredPaneIfChanged = useCallback((paneId: string | null) => {
128
+ if (commandBarOpen) return;
121
129
  setHoveredPaneId((current) => (current === paneId ? current : paneId));
122
- }, []);
130
+ }, [commandBarOpen]);
123
131
  const [menuState, setMenuState] = useState<ActionMenuState | null>(null);
124
132
  const [transientFocusLayoutState, setTransientFocusLayoutState] = useState<TransientFocusLayoutState | null>(null);
125
133
  const transientFocusLayoutStateRef = useRef<TransientFocusLayoutState | null>(null);
@@ -130,6 +138,9 @@ export function Shell({
130
138
  setHoveredMenuItemId(null);
131
139
  }, []);
132
140
  const overlayOpen = commandBarOpen || dialogOpen || !!menuState;
141
+ useEffect(() => {
142
+ if (commandBarOpen) setHoveredPaneId(null);
143
+ }, [commandBarOpen]);
133
144
 
134
145
  const dragRuntime = useShellDragRuntimeState({
135
146
  contentHeight,
@@ -164,13 +175,14 @@ export function Shell({
164
175
  dispatch(hasFocusTarget
165
176
  ? { type: "UPDATE_LAYOUT", layout: nextLayout, focusedPaneId: options.focusedPaneId ?? null }
166
177
  : { type: "UPDATE_LAYOUT", layout: nextLayout });
178
+ const currentState = stateRef.current;
167
179
  scheduleConfigSave(syncConfigActiveLayoutState(
168
- { ...config, layout: nextLayout },
169
- paneState,
170
- hasFocusTarget ? (options.focusedPaneId ?? null) : focusedPaneId,
171
- activePanel,
180
+ { ...currentState.config, layout: nextLayout },
181
+ currentState.paneState,
182
+ hasFocusTarget ? (options.focusedPaneId ?? null) : currentState.focusedPaneId,
183
+ currentState.activePanel,
172
184
  ));
173
- }, [activePanel, config, dispatch, focusedPaneId, paneState]);
185
+ }, [dispatch, stateRef]);
174
186
 
175
187
  const focusPane = useCallback((paneId: string) => {
176
188
  dispatch({ type: "FOCUS_PANE", paneId });
@@ -252,7 +264,6 @@ export function Shell({
252
264
  gridlockVisiblePanes,
253
265
  handleFloatingClose,
254
266
  openFocusedPaneSettings,
255
- openLayoutMenu,
256
267
  openPaneSettings,
257
268
  popOutFocusedPane,
258
269
  toggleFocusedPaneFloating,
@@ -271,6 +282,9 @@ export function Shell({
271
282
  visibleLayout,
272
283
  width,
273
284
  });
285
+ const openLayoutGallery = useCallback(() => {
286
+ pluginRegistry.showPane("layout-marketplace");
287
+ }, [pluginRegistry]);
274
288
  const setTransientFocusLayout = useCallback((next: TransientFocusLayoutState | null) => {
275
289
  transientFocusLayoutStateRef.current = next;
276
290
  setTransientFocusLayoutState(next);
@@ -368,24 +382,6 @@ export function Shell({
368
382
  transientFocusLayoutState,
369
383
  ]);
370
384
 
371
- useShellPaneManagementShortcuts({
372
- cancelActiveDrag,
373
- closeAllFloatingPanes,
374
- closeFocusedPane,
375
- copyFocusedPaneScreenshot,
376
- focusedPaneId,
377
- gridlockVisiblePanes,
378
- hasActiveDrag,
379
- inputCaptured,
380
- openFocusedPaneSettings,
381
- openLayoutMenu,
382
- overlayOpen,
383
- popOutFocusedPane,
384
- startWindowMode,
385
- toggleFocusedPaneFullscreen,
386
- toggleFocusedPaneFloating,
387
- });
388
-
389
385
  const dockLeafLayouts = useMemo(() => getDockLeafLayouts(activeLayout, bounds, dockGeometryOptions), [activeLayout, bounds, dockGeometryOptions]);
390
386
  const dockDividerLayouts = useMemo(() => getDockDividerLayouts(activeLayout, bounds, dockGeometryOptions), [activeLayout, bounds, dockGeometryOptions]);
391
387
  const snapGuides = useMemo(() => makeSnapGuides(width, contentHeight), [contentHeight, width]);
@@ -422,8 +418,8 @@ export function Shell({
422
418
  [config, paneState],
423
419
  );
424
420
  const getPaneTitle = useCallback(
425
- (pane: ResolvedPane): string => getPaneDisplayTitle(titleState, pane.instance, pane.def),
426
- [titleState],
421
+ (pane: ResolvedPane): string => getPaneDisplayTitle(titleState, pane.instance, pane.def, pluginRegistry.panes),
422
+ [pluginRegistry.panes, titleState],
427
423
  );
428
424
  const handlePaneQuickSetting = useCallback((paneId: string, key: string, event: any) => {
429
425
  event?.preventDefault?.();
@@ -446,6 +442,51 @@ export function Shell({
446
442
  onMouseDown: (event) => handlePaneQuickSetting(paneId, setting.key, event),
447
443
  }))
448
444
  ), [config, handlePaneQuickSetting, pluginRegistry]);
445
+ const sharePane = useCallback(async (payload: Extract<SharePayload, { kind: "pane" }>) => {
446
+ try {
447
+ const { id } = await createShare(payload);
448
+ await rendererHost.copyText(openLiveShareUrl(id));
449
+ pluginRegistry.notify({ body: "Share link copied to clipboard", type: "success" });
450
+ } catch (error) {
451
+ pluginRegistry.notify({
452
+ body: error instanceof Error ? error.message : "Could not share this pane.",
453
+ type: "error",
454
+ });
455
+ }
456
+ }, [pluginRegistry, rendererHost]);
457
+ const shareFocusedPane = useCallback(() => {
458
+ if (!publicSharing || !focusedPaneId) return false;
459
+ const pane = paneMap.get(focusedPaneId);
460
+ if (!pane) return false;
461
+ const payload = buildPaneSharePayload(
462
+ pluginRegistry,
463
+ pane.instance,
464
+ paneState[focusedPaneId] ?? {},
465
+ resolveTickerForPane(titleState, focusedPaneId),
466
+ );
467
+ if (!payload) return false;
468
+ void sharePane(payload);
469
+ return true;
470
+ }, [focusedPaneId, paneMap, paneState, pluginRegistry, publicSharing, sharePane, titleState]);
471
+
472
+ useShellPaneManagementShortcuts({
473
+ cancelActiveDrag,
474
+ closeAllFloatingPanes,
475
+ closeFocusedPane,
476
+ copyFocusedPaneScreenshot,
477
+ focusedPaneId,
478
+ gridlockVisiblePanes,
479
+ hasActiveDrag,
480
+ inputCaptured,
481
+ openFocusedPaneSettings,
482
+ openLayoutGallery,
483
+ overlayOpen,
484
+ popOutFocusedPane,
485
+ shareFocusedPane,
486
+ startWindowMode,
487
+ toggleFocusedPaneFullscreen,
488
+ toggleFocusedPaneFloating,
489
+ });
449
490
 
450
491
  const openPaneMenu = useCallback((paneId: string, rect: LayoutBounds, event?: { preventDefault?: () => void; stopPropagation?: () => void }) => {
451
492
  const pane = paneMap.get(paneId);
@@ -458,6 +499,14 @@ export function Shell({
458
499
  title: getPaneTitle(pane),
459
500
  floating: !!pane.floating,
460
501
  };
502
+ const sharePayload = publicSharing
503
+ ? buildPaneSharePayload(
504
+ pluginRegistry,
505
+ pane.instance,
506
+ paneState[paneId] ?? {},
507
+ resolveTickerForPane(titleState, paneId),
508
+ )
509
+ : null;
461
510
  const items = menuForPane(
462
511
  pane,
463
512
  visibleLayout,
@@ -469,6 +518,14 @@ export function Shell({
469
518
  openPaneSettings,
470
519
  desktopWindowBridge,
471
520
  nativePaneChrome && rendererHost.copyPngImage ? copyPaneScreenshot : undefined,
521
+ sharePayload ? () => sharePane(sharePayload) : undefined,
522
+ tickerLinkMenuItems({
523
+ instance: pane.instance,
524
+ layout: visibleLayout,
525
+ panes: pluginRegistry.panes,
526
+ state: titleState,
527
+ persistLayout,
528
+ }),
472
529
  );
473
530
  void showContextMenu(context, items, event).then((shown) => {
474
531
  if (shown) return;
@@ -492,7 +549,7 @@ export function Shell({
492
549
  items: fallbackItems,
493
550
  });
494
551
  });
495
- }, [contentHeight, copyPaneScreenshot, desktopWindowBridge, focusPane, getPaneTitle, nativePaneChrome, openPaneSettings, paneMap, persistLayout, pluginRegistry, rendererHost.copyPngImage, shortcutDisplayMode, showContextMenu, visibleLayout, width]);
552
+ }, [contentHeight, copyPaneScreenshot, desktopWindowBridge, focusPane, getPaneTitle, nativePaneChrome, openPaneSettings, paneMap, paneState, persistLayout, pluginRegistry, publicSharing, rendererHost.copyPngImage, sharePane, shortcutDisplayMode, showContextMenu, titleState, visibleLayout, width]);
496
553
 
497
554
  const {
498
555
  handleFloatingCloseMouseDown,
@@ -510,7 +567,6 @@ export function Shell({
510
567
  bounds,
511
568
  closePaneMenu,
512
569
  contentHeight,
513
- dispatch,
514
570
  dockGeometryOptions,
515
571
  dockDividerLayouts,
516
572
  dockLeafLayouts,
@@ -534,6 +590,7 @@ export function Shell({
534
590
  visibleLayout,
535
591
  width,
536
592
  windowMode,
593
+ commandBarOpen,
537
594
  });
538
595
  const windowModeDockResizePathKey = windowMode?.focus.kind === "dock-resize"
539
596
  ? windowMode.focus.pathKey
@@ -560,8 +617,9 @@ export function Shell({
560
617
  }
561
618
  : {})}
562
619
  >
563
- {/* Renders nothing; gives the QR sign-in command an always-mounted component with dialog access. */}
620
+ {/* Render nothing; give the auth commands always-mounted components with dialog access. */}
564
621
  <DeviceSignInDialogHost />
622
+ <AuthDialogHost />
565
623
  <Box
566
624
  position="absolute"
567
625
  left={0}
@@ -1,7 +1,43 @@
1
1
  import { describe, expect, test } from "bun:test";
2
2
  import { displayWidth } from "../../../utils/format";
3
3
  import { getLanguage, setLanguage, t } from "../../../i18n";
4
- import { actionMenuWidth } from "./menu";
4
+ import { actionMenuWidth, menuForPane } from "./menu";
5
+
6
+ describe("pane action menu", () => {
7
+ test("offers the global share action for a portable pane", async () => {
8
+ let shared = false;
9
+ const items = menuForPane(
10
+ {
11
+ instance: { instanceId: "chart-1", paneId: "chart-composer" },
12
+ def: { defaultPosition: "right" },
13
+ floating: false,
14
+ } as any,
15
+ {
16
+ dockRoot: { kind: "pane", instanceId: "chart-1" },
17
+ instances: [{ instanceId: "chart-1", paneId: "chart-composer" }],
18
+ floating: [],
19
+ detached: [],
20
+ },
21
+ 120,
22
+ 40,
23
+ {
24
+ hasPaneSettings: () => false,
25
+ openWindowMode: () => {},
26
+ } as any,
27
+ () => {},
28
+ () => {},
29
+ () => {},
30
+ undefined,
31
+ undefined,
32
+ () => { shared = true; },
33
+ );
34
+
35
+ const share = items.find((item) => item.type !== "divider" && item.id === "share-pane");
36
+ await share?.onSelect?.();
37
+ expect(shared).toBe(true);
38
+ expect(share?.accelerator).toBe("CmdOrCtrl+Shift+S");
39
+ });
40
+ });
5
41
 
6
42
  describe("action menu sizing", () => {
7
43
  test("uses translated terminal display width", () => {
@@ -32,6 +32,8 @@ export function menuForPane(
32
32
  openPaneSettings: (paneId: string) => void,
33
33
  desktopWindowBridge?: DesktopWindowBridge,
34
34
  copyPaneScreenshot?: (paneId: string) => void | Promise<void>,
35
+ sharePane?: () => void | Promise<void>,
36
+ linkItems: ContextMenuItem[] = [],
35
37
  ): ContextMenuItem[] {
36
38
  const baseActions: ContextMenuItem[] = [];
37
39
  if (pluginRegistry.hasPaneSettings(pane.instance.instanceId)) {
@@ -42,6 +44,14 @@ export function menuForPane(
42
44
  onSelect: () => openPaneSettings(pane.instance.instanceId),
43
45
  });
44
46
  }
47
+ if (sharePane) {
48
+ baseActions.push({
49
+ id: "share-pane",
50
+ label: "Share Pane",
51
+ accelerator: PANE_MANAGEMENT_ACCELERATORS.share,
52
+ onSelect: sharePane,
53
+ });
54
+ }
45
55
  if (copyPaneScreenshot) {
46
56
  baseActions.push({
47
57
  id: "copy-screenshot",
@@ -91,6 +101,10 @@ export function menuForPane(
91
101
  onSelect: () => persistLayout(removePane(layout, pane.instance.instanceId)),
92
102
  });
93
103
 
104
+ if (linkItems.length > 0) {
105
+ baseActions.push(contextMenuDivider("pane:link-divider"), ...linkItems);
106
+ }
107
+
94
108
  baseActions.push(
95
109
  contextMenuDivider("pane:layout-divider"),
96
110
  {
@@ -22,6 +22,7 @@ interface UseShellNativePointerRuntimeOptions {
22
22
  setMenuState: Dispatch<SetStateAction<ActionMenuState | null>>;
23
23
  transientFocusActive: boolean;
24
24
  windowMode: WindowEditState | null;
25
+ commandBarOpen?: boolean;
25
26
  }
26
27
 
27
28
  export function useShellNativePointerRuntime({
@@ -38,6 +39,7 @@ export function useShellNativePointerRuntime({
38
39
  setMenuState,
39
40
  transientFocusActive,
40
41
  windowMode,
42
+ commandBarOpen = false,
41
43
  }: UseShellNativePointerRuntimeOptions) {
42
44
  const {
43
45
  dragRef,
@@ -59,6 +61,11 @@ export function useShellNativePointerRuntime({
59
61
  }, [focusPane, menuState, setHoveredMenuItemId, setMenuState]);
60
62
 
61
63
  const handleNativePaneMouseDown = useCallback((paneId: string, event: ShellMouseEvent) => {
64
+ if (commandBarOpen) {
65
+ event.stopPropagation();
66
+ event.preventDefault();
67
+ return;
68
+ }
62
69
  if (windowMode) {
63
70
  selectWindowModePane(paneId);
64
71
  event.stopPropagation();
@@ -69,10 +76,11 @@ export function useShellNativePointerRuntime({
69
76
  if (event.isDefaultPrevented?.()) return;
70
77
  focusNativePane(paneId);
71
78
  });
72
- }, [focusNativePane, selectWindowModePane, windowMode]);
79
+ }, [commandBarOpen, focusNativePane, selectWindowModePane, windowMode]);
73
80
 
74
81
  const startNativeFloatingDrag = useCallback((paneId: string, rect: FloatingRect, event: ShellMouseEvent) => {
75
82
  if (!nativePaneChrome) return;
83
+ if (commandBarOpen) return;
76
84
  if (windowMode) return;
77
85
  if (transientFocusActive) return;
78
86
  if (event.button === 2) return;
@@ -89,10 +97,11 @@ export function useShellNativePointerRuntime({
89
97
  };
90
98
  updateDragFloatingRect({ paneId, rect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height } });
91
99
  event.preventDefault();
92
- }, [dragRef, focusNativePane, getShellPointer, nativePaneChrome, transientFocusActive, updateDragFloatingRect, windowMode]);
100
+ }, [commandBarOpen, dragRef, focusNativePane, getShellPointer, nativePaneChrome, transientFocusActive, updateDragFloatingRect, windowMode]);
93
101
 
94
102
  const startNativeDockedDrag = useCallback((paneId: string, rect: LayoutBounds, event: ShellMouseEvent) => {
95
103
  if (!nativePaneChrome) return;
104
+ if (commandBarOpen) return;
96
105
  if (windowMode) return;
97
106
  if (transientFocusActive) return;
98
107
  if (event.button === 2) return;
@@ -108,10 +117,11 @@ export function useShellNativePointerRuntime({
108
117
  origRect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height },
109
118
  };
110
119
  event.preventDefault();
111
- }, [dragRef, focusNativePane, getShellPointer, nativePaneChrome, transientFocusActive, windowMode]);
120
+ }, [commandBarOpen, dragRef, focusNativePane, getShellPointer, nativePaneChrome, transientFocusActive, windowMode]);
112
121
 
113
122
  const startNativeFloatResize = useCallback((paneId: string, rect: FloatingRect, event: ShellMouseEvent) => {
114
123
  if (!nativePaneChrome) return;
124
+ if (commandBarOpen) return;
115
125
  if (transientFocusActive) return;
116
126
 
117
127
  const pointer = getShellPointer(event);
@@ -130,10 +140,11 @@ export function useShellNativePointerRuntime({
130
140
  updateDragFloatingRect({ paneId, rect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height } });
131
141
  event.stopPropagation();
132
142
  event.preventDefault();
133
- }, [dragRef, focusNativePane, getShellPointer, nativePaneChrome, selectWindowModePane, transientFocusActive, updateDragFloatingRect, windowMode]);
143
+ }, [commandBarOpen, dragRef, focusNativePane, getShellPointer, nativePaneChrome, selectWindowModePane, transientFocusActive, updateDragFloatingRect, windowMode]);
134
144
 
135
145
  const startNativeDividerDrag = useCallback((divider: DockDividerLayout, event: ShellMouseEvent) => {
136
146
  if (!nativePaneChrome) return;
147
+ if (commandBarOpen) return;
137
148
  if (transientFocusActive) return;
138
149
 
139
150
  const pointer = getShellPointer(event);
@@ -157,27 +168,30 @@ export function useShellNativePointerRuntime({
157
168
  });
158
169
  event.stopPropagation();
159
170
  event.preventDefault();
160
- }, [dragRef, getShellPointer, menuState, nativePaneChrome, setHoveredMenuItemId, setMenuState, transientFocusActive, updateDividerPreview]);
171
+ }, [commandBarOpen, dragRef, getShellPointer, menuState, nativePaneChrome, setHoveredMenuItemId, setMenuState, transientFocusActive, updateDividerPreview]);
161
172
 
162
173
  const handlePaneAction = useCallback((paneId: string, rect: LayoutBounds, event: ShellMouseEvent) => {
174
+ if (commandBarOpen) return;
163
175
  if (windowMode) return;
164
176
  if (event.button === 2) return;
165
177
  event.stopPropagation();
166
178
  event.preventDefault();
167
179
  openPaneMenu(paneId, rect, event);
168
- }, [openPaneMenu, windowMode]);
180
+ }, [commandBarOpen, openPaneMenu, windowMode]);
169
181
 
170
182
  const handleNativePaneContextMenu = useCallback((paneId: string, rect: LayoutBounds, event: ShellMouseEvent) => {
183
+ if (commandBarOpen) return;
171
184
  if (windowMode) return;
172
185
  openPaneMenu(paneId, rect, event);
173
- }, [openPaneMenu, windowMode]);
186
+ }, [commandBarOpen, openPaneMenu, windowMode]);
174
187
 
175
188
  const handleFloatingCloseMouseDown = useCallback((paneId: string, event: ShellMouseEvent) => {
189
+ if (commandBarOpen) return;
176
190
  if (windowMode) return;
177
191
  event.stopPropagation();
178
192
  event.preventDefault();
179
193
  handleFloatingClose(paneId);
180
- }, [handleFloatingClose, windowMode]);
194
+ }, [commandBarOpen, handleFloatingClose, windowMode]);
181
195
 
182
196
  return {
183
197
  handleFloatingCloseMouseDown,
@@ -54,10 +54,6 @@ export function useShellPaneActions({
54
54
  visibleLayout,
55
55
  width,
56
56
  }: UseShellPaneActionsOptions) {
57
- const openLayoutMenu = useCallback(() => {
58
- pluginRegistry.openCommandBar("LAY ");
59
- }, [pluginRegistry]);
60
-
61
57
  const openPaneSettings = useCallback((paneId: string) => {
62
58
  pluginRegistry.openPaneSettingsFn(paneId);
63
59
  closePaneMenu();
@@ -143,7 +139,6 @@ export function useShellPaneActions({
143
139
  gridlockVisiblePanes,
144
140
  handleFloatingClose,
145
141
  openFocusedPaneSettings,
146
- openLayoutMenu,
147
142
  openPaneSettings,
148
143
  popOutFocusedPane,
149
144
  toggleFocusedPaneFloating,
@@ -21,9 +21,10 @@ interface ShellPaneManagementShortcutOptions {
21
21
  hasActiveDrag(): boolean;
22
22
  inputCaptured: boolean;
23
23
  openFocusedPaneSettings(): boolean;
24
- openLayoutMenu(): void;
24
+ openLayoutGallery(): void;
25
25
  overlayOpen: boolean;
26
26
  popOutFocusedPane(): boolean;
27
+ shareFocusedPane(): boolean;
27
28
  startWindowMode(paneId?: string, mode?: WindowEditMode): void;
28
29
  toggleFocusedPaneFullscreen(): boolean;
29
30
  toggleFocusedPaneFloating(): boolean;
@@ -39,9 +40,10 @@ export function useShellPaneManagementShortcuts({
39
40
  hasActiveDrag,
40
41
  inputCaptured,
41
42
  openFocusedPaneSettings,
42
- openLayoutMenu,
43
+ openLayoutGallery,
43
44
  overlayOpen,
44
45
  popOutFocusedPane,
46
+ shareFocusedPane,
45
47
  startWindowMode,
46
48
  toggleFocusedPaneFullscreen,
47
49
  toggleFocusedPaneFloating,
@@ -119,8 +121,11 @@ export function useShellPaneManagementShortcuts({
119
121
  case "copy-screenshot":
120
122
  handled = copyFocusedPaneScreenshot();
121
123
  break;
122
- case "layout-actions":
123
- openLayoutMenu();
124
+ case "share":
125
+ handled = shareFocusedPane();
126
+ break;
127
+ case "layout-gallery":
128
+ openLayoutGallery();
124
129
  handled = true;
125
130
  break;
126
131
  case "gridlock-all":
@@ -6,9 +6,10 @@ export const PANE_MANAGEMENT_ACCELERATORS = {
6
6
  toggleFloating: "CmdOrCtrl+Shift+D",
7
7
  popOut: "CmdOrCtrl+Shift+O",
8
8
  copyScreenshot: "CmdOrCtrl+Shift+C",
9
+ share: "CmdOrCtrl+Shift+S",
9
10
  close: "CmdOrCtrl+W",
10
11
  closeAllFloating: "CmdOrCtrl+Alt+W",
11
- layoutActions: "CmdOrCtrl+Shift+L",
12
+ layoutGallery: "CmdOrCtrl+Shift+L",
12
13
  gridlockAll: "CmdOrCtrl+Shift+G",
13
14
  windowMode: "CmdOrCtrl+Shift+M",
14
15
  windowResizeMode: "CmdOrCtrl+Shift+R",
@@ -20,9 +21,10 @@ export type PaneManagementShortcut =
20
21
  | "toggle-floating"
21
22
  | "pop-out"
22
23
  | "copy-screenshot"
24
+ | "share"
23
25
  | "close"
24
26
  | "close-all-floating"
25
- | "layout-actions"
27
+ | "layout-gallery"
26
28
  | "gridlock-all"
27
29
  | "window-mode"
28
30
  | "window-resize-mode";
@@ -39,10 +41,11 @@ export function resolvePaneManagementShortcut(
39
41
  if (!shifted && name === ",") return "settings";
40
42
  if (!shifted || event.alt) return null;
41
43
  if (name === "c") return "copy-screenshot";
44
+ if (name === "s") return "share";
42
45
  if (name === "d") return "toggle-floating";
43
46
  if (name === "f") return "toggle-fullscreen";
44
47
  if (name === "o") return "pop-out";
45
- if (name === "l") return "layout-actions";
48
+ if (name === "l") return "layout-gallery";
46
49
  if (name === "g") return "gridlock-all";
47
50
  if (name === "m") return "window-mode";
48
51
  if (name === "r") return "window-resize-mode";
@@ -0,0 +1,82 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { createInitialState } from "../../../state/app/context";
3
+ import {
4
+ createDefaultConfig,
5
+ createPaneInstance,
6
+ findPaneInstance,
7
+ TICKER_RESEARCH_PANE_ID,
8
+ type LayoutConfig,
9
+ } from "../../../types/config";
10
+ import type { PaneDef } from "../../../types/plugin";
11
+ import { getPaneDisplayTitle } from "../pane/title";
12
+ import { tickerLinkMenuItems } from "./ticker-link-menu";
13
+
14
+ const panes = new Map<string, PaneDef>([
15
+ ["portfolio-list", {
16
+ id: "portfolio-list",
17
+ name: "Portfolio",
18
+ component: () => null,
19
+ defaultPosition: "left",
20
+ tickerSource: true,
21
+ }],
22
+ [TICKER_RESEARCH_PANE_ID, {
23
+ id: TICKER_RESEARCH_PANE_ID,
24
+ name: "Ticker Research",
25
+ component: () => null,
26
+ defaultPosition: "right",
27
+ }],
28
+ ]);
29
+
30
+ describe("tickerLinkMenuItems", () => {
31
+ test("pins the current ticker when unlinking and can link back to the source", () => {
32
+ const state = createInitialState(createDefaultConfig("/tmp/gloomberb-link-menu-test"));
33
+ state.paneState["portfolio-list:main"] = { collectionId: "main", cursorSymbol: "AAPL" };
34
+ const target = findPaneInstance(state.config.layout, "ticker-detail:main")!;
35
+ let layout: LayoutConfig = state.config.layout;
36
+
37
+ expect(getPaneDisplayTitle(state, target, panes.get(TICKER_RESEARCH_PANE_ID)!, panes)).toBe(
38
+ "AAPL ⧉ Linked to Main Portfolio",
39
+ );
40
+
41
+ tickerLinkMenuItems({
42
+ instance: target,
43
+ layout,
44
+ panes,
45
+ state,
46
+ persistLayout: (nextLayout) => { layout = nextLayout; },
47
+ }).find((item) => item.id === "unlink:portfolio-list:main")?.onSelect?.();
48
+
49
+ const pinned = findPaneInstance(layout, target.instanceId)!;
50
+ expect(pinned.binding).toEqual({ kind: "fixed", symbol: "AAPL" });
51
+
52
+ const previousFollower = createPaneInstance(TICKER_RESEARCH_PANE_ID, {
53
+ instanceId: "ticker-detail:previous",
54
+ binding: { kind: "follow", sourceInstanceId: "portfolio-list:main" },
55
+ });
56
+ layout = {
57
+ ...layout,
58
+ instances: [...layout.instances, previousFollower],
59
+ floating: [
60
+ ...layout.floating,
61
+ { instanceId: previousFollower.instanceId, x: 1, y: 1, width: 40, height: 12 },
62
+ ],
63
+ };
64
+ const pinnedState = { ...state, config: { ...state.config, layout } };
65
+ tickerLinkMenuItems({
66
+ instance: pinned,
67
+ layout,
68
+ panes,
69
+ state: pinnedState,
70
+ persistLayout: (nextLayout) => { layout = nextLayout; },
71
+ }).find((item) => item.id === "link:portfolio-list:main")?.onSelect?.();
72
+
73
+ expect(findPaneInstance(layout, target.instanceId)?.binding).toEqual({
74
+ kind: "follow",
75
+ sourceInstanceId: "portfolio-list:main",
76
+ });
77
+ expect(findPaneInstance(layout, previousFollower.instanceId)?.binding).toEqual({
78
+ kind: "fixed",
79
+ symbol: "AAPL",
80
+ });
81
+ });
82
+ });