aboutsummaryrefslogtreecommitdiffstats
path: root/tools/avatar-thumb-test.js
Commit message (Collapse)AuthorAgeFilesLines
* The front door points at your own keys.Adam Soltys2026-09-131-1/+1
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Four buttons of equal weight, and only the sign-in ones said what they did. "Get started" said nothing, so the concrete offer on that screen was Google — which is a convenience wrapped around a seed phrase, not an alternative to one. Now the page says what the wallet is before it offers a button, the way in reads "Create a new wallet" with the cost under it (a minute, twelve words), and the three sign-ins sit below a line labelled "or, if you already have an account". And the second half of the same problem: a wallet that arrives by sign-in has a recovery phrase its owner has never seen and nobody can reissue. So the wizard shows it once, right after the first login, while it still reads as setting up rather than as a warning out of nowhere. "I've written it down" is the way on; "Later" is a real answer, and the wallet then keeps one quiet line — not a modal, not dismissible, gone the moment the phrase is actually seen, here or in settings. seedSeen records that per account: creating shows the words, importing means they typed them, revealing in settings counts. Only a derived seed starts false, which is exactly the case worth asking about. tools/onboard-backup-test.js covers both halves, including that "Later" leaves a reminder that survives a reload and leads somewhere. The sixteen tests that clicked the old button by its text are updated, and it has an id now so its copy can change without breaking them again. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_019mrt228Upn9nNCwGixzSLx
* Bigger faces in the feed.Adam Soltys2026-09-131-2/+32
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Measured on a Pixel 9: Amethyst gives a feed avatar 55dp, we gave it 30 — the same circle the inbox and chat rows use, where a face is a label on a line. In a feed the face is the point. Feed rows now get 44px; everywhere else is untouched. The catch, and the reason this isn't a one-line change: the cached thumbnail was 96px square, sized for a 30px circle at 3x. A 44px circle no longer fits inside it, and the fallback is to layer the ORIGINAL over the thumbnail — which for a feed of twenty rows means twenty full-size downloads, one of which in this wallet's own cache was a 5MB JPEG. That is exactly the slow-avatar problem we already fixed once. So the thumbnail grows with the circle: 144px, which covers 44 at 3x with room to spare. Faces cached at the old size are remade once (thumbPx says which size a thumbnail was made for), and the old one keeps painting meanwhile, so nobody watches a grey circle while it happens. The per-face cap goes 9000 → 16000 chars and the shared budget 180k → 280k, both scaled to the new size: the busiest picture on hand measures 12827 chars at 144px, and at the old cap it would have been rejected and left fetching its original forever. avatar-thumb-test gains the guard that would have caught all of this: the thumbnail must cover the biggest circle in the app at 3x. It failed at 128px (132 needed) — which is why THUMB_PX is 144 and not 128. Its redirect assertion was also order-dependent — the seed writes the profile into every wallet's cache but only the painted one makes a thumbnail, so it asked the first row and got a coin toss. It takes the best copy now. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_019mrt228Upn9nNCwGixzSLx
* Retry avatar thumbnail failures from the old download methodAdam Soltys2026-09-101-10/+48
|
* The thumbnail fetches the bytes instead of trusting a crossOrigin <img>.Adam Soltys2026-09-101-0/+25
| | | | | | | | | | | | | | | | | | | | | | | | | | Pinguimdarelva's avatar still flashed, and the reason was one I'd have kept guessing at: from the live browser, `new Image()` with crossOrigin set FAILS for that picture, while `fetch(url, {mode:'cors', cache:'reload'})` for the very same URL returns 200. The host allows us perfectly well. The picture sits behind a 302, and the avatar is painted as an ordinary background-image — so the bytes are already in the HTTP cache under the redirect's target as a no-CORS entry, and the crossOrigin <img> reuses that entry and fails the CORS check. The image loads, the CORS copy "errors", and the face gets written off. So the thumbnailer fetches the bytes itself and draws from a blob: no crossOrigin element, no cache collision, and an untainted canvas without depending on the element's CORS bookkeeping. With no taint left to detect, failures no longer split into hard and soft — each one just backs off, from six hours up to a fortnight, so a host that truly won't have us is asked twice a month instead of every boot. The test now serves a picture behind a redirect and loads it plainly before thumbnailing it, which is the shape that broke; it fails on the old code. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_019mrt228Upn9nNCwGixzSLx
* A face we've never seen is drawn from the pubkey, not left blank.Adam Soltys2026-09-101-0/+13
| | | | | | | | | | | | | | | | | | | | | | | 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) <[email protected]> Claude-Session: https://claude.ai/code/session_019mrt228Upn9nNCwGixzSLx
* Ship the punks at the size we actually draw them.Adam Soltys2026-09-101-0/+37
| | | | | | | | | | | | | | | | | | | | 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
* Keep a thumbnail of every face, so they're on screen in the first frame.Adam Soltys2026-09-101-0/+107
A profile picture is whatever its owner uploaded, and that is very often the full-size original. Measured against the faces this wallet had cached: one avatar is a 4,988,208-byte JPEG, another 3,901,966 — megabytes fetched and decoded to paint a 30px circle. That is the second of blank circles on the messages page after a refresh, and it repeats every time the upstream cache header expires. So the first time we see a picture we downscale it once (96px, webp) and keep the thumbnail beside the profile in localStorage, which is read synchronously at boot. Small avatars then paint from it alone and never ask the network for the original again; the 64px profile avatar layers them, so the thumbnail shows instantly while the full-size original loads over the top. Reading the pixels needs CORS — most avatar hosts allow it (6 of the 9 here), and the ones that don't are remembered as failures rather than retried every boot. A refresh of the profile carries the thumbnail across when the picture hasn't changed. Thumbnails live on a 180k budget, oldest face first, and Data Saver opts out of making them at all. tools/avatar-thumb-test.js watches a wallet meet a large cross-origin picture and checks the next boot fetches no picture at all. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_019mrt228Upn9nNCwGixzSLx