Repository navigation
fix(media): keep transparency in image thumbnails - #8772
Closed
johnmatthewtennant wants to merge 2 commits into
Closed
johnmatthewtennant wants to merge 2 commits into
johnmatthewtennant wants to merge 2 commits into
Conversation
The relay encoded every image thumbnail as JPEG, so transparent pixels
in PNG, WebP and GIF uploads turned black. Clients use the thumbnail
for small views, most visibly avatars.
When a thumbnail has transparent pixels, write it as PNG under
`{sha256}.thumb.png` and record `thumb_mime` in the sidecar. The public
path stays `{sha256}.thumb.jpg`: GET and HEAD serve whichever object the
sidecar records, with its Content-Type, so existing clients need no
change. The imeta thumbnail check and the bucket index follow the
recorded key. Opaque images keep JPEG thumbnails, and only images with
an alpha channel are scanned.
Images uploaded before this keep their JPEG thumbnails.
Fixes AIDA-2308
Co-authored-by: Simone <0828be588d5c9c4d092c5b1cf01761d5aef63d6b104807fd8e8284d517dd1a99@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: John Matthew Tennant <[email protected]>
A transparent GIF avatar showed the same black background as a PNG. Run the transparent thumbnail test for GIF as well as PNG. Refs AIDA-2308 Co-authored-by: Simone <0828be588d5c9c4d092c5b1cf01761d5aef63d6b104807fd8e8284d517dd1a99@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: John Matthew Tennant <[email protected]>
johnmatthewtennant
added a commit
that referenced
this pull request
Nov 4, 2026
## Summary
This fixes transparent images showing a black background. Image
thumbnails were always JPEG, which can't store transparency, so a
transparent PNG or GIF upload, such as an agent avatar, rendered on
black wherever a client showed its thumbnail.
The relay now writes a lossy WebP thumbnail at `{sha256}.thumb.webp`,
which keeps transparency, alongside the existing `{sha256}.thumb.jpg`:
- **Encoding:** the WebP comes from libwebp through the `webp` crate,
built from vendored C source, so the relay needs no system library. A
GIF uses its first frame.
- **Upload responses:** the upload descriptor's `thumb`, and its
per-tenant rewrite, point to the WebP.
- **JPEG kept:** installed clients derive `.thumb.jpg` themselves, so
the relay keeps writing it until clients move to WebP with a JPEG
fallback. Existing images still have only their JPEG thumbnail.
- **Serving:** `/media/` serves both variants, each with its own content
type. Other `.thumb.*` extensions still return 404.
- **imeta:** a `thumb` may name either variant, and the relay checks
that the named object exists.
- **Storage sweep:** the bucket taxonomy counts both variants as the
image's thumbnail bytes instead of classifying `.thumb.webp` as unknown.
| 320px thumbnail | JPEG | PNG | lossy WebP (q80) |
|---|---|---|---|
| Transparent avatar (starter-team `fizz.png`) | 11.3 KB, black
background | 65.1 KB | 13.8 KB |
| Opaque avatar (`sprout-agent.png`) | 23.6 KB | 156.5 KB | 12.3 KB |
| Screenshot (`channel-thread.png`) | 13.4 KB | 62.5 KB | 6.4 KB |
Encoding a WebP thumbnail takes 3 to 20 ms.
### Related issue
Supersedes #8772, which served a PNG at the `.thumb.jpg` path.
### Testing
Red/green against a local relay on `:3000`, built from the branch under
test, with Postgres, Redis and MinIO from `docker compose`:
```bash
cargo test -p buzz-test-client --test e2e_media test_transparent_png_thumbnail_is_webp_with_alpha -- --ignored
cargo test -p buzz-test-client --test e2e_media_extended test_ws_imeta_webp_thumb -- --ignored
```
- **On `main`:** both tests fail. The descriptor's `thumb` is
`.thumb.jpg`, and the WebSocket send is rejected with `invalid: imeta
thumb must be a local .thumb.jpg path`.
- **On this branch:** both tests pass:
- Uploading a half-transparent PNG returns a `thumb` ending
`.thumb.webp`.
- HEAD and GET on that URL return `Content-Type: image/webp`.
- The decoded thumbnail keeps alpha 0 on the transparent half and alpha
255 on the opaque half.
- `.thumb.jpg` still returns `image/jpeg`.
- A kind 9 message whose imeta `thumb` names the `.thumb.webp` is
accepted.
## Screenshots
The starter-team `fizz.png` avatar uploaded to a relay built from this
branch. Both thumbnails are shown as round avatars beside the original
PNG. `.thumb.jpg` fills the transparent area with a solid disc, and
`.thumb.webp` keeps the transparency.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Signed-off-by: John Matthew Tennant <[email protected]>
Co-authored-by: Simone <0828be588d5c9c4d092c5b1cf01761d5aef63d6b104807fd8e8284d517dd1a99@buzz.block.builderlab.xyz>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Contributor
Author
|
Superseded by #9003 (WebP thumbnails), merged 2026-11-04. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes a bug: image thumbnails lose transparency. The relay encodes every thumbnail as JPEG, so the transparent pixels of a PNG, WebP or GIF upload turn black. Clients use
{sha256}.thumb.jpgfor small views, so transparent agent avatars appear on a black square.Now, when a thumbnail has transparent pixels, the relay writes it as PNG and serves it with
Content-Type: image/png. Opaque images keep JPEG thumbnails.{sha256}.thumb.jpg. Clients build that path, and some only accept that suffix, so none of them needs a change.{sha256}.thumb.png, and the sidecar recordsthumb_mime: "image/png". GET and HEAD of.thumb.jpgserve whichever object the sidecar names, with its Content-Type. Sidecars without the field still mean JPEG.thumbcheck looks for the recorded thumbnail object, so messages with a transparent image's thumbnail are still accepted..thumb.pngkeys as thumbnails and adds the bytes of both thumbnail keys when a sha has both.Images uploaded before this change keep their JPEG thumbnails. Re-uploading the same bytes reuses the existing sidecar, so a backfill would be needed to fix those.
Related issue
Fixes AIDA-2308 (internal tracker: transparent agent avatars render on black). No matching public issue or PR found.
Testing
Red/green against a local relay with Postgres, Redis and MinIO:
cargo test -p buzz-test-client --test e2e_media --test e2e_media_extended -- --ignoredBefore (relay at
main), the new transparent-PNG test fails because the thumbnail is still a JPEG:After, the media e2e tests pass, including:
test_transparent_png_thumbnail_keeps_alpha: uploads a half-transparent PNG. HEAD and GET of{sha256}.thumb.jpgreturnimage/png, transparent pixels decode with alpha 0, and opaque pixels keep their color.test_upload_and_get: an opaque JPEG's thumbnail is stillimage/jpeg.test_ws_imeta_thumb_of_transparent_png: a kind 9 with an imetathumbfor the transparent upload is accepted. Without the imeta change, the relay rejects it withimeta thumb references missing thumbnail.