diff --git a/apps/desktop/src/components/Avatar.tsx b/apps/desktop/src/components/Avatar.tsx
index 8a18963..edb14e0 100644
--- a/apps/desktop/src/components/Avatar.tsx
+++ b/apps/desktop/src/components/Avatar.tsx
@@ -1,6 +1,8 @@
// Reusable avatar that prefers an uploaded image and falls back to a coloured
// letter circle. Use this everywhere the app needs to render a profile.
+import { useEffect, useState } from 'react';
+
import { useCachedAvatarUrl } from '../lib/avatarCache';
interface Props {
@@ -27,7 +29,13 @@ export function Avatar({
loading = 'lazy',
}: Props) {
const effectiveUrl = useCachedAvatarUrl(url);
- if (effectiveUrl) {
+ // If the image URL is non-empty but unreachable (e.g. the storage object is
+ // missing / 404s), the bare
would render broken with no fallback.
+ // Track a load error and degrade to the letter circle instead. Reset on URL
+ // change so a fresh, valid avatar is retried.
+ const [failed, setFailed] = useState(false);
+ useEffect(() => setFailed(false), [effectiveUrl]);
+ if (effectiveUrl && !failed) {
return (
setFailed(true)}
/>
);
}