From 1054c86e45c8ab8671dd46ec8e244daceaa70482 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Sat, 26 Sep 2026 07:38:51 -0700 Subject: Notifications: the faces the page will paint are warmed before it opens. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The list was already cached locally and refreshed from the chat home, but each row only asked for its author's profile when painted, so the page opened as quiet circles filling in one by one. Now the actors' names are fetched in one batch and each picture decoded into a thumbnail the moment the list is read from disk or grows — which the chat home does before the page is ever opened. Test: notif-faces (stub relay answers the kind-0 batch; the pictures are fetched and cached from the chat home; every row has its face on the first frame). Note for tests: serve /verify-worker.js from dist, or every relay answer waits four seconds for the inline fallback. Co-Authored-By: Claude Fable 5.1 --- src/features/messages.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/features/messages.js b/src/features/messages.js index 65dec9d..1910546 100644 --- a/src/features/messages.js +++ b/src/features/messages.js @@ -7993,10 +7993,24 @@ export function messagesFeature(ctx) { const s = st(); const stored = (!s.notifsPk || s.notifsPk === identityPk()) ? (s.notifs || []) : []; notif = { status: stored.length ? 'ready' : 'loading', items: stored }; + warmNotifFaces(stored); refreshNotifs(); } return notif; } + // The faces the notifications page will paint, fetched and decoded ahead + // of it: the names in one batch, then each picture through the cache and + // into a thumbnail. Runs when the list is read from disk or grows (the + // chat home does both before the page is ever opened), so opening it + // paints faces rather than quiet circles that fill in one by one. + const notifWarmed = new Set(); + function warmNotifFaces(items) { + const pks = [...new Set((items || []).map((x) => x && x.actor).filter((pk) => pk && !notifWarmed.has(pk)))].slice(0, 80); + if (!pks.length || typeof Image === 'undefined') return; + for (const pk of pks) { notifWarmed.add(pk); liveProfileOf(pk); } + const deadline = Date.now() + READY_MS; + for (const pk of pks) warmAvatar(pk, deadline).catch(() => {}); + } // What an event says happened, or null if it isn't about you after all. function notifItem(ev) { const my = myPubkeys(); @@ -8031,6 +8045,7 @@ export function messagesFeature(ctx) { const add = (evs || []).map((ev) => (ev.kind === 1 && hidden(ev) ? null : notifItem(ev))) .filter((x) => x && !hiddenPk(x.actor) && !known.has(x.id) && known.add(x.id)); if (!add.length) return false; + warmNotifFaces(add); // One zap, two receipts: a coinos zap publishes its own (9737) beside the // LNURL server's (9735). The same person, post and amount within a few // minutes is one row. @@ -8164,7 +8179,7 @@ export function messagesFeature(ctx) { notifSeenAtOpen = notifSeen(); ui.chatOpen = true; ui.msgView = 'notifs'; - notifNow(); + warmNotifFaces(notifNow().items); refreshNotifs(true).catch(() => {}); watchNotifs(); markNotifsSeen(); -- cgit v1.2.3