diff --git a/ui/src/e2e/new-session-page.prompt-attachments.e2e.test.ts b/ui/src/e2e/new-session-page.prompt-attachments.e2e.test.ts index edf2d7f8fcaa..24bf0e0fbb8e 100644 --- a/ui/src/e2e/new-session-page.prompt-attachments.e2e.test.ts +++ b/ui/src/e2e/new-session-page.prompt-attachments.e2e.test.ts @@ -33,13 +33,27 @@ async function withNewSessionPage(run: (page: Page) => Promise): Promise { - it("grows the first prompt through ten lines before using a narrow scrollbar", async () => { + it("grows the first prompt downward without moving the identity, then caps at ten lines", async () => { await withNewSessionPage(async (page) => { const gateway = await installMockGateway(page); await page.goto(`${suite.server.baseUrl}new`); const message = page.locator(".new-session-page__message"); await message.waitFor(); + const identity = page.locator( + ".agent-chat__welcome-clawd, .agent-chat__welcome-avatar, .agent-chat__avatar--text", + ); + const triggers = page.locator(".new-session-page__triggers"); + const composer = page.locator(".new-session-page__composer"); + const [identityBox, triggersBox, composerBox] = await Promise.all([ + identity.boundingBox(), + triggers.boundingBox(), + composer.boundingBox(), + ]); + expect(identityBox).not.toBeNull(); + expect(triggersBox).not.toBeNull(); + expect(composerBox).not.toBeNull(); + const initial = await message.evaluate((element) => ({ height: element.clientHeight, overflowY: getComputedStyle(element).overflowY, @@ -73,8 +87,16 @@ suite.define(() => { overflowY: getComputedStyle(element).overflowY, scrollHeight: element.scrollHeight, })); + const [expandedIdentityBox, expandedTriggersBox, expandedComposerBox] = await Promise.all([ + identity.boundingBox(), + triggers.boundingBox(), + composer.boundingBox(), + ]); expect(capped.clientHeight).toBeLessThan(capped.scrollHeight); expect(capped.overflowY).toBe("auto"); + expect(expandedIdentityBox?.y).toBeCloseTo(identityBox?.y ?? 0, 0); + expect(expandedTriggersBox?.y).toBeCloseTo(triggersBox?.y ?? 0, 0); + expect(expandedComposerBox?.y).toBeCloseTo(composerBox?.y ?? 0, 0); await captureUiProof(page, "new-session-composer-capped-scrollbar.png"); const start = page.getByRole("button", { name: "Start session" }); await expect(start.isVisible()).resolves.toBe(true); diff --git a/ui/src/styles/new-session.css b/ui/src/styles/new-session.css index be79f70736e9..4f91a96987fd 100644 --- a/ui/src/styles/new-session.css +++ b/ui/src/styles/new-session.css @@ -1,7 +1,5 @@ -/* Full-page new-session draft: the start-screen welcome centers in the pane - with the draft block (clean target row + composer) between the hero and the - recent chats, so the draft reads as the start screen plus "where to start" - controls. */ +/* Full-page new-session draft: the welcome and draft form one top-anchored + composition, with the target row and composer between hero and recents. */ openclaw-new-session-page { display: flex; flex: 1 1 0; @@ -30,16 +28,15 @@ openclaw-new-session-page { padding: clamp(44px, 5vh, 52px) clamp(6px, 1vw, 12px) 6px; } -/* The shared welcome stretches (flex: 1) and centers via justify-content, - which clips overflow above the scroll origin once the draft content is - taller than the pane (short window, open folder browser). Content-size it - here; its margin: auto still centers whenever there is spare room. */ +/* Content-size the shared welcome and top-anchor it inside the page scroll. + Auto vertical margins would re-center the identity as the textarea grows. */ .new-session-page .agent-chat__welcome { flex: 0 0 auto; width: 100%; max-width: none; min-height: auto; box-sizing: border-box; + margin: 0 auto auto; } /* Draft block inside the centered welcome column; the welcome centers text,