fix: improve agent model picker layout (#2941)

This commit is contained in:
Carlos Florêncio
2026-08-23 17:41:49 +00:00
committed by GitHub
parent a40fef5c94
commit e9c5d099d7
5 changed files with 63 additions and 2 deletions
@@ -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}
/>
<PopoverContent
align="end"
align={popoverAlign}
side={popoverSide}
className="w-[min(24rem,calc(100vw-1rem))] max-h-[min(32rem,calc(100vh-1rem))] gap-2 overflow-hidden p-2"
>
@@ -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",
@@ -338,7 +338,7 @@ function CapabilitiesRowContent({
<div className={gapCls}>
<div className="flex items-end gap-2" data-testid="profile-capabilities-model-row">
<div
className={`flex-1 min-w-0 ${gapCls}`}
className={`${hasModes ? "flex-1" : "w-full md:max-w-xl"} min-w-0 ${gapCls}`}
data-settings-dirty={profileModelIsDirty(profile, baselineProfile)}
data-settings-dirty-level="container"
>
@@ -109,6 +109,7 @@ export function ModelPicker({
}
placeholder={placeholder ?? t("settings:selectAModel")}
ariaLabel={ariaLabel}
popoverAlign="start"
disabled={disabled}
configOptionsLoading={configOptionsLoading}
keepOpenOnModelChange={keepOpenOnModelChange}
@@ -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);