diff options
| author | Adam Soltys <[email protected]> | 2026-09-10 22:51:56 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-09-10 22:51:56 -0700 |
| commit | 82cd8da7f5ab285b3a91f7284fb8d845400012ca (patch) | |
| tree | ebf9d6d638ba110ff1e17624148ef7d9ac7412b5 /tools | |
| parent | ac675185e362930f2086e01ab578fdd3423e4c79 (diff) | |
| download | coinosv3-82cd8da7f5ab285b3a91f7284fb8d845400012ca.tar.gz coinosv3-82cd8da7f5ab285b3a91f7284fb8d845400012ca.zip | |
Ship the punks at the size we actually draw them.
The art is 240x240 (20KB each, 1.5MB for the set) because that's what a punk
published as someone's nostr picture should be — but the circles the app
draws are 18 to 64px, so a new wallet with nothing cached was fetching eight
times the pixels it could show, one request per face.
punks-sm carries a 128px copy of each: 4KB instead of 20KB, indistinguishable
at 64px. Small circles and the wizard's picker tiles load those; only the 64px
profile avatar and the value the wizard publishes stay full-size.
The bigger win is that a punk picture is our own art wherever it turns up.
Everyone who keeps the default publishes v3.coinos.io/punks/N.webp as their
nostr picture, so those faces now paint straight off disk at the size being
drawn — no request, no thumbnail to make, and no broken circle when the
legacy coinos.io copies 502 (which they do for a third of the set).
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_019mrt228Upn9nNCwGixzSLx
Diffstat (limited to 'tools')
| -rw-r--r-- | tools/avatar-thumb-test.js | 37 |
1 files changed, 37 insertions, 0 deletions
diff --git a/tools/avatar-thumb-test.js b/tools/avatar-thumb-test.js index 48830be..50ceadc 100644 --- a/tools/avatar-thumb-test.js +++ b/tools/avatar-thumb-test.js @@ -31,6 +31,11 @@ const server = Bun.serve({ port: 5235, async fetch(req) { const u = new URL(req.url); + if (u.pathname.startsWith('/punks')) { + const f = Bun.file('dist' + u.pathname); + if (await f.exists()) return new Response(await f.arrayBuffer(), { headers: { 'content-type': 'image/webp' } }); + return new Response('no', { status: 404 }); + } if (u.pathname === '/big.png') { originalHits++; const bytes = await Bun.file('/tmp/claude-1000/-home-adam-coinosv3/61dea255-d26d-411d-be8b-92dfb0a75887/scratchpad/big.png').arrayBuffer(); @@ -102,6 +107,38 @@ try { await waitText('receive', 20000); await sleep(2000); check('a later boot fetches no picture at all', originalHits === 0, originalHits + ' request(s)'); + + // A punk picture is our own art: painted from disk at the size being drawn, + // never fetched from coinos.io (whose copies 502 for a third of the set). + await page.evaluate((pkHex) => { + for (const k of Object.keys(localStorage).filter((x) => /^btc-wallet-cache:[0-9a-f]+$/.test(x))) + localStorage.setItem(k + ':profiles', JSON.stringify({ + [pkHex]: { name: 'Punk Test', picture: 'https://v3.coinos.io/punks/7.webp', t: Date.now() }, + })); + }, pk); + await page.reload({ waitUntil: 'domcontentloaded' }); + await waitText('receive', 20000); + await sleep(1200); + const punkStyle = await page.evaluate(() => { + const a = document.querySelector('.header-avatar .ava-img') || document.querySelector('.ava-img'); + return a ? a.getAttribute('style') : ''; + }); + check('a punk picture paints from the local small copy', /punks-sm\/7\.webp/.test(punkStyle), punkStyle.slice(0, 60)); + check('...and never from coinos.io', !/coinos\.io/.test(punkStyle)); + + // And someone with no picture at all still gets a punk — the small one. + await page.evaluate((pkHex) => { + for (const k of Object.keys(localStorage).filter((x) => /^btc-wallet-cache:[0-9a-f]+$/.test(x))) + localStorage.setItem(k + ':profiles', JSON.stringify({ [pkHex]: { name: 'Bare', picture: null, t: Date.now() } })); + }, pk); + await page.reload({ waitUntil: 'domcontentloaded' }); + await waitText('receive', 20000); + await sleep(1200); + const punkSrc = await page.evaluate(() => { + const i = document.querySelector('.header-avatar img.punk') || document.querySelector('img.punk'); + return i ? i.getAttribute('src') : ''; + }); + check('the default face is the small punk', /^punks-sm\//.test(punkSrc), punkSrc); } finally { await browser.close(); server.stop(true); } console.log(ok ? '\n✅ faces come back instantly' : '\n❌ failed'); process.exit(ok ? 0 : 1); |
