Avatars: a sender without one logged a console error; phone list rows spaced 20px

A missing avatar was a 404, which the browser reports as an error for every
such sender on every page. It is now an empty 204: the <img> still fails to
decode it and the initials show through, but nothing is logged, and the miss
carries the same one-day browser cache as a hit so it stops being re-fetched.

On a phone the mail list's rows sit 20px apart instead of 7px.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-30 21:35:26 +02:00
co-authored by Claude Opus 5.5
parent 0ddfc8c658
commit 8dc60dbf5c
4 changed files with 24 additions and 15 deletions
+1 -1
View File
@@ -2,7 +2,7 @@ import { useState } from "react";
// Overlay for a server-fetched sender avatar (Gravatar / domain favicon). Drop it
// inside an existing initials circle: on success the image covers the initials; on a
// miss (404/error) it renders nothing so the initials show through. The parent must
// miss (an empty 204, or an error) it renders nothing so the initials show through. The parent must
// be `position: relative` and round. Fetched via /api/avatar (same origin), so the
// sender never sees the user's IP or read activity.
export function AvatarImage({ email }: { email?: string | null }) {
+3 -2
View File
@@ -146,7 +146,8 @@ export function MessageList({
const virtualizer = useVirtualizer({
count: threads.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 75, // 68 row + 7px gap
// 68 row + the gap above it: 7px, 20px on a phone.
estimateSize: () => 68 + (compact ? 20 : 7),
overscan: 8,
});
@@ -203,7 +204,7 @@ export function MessageList({
transform: `translateY(${item.start}px)`,
}}
// A row leaving the list lets the ones below glide up into its place.
className={`group absolute inset-x-0 px-2 pt-[7px] motion-safe:transition-transform motion-safe:duration-200 motion-safe:ease-out ${
className={`group absolute inset-x-0 px-2 ${compact ? "pt-5" : "pt-[7px]"} motion-safe:transition-transform motion-safe:duration-200 motion-safe:ease-out ${
dimmed ? "pointer-events-none opacity-40" : "cursor-pointer"
}`}
>