diff --git a/apps/desktop/src/components/Avatar.tsx b/apps/desktop/src/components/Avatar.tsx index 0e9cf06..8a18963 100644 --- a/apps/desktop/src/components/Avatar.tsx +++ b/apps/desktop/src/components/Avatar.tsx @@ -11,6 +11,11 @@ interface Props { // brand-tinted fallback; callers can override (e.g. to colour-by-id). fallbackClass?: string; alt?: string; + // Browser loading hint. Use 'eager' for above-the-fold avatars (e.g. the + // active conversation header, call tiles). Defaults to 'lazy' so off-screen + // avatars (chat list rows, friends list, popovers) don't hammer Supabase + // Storage on initial render. + loading?: 'eager' | 'lazy'; } export function Avatar({ @@ -19,6 +24,7 @@ export function Avatar({ className = 'h-10 w-10', fallbackClass = 'bg-accent/20 text-accent', alt, + loading = 'lazy', }: Props) { const effectiveUrl = useCachedAvatarUrl(url); if (effectiveUrl) { @@ -28,6 +34,7 @@ export function Avatar({ alt={alt ?? displayName ?? ''} className={'shrink-0 rounded-full object-cover ' + className} draggable={false} + loading={loading} /> ); } diff --git a/apps/desktop/src/components/CallParticipantTile.tsx b/apps/desktop/src/components/CallParticipantTile.tsx index 3d1f3d7..6d3e328 100644 --- a/apps/desktop/src/components/CallParticipantTile.tsx +++ b/apps/desktop/src/components/CallParticipantTile.tsx @@ -291,6 +291,7 @@ function AudioContent({ src={avatarUrl} alt="" className="relative h-full w-full rounded-full object-cover" + loading="eager" /> ) : ( {avatarUrl ? ( - + ) : ( letter )} diff --git a/apps/desktop/src/components/ConversationHeader.tsx b/apps/desktop/src/components/ConversationHeader.tsx index 92d3d80..f7d485a 100644 --- a/apps/desktop/src/components/ConversationHeader.tsx +++ b/apps/desktop/src/components/ConversationHeader.tsx @@ -129,9 +129,9 @@ function HeaderBar({ className="relative shrink-0 rounded-full text-left transition enabled:cursor-pointer enabled:hover:ring-2 enabled:hover:ring-accent/50 focus:outline-none focus-visible:ring-2 focus-visible:ring-accent/50 disabled:cursor-default" > {isDm ? ( - + ) : peerAvatar ? ( - + ) : (
diff --git a/apps/desktop/src/context/AuthContext.tsx b/apps/desktop/src/context/AuthContext.tsx index 6adc9a0..7724e97 100644 --- a/apps/desktop/src/context/AuthContext.tsx +++ b/apps/desktop/src/context/AuthContext.tsx @@ -204,6 +204,14 @@ export function AuthProvider({ children }: { children: ReactNode }) { void registerWebPush(installId); }, [session]); + // Pre-warm Supabase: fires the first round-trip in the background so the + // first user-triggered query (e.g. loading conversations) doesn't pay + // the cold-connection latency. + useEffect(() => { + if (!session) return; + void supabase.from('profiles').select('id').limit(1).then(() => undefined); + }, [session]); + // Phase 3: ensure this install owns exactly one devices row. The row is // pure session-list telemetry — it does not carry any cryptographic // material since the per-user-key refactor. We re-use the row across