From 3289a25e57fe398176bfc85bd6849711ffa40352 Mon Sep 17 00:00:00 2001 From: ifBars <114284668+ifBars@users.noreply.github.com> Date: Wed, 25 Mar 2026 09:57:09 -0700 Subject: [PATCH] fix(web): prevent provider model submenu overlap (#1403) --- .../chat/ProviderModelPicker.browser.tsx | 45 +++++++++++++++++++ .../components/chat/ProviderModelPicker.tsx | 2 +- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/apps/web/src/components/chat/ProviderModelPicker.browser.tsx b/apps/web/src/components/chat/ProviderModelPicker.browser.tsx index 1694b374c8ebd03406960bfbd7337090382d51a5..1aef9c46c13f6808fb264a5f593e4e4dbeb06928 100644 --- a/apps/web/src/components/chat/ProviderModelPicker.browser.tsx +++ b/apps/web/src/components/chat/ProviderModelPicker.browser.tsx @@ -71,6 +71,51 @@ describe("ProviderModelPicker", () => { } }); + it("opens provider submenus with a visible gap from the parent menu", async () => { + const mounted = await mountPicker({ + provider: "claudeAgent", + model: "claude-opus-4-6", + lockedProvider: null, + }); + + try { + await page.getByRole("button").click(); + const providerTrigger = page.getByRole("menuitem", { name: "Codex" }); + await providerTrigger.hover(); + + await vi.waitFor(() => { + expect(document.body.textContent ?? "").toContain("GPT-5 Codex"); + }); + + const providerTriggerElement = Array.from( + document.querySelectorAll('[role="menuitem"]'), + ).find((element) => element.textContent?.includes("Codex")); + if (!providerTriggerElement) { + throw new Error("Expected the Codex provider trigger to be mounted."); + } + + const providerTriggerRect = providerTriggerElement.getBoundingClientRect(); + const modelElement = Array.from( + document.querySelectorAll('[role="menuitemradio"]'), + ).find((element) => element.textContent?.includes("GPT-5 Codex")); + if (!modelElement) { + throw new Error("Expected the submenu model option to be mounted."); + } + + const submenuPopup = modelElement.closest('[data-slot="menu-sub-content"]'); + if (!(submenuPopup instanceof HTMLElement)) { + throw new Error("Expected submenu popup to be mounted."); + } + + const submenuRect = submenuPopup.getBoundingClientRect(); + + expect(submenuRect.left).toBeGreaterThanOrEqual(providerTriggerRect.right); + expect(submenuRect.left - providerTriggerRect.right).toBeGreaterThanOrEqual(2); + } finally { + await mounted.cleanup(); + } + }); + it("shows models directly when the provider is locked mid-thread", async () => { const mounted = await mountPicker({ provider: "claudeAgent", diff --git a/apps/web/src/components/chat/ProviderModelPicker.tsx b/apps/web/src/components/chat/ProviderModelPicker.tsx index 95f27f39cd1a240dab6f2236c162ffce4a3bf29d..2fc20a97314d393accdc531e8e1a3aaeb2da78ca 100644 --- a/apps/web/src/components/chat/ProviderModelPicker.tsx +++ b/apps/web/src/components/chat/ProviderModelPicker.tsx @@ -153,7 +153,7 @@ export const ProviderModelPicker = memo(function ProviderModelPicker(props: { /> {option.label} - +