aboutsummaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-09-10 22:51:56 -0700
committerAdam Soltys <[email protected]>2026-09-10 22:51:56 -0700
commit82cd8da7f5ab285b3a91f7284fb8d845400012ca (patch)
treeebf9d6d638ba110ff1e17624148ef7d9ac7412b5 /tools
parentac675185e362930f2086e01ab578fdd3423e4c79 (diff)
downloadcoinosv3-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.js37
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);