/mints was a snapshot of whatever the API held when `astro build` ran, and stayed
that until the next build: a mint indexed at noon was reviewable at once — the 404
resolver saw to that — and simply had no card until 03:30. Every card's rating,
review count and status were as stale as the page.
The three index pages and the home page's three top-six strips now refetch
`GET /api/mints?type=…` once, after paint, and rebuild their grids. The
prerendered cards stay: they are the first paint, what a crawler indexes, and the
whole page without JavaScript. Hydration only ever replaces them with something
newer, and never with nothing — neither a failed fetch nor a well-formed empty
array touches a grid that has cards in it.
To make that affordable, the list payload grew the facts a chip is drawn from:
`nuts`, `capabilities`, and the two probed LNURL fields. /mints and /lnurl-mints
were fetching `GET /api/mints/:host` once per mint at build time to read two
booleans off each; that N+1 is gone from both, which takes the build from
fifty-six requests to one and is what makes the same read possible in a browser.
Additive: `MintDetail` already had all four.
web/src/lib/mint-cards.ts is MintCard.astro's parallel renderer, the same
relationship review-cards.ts has with the reviews panel. Same classes, same
data-* attributes — the sort, the search, the rank chips and the shared-element
view transitions all read the DOM — and the same i18n, through the page's own
inlined catalog rather than a build-time one.
Base.astro gained `clientNamespaces`, so the home page can inline the `home.`
catalog its strips need to rewrite "All 60 mints →" without putting 2KB of
marketing copy on 1,300 mint pages. check-i18n reads the prop off the page, so
the two cannot disagree.
Verified in Chromium against the built site: 60 prerendered cards become 61
including a mint inserted after the build; sort, search and hide-offline operate
on the new cards; /es/mints renders "En línea", "54 reseñas", "4,9" and "Solo
fundir"; JavaScript disabled still shows all 60; an aborted or empty API leaves
the grid alone; and a navigation away and back re-hydrates. 2016 pages build,
link and hreflang checks pass, 30 web tests pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
For about a year the production RELAYS list did not include the relay carrying
the kind 38000/38172 archive. Every backfill read about thirty events, wrote them
faithfully, reported ok=true, and the nightly build republished an index of eight
mints. Nothing measured the difference between "the cycle completed" and "the
cycle read anything", so nothing went red.
Three signals now do:
- Per-relay attribution. queryRelays() replaces pool.querySync(), which merges
every relay into one deduplicated array and throws away who sent what. It
keeps one subscription per relay over the pool's existing sockets and shares
a single alreadyHaveEvent across them, so an event five relays carry is still
verified once; receivedEvent fires before that check, which is what makes the
per-relay count mean "what this relay contributed". The deadline moved out of
each Subscription's own EOSE timer so `eose` means a frame arrived rather than
something timed out.
- A WARN naming any relay that will not connect, on every cycle, and any relay
that connected and sent nothing, on backfills only. An incremental cycle is
supposed to come back empty.
- BACKFILL_MIN_EVENTS, default 200. Under it, ERROR discovery starvation
suspected and a flag health reports as discovery_starved, forcing 503. Sticky
across incremental cycles so an hourly cycle finding four events cannot clear
what a backfill diagnosed; stored in the database so a restart cannot either.
A fresh database is starved until its first backfill lands. That is intended: it
holds the build's health gate rather than publishing a site made from nothing.
Verified against the live relay set — 1528 events, five relays connected, EOSE on
all five, health 200 — and against an unreachable list, which produces the two
WARN lines, the ERROR, and 503.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Introduce lnurl as a first-class mint type with probe/announcement fields
and shared helpers the API and web can both rely on.
Co-authored-by: Cursor <cursoragent@cursor.com>