feat(map): spots and shops come only from the community relay
Owner decision: the map's spots + shops are canonical (relay.skate-spots.org only), not blended with the viewer's personal relays. useSkateSpots and useShops now use queryCanonical with a stable 'canonical' query-key suffix. The spot detail page (useSkateSpot) stays blended so shared links to a spot published only on a user relay still resolve. Verified with a headless map screenshot showing canonical clusters.
This commit is contained in:
parent
6751642eed
commit
f88dfe79ab
3 changed files with 24 additions and 17 deletions
23
AGENTS.md
23
AGENTS.md
|
|
@ -1696,16 +1696,19 @@ session with **no other memory of this project**. So:
|
|||
cannot catch this** — it boots a clean profile with no persisted cache, so it
|
||||
passed while real users crashed. Any new field on an allow-listed query needs
|
||||
both.
|
||||
- ⚠️ **The map and the stats legitimately differ — do NOT "fix" the map to
|
||||
`queryCanonical` (2026-10-01).** The map (`useSkateSpots` →
|
||||
`querySkateContent`) reads the community relay **+ the reader's own relays**;
|
||||
the stats read the community relay only (`queryCanonical`). Forcing canonical
|
||||
on the map would hide spots for a user who opted out of the community relay
|
||||
(`useSkateRelay=false`) — including **their own**. Two more reasons a "23"
|
||||
cluster ≠ a country row: a cluster bubble is proximity (may span countries),
|
||||
and the region card only counts locations **with a location tag** while the
|
||||
map draws every pin. The number comparable to the map is the hero
|
||||
**"Skate Spots"** card (`totalSpots`), not the region rows.
|
||||
- ✅ **The map is now CANONICAL for spots + shops (owner decision, 2026-10-26).**
|
||||
`useSkateSpots` and `useShops` use `queryCanonical` (community relay
|
||||
`relay.skate-spots.org` ONLY) with a stable `'canonical'` query-key suffix, so
|
||||
the map always matches the community dataset regardless of the viewer's
|
||||
personal relays. This **supersedes** the 2026-10-01 note below (which kept the
|
||||
map blended so a `useSkateRelay=false` user still saw their own pins). The
|
||||
spot **detail** page (`useSkateSpot`) stays blended so shared links to a spot
|
||||
published only on a user relay still resolve. Remaining map↔stats deltas:
|
||||
a cluster bubble is proximity (may span countries), and the region card only
|
||||
counts locations **with a location tag** while the map draws every pin — the
|
||||
number comparable to the map is the hero **"Skate Spots"** card.
|
||||
- ~~The map and the stats legitimately differ — do NOT "fix" the map to
|
||||
`queryCanonical` (2026-10-01).~~ (superseded above)
|
||||
- **Moderation now applies to the stats too (2026-10-01):** `useGrowthStats`
|
||||
calls `useAdminModeration()` and drops hidden content inside `dedup()`, so a
|
||||
spot the community removed no longer inflates `totalSpots`, the regions, or
|
||||
|
|
|
|||
|
|
@ -3,15 +3,16 @@ import { SKATE_SHOP_KIND, SKATE_SPOT_KIND, parseSkateShop, parseSkateSpot, type
|
|||
import { useSkateRelays } from '@/hooks/useSkateRelays';
|
||||
|
||||
export function useShops(limit = 100) {
|
||||
const { querySkateContent, getQueryKeySuffix } = useSkateRelays();
|
||||
// Map/shop directory is canonical: shops come from the community relay only.
|
||||
const { queryCanonical } = useSkateRelays();
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['shop-directory', 'list', limit, getQueryKeySuffix()],
|
||||
queryKey: ['shop-directory', 'list', limit, 'canonical'],
|
||||
queryFn: async () => {
|
||||
const signal = AbortSignal.timeout(10000);
|
||||
|
||||
// Query new shop kind (30017)
|
||||
const newShops = await querySkateContent(
|
||||
const newShops = await queryCanonical(
|
||||
[
|
||||
{
|
||||
kinds: [SKATE_SHOP_KIND],
|
||||
|
|
@ -22,7 +23,7 @@ export function useShops(limit = 100) {
|
|||
{ signal, requireAny: true }
|
||||
);
|
||||
// Query old shops (kind 30818 with 'shop' type) for backward compatibility
|
||||
const oldShops = await querySkateContent(
|
||||
const oldShops = await queryCanonical(
|
||||
[
|
||||
{
|
||||
kinds: [SKATE_SPOT_KIND],
|
||||
|
|
|
|||
|
|
@ -20,11 +20,14 @@ const QUERY_TIMEOUT = 15000; // 15s — mobile cold-start needs mo
|
|||
*/
|
||||
export function useSkateSpots(limit = 100, options: { includeHidden?: boolean } = {}) {
|
||||
const { includeHidden = false } = options;
|
||||
const { querySkateContent, getQueryKeySuffix } = useSkateRelays();
|
||||
// The map is CANONICAL: spots come from the community relay only, so what is
|
||||
// shown always matches the community dataset regardless of the viewer's
|
||||
// personal relays. (Details/profile pages stay blended via useSkateSpot.)
|
||||
const { queryCanonical } = useSkateRelays();
|
||||
const hidden = useAdminModeration();
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['skate-spots', limit, includeHidden, getQueryKeySuffix()],
|
||||
queryKey: ['skate-spots', limit, includeHidden, 'canonical'],
|
||||
select: (spots: SkateSpot[]) =>
|
||||
includeHidden ? spots : filterAdminHidden(spots, SKATE_SPOT_KIND, hidden),
|
||||
queryFn: async () => {
|
||||
|
|
@ -41,7 +44,7 @@ export function useSkateSpots(limit = 100, options: { includeHidden?: boolean }
|
|||
const signal = AbortSignal.timeout(QUERY_TIMEOUT);
|
||||
|
||||
try {
|
||||
const events = await querySkateContent(
|
||||
const events = await queryCanonical(
|
||||
[
|
||||
{
|
||||
kinds: [SKATE_SPOT_KIND],
|
||||
|
|
|
|||
Loading…
Reference in a new issue