From 45f4b91a36145f2ce642548c34f699f1b529bcf5 Mon Sep 17 00:00:00 2001 From: Taylor Ho Date: Thu, 13 Aug 2026 10:51:49 -0700 Subject: [PATCH] fix(desktop): more compact "compact" link previews (#5629) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Category:** improvement **User Impact:** Compact link previews now use a single-line title and smaller thumbnail, making conversations easier to scan. **Problem:** Compact previews gave long titles and oversized thumbnails too much visual weight in the message timeline. **Solution:** Keep titles to one ellipsized line and reduce image thumbnails to a 104×64 treatment while preserving the existing wide aspect ratio; Rich previews remain unchanged.
File changes **desktop/src/shared/ui/compact-link-preview-attachment.tsx** Tightens the Compact presentation with a single-line title and smaller wide thumbnail, leaving Rich previews untouched. **desktop/tests/e2e/messaging.spec.ts** Adds focused coverage for title overflow, exact 64px card and 104×64 thumbnail geometry, and successful decoded-image rendering using a realistic fixture, plus an optional visual capture. **desktop/tests/fixtures/github-pr-5629-og.png** Provides realistic visible image bytes for the compact-preview image-rendering E2E path.
## Reproduction steps 1. Launch the desktop app with link preview style set to Compact. 2. Send a link whose preview has an image and a long title. 3. Confirm the thumbnail renders at the smaller wide size and the title truncates to one line with an ellipsis. 4. Switch link preview style to Rich and confirm its presentation is unchanged. ## Screenshot ![Compact link preview at 64px tall with a decoded real-image thumbnail and one-line truncated title](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/5629/compact-link-preview-real-image-64px.png) --------- Signed-off-by: Taylor Ho Co-authored-by: Carl --- .../ui/compact-link-preview-attachment.tsx | 8 +-- desktop/tests/e2e/messaging.spec.ts | 59 +++++++++++++++++- desktop/tests/fixtures/github-pr-5629-og.png | Bin 0 -> 4405 bytes 3 files changed, 61 insertions(+), 6 deletions(-) create mode 100644 desktop/tests/fixtures/github-pr-5629-og.png diff --git a/desktop/src/shared/ui/compact-link-preview-attachment.tsx b/desktop/src/shared/ui/compact-link-preview-attachment.tsx index 7b2fb48ff..ad71e8b6e 100644 --- a/desktop/src/shared/ui/compact-link-preview-attachment.tsx +++ b/desktop/src/shared/ui/compact-link-preview-attachment.tsx @@ -79,7 +79,7 @@ export function CompactLinkPreviewAttachment({ className={cn( "w-full bg-transparent no-underline shadow-none hover:bg-transparent", reserveImage - ? "h-21 min-h-21 max-h-21 gap-0 border-0 p-0 hover:border-transparent" + ? "gap-0 border-0 px-0 py-0 hover:border-transparent" : "rounded-none border-0 border-l-[3px] border-border px-0 py-1 pl-3 hover:border-border", )} data-image-state={preview.imageState} @@ -89,7 +89,7 @@ export function CompactLinkPreviewAttachment({ {reserveImage ? ( @@ -110,7 +110,7 @@ export function CompactLinkPreviewAttachment({ )} ) : null} - + {hostname} - + {preview.title} {preview.description ? ( diff --git a/desktop/tests/e2e/messaging.spec.ts b/desktop/tests/e2e/messaging.spec.ts index 397441368..cbe19133c 100644 --- a/desktop/tests/e2e/messaging.spec.ts +++ b/desktop/tests/e2e/messaging.spec.ts @@ -1,3 +1,5 @@ +import { readFileSync } from "node:fs"; + import { expect, test, type Locator } from "@playwright/test"; import { waitForAnimations } from "../helpers/animations"; @@ -5,6 +7,11 @@ import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge"; import { expectCornerRadiusPx, expectSmoothCorners } from "../helpers/css"; import { openSettings } from "../helpers/settings"; +const LINK_PREVIEW_IMAGE = readFileSync( + new URL("../fixtures/github-pr-5629-og.png", import.meta.url), +); +const LINK_PREVIEW_IMAGE_DATA_URL = `data:image/png;base64,${LINK_PREVIEW_IMAGE.toString("base64")}`; + async function waitForReadyComposerSnapshots( page: import("@playwright/test").Page, count = 1, @@ -208,8 +215,7 @@ test.beforeEach(async ({ page }, testInfo) => { siteName: "GitHub", description: "A polished, stable preview for shared links.", - imageDataUrl: - "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=", + imageDataUrl: LINK_PREVIEW_IMAGE_DATA_URL, imageDomain: "opengraph.githubassets.com", faviconDataUrl: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=", @@ -1302,6 +1308,55 @@ test("composer link preview embeds stay attachment-sized while loading and ready } }); +test("compact link preview image geometry truncates long titles to one line", async ({ + page, +}) => { + const previewUrl = "https://github.com/block/buzz/pull/3246?geometry=1"; + await page.route("http://localhost:3000/media/*.png", (route) => + route.fulfill({ + body: LINK_PREVIEW_IMAGE, + contentType: "image/png", + }), + ); + await page.setViewportSize({ width: 800, height: 700 }); + await page.goto("/"); + await page.getByTestId("channel-general").click(); + await page.getByTestId("message-input").fill(previewUrl); + await waitForReadyComposerSnapshots(page); + await page.getByTestId("send-message").click(); + + const row = page.getByTestId("message-row").last(); + const card = row.locator('[data-link-preview="github-pull-request"]'); + const thumbnail = card.locator("[data-link-preview-thumbnail]"); + const title = card.locator('[data-slot="attachment-title"]'); + const image = thumbnail.locator("img"); + await expect(card).toHaveAttribute("data-image-state", "image"); + await expect(image).toBeVisible(); + await expect + .poll(() => image.evaluate((element) => element.naturalWidth)) + .toBeGreaterThan(0); + await expect(card).toHaveCSS("height", "64px"); + await expect(thumbnail).toHaveCSS("height", "64px"); + await expect(thumbnail).toHaveCSS("width", "104px"); + await expect(title).toHaveText( + "Ship a wider horizontal preview with a two-line title that wraps cleanly", + ); + await expect(title).toHaveCSS("white-space", "nowrap"); + await expect + .poll(() => + title.evaluate((element) => element.scrollWidth - element.clientWidth), + ) + .toBeGreaterThan(1); + + if (process.env.BUZZ_LINK_PREVIEW_SCREENSHOTS_DIR) { + await waitForAnimations(page); + await row.screenshot({ + animations: "disabled", + path: `${process.env.BUZZ_LINK_PREVIEW_SCREENSHOTS_DIR}/recipient-compact-long-title.png`, + }); + } +}); + test("composer no-image link embeds keep the attachment footprint", async ({ page, }) => { diff --git a/desktop/tests/fixtures/github-pr-5629-og.png b/desktop/tests/fixtures/github-pr-5629-og.png new file mode 100644 index 0000000000000000000000000000000000000000..f20c2c3411b98b0c47e3ae04baf5b1b5bfca8794 GIT binary patch literal 4405 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 vC