| Commit message (Collapse) | Author | Age | Files | Lines |
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
A suite run let ~90 throwaway test wallets subscribe and publish on public
relays from one home address, and nos.lol and relay.damus.io banned it.
tools/puppeteer.js wraps puppeteer-core: every socket to a non-local host
gets an in-page stand-in relay (EVENT -> OK, REQ -> EOSE). Tests that read
real content launch with { relays: 'read' } (real reads, publishes answered
in place); feed-live, chat-layout and cordn-ui launch with { relays: 'live' }.
LIVE_RELAYS=read|1|host,... overrides per run.
profile-key-qr: measure the QR again if a repaint swapped it out.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
localStorage is ~5M characters per site whatever the device, and a phone
had filled it. Profiles, faceless list, reports, follow packs, profile
pages, relay lists, the moderation baseline, community directory, link
previews, video sizes/probes and search profiles (~3M on that phone) now
live in IndexedDB, which shares the site's disk allowance (GBs).
cacheStorage keeps localStorage's string API: reads from memory,
coalesced write-behind, flushed on hide. Copies migrate on open and are
removed once written. Boot waits for it (max 3s) beside the Ark store;
caches read at construction re-merge on ready; an early write can't
replace the stored copy. A copy an older build writes to localStorage
later moves in on first ask. Wallet wipes reach it; private mode stays on
localStorage. Read markers, drafts, follows, note threads and sync
outboxes stay in localStorage on purpose (cross-tab consistency, early
server-rendered seeding). Also asks navigator.storage.persist().
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
keep their first look.
Work by Codex, wrapped up and verified here.
Threads: a viewed conversation is persisted (thread-cache.js, up to 12
threads / 160 events), so a refresh or a cached note link shows the root
and ancestors at once instead of the selected reply and a spinner; relays
are re-asked in the background and an empty answer keeps the cache.
Feed window (feed-window.js): arrivals are merged without inserting
between visible rows — a gap fill under the reader's eyes waits until
that part of the feed is offscreen, prepends anchor the reading position,
the notice counts what went in above, and only its tap goes to the top.
Older pages are prepared a few screens ahead; a page's pictures and faces
are decoded before it is shown.
Rows keep the presentation they were admitted with (feedPaint): background
profile and quote refreshes no longer rewrite text or swap faces mid-read;
media dimensions are known before paint. Thumbnails queue instead of being
dropped past the first three.
Tests: thread-cache, feed-window, feed-stability; feed-live updated for
the new arrival rules (the notice counts up while you read).
Co-Authored-By: Claude Fable 5.1 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
| |
No create/import tabs and no Generate button inside the wizard: the words
appear the moment the button is tapped. Underneath, "Have an existing seed
or entropy to import?" swaps to the import pane (which has its own entropy
door), and "Create a new wallet instead" swaps back with fresh words. The
create/import card elsewhere (adding an account) keeps its tabs. Tests
that entered through the old tab go through the new line.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Tmfg8k5f3TbNJS34qiqHAp
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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
|
| | |
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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
|
|
|
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
|