From 9f55bf67456be10ff7c8238bf0d9e12e582848f6 Mon Sep 17 00:00:00 2001 From: Taylor Ho Date: Mon, 24 Aug 2026 11:56:27 -0700 Subject: [PATCH] fix(desktop): align jump-to-latest pill with composer height (#6606) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Category:** fix **User Impact:** Jump to Latest now stays above the composer as a draft grows to multiple lines. **Problem:** On WebKit, the pill's transform could retain a stale inherited composer-height value after the composer expanded, leaving the control stranded inside the composer. **Solution:** Position and animate the pill with its absolute bottom offset, which consumes the live composer height through layout rather than a promoted transform layer. A smoke test now verifies that the pill rises by the full composer growth and remains clear of the composer.
File changes **desktop/src/features/messages/ui/MessageTimeline.tsx** Anchor Jump to Latest with a live bottom offset instead of a translated compositor layer so composer resizing reliably moves it. **desktop/tests/e2e/smoke.spec.ts** Add coverage that expands a detached timeline's composer and checks the pill tracks the full height increase without overlapping it.
## Reproduction steps 1. Open a channel with enough messages to scroll. 2. Scroll away from the newest message until Jump to Latest appears. 3. Add several lines to the composer without sending. 4. Confirm Jump to Latest rises with the composer and remains directly above it. ## Validation - `pnpm --dir desktop test` — 5,397 passed - `pnpm --dir desktop check` — passed with four existing informational warnings outside this diff - `pnpm --dir desktop typecheck` — passed - `pnpm --dir desktop exec playwright test tests/e2e/smoke.spec.ts --project=smoke` — 26 passed - Push hooks — desktop check, typecheck, and tests passed ## Screenshots / Demos Both captures use the same long, mid-history timeline and the same four-line composer. | Before | After | | --- | --- | | The stale pill position overflows into the expanded composer. | The pill tracks the live composer height and stays clear above it. | | ![Before — Jump to Latest overlaps the tall composer](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6606/jump-pill-tall-composer-before.png) | ![After — Jump to Latest clears the tall composer](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6606/jump-pill-tall-composer-after.png) | Signed-off-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz> Co-authored-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz> --- .../features/messages/ui/MessageTimeline.tsx | 6 ++- desktop/tests/e2e/smoke.spec.ts | 52 +++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/desktop/src/features/messages/ui/MessageTimeline.tsx b/desktop/src/features/messages/ui/MessageTimeline.tsx index df9af09e67..fa8bb4e9f6 100644 --- a/desktop/src/features/messages/ui/MessageTimeline.tsx +++ b/desktop/src/features/messages/ui/MessageTimeline.tsx @@ -863,8 +863,12 @@ const MessageTimelineBase = React.forwardRef<
{ + const input = page.getByTestId("message-input"); + + await page.goto("/"); + await page.getByTestId("channel-general").click(); + await expect(page.getByTestId("chat-title")).toHaveText("general"); + + await ensureTimelineScrollable(page, `Jump pill growth ${Date.now()}`); + await page.waitForTimeout(400); + const timeline = page.getByTestId("message-timeline"); + await timeline.evaluate((element) => { + element.dispatchEvent(new WheelEvent("wheel", { deltaY: -100 })); + element.scrollTop = 0; + element.dispatchEvent(new Event("scroll", { bubbles: true })); + }); + + const jumpToLatest = page.getByTestId("message-scroll-to-latest"); + const composer = page.getByTestId("message-composer"); + await expect(jumpToLatest).toBeVisible(); + const initialPillBox = await jumpToLatest.boundingBox(); + const initialComposerBox = await composer.boundingBox(); + + await input.fill( + [ + "Composer growth line one", + "Composer growth line two", + "Composer growth line three", + "Composer growth line four", + ].join("\n"), + ); + + await expect + .poll(async () => (await composer.boundingBox())?.height ?? 0) + .toBeGreaterThan((initialComposerBox?.height ?? 0) + 40); + await page.waitForTimeout(250); + + const expandedPillBox = await jumpToLatest.boundingBox(); + const expandedComposerBox = await composer.boundingBox(); + expect(initialPillBox).not.toBeNull(); + expect(initialComposerBox).not.toBeNull(); + expect(expandedPillBox).not.toBeNull(); + expect(expandedComposerBox).not.toBeNull(); + + const composerGrowth = + (expandedComposerBox?.height ?? 0) - (initialComposerBox?.height ?? 0); + const pillLift = (initialPillBox?.y ?? 0) - (expandedPillBox?.y ?? 0); + expect(pillLift).toBeGreaterThanOrEqual(composerGrowth - 2); + expect( + (expandedPillBox?.y ?? 0) + (expandedPillBox?.height ?? 0), + ).toBeLessThanOrEqual(expandedComposerBox?.y ?? 0); +});