From 2530b6a1bcdb1e57a7774ae84d81864169318b69 Mon Sep 17 00:00:00 2001 From: ifBars Date: Wed, 25 Mar 2026 05:03:16 -0700 Subject: [PATCH] fix(web): prevent provider model submenu overlap --- .../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 1694b374c8..1aef9c46c1 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 95f27f39cd..2fc20a9731 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} - +