gloomberb 0.11.0 → 0.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (392) hide show
  1. package/README.md +38 -15
  2. package/README.zh-CN.md +12 -12
  3. package/package.json +10 -3
  4. package/src/api-client/auth.ts +14 -10
  5. package/src/api-client/data.ts +54 -0
  6. package/src/api-client/index.test.ts +124 -0
  7. package/src/api-client/index.ts +139 -4
  8. package/src/api-client/paths.ts +89 -0
  9. package/src/api-client/request.ts +15 -2
  10. package/src/api-client/socket-health.test.ts +1 -0
  11. package/src/api-client/socket.ts +2 -1
  12. package/src/api-client/types.ts +163 -0
  13. package/src/app/pane-runtime/index.tsx +4 -1
  14. package/src/app/pane-runtime/plugin-bindings.test.ts +117 -0
  15. package/src/app/pane-runtime/plugin-bindings.ts +25 -4
  16. package/src/app/pane-runtime/ticker-inspector-runtime.ts +25 -14
  17. package/src/app/runtime/desktop-deeplink.test.ts +16 -0
  18. package/src/app/runtime/desktop-deeplink.ts +66 -2
  19. package/src/app/runtime/desktop-menu.ts +3 -0
  20. package/src/app/runtime/plugin-bindings.ts +30 -28
  21. package/src/app/runtime/update.ts +8 -6
  22. package/src/app.tsx +21 -7
  23. package/src/architecture/import-boundaries.test.ts +5 -1
  24. package/src/capabilities/factories.ts +4 -0
  25. package/src/cli/commands/automation.test.ts +9 -0
  26. package/src/cli/commands/automation.ts +22 -73
  27. package/src/cli/index.ts +10 -6
  28. package/src/cli/registry.ts +1 -1
  29. package/src/components/chart/composite/composite-chart.test.tsx +45 -2
  30. package/src/components/chart/composite/composite-chart.tsx +16 -12
  31. package/src/components/chart/composite/format.test.ts +34 -0
  32. package/src/components/chart/composite/format.ts +33 -10
  33. package/src/components/chart/composite/scene.ts +0 -21
  34. package/src/components/chart/composite/tools.ts +49 -0
  35. package/src/components/chart/core/axis-format.ts +13 -3
  36. package/src/components/chart/core/index.test.ts +7 -0
  37. package/src/components/chart/core/renderer.ts +3 -4
  38. package/src/components/command-bar/commands/direct/index.ts +4 -0
  39. package/src/components/command-bar/commands/registry.ts +8 -1
  40. package/src/components/command-bar/layout-items/current-layout.ts +1 -1
  41. package/src/components/command-bar/pane-templates/workflow.ts +4 -0
  42. package/src/components/command-bar/routes/root/shortcut-feedback.ts +3 -0
  43. package/src/components/command-bar/routes/root/shortcuts.test.ts +19 -0
  44. package/src/components/command-bar/routes/root/shortcuts.ts +3 -1
  45. package/src/components/command-bar/surface/index.test.tsx +6 -6
  46. package/src/components/command-bar/surface/index.tsx +4 -1
  47. package/src/components/command-bar/surface/pane.test.tsx +66 -2
  48. package/src/components/command-bar/view-model.test.ts +2 -1
  49. package/src/components/command-bar/workflow/ops.test.ts +54 -0
  50. package/src/components/command-bar/workflow/ops.ts +1 -1
  51. package/src/components/company-logo.test.ts +19 -0
  52. package/src/components/company-logo.tsx +60 -0
  53. package/src/components/feed-data-table/stack-view.tsx +7 -1
  54. package/src/components/index.ts +1 -0
  55. package/src/components/layout/detached-pane-shell.tsx +85 -15
  56. package/src/components/layout/header.tsx +5 -5
  57. package/src/components/layout/pane/title.ts +31 -3
  58. package/src/components/layout/shell/active-drag.ts +4 -11
  59. package/src/components/layout/shell/drag/index.ts +4 -20
  60. package/src/components/layout/shell/drag/runtime.ts +3 -4
  61. package/src/components/layout/shell/index.test.tsx +17 -3
  62. package/src/components/layout/shell/index.tsx +89 -31
  63. package/src/components/layout/shell/menu.test.ts +37 -1
  64. package/src/components/layout/shell/menu.ts +14 -0
  65. package/src/components/layout/shell/native/pointer-runtime.ts +22 -8
  66. package/src/components/layout/shell/pane/actions.ts +0 -5
  67. package/src/components/layout/shell/pane/management-shortcuts.ts +9 -4
  68. package/src/components/layout/shell/shortcuts.ts +6 -3
  69. package/src/components/layout/shell/ticker-link-menu.test.ts +82 -0
  70. package/src/components/layout/shell/ticker-link-menu.ts +78 -0
  71. package/src/components/layout/status-bar.test.tsx +62 -61
  72. package/src/components/layout/status-bar.tsx +53 -75
  73. package/src/components/onboarding/account-step/form-panel.tsx +4 -2
  74. package/src/components/onboarding/account-step/index.tsx +1 -1
  75. package/src/components/onboarding/account-step/signed-in-panel.tsx +1 -1
  76. package/src/components/onboarding/onboarding-wizard.test.tsx +44 -1
  77. package/src/components/onboarding/onboarding-wizard.tsx +13 -5
  78. package/src/components/onboarding/wizard-account.ts +4 -23
  79. package/src/components/table-view-shared.test.ts +11 -0
  80. package/src/components/table-view-shared.tsx +26 -0
  81. package/src/components/ui/data-table/index.tsx +19 -1
  82. package/src/components/ui/fields.tsx +5 -1
  83. package/src/core/state/app/layout-reducer.ts +36 -0
  84. package/src/core/state/app/layout.ts +10 -5
  85. package/src/core/state/app/state.ts +0 -13
  86. package/src/core/state/app/types.ts +6 -4
  87. package/src/data/config/chart-settings.ts +1 -1
  88. package/src/data/config/layout.ts +2 -0
  89. package/src/data/config/store/index.test.ts +27 -6
  90. package/src/data/config/store/normalize.ts +3 -5
  91. package/src/data/plugin-state-store.ts +11 -0
  92. package/src/data/sqlite/cache.test.ts +10 -0
  93. package/src/i18n/es.ts +13 -5
  94. package/src/i18n/ja.ts +13 -5
  95. package/src/i18n/ko.ts +13 -5
  96. package/src/i18n/zh-cn.ts +13 -5
  97. package/src/i18n/zh-tw.ts +13 -5
  98. package/src/layout-marketplace/api.test.ts +54 -0
  99. package/src/layout-marketplace/api.ts +65 -0
  100. package/src/layout-marketplace/gallery-desktop.test.tsx +262 -0
  101. package/src/layout-marketplace/gallery-desktop.tsx +375 -0
  102. package/src/layout-marketplace/gallery-terminal.tsx +376 -0
  103. package/src/layout-marketplace/gallery.test.tsx +84 -0
  104. package/src/layout-marketplace/gallery.tsx +323 -0
  105. package/src/layout-marketplace/geometry.test.ts +47 -0
  106. package/src/layout-marketplace/geometry.ts +80 -0
  107. package/src/layout-marketplace/mini-workspace.test.tsx +122 -0
  108. package/src/layout-marketplace/mini-workspace.tsx +428 -0
  109. package/src/layout-marketplace/model.test.ts +118 -0
  110. package/src/layout-marketplace/model.ts +171 -0
  111. package/src/layout-marketplace/name-dialog.tsx +60 -0
  112. package/src/layout-marketplace/pane-imagery.ts +85 -0
  113. package/src/layout-marketplace/pane.tsx +20 -0
  114. package/src/layout-marketplace/payload.test.ts +246 -0
  115. package/src/layout-marketplace/payload.ts +624 -0
  116. package/src/layout-marketplace/use-marketplace.ts +63 -0
  117. package/src/market-data/coordinator/index.test.ts +2 -2
  118. package/src/market-data/coordinator/index.ts +1 -1
  119. package/src/market-data/hooks.tsx +0 -41
  120. package/src/market-data/market/format.test.ts +7 -0
  121. package/src/market-data/market/freshness.ts +5 -2
  122. package/src/market-data/quotes/freshness.test.ts +16 -0
  123. package/src/news/aggregator.test.ts +92 -3
  124. package/src/news/aggregator.ts +98 -18
  125. package/src/news/hooks.test.tsx +2 -0
  126. package/src/news/hooks.ts +27 -1
  127. package/src/news/news-model.ts +5 -2
  128. package/src/news/types.ts +7 -0
  129. package/src/plugins/broker-sync/broker-sync.test.ts +58 -0
  130. package/src/plugins/broker-sync/normalize.ts +42 -5
  131. package/src/plugins/builtin/account-management/pane.tsx +5 -5
  132. package/src/plugins/builtin/ai/index.tsx +18 -2
  133. package/src/plugins/builtin/ai/screener/pane.test.tsx +47 -4
  134. package/src/plugins/builtin/ai/screener/pane.tsx +35 -12
  135. package/src/plugins/builtin/alerts/alert-engine.test.ts +53 -1
  136. package/src/plugins/builtin/alerts/alert-engine.ts +34 -1
  137. package/src/plugins/builtin/alerts/index.test.tsx +49 -6
  138. package/src/plugins/builtin/alerts/index.tsx +11 -6
  139. package/src/plugins/builtin/alerts/pane.tsx +51 -9
  140. package/src/plugins/builtin/alerts/quotes.ts +11 -3
  141. package/src/plugins/builtin/alerts/types.ts +1 -0
  142. package/src/plugins/builtin/analytics/index.tsx +11 -1
  143. package/src/plugins/builtin/analytics/pane-model.ts +6 -4
  144. package/src/plugins/builtin/broker-manager/index.tsx +3 -0
  145. package/src/plugins/builtin/buildout/model/index.ts +1 -1
  146. package/src/plugins/builtin/buildout/pane/index.tsx +17 -18
  147. package/src/plugins/builtin/cds/client.test.ts +83 -0
  148. package/src/plugins/builtin/cds/client.ts +67 -0
  149. package/src/plugins/builtin/cds/index.tsx +41 -0
  150. package/src/plugins/builtin/cds/model.test.ts +232 -0
  151. package/src/plugins/builtin/cds/model.ts +401 -0
  152. package/src/plugins/builtin/cds/pane.test.tsx +132 -0
  153. package/src/plugins/builtin/cds/pane.tsx +358 -0
  154. package/src/plugins/builtin/chart-composer/chart-spec.ts +31 -0
  155. package/src/plugins/builtin/chart-composer/index.tsx +88 -2
  156. package/src/plugins/builtin/chart-composer/pane-share.test.ts +71 -0
  157. package/src/plugins/builtin/chart-composer/pane.tsx +103 -15
  158. package/src/plugins/builtin/chat/content/index.tsx +4 -1
  159. package/src/plugins/builtin/chat/controller/channels.ts +7 -1
  160. package/src/plugins/builtin/chat/controller/index.ts +14 -9
  161. package/src/plugins/builtin/chat/controller/notifications.ts +13 -3
  162. package/src/plugins/builtin/chat/controller/persistence.ts +0 -3
  163. package/src/plugins/builtin/chat/controller/realtime.ts +3 -3
  164. package/src/plugins/builtin/chat/controller/send.ts +1 -3
  165. package/src/plugins/builtin/chat/controller/session-runtime.ts +18 -11
  166. package/src/plugins/builtin/chat/controller/state.ts +1 -3
  167. package/src/plugins/builtin/chat/controller/storage.ts +0 -2
  168. package/src/plugins/builtin/chat/controller/utils.ts +4 -6
  169. package/src/plugins/builtin/chat/controller/view.ts +2 -2
  170. package/src/plugins/builtin/chat/controller.test.ts +142 -14
  171. package/src/plugins/builtin/chat/message/desktop.test.tsx +88 -0
  172. package/src/plugins/builtin/chat/message/desktop.tsx +3 -1
  173. package/src/plugins/builtin/chat/message/profile-popover.test.tsx +1 -29
  174. package/src/plugins/builtin/chat/profile-popover.ts +53 -22
  175. package/src/plugins/builtin/chat/sidebar.test.tsx +81 -0
  176. package/src/plugins/builtin/chat/sidebar.tsx +63 -0
  177. package/src/plugins/builtin/chat/test-harness.tsx +29 -1
  178. package/src/plugins/builtin/cloud/auth-actions.tsx +14 -7
  179. package/src/plugins/builtin/cloud/auth-commands.ts +21 -51
  180. package/src/plugins/builtin/cloud/auth-dialog.tsx +296 -0
  181. package/src/{components/onboarding/account-step/model.test.ts → plugins/builtin/cloud/auth-model.test.ts} +1 -1
  182. package/src/{components/onboarding/account-step/model.ts → plugins/builtin/cloud/auth-model.ts} +37 -3
  183. package/src/plugins/builtin/cloud/browser.tsx +9 -0
  184. package/src/plugins/builtin/cloud/buildout-module.tsx +23 -0
  185. package/src/plugins/builtin/cloud/device-signin.ts +2 -8
  186. package/src/plugins/builtin/cloud/index.tsx +2 -0
  187. package/src/plugins/builtin/cloud/plugin.tsx +14 -24
  188. package/src/plugins/builtin/cloud/upgrade-command.ts +13 -6
  189. package/src/plugins/builtin/cloud-tweets/pane-share.test.ts +56 -0
  190. package/src/plugins/builtin/cloud-tweets/pane.tsx +20 -5
  191. package/src/plugins/builtin/cloud-tweets/registration.ts +50 -6
  192. package/src/plugins/builtin/composite-plugins.ts +3 -1
  193. package/src/plugins/builtin/congress-trades/detail.tsx +43 -1
  194. package/src/plugins/builtin/congress-trades/model.test.ts +85 -0
  195. package/src/plugins/builtin/congress-trades/model.ts +69 -1
  196. package/src/plugins/builtin/congress-trades/pane.tsx +51 -2
  197. package/src/plugins/builtin/connections/index.ts +3 -0
  198. package/src/plugins/builtin/dividend-yield/client.ts +18 -0
  199. package/src/plugins/builtin/dividend-yield/pane.tsx +7 -6
  200. package/src/plugins/builtin/earnings/index.tsx +5 -3
  201. package/src/plugins/builtin/earnings/model.test.ts +19 -1
  202. package/src/plugins/builtin/earnings/model.ts +17 -15
  203. package/src/plugins/builtin/econ/calendar-model.ts +0 -5
  204. package/src/plugins/builtin/econ/index.test.tsx +6 -1
  205. package/src/plugins/builtin/fear-greed/charts.tsx +12 -8
  206. package/src/plugins/builtin/help/index.tsx +2 -2
  207. package/src/plugins/builtin/holders/thirteenf-match.ts +1 -2
  208. package/src/plugins/builtin/insider/index.tsx +27 -11
  209. package/src/plugins/builtin/kelly-sizer/index.tsx +4 -1
  210. package/src/plugins/builtin/layout-manager/index.tsx +17 -4
  211. package/src/plugins/builtin/news/index.tsx +11 -6
  212. package/src/plugins/builtin/news/wire/breaking/notifications.test.ts +2 -0
  213. package/src/plugins/builtin/news/wire/breaking/pane.test.tsx +2 -0
  214. package/src/plugins/builtin/news/wire/breaking/pane.tsx +4 -1
  215. package/src/plugins/builtin/news/wire/feed-config.test.ts +15 -1
  216. package/src/plugins/builtin/news/wire/feed-config.ts +25 -5
  217. package/src/plugins/builtin/news/wire/index.tsx +3 -3
  218. package/src/plugins/builtin/news/wire/industry-pane.tsx +7 -2
  219. package/src/plugins/builtin/news/wire/news/footer.ts +34 -1
  220. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +4 -1
  221. package/src/plugins/builtin/news/wire/news/table.tsx +8 -2
  222. package/src/plugins/builtin/news/wire/rss/source.test.ts +12 -0
  223. package/src/plugins/builtin/news/wire/rss/source.ts +1 -1
  224. package/src/plugins/builtin/notes/files.ts +80 -0
  225. package/src/plugins/builtin/notes/index.tsx +3 -0
  226. package/src/plugins/builtin/notes/sync.test.ts +49 -0
  227. package/src/plugins/builtin/notes/sync.ts +98 -0
  228. package/src/plugins/builtin/options/index.tsx +1 -0
  229. package/src/plugins/builtin/options/view.test.tsx +25 -3
  230. package/src/plugins/builtin/options/view.tsx +1 -0
  231. package/src/plugins/builtin/portfolio-list/account-metrics.test.ts +22 -0
  232. package/src/plugins/builtin/portfolio-list/account-metrics.ts +12 -7
  233. package/src/plugins/builtin/portfolio-list/cli/render.ts +5 -3
  234. package/src/plugins/builtin/portfolio-list/column-values.ts +15 -4
  235. package/src/plugins/builtin/portfolio-list/header.tsx +13 -3
  236. package/src/plugins/builtin/portfolio-list/index.tsx +8 -0
  237. package/src/plugins/builtin/portfolio-list/metrics.test.ts +31 -1
  238. package/src/plugins/builtin/portfolio-list/pane/index.test.tsx +1 -0
  239. package/src/plugins/builtin/portfolio-list/pane/index.tsx +10 -7
  240. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +6 -1
  241. package/src/plugins/builtin/portfolio-list/position-metrics.ts +25 -6
  242. package/src/plugins/builtin/portfolio-list/summary/index.tsx +35 -21
  243. package/src/plugins/builtin/portfolio-list/summary/totals.ts +20 -6
  244. package/src/plugins/builtin/research/corporate-actions-pane.tsx +5 -12
  245. package/src/plugins/builtin/research/equity-diagnostic-pane.test.tsx +329 -0
  246. package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +582 -0
  247. package/src/plugins/builtin/research/index.test.ts +14 -0
  248. package/src/plugins/builtin/research/index.tsx +29 -0
  249. package/src/plugins/builtin/sec/index.tsx +24 -9
  250. package/src/plugins/builtin/sectors/index.tsx +14 -0
  251. package/src/plugins/builtin/shared/cloud-upgrade.ts +18 -11
  252. package/src/plugins/builtin/shared/public-pane.test.ts +12 -0
  253. package/src/plugins/builtin/shared/public-pane.ts +9 -0
  254. package/src/plugins/builtin/shared/public-share.ts +23 -0
  255. package/src/plugins/builtin/shared/ticker-source.ts +32 -0
  256. package/src/plugins/builtin/shared/ticker-surface.test.ts +27 -0
  257. package/src/plugins/builtin/shared/ticker-surface.ts +17 -0
  258. package/src/plugins/builtin/substack/api/cache.ts +7 -3
  259. package/src/plugins/builtin/substack/api/loaders.ts +36 -12
  260. package/src/plugins/builtin/substack/api/types.ts +2 -0
  261. package/src/plugins/builtin/substack/pane-state.ts +1 -1
  262. package/src/plugins/builtin/substack/pane.tsx +55 -12
  263. package/src/plugins/builtin/thirteenf/api.test.ts +1 -1
  264. package/src/plugins/builtin/thirteenf/api.ts +40 -18
  265. package/src/plugins/builtin/thirteenf/data.ts +15 -7
  266. package/src/plugins/builtin/thirteenf/pane.tsx +45 -10
  267. package/src/plugins/builtin/ticker-detail/index.tsx +10 -0
  268. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +12 -4
  269. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +8 -2
  270. package/src/plugins/builtin/ticker-detail/settings.ts +12 -0
  271. package/src/plugins/builtin/treasury-auctions/index.tsx +2 -0
  272. package/src/plugins/builtin/world-indices/index.tsx +2 -0
  273. package/src/plugins/catalog-browser.test.ts +78 -0
  274. package/src/plugins/catalog-browser.ts +136 -0
  275. package/src/plugins/gridlock-notification.ts +2 -1
  276. package/src/plugins/ibkr/flex/index.test.ts +19 -0
  277. package/src/plugins/ibkr/flex/index.ts +1 -1
  278. package/src/plugins/pane-manager/gridlock.ts +87 -3
  279. package/src/plugins/pane-manager.test.ts +30 -0
  280. package/src/plugins/pane-manager.ts +6 -1
  281. package/src/plugins/prediction-markets/chart.tsx +2 -2
  282. package/src/plugins/prediction-markets/controller/catalog.ts +77 -2
  283. package/src/plugins/prediction-markets/controller/data.ts +7 -1
  284. package/src/plugins/prediction-markets/controller/index.ts +9 -2
  285. package/src/plugins/prediction-markets/controller/keyboard.ts +10 -14
  286. package/src/plugins/prediction-markets/detail/overview.tsx +21 -0
  287. package/src/plugins/prediction-markets/detail/pane.tsx +43 -34
  288. package/src/plugins/prediction-markets/detail/trades.tsx +34 -9
  289. package/src/plugins/prediction-markets/detail.test.tsx +29 -2
  290. package/src/plugins/prediction-markets/index.tsx +8 -4
  291. package/src/plugins/prediction-markets/navigation.ts +0 -1
  292. package/src/plugins/prediction-markets/pane.test.tsx +77 -0
  293. package/src/plugins/prediction-markets/pane.tsx +12 -0
  294. package/src/plugins/prediction-markets/plugin.test.tsx +36 -0
  295. package/src/plugins/prediction-markets/services/kalshi/adapter.ts +49 -9
  296. package/src/plugins/prediction-markets/services/polymarket/adapter.ts +31 -0
  297. package/src/plugins/prediction-markets/test-helpers.tsx +9 -0
  298. package/src/plugins/registry/index.ts +2 -0
  299. package/src/plugins/ticker-navigation.test.ts +24 -0
  300. package/src/plugins/ticker-navigation.ts +40 -0
  301. package/src/remote/schema.ts +1 -1
  302. package/src/renderers/browser/app-services.ts +61 -0
  303. package/src/renderers/browser/cloud-transport.test.ts +68 -0
  304. package/src/renderers/browser/cloud-transport.ts +48 -0
  305. package/src/renderers/browser/config-host.ts +58 -0
  306. package/src/renderers/browser/deeplink-bridge.test.ts +41 -0
  307. package/src/renderers/browser/deeplink-bridge.ts +22 -0
  308. package/src/renderers/browser/error-boundary.tsx +23 -0
  309. package/src/renderers/browser/main.tsx +64 -0
  310. package/src/renderers/browser/persistence.ts +74 -0
  311. package/src/renderers/browser/storage.test.ts +66 -0
  312. package/src/renderers/browser/storage.ts +50 -0
  313. package/src/renderers/browser/ticker-repository.ts +59 -0
  314. package/src/renderers/browser/ui-host.tsx +40 -0
  315. package/src/renderers/cloudflare/worker.test.ts +136 -0
  316. package/src/renderers/cloudflare/worker.ts +116 -0
  317. package/src/renderers/electrobun/bun/application-menu/index.ts +3 -2
  318. package/src/renderers/electrobun/bun/desktop/initialization.ts +1 -0
  319. package/src/renderers/electrobun/bun/desktop/plugin-state.ts +7 -1
  320. package/src/renderers/electrobun/view/app-services.ts +14 -3
  321. package/src/renderers/electrobun/view/build-assets.ts +12 -10
  322. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -1
  323. package/src/renderers/electrobun/view/data-table/index.tsx +10 -1
  324. package/src/renderers/electrobun/view/desktop/controls.tsx +2 -0
  325. package/src/renderers/electrobun/view/dom-ui-host.tsx +143 -0
  326. package/src/renderers/electrobun/view/host/box.test.tsx +33 -0
  327. package/src/renderers/electrobun/view/host/box.tsx +11 -6
  328. package/src/renderers/electrobun/view/host/chart-surface.tsx +7 -1
  329. package/src/renderers/electrobun/view/host/input.tsx +11 -7
  330. package/src/renderers/electrobun/view/host/native.ts +0 -5
  331. package/src/renderers/electrobun/view/main.tsx +2 -0
  332. package/src/renderers/electrobun/view/remote/persistence.ts +5 -0
  333. package/src/renderers/electrobun/view/styles.css +107 -7
  334. package/src/renderers/electrobun/view/toast-host.test.tsx +78 -0
  335. package/src/renderers/electrobun/view/toast-host.tsx +69 -43
  336. package/src/renderers/electrobun/view/ui-host.tsx +6 -137
  337. package/src/renderers/opentui/start.tsx +2 -0
  338. package/src/renderers/share/layout-view.test.tsx +58 -0
  339. package/src/renderers/share/layout-view.tsx +133 -0
  340. package/src/renderers/share/main.tsx +102 -0
  341. package/src/renderers/share/styles.css +54 -0
  342. package/src/renderers/share/view.test.tsx +100 -0
  343. package/src/renderers/share/view.tsx +150 -0
  344. package/src/shares/api.test.ts +105 -0
  345. package/src/shares/api.ts +127 -0
  346. package/src/shares/chart-snapshot.test.ts +34 -0
  347. package/src/shares/chart-snapshot.ts +28 -0
  348. package/src/shares/location.test.ts +19 -0
  349. package/src/shares/location.ts +11 -0
  350. package/src/shares/pane.test.ts +110 -0
  351. package/src/shares/pane.ts +54 -0
  352. package/src/shares/payload.ts +177 -0
  353. package/src/sources/gloomberb-cloud/index.test.ts +61 -1
  354. package/src/sources/gloomberb-cloud/index.ts +86 -14
  355. package/src/sources/gloomberb-cloud/normalizers.test.ts +34 -0
  356. package/src/sources/gloomberb-cloud/normalizers.ts +5 -5
  357. package/src/sources/provider-router/history.test.ts +2 -2
  358. package/src/sources/provider-router/news.test.ts +34 -0
  359. package/src/sources/provider-router/news.ts +15 -1
  360. package/src/sources/sec-edgar.test.ts +59 -0
  361. package/src/sources/sec-edgar.ts +63 -23
  362. package/src/sources/yahoo-finance/mappers.ts +3 -0
  363. package/src/sources/yahoo-finance/snapshots.ts +10 -2
  364. package/src/sources/yahoo-finance/symbols.test.ts +10 -0
  365. package/src/sources/yahoo-finance/symbols.ts +13 -8
  366. package/src/sources/yahoo-finance.test.ts +1 -1
  367. package/src/sources/yahoo-finance.ts +6 -2
  368. package/src/state/app/bootstrap.ts +6 -7
  369. package/src/state/app/context/index.test.ts +41 -1
  370. package/src/state/app/context/index.test.tsx +34 -16
  371. package/src/state/selectors-ui.ts +0 -8
  372. package/src/sync/baseline.ts +32 -0
  373. package/src/sync/controller.test.ts +124 -1
  374. package/src/sync/controller.ts +54 -12
  375. package/src/sync/core-contributors.test.ts +44 -0
  376. package/src/sync/core-contributors.ts +59 -10
  377. package/src/sync/react.ts +22 -2
  378. package/src/sync/types.ts +12 -0
  379. package/src/theme/font-scale.ts +0 -11
  380. package/src/tickers/search/index.test.ts +27 -0
  381. package/src/tickers/search/upsert.ts +10 -0
  382. package/src/types/capability-route-source.ts +2 -1
  383. package/src/types/config.ts +35 -5
  384. package/src/types/desktop-menu.ts +1 -0
  385. package/src/types/plugin.ts +36 -0
  386. package/src/ui/context-menu.tsx +1 -1
  387. package/src/ui/host.tsx +4 -0
  388. package/src/ui/toast.tsx +2 -0
  389. package/src/utils/back-navigation.ts +0 -4
  390. package/src/utils/price-history.ts +11 -10
  391. package/src/utils/sec.test.ts +6 -0
  392. package/src/utils/sec.ts +1 -1
