Commit graph

8 commits

Author SHA1 Message Date
SkateSpots
ef01c3a5ed fix(map): stop deleted spots flashing back from cache
A NIP-09 kind-5 delete is only honoured by relays and was never applied
locally, so on refresh the persisted React Query cache and the Dexie spot
cache re-rendered a just-deleted spot for a few seconds until fresh data
(or the admin hidden-label query) filtered it out.

Add src/lib/localDeletions.ts: a device-local tombstone keyed by coord
and event id, written optimistically by useDeleteEntity on the owner path
(rolled back if signing/publishing fails). The shared filterAdminHidden/
filterAdminHiddenMixed now also drop tombstoned items, so every list hook
filters them from first render with no per-hook changes. useSkateSpot
returns null for a tombstoned spot, and cacheSpots/cacheSpot refuse and
evict tombstoned rows. Prune by tombstone only (callers use limits 8-2000,
so pruning by 'not in fresh result' would wipe valid cached spots).
2026-10-09 09:03:16 +07:00
SkateSpots
c79373ed05 feat(offline): the map survives a dead relay (and says so) + skater@skate-spots.org
Relay outages no longer look like 'no spots'. querySkateContent swallowed every
relay error and returned [], so a dead relay would blank the map AND overwrite
good cached data with an empty list (then persist it). Now:

- querySkateContent/queryCanonical take an opt-in 'requireAny' that throws when
  NO relay answered. Only the map's own feeds use it, so the blast radius is
  small (79 other call sites keep today's behaviour).
- useSkateSpots/useSkateSpot fall back to the local IndexedDB cache on failure,
  so the map still works at the park; they only error when nothing is saved.
- The persister refuses to persist an empty array, so an outage can't wipe the
  last-known map on the next cold start.
- MapPage shows an honest 'Offline · showing saved spots' banner (reusing the
  existing relayProbe/useRelayStatus) and a 'Can't reach the relays' card
  instead of falsely claiming 'No spots mapped yet'.

Also seeds skater@skate-spots.org for the second admin (caba497f…) and reserves
the name so nobody else can claim it.
2026-09-28 12:48:54 +07:00
SkateSpots
e549aeee3a feat(spots): reports reach the admin, duplicates are caught, contributors are told
1) Reports were a VOID: ReportDialog publishes NIP-56 (1984) but the only reader
   in the app was a count on marketplace cards, so an admin never saw one. Now
   useSpotReports batch-reads kind 1984 by '#e' (one query for every spot) and
   the review queue ranks reported spots ABOVE the heuristics with a red
   'REPORTED: ...' chip. useNotASpotReport publishes to getWriteRelays()
   (community relay + the reporter's), not the default pool — the admin reads
   from the community relay, so reports written only to personal relays would
   never arrive. Verified end-to-end: published a report from Node, saw it
   surface in the queue.

2) Duplicate guard in CreateSpotPage: on submit, existing spots within 60 m are
   listed with distances + links, with 'It's a different spot — publish anyway'.
   Targets the clone-spam failure mode (five near-identical pins).

3) Contributor feedback: useSkateSpot no longer nulls a hidden spot for its
   AUTHOR (it used to silently 404 for the person who made it), and
   SpotDetailPage shows an author-only banner for hidden/verified.

tsc/eslint clean, 101 tests, smoke PASS.
2026-09-28 09:45:36 +07:00
SkateSpots
e44f8b81a7 feat(moderation): reversible verdicts (undo) + community photos as a signal
Undo (latest verdict wins):
- MODERATION_LABEL_CLEARED = 'cleared' — the neutral undo verdict.
- useAdminModeration no longer UNIONS labels (which made a hide permanent in the
  UI); it keeps the NEWEST verdict per target (created_at, with device-local
  verdicts overlaid by timestamp), so 'cleared' supersedes an earlier
  'hidden'/'verified'. useAdminClear() publishes it.
- Local mirror is now a verdict map (skatespots_local_admin_verdicts_v1: key ->
  {verdict, at}) with migration from the legacy hidden/confirmed arrays.
- useSkateSpots(limit, { includeHidden: true }) — ADMIN TOOLING ONLY: without it
  a hidden spot leaves the list, so a verdict could never be undone. The review
  queue uses it, keeps actioned rows (Hidden/Confirmed badge + Undo), and sorts
  un-actioned first.

Community photos as a signal:
- useBatchSpotPhotos: ONE #a query for every spot (the single-spot hook already
  queried by #a, so batching is free), counting only photos from people OTHER
  than the mapper. Folded into the 'no community signals' flag.

