+186
@@ -0,0 +1,186 @@
|
||||
# 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:
|
||||
|
||||
```bash
|
||||
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.
|
||||
Reference in New Issue
Block a user