Skip to content

Commit 2530b6a

Browse files
committed
fix(web): prevent provider model submenu overlap
1 parent bf71e0b commit 2530b6a

File tree

2 files changed

+46
-1
lines changed

2 files changed

+46
-1
lines changed

apps/web/src/components/chat/ProviderModelPicker.browser.tsx

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,51 @@ describe("ProviderModelPicker", () => {
7171
}
7272
});
7373

74+
it("opens provider submenus with a visible gap from the parent menu", async () => {
75+
const mounted = await mountPicker({
76+
provider: "claudeAgent",
77+
model: "claude-opus-4-6",
78+
lockedProvider: null,
79+
});
80+
81+
try {
82+
await page.getByRole("button").click();
83+
const providerTrigger = page.getByRole("menuitem", { name: "Codex" });
84+
await providerTrigger.hover();
85+
86+
await vi.waitFor(() => {
87+
expect(document.body.textContent ?? "").toContain("GPT-5 Codex");
88+
});
89+
90+
const providerTriggerElement = Array.from(
91+
document.querySelectorAll<HTMLElement>('[role="menuitem"]'),
92+
).find((element) => element.textContent?.includes("Codex"));
93+
if (!providerTriggerElement) {
94+
throw new Error("Expected the Codex provider trigger to be mounted.");
95+
}
96+
97+
const providerTriggerRect = providerTriggerElement.getBoundingClientRect();
98+
const modelElement = Array.from(
99+
document.querySelectorAll<HTMLElement>('[role="menuitemradio"]'),
100+
).find((element) => element.textContent?.includes("GPT-5 Codex"));
101+
if (!modelElement) {
102+
throw new Error("Expected the submenu model option to be mounted.");
103+
}
104+
105+
const submenuPopup = modelElement.closest('[data-slot="menu-sub-content"]');
106+
if (!(submenuPopup instanceof HTMLElement)) {
107+
throw new Error("Expected submenu popup to be mounted.");
108+
}
109+
110+
const submenuRect = submenuPopup.getBoundingClientRect();
111+
112+
expect(submenuRect.left).toBeGreaterThanOrEqual(providerTriggerRect.right);
113+
expect(submenuRect.left - providerTriggerRect.right).toBeGreaterThanOrEqual(2);
114+
} finally {
115+
await mounted.cleanup();
116+
}
117+
});
118+
74119
it("shows models directly when the provider is locked mid-thread", async () => {
75120
const mounted = await mountPicker({
76121
provider: "claudeAgent",

apps/web/src/components/chat/ProviderModelPicker.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ export const ProviderModelPicker = memo(function ProviderModelPicker(props: {
153153
/>
154154
{option.label}
155155
</MenuSubTrigger>
156-
<MenuSubPopup className="[--available-height:min(24rem,70vh)]">
156+
<MenuSubPopup className="[--available-height:min(24rem,70vh)]" sideOffset={4}>
157157
<MenuGroup>
158158
<MenuRadioGroup
159159
value={props.provider === option.value ? props.model : ""}

0 commit comments

Comments
 (0)