Files
2026-08-20 22:41:25 +02:00

11 KiB

NOTES-PAGES.md: page audit

An audit of every internal link in web/src against every route astro build actually emits, plus a crawl of the built dist/ for links that resolve to nothing and anchors that point at ids no page has. Findings first, then what was built to close them.

Audit run against 58 indexed mints, API up. Re-run it with:

pnpm build && node web/scripts/check-links.mjs web/dist

LIST_TARGETS=1 on that command prints every distinct internal target and which pages link to it. The checker exits non-zero on a problem, so it can gate a deploy.

1. Findings

Routes are listed once each. "Linked from" is where the link lives in the source, not every page that renders the component.

Route Linked from Existed before Action
/ Topbar brand, 404 yes none
/mints Topbar nav, Footer, home hero + CTA + section links, mint page breadcrumb, 404 yes none
/mints?sort=reviews ?sort=rating ?sort=recent home quick filters yes (handled by the island) none
/mints?nut=17 home quick filter "Supports WebSockets" route yes, filter no not fixed, see below
/mints#mint-search Topbar search affordance id exists, link never renders not fixed, see below
/mint/[host] MintCard, home review cards yes, 58 prerendered none
/about Topbar nav, Footer, home CTA yes none
/about#nip87 home "Signed, not submitted" card yes none
/about#nuts mint page NUT panel yes none
/wallets Topbar nav, Footer yes none
/#latest-reviews Topbar nav "Reviews" anchor existed changed: nav now points at /reviews
/reviews nothing linked to it no built
/terms nothing linked to it no built, linked from the footer
/privacy nothing linked to it no built, linked from the footer
/disclaimer nothing linked to it no built, linked from the footer and every mint page
/404 (served by nginx on a miss) yes reworked, see below
/sitemap.xml nothing no built
/robots.txt nothing no built
/api/*, /icons/* mint icons, islands not in dist by design none: nginx proxies both to the API

The link crawl over the built site found zero broken internal links and zero dead anchors, before and after. Nothing pointed at the missing pages, which is exactly why they were easy to miss: the gap was between the page inventory and the navigation, not inside the navigation.

Findings not closed by this task

/mints?nut=17 does nothing. The home page offers "Supports WebSockets" as a quick filter and links to /mints?nut=17. The /mints island reads q and sort from the query string and ignores everything else, so the link lands on an unfiltered list. The fix is not one line: MintListItem (the payload /api/mints returns and the only data /mints has) carries no nuts array, so MintCard has nothing to put in a data-nuts attribute. Either the API adds nuts to the list payload, or /mints fetches all 58 mint details at build time the way /mint/[host] already does. Worth doing, out of scope for a page audit.

/mints#mint-search never renders. Topbar shows a "Search N mints" affordance only when a mintCount prop is passed. Nothing passes a number: the mint page passes mintCount={undefined} explicitly and no other page passes it at all, so the anchor is never in the emitted HTML. The knock-on is in Base.astro: the / key handler falls back to "no search box on this page, so follow the topbar search link", and that branch is unreachable, so / does nothing on the mint pages, /about, /wallets and the three new legal pages. Either pass the real count from /mints and the mint pages, or drop the affordance and the fallback branch.

Islands and the view transitions router. Base.astro now renders <ClientRouter /> (added by concurrent work on the site, not by this task). Under it, a same-site navigation swaps the document without re-running page scripts, and no island on the site re-initialises on astro:page-load: /mints search and sort, the mint page reviews panel, the pulse ticker and the new /reviews feed all run once, on the first full page load. Every island needs the same astro:page-load treatment the reveal script in Base.astro already has. Flagged rather than fixed here, because it is one cross-cutting change across every island and it belongs with the router work.

2. What was built, and what was already there

Built

  • /reviews (web/src/pages/reviews.astro). The global feed: every mint's reviews in one list, newest first.
    • Build time: the 30 most recent written reviews are read from the relays and prerendered, so the page has real content for a crawler and for a reader with JavaScript off. The controls hide themselves through a <noscript> rule rather than sitting there doing nothing.
    • Runtime: the island re-queries the relays for kind 38000 with no mint filter and resolves each event back to a known mint, then paginates 20 items to a page. Measured against the live relays: 644 events, 156 resolving to indexed mints across 25 mints.
    • Filters: All, 5 star, Critical, each with a live count, plus a mint filter. The mint filter is a text input backed by a <datalist> of every mint, so typing part of a name or a domain narrows it; 58 options is too many to scroll. ?mint= in the URL preselects one.
    • Cards are the mint page's review cards, literally: web/src/lib/review-cards.ts was extracted out of the reviews island so both render from the same functions. Same identity resolution (kind 0 name, NIP-05, generated avatar, "Anon" fallback), same copy-to-clipboard npub, same provenance note. Each card links to its mint.
    • Same rating-only rule, applied per day: a review with a body is a card, and every rating with no comment from the same day collapses into that day's single summary line, which sits in the feed at that day's newest rating. Most review events on the network are ratings with no comment (586 of 644 in the measured run), so without this the feed would be nothing else. An opened summary stays open across filter and page changes.
  • /terms, /privacy, /disclaimer. Prose in .prose at 720px, h1 plus h2 sections, sentence case, no em dashes, dated. The disclaimer is linked from the site footer and from the bottom of every mint page, which is where the decision it is about actually gets made.
  • /sitemap.xml (web/src/pages/sitemap.xml.ts). Generated at build: 8 static pages plus one entry per prerendered mint page, 66 URLs in the audit run. Written by hand rather than with @astrojs/sitemap because a mint page has a real lastmod available (the newest of last_review_at and last_online) and the integration would stamp every URL with the build time instead.
  • /robots.txt (web/src/pages/robots.txt.ts). Allows everything and names the sitemap. Generated rather than dropped in public/ so the Sitemap: line follows SITE_URL instead of hardcoding the production host.
  • web/scripts/check-links.mjs. The link checker this audit ran on.

Changed

  • /404. It already existed, was styled, and already held the mint fallback (on a /mint/{host} miss it asks the API and renders a summary behind the page level skeleton). It was missing three things the inventory asks for: the pixel moai, the "This page does not exist" heading, and a search input. All three added; the fallback is untouched and still runs before anything says the page is missing. The moai is 44px, because DESIGN.md says it is never scaled large.
  • Topbar: "Reviews" pointed at /#latest-reviews, a section of the home page. It now points at /reviews.
  • Footer: gained a second row with Disclaimer, Terms and Privacy, an "All reviews" link, and the one line worth repeating on every page ("mints are custodial, a mint can disappear with your money").
  • Home page: the "Latest reviews" strip's section link said "All mints →" and went to /mints. It now says "All reviews →" and goes to /reviews.
  • web/src/components/Reviews.astro: the card builders moved out to lib/review-cards.ts. Behaviour is unchanged, verified in a browser: same cards, same counts, same filters, same collapsed summary.
  • shared/src/nostr.ts: WRITTEN_MIN_CHARS moved here, since the build time feed and both browser islands now all need the same answer to "is this a written review".

Already present, verified rather than touched

  • /wallets is real content, not a stub: eight wallets with platforms and a one-line description each, a card grid on the shared design system, and a closing note that the list is not an endorsement. Worth recording why it looks curated rather than ported: per NOTES.md, the old React site had no wallet directory to port, only a header link to docs.cashu.space/wallets.
  • /about has every section FRONTEND.md specifies, with the anchor ids the rest of the site links to: #cashu, #mints, #nip87, #how, #relays, #nuts, #numbers, #source.
  • /mint/[host] prerenders all 58 mints and the client fallback handles unknown hosts.

3. For the operator, before this goes live

Two things in the new pages depend on how the site is deployed, and this repo cannot know either. Both are written conditionally and one is marked on the page itself.

  1. Log retention (/privacy, "Server logs"). The page says the web server keeps standard access logs with IP, time, request, status, user agent and referrer, which is true of any default nginx. It carries a visible operator note saying the retention figure needs confirming: the nginx config in README.md does not set access_log at all, so the real answer is your distribution's default (on Debian and Ubuntu, logrotate daily keeping 14 days). Confirm what your server actually does, write the number in, and delete the note and the .operator-note style.
  2. Contact route (/terms, "Contact"). It points takedown requests at the GitHub repository and names Azzamo as the operator. If there should be an email address or a Nostr contact instead, that is the paragraph to change.

Neither page invents a legal entity, a jurisdiction, a governing law clause or a company name, because none of those are knowable from the code. If the site needs them, they have to come from whoever runs it.

4. Verification

  • pnpm build: 67 pages (58 mints, 7 static pages, 404) plus sitemap.xml and robots.txt.
  • pnpm typecheck: 0 errors.
  • node web/scripts/check-links.mjs web/dist: 67 pages, 97 distinct internal targets, no broken internal links, no dead anchors.
  • Headless browser over astro preview: /reviews loads 156 live reviews, the rating filters and the mint filter both narrow the list and the counts, pagination works, day summaries open and stay open across a re-render, and a card's mint link resolves (/mint/mint.belgianbitcoinembassy.org → 200). With JavaScript off, 30 prerendered cards render and the controls are hidden. The mint page reviews panel is unchanged after the extraction. /mint/does-not-exist.example.com still runs the API lookup and then shows the moai, the heading and the search box.