Verified by seeding local verdicts: none -> flags + Confirm/Hide; hidden ->
Undo; verified -> Undo; cleared -> back to neutral. tsc/eslint clean, 101 tests,
smoke PASS.
2026-09-28 01:06:04 +07:00
shakespeare.diy
13eece3a1e Admins can delete/take down any listing (spot, crew, tour, event, shop)
The two SkateSpots admin pubkeys (already in src/lib/admins.ts) can now remove
any listing, not just their own. Because a NIP-09 deletion signed by a
non-author isn't honored by most relays, admin takedowns are made reliable with
a client-side moderation layer:

- useDeleteEntity: owners still publish a plain NIP-09 kind:5. Admins acting on
  someone else's content publish the kind:5 AND a NIP-32 kind:1985 `hidden`
  label referencing the target coordinate/id, authored by the admin.
- useAdminModeration (new): fetches admin-authored `hidden` labels app-wide and
  exposes a fast lookup + filterAdminHidden() helper. Only labels whose author
  is in the hardcoded ADMIN_PUBKEYS are honored (author = trust boundary,
  re-verified client-side; never derived from a d-tag).
- Wired the hide filter into the list + detail hooks for spots, crews (incl.
  by-focus), tours, events, and shops via React Query `select`, so removed
  content disappears from lists and 404s on its detail page.

UI:
- DeleteEntityDialog now renders for owner OR admin, with distinct
  "Remove (admin)" framing and copy (used on Crew, Coach, Tour, Event pages).
- SpotDetailPage & ShopDetailPage: routed their inline delete through
  useDeleteEntity, show the delete button for admins, and relabel the confirm
  dialog for admin takedowns.
- Added the delete control to Tour and Event detail pages (previously none).

SECURITY.md: documented the admin moderation trust boundary and extended the
auditor file map. Verified build + no console errors.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-18 17:51:34 +07:00
shakespeare.diy
72cb690e64 fix: increase relay query timeouts and fix AppConfigSchema for cold mobile loads
- Raise QUERY_TIMEOUT in useSkateSpots from 8s to 15s
- Raise AbortSignal.timeout in useLabProfiles and useSkateEvents from 10s to 15s
- Add useSkateRelay to AppConfigSchema so it's preserved when reading from localStorage

On a fresh phone (no warm WebSocket, no TanStack Query cache), the app
needs to: load JS bundle, boot React providers, open a WebSocket to
goskatespots.nostr1.com, and receive EOSE — all before the timeout.
On slow mobile connections this easily exceeded the old 8-10s limits,
causing /home to show empty sections while /map worked (the connection
was already warm by then). 15s gives cold connections enough headroom.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 15:28:26 +07:00
shakespeare.diy
bd3e4bee4e perf: lazy loading, faster timeouts, better caching, chunk splitting
- AppRouter: lazy load all 40+ pages (only Index is eager)
  → initial JS bundle no longer includes code for pages never visited

- MapPage: lazy load SpotMap/Leaflet (~150KB) behind Suspense
  → Leaflet only downloads when user navigates to /map

- useSkateSpots/useShops/useMarketplace/useTours:
  → timeout: 30s → 8s (fail fast, show error sooner)
  → staleTime: 30s → 5min (trust cache, fewer relay round-trips)
  → gcTime: 5min → 30min (keep data in memory longer)
  → refetchOnMount: 'always' → true (use cache when data is fresh)

- Remove console.log/warn/debug spam from production hooks

- vite.config: manual chunk splitting for leaflet, nostr-tools, bitcoin
  libs, radix-ui, react-router — each chunk cached independently
  by browser, only re-downloaded when that library updates

- vite.config: esbuild drops console.log/warn/debug in prod build

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 11:32:24 +07:00
shakespeare.diy
e29d35f1e4 fix: restore full skate-spots source tree from misplaced src/src/ copy
The entire skate-spots codebase was accidentally copied into src/src/ and
public/public/ instead of src/ and public/ directly. This left the real
src/ directory as blank MKStack template stubs, explaining why the landing
page showed the default template instead of SkateSpots.

Changes made:
- Copied all files from src/src/ up to src/ (App.tsx, AppRouter.tsx, main.tsx,
  all pages, components, hooks, contexts, lib, i18n, test)
- Copied all files from public/public/ up to public/ (images, manifest, etc.)
- Deleted src/src/ and public/public/ nested directories
- Removed HelloWorld.tsx template stub from pages/
- Retained the previously fixed index.css (Tailwind v3 directives)

Full skate-spots feature set is now active:
- All 30+ routes (map, events, spots, coaches, marketplace, wallet, etc.)
- NWCProvider, LanguageProvider, PWAInstallPrompt, AmberCallbackHandler
- Skate community relays (goskatespots.nostr1.com, wotr.relatr.xyz, etc.)
- Outfit Variable font, Amber pre-check in main.tsx
- All hooks (useSkateSpots, useCoaches, useZaps, etc.)
- i18n support (en/es)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-15 22:18:28 +07:00
Renamed from src/src/hooks/useSkateSpots.ts (Browse further)