@@ -1,3 +1,4 @@
1
+ import { formatMarketPriceWithCurrency } from "../../../market-data/market/format";
1
2
  import type { ResolvedSeries, TimeSeriesPoint } from "../../../time-series/types";
2
3
  import type { CompositeAxisDomain } from "./types";
3
4
 
@@ -25,23 +26,37 @@ function compactNumber(value: number): string {
25
26
  return value.toPrecision(3);
26
27
  }
27
28
 
28
- function currencyPrefix(unit: string): string {
29
+ function unitCurrencyCode(unit: string): string | null {
29
30
  const currency = unit.trim().toUpperCase().split(/[\s/]/)[0] ?? "";
30
- return CURRENCY_SYMBOLS[currency] ?? "";
31
+ return CURRENCY_SYMBOLS[currency] ? currency : null;
32
+ }
33
+
34
+ function currencyPrefix(unit: string): string {
35
+ const currency = unitCurrencyCode(unit);
36
+ return currency ? CURRENCY_SYMBOLS[currency] ?? "" : "";
37
+ }
38
+
39
+ function formatFullCurrencyValue(value: number, unit: string): string | null {
40
+ const currency = unitCurrencyCode(unit);
41
+ return currency ? formatMarketPriceWithCurrency(value, currency) : null;
31
42
  }
32
43
 
33
44
  export function formatCompositeSeriesValue(value: number, series: ResolvedSeries): string {
34
- const unit = series.unit.trim();
35
- const group = series.unitGroup.toLowerCase();
45
+ return formatChartLegendValue(value, series.unit, series.unitGroup);
46
+ }
47
+
48
+ export function formatChartLegendValue(value: number, unit: string, unitGroup = ""): string {
49
+ const trimmed = unit.trim();
50
+ const group = unitGroup.toLowerCase();
36
51
  const compact = compactNumber(value);
37
- if (group.includes("percent") || unit === "%" || unit.toLowerCase().includes("percent")) {
52
+ if (group.includes("percent") || trimmed === "%" || trimmed.toLowerCase().includes("percent")) {
38
53
  return `${compact}%`;
39
54
  }
40
- if (group.includes("ratio") || unit.toLowerCase() === "x") return `${compact}x`;
41
- if (group.startsWith("derived-unit:")) return `${compact} ${unit}`;
42
- const currency = currencyPrefix(unit);
43
- if (currency) return `${currency}${compact}`;
44
- return unit && unit.length <= 6 ? `${compact}${unit.startsWith("/") ? "" : " "}${unit}` : compact;
55
+ if (group.includes("ratio") || trimmed.toLowerCase() === "x") return `${compact}x`;
56
+ if (group.startsWith("derived-unit:")) return `${compact} ${trimmed}`;
57
+ const fullPrice = formatFullCurrencyValue(value, trimmed);
58
+ if (fullPrice) return fullPrice;
59
+ return trimmed && trimmed.length <= 6 ? `${compact}${trimmed.startsWith("/") ? "" : " "}${trimmed}` : compact;
45
60
  }
46
61
 
47
62
  export function formatCompositeAxisValue(value: number, domain: CompositeAxisDomain): string {
@@ -55,6 +70,14 @@ export function formatCompositeAxisValue(value: number, domain: CompositeAxisDom
55
70
  return `${currencyPrefix(domain.unit)}${compact}`;
56
71
  }
57
72
 
73
+ export function formatCompositeCursorValue(value: number, domain: CompositeAxisDomain): string {
74
+ const group = domain.unitGroup.toLowerCase();
75
+ if (group.startsWith("derived-unit:")) return compactNumber(value);
76
+ const fullPrice = formatFullCurrencyValue(value, domain.unit);
77
+ if (fullPrice) return fullPrice;
78
+ return formatCompositeAxisValue(value, domain);
79
+ }
80
+
58
81
  export function compositeAxisTicks(domain: CompositeAxisDomain, count = 3): Array<{ ratio: number; value: number; label: string }> {
59
82
  const tickCount = Math.max(2, Math.floor(count));
60
83
  return Array.from({ length: tickCount }, (_, index) => {
@@ -17,7 +17,6 @@ import type {
17
17
  import {
18
18
  buildCompositeTimeScale,
19
19
  projectCompositeTimestamp,
20
- unprojectCompositeTimestamp,
21
20
  } from "./time-scale";
22
21
  import type { CompositeTimeScale } from "./types";
23
22
 
@@ -523,26 +522,6 @@ export function resolveCompositeCursorDate(scene: CompositeChartScene, localX: n
523
522
  return scene.dates[nearestIndex] ?? null;
524
523
  }
525
524
 
526
- export function resolveCompositeTimeAxisDate(
527
- scene: CompositeChartScene,
528
- ratio: number,
529
- ): Date {
530
- const safeRatio = Math.max(0, Math.min(1, ratio));
531
- if (scene.timeScale.kind === "market" && scene.dates.length > 0) {
532
- let nearestIndex = 0;
533
- let nearestDistance = Number.POSITIVE_INFINITY;
534
- scene.dateRatios.forEach((candidate, index) => {
535
- const distance = Math.abs(candidate - safeRatio);
536
- if (distance < nearestDistance) {
537
- nearestIndex = index;
538
- nearestDistance = distance;
539
- }
540
- });
541
- return scene.dates[nearestIndex] ?? new Date(scene.startTime);
542
- }
543
- return new Date(unprojectCompositeTimestamp(scene.timeScale, safeRatio));
544
- }
545
-
546
525
  export function resolveAdjacentCompositeCursorDate(
547
526
  scene: CompositeChartScene,
548
527
  current: Date | null,
@@ -27,6 +27,55 @@ export interface ChartDrawing {
27
27
  color: string;
28
28
  }
29
29
 
30
+ export const CHART_DRAWINGS_SETTING_KEY = "chartDrawings";
31
+ const MAX_SHARED_CHART_DRAWINGS = 100;
32
+ const MAX_SHARED_DRAWING_POINTS = 5_000;
33
+
34
+ export function parseChartDrawings(value: unknown): ChartDrawing[] {
35
+ if (!Array.isArray(value) || value.length > MAX_SHARED_CHART_DRAWINGS) return [];
36
+ let pointCount = 0;
37
+ const drawings: ChartDrawing[] = [];
38
+ for (const entry of value) {
39
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) return [];
40
+ const drawing = entry as Record<string, unknown>;
41
+ if (!Object.keys(drawing).every((key) => ["id", "panelId", "points", "color"].includes(key))) return [];
42
+ if (
43
+ typeof drawing.id !== "string"
44
+ || drawing.id.length === 0
45
+ || drawing.id.length > 160
46
+ || typeof drawing.panelId !== "string"
47
+ || drawing.panelId.length === 0
48
+ || drawing.panelId.length > 120
49
+ || typeof drawing.color !== "string"
50
+ || !/^#[0-9a-f]{6}$/i.test(drawing.color)
51
+ || !Array.isArray(drawing.points)
52
+ || drawing.points.length < 2
53
+ ) return [];
54
+ pointCount += drawing.points.length;
55
+ if (pointCount > MAX_SHARED_DRAWING_POINTS) return [];
56
+ const points: ChartDrawingPoint[] = [];
57
+ for (const point of drawing.points) {
58
+ if (!point || typeof point !== "object" || Array.isArray(point)) return [];
59
+ const raw = point as Record<string, unknown>;
60
+ if (
61
+ !Object.keys(raw).every((key) => key === "time" || key === "value")
62
+ || typeof raw.time !== "number"
63
+ || !Number.isFinite(raw.time)
64
+ || typeof raw.value !== "number"
65
+ || !Number.isFinite(raw.value)
66
+ ) return [];
67
+ points.push({ time: raw.time, value: raw.value });
68
+ }
69
+ drawings.push({
70
+ id: drawing.id,
71
+ panelId: drawing.panelId,
72
+ points,
73
+ color: drawing.color,
74
+ });
75
+ }
76
+ return drawings;
77
+ }
78
+
30
79
  export interface ChartDrawingHit {
31
80
  drawing: ChartDrawing;
32
81
  /** Grabbed endpoint, or null when the whole shape was grabbed. */
@@ -1,4 +1,9 @@
1
- import { formatCompactMarketPriceWithCurrency, formatMarketPrice, resolveAssetDisplayKind } from "../../../market-data/market/format";
1
+ import {
2
+ formatCompactMarketPriceWithCurrency,
3
+ formatMarketPrice,
4
+ formatMarketPriceWithCurrency,
5
+ resolveAssetDisplayKind,
6
+ } from "../../../market-data/market/format";
2
7
  import type { ChartAxisMode } from "./types";
3
8
 
4
9
  export function formatPrice(
@@ -27,7 +32,7 @@ export function formatPriceWithCurrency(
27
32
  minimumFractionDigits = 0,
28
33
  fixedFractionDigits?: number,
29
34
  ): string {
30
- return formatCompactMarketPriceWithCurrency(value, currency, {
35
+ return formatMarketPriceWithCurrency(value, currency, {
31
36
  assetCategory,
32
37
  fixedFractionDigits,
33
38
  minimumFractionDigits,
@@ -93,7 +98,11 @@ export function formatAxisValue(
93
98
  if (axisMode === "percent" && basePrice !== 0) {
94
99
  return formatPercentAxisValue(((value - basePrice) / basePrice) * 100);
95
100
  }
96
- return formatPriceWithCurrency(value, currency, assetCategory, priceRange, 0, 0, fixedFractionDigits);
101
+ return formatCompactMarketPriceWithCurrency(value, currency, {
102
+ assetCategory,
103
+ fixedFractionDigits,
104
+ priceRange,
105
+ });
97
106
  }
98
107
 
99
108
  export function formatCursorAxisValue(
@@ -136,6 +145,7 @@ function getCursorAxisMinimumFractionDigits(value: number, assetCategory?: strin
136
145
  case "cash":
137
146
  return 4;
138
147
  case "crypto":
148
+ if (Math.abs(value) >= 100) return 2;
139
149
  return Math.abs(value) >= 1 ? 4 : 6;
140
150
  case "equity":
141
151
  case "contract":
@@ -448,6 +448,13 @@ describe("renderChart", () => {
448
448
  expect(formatAxisValue(1.17364, "price", 0, "USD", "CURRENCY")).toBe("$1.17364");
449
449
  });
450
450
 
451
+ test("keeps legend and cursor prices precise while axis ticks stay compact", () => {
452
+ expect(formatAxisValue(79_432.18, "price", 0, "USD", "CRYPTO")).toBe("$79.4K");
453
+ expect(formatCursorAxisValue(79_432.18, "price", 0, "USD", "CRYPTO")).toBe("$79,432.18");
454
+ expect(formatAxisValue(79_432.18, "price", 0, "USD", "STK")).toBe("$79.4K");
455
+ expect(formatCursorAxisValue(79_432.18, "price", 0, "USD", "STK")).toBe("$79,432.18");
456
+ });
457
+
451
458
  test("adapts FX axis precision to the visible price range", () => {
452
459
  expect(formatAxisValue(1.167815, "price", 0, "USD", "CURRENCY", 0.12)).toBe("$1.17");
453
460
  expect(formatAxisValue(1.167815, "price", 0, "USD", "CURRENCY", 0.0024)).toBe("$1.1678");
@@ -3,7 +3,6 @@ import type { ProjectedChartPoint } from "./data";
3
3
  import { buildTimeAxis } from "./time-axis";
4
4
  import {
5
5
  formatAxisValue,
6
- formatPriceWithCurrency,
7
6
  getAxisFractionDigitFloor,
8
7
  resolveAxisFractionDigits,
9
8
  } from "./axis-format";
@@ -152,13 +151,13 @@ export function renderChart(
152
151
  const axisFractionDigits = axisMode === "price"
153
152
  ? resolveAxisFractionDigits(
154
153
  gridLines.map((line) => line.price),
155
- (price, fixedFractionDigits) => formatPriceWithCurrency(
154
+ (price, fixedFractionDigits) => formatAxisValue(
156
155
  price,
156
+ axisMode,
157
+ points[0]!.close,
157
158
  currency,
158
159
  assetCategory,
159
160
  priceRange,
160
- 0,
161
- 0,
162
161
  fixedFractionDigits,
163
162
  ),
164
163
  getAxisFractionDigitFloor(assetCategory, priceRange),
@@ -87,6 +87,10 @@ export function runDirectCommandAction(options: {
87
87
  closeAll({ revertThemePreview: false });
88
88
  pluginRegistry.showPane("help");
89
89
  return;
90
+ case "layout-marketplace":
91
+ closeAll({ revertThemePreview: false });
92
+ pluginRegistry.showPane("layout-marketplace");
93
+ return;
90
94
  case "pane-settings":
91
95
  if (state.focusedPaneId) openPaneSettingsRoute(state.focusedPaneId);
92
96
  return;
@@ -138,8 +138,15 @@ export const commands: Command[] = [
138
138
  category: "Config",
139
139
  },
140
140
  {
141
- id: "layout",
141
+ id: "layout-marketplace",
142
142
  prefix: "LAY",
143
+ label: "Layouts",
144
+ description: "Open the layout browser to switch, publish, or add layouts",
145
+ category: "Config",
146
+ },
147
+ {
148
+ id: "layout",
149
+ prefix: "LMA",
143
150
  label: "Layout Actions",
144
151
  description: "Organize panes and saved layouts",
145
152
  hasArg: true,
@@ -79,7 +79,7 @@ export function buildCurrentLayoutItems({
79
79
  },
80
80
  {
81
81
  id: "layout-gridlock",
82
- label: "Gridlock All Windows",
82
+ label: "Tidy Windows",
83
83
  detail: currentLayout.floating.length > 0
84
84
  ? "Infer a tiled layout from the current window positions"
85
85
  : "Retile all panes from their current arrangement",
@@ -112,6 +112,10 @@ export function useCommandBarPaneTemplateActions({
112
112
  const trimmedArg = rawArg?.trim() || "";
113
113
  const argKind = template.shortcut?.argKind ?? template.shortcut?.argPlaceholder;
114
114
  if (argKind === "ticker") {
115
+ if (!trimmedArg && template.shortcut?.argOptional) {
116
+ await openPaneTemplateDirect(template);
117
+ return;
118
+ }
115
119
  const resolvedTicker = await resolveTickerInput(
116
120
  trimmedArg || undefined,
117
121
  activeTickerSymbol,
@@ -34,6 +34,9 @@ export function buildRootShortcutFeedback({
34
34
  if (rootShortcutIntent.source === "pane-template") {
35
35
  const argKind = getPaneTemplateArgKind(rootShortcutIntent.template);
36
36
  if (argKind === "ticker") {
37
+ if (rootShortcutIntent.template.shortcut?.argOptional && !rootShortcutIntent.argText) {
38
+ return `Shortcut: ${rootShortcutIntent.label}`;
39
+ }
37
40
  const symbol = normalizeTickerInput(activeTickerSymbol, rootShortcutIntent.argText);
38
41
  if (symbol) {
39
42
  return rootShortcutIntent.kind === "inferred-complete"
@@ -46,6 +46,13 @@ const paneTemplates: PaneTemplateDef[] = [
46
46
  description: "Search provider instruments",
47
47
  shortcut: { prefix: "SRCH", argPlaceholder: "query", argKind: "text" },
48
48
  },
49
+ {
50
+ id: "cds-pane",
51
+ paneId: "cds",
52
+ label: "Single-Name CDS",
53
+ description: "CDS shortcut",
54
+ shortcut: { prefix: "CDS", argPlaceholder: "ticker", argKind: "ticker", argOptional: true },
55
+ },
49
56
  ];
50
57
 
51
58
  const pluginCommands: CommandDef[] = [{
@@ -135,6 +142,18 @@ describe("ticker data root shortcuts", () => {
135
142
  }
136
143
  });
137
144
 
145
+ test("optional ticker shortcuts do not infer the active ticker", () => {
146
+ const bare = parse("CDS", "MSFT");
147
+ expect(bare.kind).toBe("partial");
148
+ if (bare.kind === "none") throw new Error("Expected shortcut intent");
149
+ expect(bare.completionQuery).toBeNull();
150
+
151
+ const scoped = parse("CDS ORCL", "MSFT");
152
+ expect(scoped.kind).toBe("complete");
153
+ if (scoped.kind === "none") throw new Error("Expected shortcut intent");
154
+ expect(scoped.argText).toBe("ORCL");
155
+ });
156
+
138
157
  test("EM accepts optional text tickers without active ticker inference", () => {
139
158
  const bare = parse("EM", "AAPL");
140
159
  expect(bare.kind).toBe("partial");
@@ -145,7 +145,9 @@ export function parseRootShortcutIntent({
145
145
  if (!match) return { kind: "none" };
146
146
 
147
147
  const argText = trimmed.slice(match.prefix.length).trim();
148
- const inferredArg = inferShortcutArg(match.argKind, activeTicker);
148
+ const inferredArg = match.source === "pane-template" && match.template?.shortcut?.argOptional
149
+ ? null
150
+ : inferShortcutArg(match.argKind, activeTicker);
149
151
  const completionQuery = inferredArg ? `${match.prefix} ${inferredArg}` : null;
150
152
 
151
153
  let kind: Exclude<ShortcutIntentKind, "none">;
@@ -179,17 +179,17 @@ describe("CommandBar", () => {
179
179
  expect(testSetup.captureCharFrame()).not.toContain("GitHub Light");
180
180
  });
181
181
 
182
- test("runs plugin command shortcuts from the root query", async () => {
182
+ test("finds the tidy windows command by its snap alias", async () => {
183
183
  const calls: string[] = [];
184
184
 
185
185
  testSetup = await testRender(<CommandBarHarness
186
- query="GL"
186
+ query="snap"
187
187
  configurePluginRegistry={(pluginRegistry) => {
188
188
  (pluginRegistry.commands as Map<string, any>).set("gridlock-all", {
189
189
  id: "gridlock-all",
190
- label: "Gridlock All Windows",
191
- description: "Arrange all visible panes into a tiled grid",
192
- keywords: ["grid", "gridlock", "tile", "arrange", "windows", "layout"],
190
+ label: "Tidy Windows",
191
+ description: "Arrange every window into one tiled layout",
192
+ keywords: ["tidy", "snap", "grid", "gridlock", "tile", "arrange", "organize", "organise", "cleanup", "dock", "floating", "windows", "layout"],
193
193
  shortcut: "GL",
194
194
  category: "config",
195
195
  execute: async () => {
@@ -215,7 +215,7 @@ describe("CommandBar", () => {
215
215
  await testSetup.renderOnce();
216
216
 
217
217
  const frame = testSetup.captureCharFrame();
218
- expect(frame).toContain("Gridlock All Windows");
218
+ expect(frame).toContain("Tidy Windows");
219
219
  expect(frame).toContain("GL");
220
220
 
221
221
  await act(async () => {
@@ -50,7 +50,7 @@ export function CommandBar({
50
50
  activePortfolio,
51
51
  activeTickerData,
52
52
  activeTickerSymbol,
53
- availableCommands,
53
+ availableCommands: allAvailableCommands,
54
54
  cellHeightPx,
55
55
  cellWidthPx,
56
56
  dispatch,
@@ -67,6 +67,9 @@ export function CommandBar({
67
67
  titleBarOverlay,
68
68
  visibleListStateRef,
69
69
  } = useCommandBarEnvironment();
70
+ const availableCommands = useMemo(() => onCheckForUpdates
71
+ ? allAvailableCommands
72
+ : allAvailableCommands.filter((command) => command.id !== "check-for-updates"), [allAvailableCommands, onCheckForUpdates]);
70
73
  const {
71
74
  applyThemePreview,
72
75
  clearThemePreview,
@@ -142,6 +142,23 @@ function registerOptionalTextPane(pluginRegistry: MutablePaneRegistry): void {
142
142
  });
143
143
  }
144
144
 
145
+ function registerOptionalTickerPane(pluginRegistry: MutablePaneRegistry): void {
146
+ mutableRegistryMap(pluginRegistry.panes).set("optional-ticker", {
147
+ id: "optional-ticker",
148
+ name: "Optional Ticker",
149
+ component: () => null,
150
+ defaultPosition: "right",
151
+ defaultMode: "floating",
152
+ });
153
+ mutableRegistryMap(pluginRegistry.paneTemplates).set("optional-ticker-pane", {
154
+ id: "optional-ticker-pane",
155
+ paneId: "optional-ticker",
156
+ label: "Optional Ticker",
157
+ description: "Open market-wide or for one ticker.",
158
+ shortcut: { prefix: "OT", argPlaceholder: "ticker", argKind: "ticker", argOptional: true },
159
+ });
160
+ }
161
+
145
162
  function registerQueryOnlyPane(pluginRegistry: MutablePaneRegistry): void {
146
163
  mutableRegistryMap(pluginRegistry.panes).set("prediction-markets", {
147
164
  id: "prediction-markets",
@@ -204,7 +221,7 @@ describe("CommandBar pane and layout routes", () => {
204
221
 
205
222
  test("renders layout mode with focused pane actions", async () => {
206
223
  testSetup = await testRender(<CommandBarHarness
207
- query="LAY "
224
+ query="LMA "
208
225
  configureConfig={layoutModeConfig}
209
226
  configureState={layoutModeState}
210
227
  />, {
@@ -248,9 +265,29 @@ describe("CommandBar pane and layout routes", () => {
248
265
  expect(actions.some((action) => action.type === "UNDO_LAYOUT")).toBe(true);
249
266
  });
250
267
 
268
+ test("opens Layouts as a normal pane from LAY", async () => {
269
+ const opened: string[] = [];
270
+ testSetup = await testRender(
271
+ <CommandBarHarness
272
+ query="LAY"
273
+ live
274
+ configurePluginRegistry={(pluginRegistry) => {
275
+ pluginRegistry.showPane = (paneId) => opened.push(paneId);
276
+ }}
277
+ />,
278
+ { width: 90, height: 18 },
279
+ );
280
+ await testSetup.renderOnce();
281
+ expect(testSetup.captureCharFrame()).toContain("Layouts");
282
+
283
+ await clickFrameText("Layouts");
284
+
285
+ expect(opened).toEqual(["layout-marketplace"]);
286
+ });
287
+
251
288
  test("renders filtered saved layouts with textual previews", async () => {
252
289
  testSetup = await testRender(<CommandBarHarness
253
- query="LAY Research"
290
+ query="LMA Research"
254
291
  configureConfig={layoutModeConfig}
255
292
  configureState={layoutModeState}
256
293
  />, {
@@ -353,6 +390,33 @@ describe("CommandBar pane and layout routes", () => {
353
390
  expect(testSetup.captureCharFrame()).not.toContain("Create Pane");
354
391
  });
355
392
 
393
+ test("keeps an optional ticker shortcut market-wide when a ticker is active", async () => {
394
+ const created: CreatedPaneCall[] = [];
395
+
396
+ testSetup = await testRender(<CommandBarHarness
397
+ query="OT"
398
+ selectedTicker="AAPL"
399
+ live
400
+ configurePluginRegistry={(pluginRegistry) => {
401
+ registerOptionalTickerPane(pluginRegistry);
402
+ recordPaneCreations(pluginRegistry, created);
403
+ }}
404
+ />, {
405
+ width: 100,
406
+ height: 18,
407
+ });
408
+
409
+ await testSetup.renderOnce();
410
+
411
+ await act(async () => {
412
+ testSetup!.mockInput.pressEnter();
413
+ await Bun.sleep(0);
414
+ await testSetup!.renderOnce();
415
+ });
416
+
417
+ expect(created).toEqual([{ templateId: "optional-ticker-pane", options: undefined }]);
418
+ });
419
+
356
420
  for (const scenario of [
357
421
  {
358
422
  query: "prediction markets",
@@ -14,7 +14,8 @@ describe("command bar view model helpers", () => {
14
14
  expect(resolveCommandBarMode("T NVDA")).toMatchObject({ kind: "search", badge: "T" });
15
15
  expect(resolveCommandBarMode("TH ")).toMatchObject({ kind: "themes", badge: "THEMES" });
16
16
  expect(resolveCommandBarMode("PL notes")).toMatchObject({ kind: "plugins", badge: "PLUGINS" });
17
- expect(resolveCommandBarMode("LAY ")).toMatchObject({ kind: "layout", badge: "LAYOUT" });
17
+ expect(resolveCommandBarMode("LAY")).toMatchObject({ kind: "direct-command", badge: "COMMAND" });
18
+ expect(resolveCommandBarMode("LMA ")).toMatchObject({ kind: "layout", badge: "LAYOUT" });
18
19
  expect(resolveCommandBarMode("NP ")).toMatchObject({ kind: "default", badge: "FILTER" });
19
20
  expect(resolveCommandBarMode("PS")).toMatchObject({ kind: "direct-command", badge: "COMMAND" });
20
21
  expect(resolveCommandBarMode("AW")).toMatchObject({ kind: "direct-command", badge: "COMMAND" });
@@ -3,6 +3,7 @@ import { cloneLayout, createDefaultConfig, findPaneInstance, type LayoutConfig }
3
3
  import { createInitialState } from "../../../state/app/context";
4
4
  import { createTestDataProvider } from "../../../test-support/data-provider";
5
5
  import { applyPaneSettingFieldValue, createPaneTemplateOrThrow } from "./ops";
6
+ import type { TickerRecord } from "../../../types/ticker";
6
7
 
7
8
  function makeDataProvider() {
8
9
  return createTestDataProvider({ id: "test" });
@@ -68,6 +69,59 @@ describe("createPaneTemplateOrThrow", () => {
68
69
  expect(placeCalls).toHaveLength(0);
69
70
  });
70
71
 
72
+ test("uses an explicit shared symbol instead of the recipient's active ticker", async () => {
73
+ const config = createDefaultConfig("/tmp/gloomberb-workflow-ops-test");
74
+ const state = createInitialState(config);
75
+ const msft: TickerRecord = {
76
+ metadata: {
77
+ ticker: "MSFT",
78
+ exchange: "NASDAQ",
79
+ currency: "USD",
80
+ name: "Microsoft",
81
+ portfolios: [],
82
+ watchlists: [],
83
+ positions: [],
84
+ broker_contracts: [],
85
+ custom: {},
86
+ tags: [],
87
+ },
88
+ };
89
+ state.tickers.set("MSFT", msft);
90
+ let createdSymbol: string | null | undefined;
91
+
92
+ await createPaneTemplateOrThrow("ticker-pane", { symbol: "MSFT" }, {
93
+ dataProvider: makeDataProvider() as any,
94
+ tickerRepository: makeTickerRepository() as any,
95
+ dispatch: () => {},
96
+ getState: () => state,
97
+ pluginRegistry: {
98
+ paneTemplates: new Map([["ticker-pane", {
99
+ id: "ticker-pane",
100
+ paneId: "ticker-view",
101
+ label: "Ticker View",
102
+ description: "Ticker View",
103
+ shortcut: { prefix: "TV", argPlaceholder: "ticker", argKind: "ticker" },
104
+ createInstance: (_context: unknown, options: { symbol?: string } | undefined) => {
105
+ createdSymbol = options?.symbol;
106
+ return { binding: { kind: "fixed", symbol: options?.symbol ?? "" } };
107
+ },
108
+ }]]),
109
+ panes: new Map([["ticker-view", {
110
+ id: "ticker-view",
111
+ name: "Ticker View",
112
+ component: () => null,
113
+ defaultPosition: "right",
114
+ }]]),
115
+ getPaneTemplatePluginId: () => undefined,
116
+ events: { emit: () => {} },
117
+ } as any,
118
+ buildPaneInstance: () => ({ instanceId: "ticker-view:1", paneId: "ticker-view" }) as any,
119
+ placePaneInstance: () => {},
120
+ });
121
+
122
+ expect(createdSymbol).toBe("MSFT");
123
+ });
124
+
71
125
  test("passes pane template instance ids through to pane creation", async () => {
72
126
  const config = createDefaultConfig("/tmp/gloomberb-workflow-ops-test");
73
127
  const state = createInitialState(config);
@@ -152,7 +152,7 @@ async function resolvePaneTemplateOptions(
152
152
  let resolvedOptions = options;
153
153
  if (template.shortcut?.argPlaceholder === "ticker") {
154
154
  const resolvedTicker = await resolveTickerInputOrThrow(
155
- resolvedOptions?.arg,
155
+ resolvedOptions?.symbol ?? resolvedOptions?.arg,
156
156
  baseContext.activeTicker,
157
157
  baseContext.activeCollectionId,
158
158
  deps,
@@ -0,0 +1,19 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { resolveCompanyLogoSrc } from "./company-logo";
3
+
4
+ describe("resolveCompanyLogoSrc", () => {
5
+ test("builds a ticker logo URL", () => {
6
+ expect(resolveCompanyLogoSrc({ symbol: "aapl", assetCategory: "STK" }))
7
+ .toMatch(/\/cloud\/logos\/ticker\/AAPL$/);
8
+ });
9
+
10
+ test("maps crypto quote pairs onto the crypto path", () => {
11
+ expect(resolveCompanyLogoSrc({ symbol: "btc-usd", assetCategory: "CRYPTO" }))
12
+ .toMatch(/\/cloud\/logos\/crypto\/BTC$/);
13
+ });
14
+
15
+ test("skips cash and options", () => {
16
+ expect(resolveCompanyLogoSrc({ symbol: "USD", assetCategory: "CASH" })).toBeNull();
17
+ expect(resolveCompanyLogoSrc({ symbol: "AAPL 240119C00190000", assetCategory: "OPT" })).toBeNull();
18
+ });
19
+ });