commit aa1771ea20c962636ff0d07f3e9a708e1c650dc6 Author: michilis Date: Thu Aug 20 22:41:25 2026 +0200 first commit Co-authored-by: Cursor diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..08e4928 --- /dev/null +++ b/.env.example @@ -0,0 +1,115 @@ +# cashumints.space configuration. +# +# Copy to .env and edit. Every value below is the built-in default, so an empty .env +# behaves exactly like no .env at all. +# +# cp .env.example .env +# +# One file at the repo root serves all three packages. It is loaded by: +# - api/ via node --env-file-if-exists=../.env (see api/package.json) +# - web/ via web/scripts/load-env.mjs, imported from astro.config.mjs +# +# Real environment variables always win over this file, so a systemd unit or a +# one-off `PORT=9000 pnpm dev:api` still overrides it. .env is gitignored. + +# ─── Ports ─────────────────────────────────────────────────────────────────── +# The two servers `pnpm dev` starts. Change PORT and API_URL together: the web +# build and the dev proxy reach the API at API_URL, so they must agree. + +# API HTTP port. +PORT=8787 + +# Astro dev server / preview port. +WEB_PORT=4321 + +# ─── Web ───────────────────────────────────────────────────────────────────── + +# Where the build and the dev proxy reach the API. A build-machine address; it is +# never written into the markup a visitor downloads. +API_URL=http://127.0.0.1:8787 + +# Browser-facing API origin, baked into the markup and the islands. Empty means +# same origin: icons resolve to /icons and islands fetch /api, which the dev proxy +# and nginx forward to API_URL. Deliberately does NOT fall back to API_URL — a +# visitor's browser cannot reach 127.0.0.1. Set it only when the API answers on its +# own origin, e.g. https://api.cashumints.space +PUBLIC_API_URL= + +# Canonical origin for canonical links, OpenGraph tags and the sitemap. +SITE_URL=https://cashumints.space + +# ─── API storage ───────────────────────────────────────────────────────────── +# Unset means api/data/, resolved from the source tree regardless of where you run +# from. Set these and they are taken as given: a relative path resolves against the +# working directory (api/ under every pnpm script), so prefer absolute paths. In +# production point both at a directory the service user owns. + +# SQLite file. +# DB_PATH=/var/lib/cashumints/cashumints.db + +# Cached mint icons, served at /icons/*. +# ICON_DIR=/var/lib/cashumints/icons + +# ─── Nostr relays ──────────────────────────────────────────────────────────── +# Three comma separated lists, all optional. Each one, unset or empty, falls back +# to the list of the same name in shared/src/nostr.ts; the values below ARE those +# defaults, spelled out so the relays this site talks to are visible in one place. +# Setting one replaces the default pool entirely — it does not add to it. + +# Review and mint-announcement relays: kind 38000 and 38172. Read by the API +# indexer (probe/discovery cycles) and by the build-time "latest reviews" fetch. +# The union of the old site's read pool and its publish pool — the two differed, +# so reviews the old site published to snort/primal were invisible to it. +RELAYS=wss://relay.cashumints.space,wss://nos.lol,wss://relay.azzamo.net,wss://relay.snort.social,wss://relay.primal.net + +# Profile relays for the BUILD (kind 0, prerendered reviewer names on the home +# page). A wider pool than RELAYS on purpose: relay.cashumints.space holds no kind +# 0 at all and snort/primal hold almost none, so the two aggregators below are what +# turn npubs into names. Read the PROFILE_RELAYS comment in shared/src/nostr.ts +# before trimming this — relay.nostr.band was measured and deliberately excluded. +PROFILE_RELAYS=wss://relay.cashumints.space,wss://nos.lol,wss://relay.azzamo.net,wss://relay.snort.social,wss://relay.primal.net,wss://purplepag.es,wss://relay.nostr.net + +# Profile relays for the BROWSER: the same kind 0 lookup, done client side for +# pubkeys the build did not resolve. PUBLIC_ means it is baked into the shipped +# markup and a visitor's browser connects to these directly, so list only relays +# that accept public websocket connections. Normally kept equal to PROFILE_RELAYS. +PUBLIC_PROFILE_RELAYS=wss://relay.cashumints.space,wss://nos.lol,wss://relay.azzamo.net,wss://relay.snort.social,wss://relay.primal.net,wss://purplepag.es,wss://relay.nostr.net + +# Review relays for the BROWSER: where the reviews panel reads from and where a +# newly signed review is published. Defaults to RELAYS above when unset, which is +# what production wants. Point it at a local relay to exercise the publish path in +# development without putting test reviews on the public network. +# PUBLIC_REVIEW_RELAYS= + +# Relays used to reach a NIP-46 remote signer (the "Remote signer" and "Primal" +# login routes). Not review relays: these carry small encrypted RPC between this +# browser and the reader's signer app, so they have to be relays both ends can +# reach. Defaults to relay.nsec.app and relay.primal.net when unset. +# PUBLIC_CONNECT_RELAYS= + +# ─── Indexer timing ────────────────────────────────────────────────────────── +# All are positive integers; anything unparseable falls back to the default. + +# Minutes between probe cycles (every mint's /v1/info). +PROBE_INTERVAL_MIN=10 + +# Minutes between discovery cycles (relay scan for new mints and reviews). +DISCOVERY_INTERVAL_MIN=60 + +# Mints probed in parallel. +PROBE_CONCURRENCY=8 + +# Per-mint request timeout, milliseconds. +PROBE_TIMEOUT_MS=5000 + +# ─── Ranking ───────────────────────────────────────────────────────────────── + +# Bayesian prior mean C. The neutral 3 is intentional — read the "Ranking" section +# of README.md before changing it. `global` uses the observed global mean instead, +# which is the literal BACKEND.md behaviour and ranks noticeably worse. +SCORE_PRIOR_MEAN=3 + +# ─── Tooling ───────────────────────────────────────────────────────────────── + +# Dev server Boneyard captures against (`pnpm bones`). Defaults to WEB_PORT. +# BONES_URL=http://localhost:4321 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5da4566 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +node_modules/ +dist/ +.astro/ +api/data/ +*.log +.DS_Store +.env diff --git a/NOTES-PAGES.md b/NOTES-PAGES.md new file mode 100644 index 0000000..818a8b5 --- /dev/null +++ b/NOTES-PAGES.md @@ -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 `` +(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 `