From 48e8e588f1a3dee26a2497d1a1757f2f294413a3 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Thu, 10 Sep 2026 23:00:21 -0700 Subject: A face we've never seen is drawn from the pubkey, not left blank. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The avatar delay left on the messages page wasn't the image at all. A person with no cached profile got an empty circle until a relay answered — and the one that stayed blank longest (npub1k6f89cg, in the live cache) is never cached at all: persistProfile deliberately refuses to write a row with no name and no picture, so every boot re-asked the relays and every boot sat blank for seconds. When it finally arrived it was a punk, which is why it looked like slow art rather than a slow lookup. The punk is derived from the pubkey alone, so it can be drawn in the first frame. It's already the right answer for everyone with no picture, and for a coinos user who kept the default it IS their published picture — same formula, same file. A real photo replaces it when it lands. A name lookup in flight still gets the quiet circle: a picture is expected there, and punk art must not flash in front of it. Also: the punk rewrite now recognises the bare relative path a few older profiles carry, not just the absolute URL. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_019mrt228Upn9nNCwGixzSLx --- src/features/messages.js | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/features/messages.js b/src/features/messages.js index b23d7ad..59dcb1d 100644 --- a/src/features/messages.js +++ b/src/features/messages.js @@ -337,7 +337,9 @@ export function messagesFeature(ctx) { // the legacy coinos.io copies 502 for a third of the set — so painting // those from the local files means no network, no broken faces, and the // right size for the circle being drawn. - const PUNK_PIC_RE = /^https?:\/\/(?:[a-z0-9-]+\.)*coinos\.io\/punks\/(\d{1,2})\.webp$/i; + // Absolute (what the wizard publishes) or the bare relative path an older + // build wrote into a few profiles. + const PUNK_PIC_RE = /^(?:https?:\/\/(?:[a-z0-9-]+\.)*coinos\.io\/)?punks\/(\d{1,2})\.webp$/i; const localPunk = (url, big) => { const m = PUNK_PIC_RE.exec(url || ''); return m ? (big ? `punks/${m[1]}.webp` : `punks-sm/${m[1]}.webp`) : null; @@ -1761,12 +1763,22 @@ export function messagesFeature(ctx) { const avatar = (pk, cls = 'chat-avatar', clickable = true) => { const p = profileOf(pk); - // While the profile is in flight, a quiet empty circle — the punk is a - // statement about having no picture, not a loading state. (`loading` on - // an entry marks a provisional seed: name known, picture still unknown.) - const node = p === null || (p && p.loading && !p.picture) + // Someone we've never cached used to get an empty circle until a relay + // answered — seconds of blankness on every boot, and for anyone whose + // kind 0 carries no picture (or none at all, which never gets cached) + // that repeated forever. The punk is derived from the pubkey alone, so + // it can be drawn in the first frame: it's already the right answer for + // everyone without a picture, and for a coinos user who kept the default + // it IS their published picture. A real photo replaces it when it lands. + // + // `loading` is different and still gets the quiet circle: a name lookup + // is in flight for that specific person, so a picture is expected and + // punk art must not flash in front of it. + const node = p && p.loading && !p.picture ? h('div', { class: cls + ' fallback loading' }) - : p.picture + : p === null + ? fallbackAvatar(h, pk, null, cls) + : p.picture // A background paints synchronously from cache; a fresh decodes // async, so recreating one per render made avatars visibly flash. // -- cgit v1.2.3