perf(P6A.T5): pre-warm Supabase + avatar loading hints
Fire a no-await profiles query in AuthContext on session establish to absorb cold-connection latency before the first user-triggered request. Add loading='lazy' default to the central Avatar component so all off-screen avatars (chat list, friends list, popovers, message senders) skip eager Supabase Storage fetches; set loading='eager' on ConversationHeader (active conv header) and CallParticipantTile inline imgs (both AudioContent and VideoStub) which are always above-the-fold when visible. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,11 @@ interface Props {
|
|||||||
// brand-tinted fallback; callers can override (e.g. to colour-by-id).
|
// brand-tinted fallback; callers can override (e.g. to colour-by-id).
|
||||||
fallbackClass?: string;
|
fallbackClass?: string;
|
||||||
alt?: 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({
|
export function Avatar({
|
||||||
@@ -19,6 +24,7 @@ export function Avatar({
|
|||||||
className = 'h-10 w-10',
|
className = 'h-10 w-10',
|
||||||
fallbackClass = 'bg-accent/20 text-accent',
|
fallbackClass = 'bg-accent/20 text-accent',
|
||||||
alt,
|
alt,
|
||||||
|
loading = 'lazy',
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const effectiveUrl = useCachedAvatarUrl(url);
|
const effectiveUrl = useCachedAvatarUrl(url);
|
||||||
if (effectiveUrl) {
|
if (effectiveUrl) {
|
||||||
@@ -28,6 +34,7 @@ export function Avatar({
|
|||||||
alt={alt ?? displayName ?? ''}
|
alt={alt ?? displayName ?? ''}
|
||||||
className={'shrink-0 rounded-full object-cover ' + className}
|
className={'shrink-0 rounded-full object-cover ' + className}
|
||||||
draggable={false}
|
draggable={false}
|
||||||
|
loading={loading}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -291,6 +291,7 @@ function AudioContent({
|
|||||||
src={avatarUrl}
|
src={avatarUrl}
|
||||||
alt=""
|
alt=""
|
||||||
className="relative h-full w-full rounded-full object-cover"
|
className="relative h-full w-full rounded-full object-cover"
|
||||||
|
loading="eager"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<span
|
<span
|
||||||
@@ -366,7 +367,7 @@ function VideoStub({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
{avatarUrl ? (
|
{avatarUrl ? (
|
||||||
<img src={avatarUrl} alt="" className="h-full w-full rounded-full object-cover" />
|
<img src={avatarUrl} alt="" className="h-full w-full rounded-full object-cover" loading="eager" />
|
||||||
) : (
|
) : (
|
||||||
letter
|
letter
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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"
|
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 ? (
|
{isDm ? (
|
||||||
<Avatar url={peerAvatar} displayName={title} className="h-10 w-10 text-base" />
|
<Avatar url={peerAvatar} displayName={title} className="h-10 w-10 text-base" loading="eager" />
|
||||||
) : peerAvatar ? (
|
) : peerAvatar ? (
|
||||||
<Avatar url={peerAvatar} displayName={title} className="h-10 w-10 text-base" />
|
<Avatar url={peerAvatar} displayName={title} className="h-10 w-10 text-base" loading="eager" />
|
||||||
) : (
|
) : (
|
||||||
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-accent/20 text-accent">
|
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-accent/20 text-accent">
|
||||||
<UsersIcon className="h-5 w-5" />
|
<UsersIcon className="h-5 w-5" />
|
||||||
|
|||||||
@@ -204,6 +204,14 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|||||||
void registerWebPush(installId);
|
void registerWebPush(installId);
|
||||||
}, [session]);
|
}, [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
|
// Phase 3: ensure this install owns exactly one devices row. The row is
|
||||||
// pure session-list telemetry — it does not carry any cryptographic
|
// pure session-list telemetry — it does not carry any cryptographic
|
||||||
// material since the per-user-key refactor. We re-use the row across
|
// material since the per-user-key refactor. We re-use the row across
|
||||||
|
|||||||
Reference in New Issue
Block a user