From 3a0f02b7624d72e73a4f92b635f5335ffd29f972 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Thu, 27 Aug 2026 23:11:01 -0700 Subject: The face arrives with the key: sign-in warms the identity's profile and preloads the avatar bytes in parallel with the seed work, so the home screen's first paint already wears the picture instead of a blank circle Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_019eA8JRq9Wr7z2fRQk99HLi --- src/features/messages.js | 11 +++++++++++ src/features/nostrlogin.js | 3 +++ 2 files changed, 14 insertions(+) (limited to 'src/features') diff --git a/src/features/messages.js b/src/features/messages.js index c3ca466..3c03ce0 100644 --- a/src/features/messages.js +++ b/src/features/messages.js @@ -202,6 +202,11 @@ export function messagesFeature(ctx) { } wallet.saveFeatureState('profiles', s); } + // Pull the picture bytes into the HTTP cache the moment we learn the URL — + // an avatar div then paints instantly instead of holding its quiet circle + // while the image downloads. + const preloadPicture = (p) => { try { if (p && p.picture) new Image().src = p.picture; } catch {} }; + function profileOf(pk) { warmProfiles(); const cur = profiles.get(pk); @@ -211,6 +216,7 @@ export function messagesFeature(ctx) { const entry = { ...(p || {}), t: Date.now() }; profiles.set(pk, entry); persistProfile(pk, entry); + preloadPicture(entry); scheduleRepaint(); }).catch(() => profiles.set(pk, { t: Date.now() })); return cur || null; @@ -1207,6 +1213,7 @@ export function messagesFeature(ctx) { }; profiles.set(pk, entry); persistProfile(pk, entry); + preloadPicture(entry); if (ui.profilePk === pk) render(); else scheduleRepaint(); }).catch(() => { if (!fullProfiles.has(pk)) fullProfiles.set(pk, {}); }); @@ -2445,6 +2452,10 @@ export function messagesFeature(ctx) { // The light profile cache, read-only — lets the onboarding wizard skip // asks (like the avatar picker) that a loaded identity already answered. cachedProfile(pk) { return profileOf(pk); }, + // Start fetching a profile (and its picture bytes) NOW — the login flow + // calls this the moment it knows the identity, so the avatar is already + // in cache by the time the home screen first paints. + warmProfile(pk) { try { prefetchProfilePage(pk); profileOf(pk); } catch {} return true; }, // Publish (merge) kind-0 fields for the current identity — the onboarding // wizard sets name + picture through this. publishProfile(fields, opts = {}) { return publishProfileFields(fields, opts); }, diff --git a/src/features/nostrlogin.js b/src/features/nostrlogin.js index a084dd1..687a389 100644 --- a/src/features/nostrlogin.js +++ b/src/features/nostrlogin.js @@ -85,6 +85,9 @@ export function nostrLoginFeature(ctx) { let signer; try { signer = await makeSigner(); + // warm the identity's profile + avatar in parallel with the seed work, + // so the home screen's first paint already has the picture + ctx.hook('warmProfile', signer.pubkey); const res = await walletForSigner(signer); if (res.mode === 'new') { // No wallet on this identity yet — make one and walk straight in. -- cgit v1.2.3