From 08bc3a8b410de19188ce780adb0a0dee31a24523 Mon Sep 17 00:00:00 2001 From: SkateSpots Date: Mon, 28 Sep 2026 12:52:04 +0700 Subject: [PATCH] docs(agents): record offline/dead-relay behaviour + nip05 seeds --- AGENTS.md | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index f23c647..df06ea3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -499,9 +499,11 @@ session with **no other memory of this project**. So: — the app works without one), **proof by signature** (a claim carries a NIP-98 kind-27235 event in the `x-nip05-auth` header, signed by the pubkey that will own the name), and `onlyIfNew` writes so two claims can't clobber each other. - Platform names are seeded in code (`skate-spots`, `skatespots` → owner pubkey) - and cannot be claimed/released by users; a small `RESERVED` list blocks - impersonation words. Client: `src/lib/skateNip05.ts` (shared validation, + Platform names are seeded in code (`skate-spots`/`skatespots` → owner pubkey, + `skater` → the second admin `caba497f…`) and cannot be claimed/released by + users; a small `RESERVED` list blocks impersonation words. Any admin can wear + a seeded handle by typing it in Edit Profile → NIP-05 (it verifies against the + seed; no claim needed). Client: `src/lib/skateNip05.ts` (shared validation, `deriveSkateNip05Handle`, `parseSkateNip05`, `skateApiBase()` for the native absolute-URL rule), `src/hooks/useSkateNip05.ts` (check/claim/release), and `src/components/SkateNip05Input.tsx` (debounced availability + Claim). @@ -515,6 +517,27 @@ session with **no other memory of this project**. So: functions with a free draft (`netlify deploy --dir=dist`), not a prod deploy. Verified end-to-end on a draft (2026-09-28): claim → resolves → a second key gets 409 → release → gone. +- **Offline / dead-relay behaviour (2026-09-28):** the app is a utility, so a + relay outage must degrade, not blank. ⚠️ `querySkateContent` used to swallow + **every** relay error and return `[]`, so a dead relay looked like "no spots" + AND overwrote good cached data with an empty list (which the persister then + saved). Now: + - `querySkateContent` / `queryCanonical` take an opt-in **`requireAny`** that + throws when NO relay answered. Only the map's own feeds pass it + (`useSkateSpots`, `useShops`, `useMarketplaceItems`, `useTours`); the other + ~75 call sites keep the old behaviour, deliberately. + - `useSkateSpots` / `useSkateSpot` catch that error and fall back to the + **Dexie spot cache** (`lib/offlineCache`), so the map still works at the + park; they only re-throw when nothing is cached (UI shows an offline card). + - `shouldDehydrateQuery` **never persists an empty array** — otherwise one + blip would wipe the persisted map for the next 24h. + - `MapPage` shows "Offline · showing saved spots" + retry, and a "Can't reach + the relays" card instead of the false "No spots mapped yet". Reachability + reuses the existing **`useRelayStatus(url)`** (`lib/relayProbe`, a real + Nostr handshake) plus `useOnlineStatus` — NOT `navigator.onLine` alone, + which is true on a captive-portal Wi-Fi. + - Not done yet: offline **map tiles** (needs a service worker on web / an + IndexedDB tile layer for native) and the app shell offline — the next slice. - **Mints directory**: `/mints` page lists Cashu mints from the community **MintRadar** project. MintRadar has **no CORS**, so it's fetched same-origin at `/api/mints/*` — prod proxies via `public/_redirects` (`/api/mints/* →