From e9c5d099d75126bea19c50fdfdd945c5f471024e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Flor=C3=AAncio?= Date: Sun, 23 Aug 2026 17:41:49 +0000 Subject: [PATCH] fix: improve agent model picker layout (#2941) --- apps/web/components/model-config-selector.tsx | 5 ++- .../settings/profile-form-fields.test.tsx | 19 ++++++++++ .../settings/profile-form-fields.tsx | 2 +- .../settings/profile-model-fields.tsx | 1 + .../tests/settings/agent-profile-acp.spec.ts | 38 +++++++++++++++++++ 5 files changed, 63 insertions(+), 2 deletions(-) diff --git a/apps/web/components/model-config-selector.tsx b/apps/web/components/model-config-selector.tsx index dc3df3cc01..8a52066870 100644 --- a/apps/web/components/model-config-selector.tsx +++ b/apps/web/components/model-config-selector.tsx @@ -183,6 +183,8 @@ export type ModelConfigSelectorProps = { placeholder?: string; ariaLabel?: string; variant?: "compact" | "field"; + /** Aligns the picker with the trigger edge for context-specific settings surfaces. */ + popoverAlign?: "start" | "center" | "end"; popoverSide?: "top" | "bottom"; triggerClassName?: string; triggerSummary?: "all" | "changed"; @@ -274,6 +276,7 @@ export const ModelConfigSelector = memo(function ModelConfigSelector({ placeholder, ariaLabel, variant = "field", + popoverAlign = "end", popoverSide = "bottom", triggerClassName: customTriggerClassName, triggerSummary = "all", @@ -333,7 +336,7 @@ export const ModelConfigSelector = memo(function ModelConfigSelector({ variant={variant} /> diff --git a/apps/web/components/settings/profile-form-fields.test.tsx b/apps/web/components/settings/profile-form-fields.test.tsx index 33bd24f08a..431b6984d0 100644 --- a/apps/web/components/settings/profile-form-fields.test.tsx +++ b/apps/web/components/settings/profile-form-fields.test.tsx @@ -198,6 +198,25 @@ describe("ProfileFormFields no-silent-model-fallback rows", () => { }); describe("ProfileFormFields model options", () => { + it("constrains a single start model field on desktop", () => { + renderForm(formData()); + + const row = screen.getByTestId("profile-capabilities-model-row"); + expect(row.firstElementChild?.className).toContain("md:max-w-xl"); + }); + + it("keeps the model and mode fields balanced when modes are available", () => { + renderForm(formData({ mode: "default" }), { + ...modelConfig, + available_modes: [{ id: "default", name: "Default" }], + current_mode_id: "default", + }); + + const row = screen.getByTestId("profile-capabilities-model-row"); + expect(row.firstElementChild?.className).toContain("flex-1"); + expect(screen.getByTestId("profile-mode-field")).not.toBeNull(); + }); + it("loads model-specific options in the profile model selector", async () => { const dynamicModelConfig: ModelConfig = { default_model: "model-a", diff --git a/apps/web/components/settings/profile-form-fields.tsx b/apps/web/components/settings/profile-form-fields.tsx index 2ee09188aa..aec630d67f 100644 --- a/apps/web/components/settings/profile-form-fields.tsx +++ b/apps/web/components/settings/profile-form-fields.tsx @@ -338,7 +338,7 @@ function CapabilitiesRowContent({
diff --git a/apps/web/components/settings/profile-model-fields.tsx b/apps/web/components/settings/profile-model-fields.tsx index d24c25cfef..65295fa896 100644 --- a/apps/web/components/settings/profile-model-fields.tsx +++ b/apps/web/components/settings/profile-model-fields.tsx @@ -109,6 +109,7 @@ export function ModelPicker({ } placeholder={placeholder ?? t("settings:selectAModel")} ariaLabel={ariaLabel} + popoverAlign="start" disabled={disabled} configOptionsLoading={configOptionsLoading} keepOpenOnModelChange={keepOpenOnModelChange} diff --git a/apps/web/e2e/tests/settings/agent-profile-acp.spec.ts b/apps/web/e2e/tests/settings/agent-profile-acp.spec.ts index 83f3b9ccb3..7280a06a4a 100644 --- a/apps/web/e2e/tests/settings/agent-profile-acp.spec.ts +++ b/apps/web/e2e/tests/settings/agent-profile-acp.spec.ts @@ -52,6 +52,44 @@ test.describe("Agent profile — ACP-first", () => { await expect(testPage.getByTestId("profile-mode-field")).toBeVisible({ timeout: 10_000 }); }); + test("profile model picker opens from the field start edge", async ({ testPage, apiClient }) => { + test.setTimeout(60_000); + + const { agents } = await apiClient.listAgents(); + const agent = agents.find((item) => item.name === "mock-agent") ?? agents[0]; + const profile = agent.profiles[0]; + + await testPage.goto(`/settings/agents/${agent.name}/profiles/${profile.id}`); + + const profileSettingsPanel = testPage.locator( + '[data-settings-target^="setting-agent-profile-"][data-settings-target$="-profile-settings"]', + ); + const selector = profileSettingsPanel.getByRole("button", { + name: "Profile start model settings", + }); + await expect(selector).toBeVisible({ timeout: 15_000 }); + await selector.click(); + + const popoverContent = testPage.locator('[data-slot="popover-content"]:visible'); + await expect(popoverContent).toBeVisible(); + await popoverContent.evaluate(async (element) => { + await Promise.all( + element.getAnimations().map((animation) => animation.finished.catch(() => undefined)), + ); + }); + + const [selectorBox, popoverBox] = await Promise.all([ + selector.boundingBox(), + popoverContent.boundingBox(), + ]); + + expect(selectorBox).not.toBeNull(); + expect(popoverBox).not.toBeNull(); + // Radix may nudge the content by a few pixels to satisfy collision padding; + // the regression is the old end alignment, which placed it ~50px inward. + expect(Math.abs(popoverBox!.x - selectorBox!.x)).toBeLessThanOrEqual(12); + }); + test("profile name edits persist across reload", async ({ testPage, apiClient }) => { test.setTimeout(60_000);