diff options
| -rw-r--r-- | src/features/messages.js | 24 | ||||
| -rw-r--r-- | tools/avatar-thumb-test.js | 13 |
2 files changed, 31 insertions, 6 deletions
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 <img> decodes // async, so recreating one per render made avatars visibly flash. // diff --git a/tools/avatar-thumb-test.js b/tools/avatar-thumb-test.js index 50ceadc..73277bc 100644 --- a/tools/avatar-thumb-test.js +++ b/tools/avatar-thumb-test.js @@ -139,6 +139,19 @@ try { return i ? i.getAttribute('src') : ''; }); check('the default face is the small punk', /^punks-sm\//.test(punkSrc), punkSrc); + + // And someone we have never cached at all — the case that used to sit as a + // blank circle until a relay answered — is drawn from their pubkey alone. + await page.evaluate(() => { + for (const k of Object.keys(localStorage).filter((x) => /^btc-wallet-cache:[0-9a-f]+:profiles$/.test(x))) localStorage.removeItem(k); + }); + await page.reload({ waitUntil: 'domcontentloaded' }); + await waitText('receive', 20000); + const strangerSrc = await page.evaluate(() => { + const i = document.querySelector('.header-avatar img.punk') || document.querySelector('img.punk'); + return i ? i.getAttribute('src') : ''; + }); + check('an uncached face is drawn straight away, not left blank', /^punks-sm\//.test(strangerSrc), strangerSrc || 'blank circle'); } finally { await browser.close(); server.stop(true); } console.log(ok ? '\n✅ faces come back instantly' : '\n❌ failed'); process.exit(ok ? 0 : 1); |
