{{ profileId ? ai.editModel : ai.addModel }}
diff --git a/src/theme/dialog/index.ts b/src/theme/dialog/index.ts
index c19dcb7ae..b1ec6d2a9 100644
--- a/src/theme/dialog/index.ts
+++ b/src/theme/dialog/index.ts
@@ -1,8 +1,13 @@
+const dialogMotion =
+ 'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:duration-180 data-[state=open]:ease-out data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:duration-120 data-[state=closed]:ease-in motion-reduce:animate-none'
+
export default {
slots: {
- overlay: 'fixed inset-0 z-40 bg-black/50',
- content:
- 'fixed top-1/2 left-1/2 z-50 flex max-h-[min(90vh,48rem)] -translate-x-1/2 -translate-y-1/2 flex-col overflow-hidden rounded-xl bg-panel shadow-[0_8px_30px_rgb(0_0_0/0.4)] outline-none',
+ overlay: `fixed inset-0 z-40 bg-black/50 ${dialogMotion}`,
+ content: [
+ 'fixed top-1/2 left-1/2 z-50 flex max-h-[min(90vh,48rem)] -translate-x-1/2 -translate-y-1/2 flex-col overflow-hidden rounded-xl bg-panel shadow-[0_8px_30px_rgb(0_0_0/0.4)] outline-none data-[state=open]:zoom-in-98 data-[state=closed]:zoom-out-98',
+ dialogMotion
+ ],
header: 'flex shrink-0 items-start justify-between gap-3 border-b border-border px-4 py-3',
heading: 'min-w-0',
title: 'text-sm font-semibold text-surface data-[visually-hidden=true]:sr-only',
diff --git a/src/theme/settings/models.ts b/src/theme/settings/models.ts
new file mode 100644
index 000000000..6749037de
--- /dev/null
+++ b/src/theme/settings/models.ts
@@ -0,0 +1,7 @@
+export const modelPanelTransition = {
+ enterActiveClass:
+ 'transition-[opacity,translate] duration-150 ease-out motion-reduce:transition-none',
+ enterFromClass: 'opacity-0 translate-x-1 motion-reduce:translate-x-0',
+ leaveActiveClass: 'transition-opacity duration-100 ease-in motion-reduce:transition-none',
+ leaveToClass: 'opacity-0'
+} as const
diff --git a/tests/e2e/settings/credentials.spec.ts b/tests/e2e/settings/credentials.spec.ts
index 11710f5b4..310cfd31e 100644
--- a/tests/e2e/settings/credentials.spec.ts
+++ b/tests/e2e/settings/credentials.spec.ts
@@ -210,7 +210,10 @@ test('remembered browser credentials survive reload and clear centrally', async
await page.getByTestId('settings-section-ai').click()
await page.locator('[data-model-id]').first().click()
await page.getByTestId('provider-settings-clear-key').click()
- await page.getByRole('button', { name: 'Back' }).click()
+ await page
+ .getByTestId('settings-model-editor')
+ .getByRole('button', { name: 'Cancel', exact: true })
+ .click()
await page.getByTestId('settings-section-general').click()
await remember.click()
await page.getByTestId('app-settings-done').click()
diff --git a/tests/e2e/settings/model-editor.spec.ts b/tests/e2e/settings/model-editor.spec.ts
new file mode 100644
index 000000000..411a1a453
--- /dev/null
+++ b/tests/e2e/settings/model-editor.spec.ts
@@ -0,0 +1,43 @@
+import { expect, test } from '@playwright/test'
+
+import { CanvasHelper } from '#tests/helpers/canvas'
+
+test('model editing keeps the Settings shell stable and isolates the form', async ({ page }) => {
+ await page.goto('/?test')
+ await new CanvasHelper(page).waitForInit()
+ await page.getByTestId('app-settings-trigger').click()
+ await page.getByTestId('settings-section-ai').click()
+ const dialog = page.getByTestId('app-settings-dialog')
+ await expect(dialog).toBeVisible()
+ await dialog.evaluate(async (element) => {
+ await Promise.all(element.getAnimations().map((animation) => animation.finished))
+ })
+ const before = await dialog.boundingBox()
+ await page.getByTestId('settings-add-model').click()
+ const editor = page.getByTestId('settings-model-editor')
+ await expect(editor).toBeVisible()
+ await expect(dialog.getByText('Chat', { exact: true })).not.toBeVisible()
+ await expect(dialog.getByRole('button', { name: 'Back', exact: true })).toHaveCount(0)
+ await expect(page.getByTestId('app-settings-done')).toHaveCount(0)
+ expect(await dialog.boundingBox()).toEqual(before)
+
+ // Accidental dismissal must not throw away an in-progress profile.
+ await page.keyboard.press('Escape')
+ await expect(editor).toBeVisible()
+ await editor.getByRole('button', { name: 'Cancel', exact: true }).click()
+ await expect(editor).toHaveCount(0)
+ await expect(page.getByTestId('app-settings-done')).toBeVisible()
+ expect(await dialog.boundingBox()).toEqual(before)
+})
+
+test('page rows keep the same compact height while renaming', async ({ page }) => {
+ await page.goto('/?test')
+ await new CanvasHelper(page).waitForInit()
+ const row = page.getByTestId('pages-row').first()
+ await expect(row).toHaveCSS('height', '24px')
+ await row.getByRole('button').dblclick()
+ await expect(page.getByTestId('pages-item-input')).toBeVisible()
+ await expect(row).toHaveCSS('height', '24px')
+ await page.getByTestId('pages-item-input').press('Escape')
+ await expect(row).toHaveCSS('height', '24px')
+})