The marketplace 'Sats Traded' was a naive sum of the self-reported amount tag on
31871, which two colluding accounts could inflate for free. tradedSats is now the
robust figure - mutual trades only (a listing attested by >=2 distinct pubkeys),
per-pair cap 3, amounts >1 BTC dropped - with tradedCount + tradedTraders, and
tradedSatsClaimed keeps the raw claim so the card shows both. Added query fields
=> PERSIST_BUSTER v5. No trade attestations exist on the relay yet (honest 0).
The region card only placed pins carrying a city/country tag, so legacy pins
(coords but no tag) drew on the map yet landed in 'Uncategorised' — the map and
the stats disagreed. useGrowthStats now falls back to a cached reverse geocode
of the pin's own coordinates (lib/derivedRegions, localStorage keyed by ~110m
coords). useDeriveRegions (mounted on /stats) fills cache misses at 1.1s spacing
for Nominatim's 1 req/s policy and invalidates growth-stats; the card shows a
'Resolving N...' note meanwhile. Display-only fallback; the admin backfill still
writes real tags. Read-time geocoding disclosed on /privacy.
The admin backfill lived only in Settings -> Advanced, so an admin staring at
the 'Uncategorised' row had no obvious way to fix it. Render the admin-only
RegionBackfillCard inline in the region section when uncategorised > 0, and
clarify the zero-state (remaining untagged pins belong to other mappers).
A spot the community has hidden as 'not a skate spot' no longer inflates the
community numbers.
- useGrowthStats calls useAdminModeration() and drops hidden content inside
dedup(), so it's excluded from totalSpots, the region breakdown and the
trusted counts (kind-agnostic: the coord is built from each event's own kind).
- The hidden-set sizes are part of the query key, so the stats recompute once
the labels load.
- Region footnote says 'community relay only, with moderation-hidden content
excluded'.
This narrows the map<->stats gap. The remaining deltas are legitimate and
documented in AGENTS.md (untagged pins; auto-hidden spots).
Answers 'why don't the map and stats match' by making the gap visible, and
brings shops into the region breakdown.
- src/lib/geocodeRegions.ts is now the single reverse-geocode helper (spot
create, shop create, shop edit), so the Nominatim call + Accept-Language + the
parse rules can't drift. Never throws.
- Shops now publish city/country/country_code (previously none), so they can
appear in the region breakdown.
- Stats: regions cover spots AND shops; a new 'Uncategorised (N)' row plus a
'{N} of {M}' footnote let the card reconcile with the location total. Section
retitled 'Spots & Shops by Region'.
- PERSIST_BUSTER v3 -> v4.
- /privacy: wording now 'spot or shop geocoding'.
The map deliberately still reads the community relay + the reader's relays
(forcing canonical would hide an opted-out user's own spots) - documented.
Answers 'how are these accounted?' and fixes the privacy-page gap it exposed.
- Spot creation now also publishes ['country_code', <ISO-3166-1 alpha-2>], kept
alongside the display 'country' name so existing search/display still work.
- Stats group countries by country_code when present, else a lowercased name;
the label is the most common raw name in the group, so a localised spelling
can't split one country across two rows.
- The tail beyond the top 6 is surfaced as an 'Other' row, and the section is
relabelled 'Spots by Region' (it counts spots, not people) with a footnote.
- PERSIST_BUSTER v2 -> v3 (GrowthStats shape changed again).
- /privacy: disclose OpenStreetMap Nominatim — spot geocoding sees your IP and
the spot's pin (never the device's GPS). It was missing from the IP list.
Docs: AGENTS.md.
/stats slammed with 'Cannot read properties of undefined' for anyone whose
IndexedDB held a growth-stats cache saved before the new fields existed —
stats.topCountries.length threw.
- Bump PERSIST_BUSTER v1 -> v2 (the documented way to invalidate a shape change;
it is wired into App.tsx).
- Read the new fields defensively (stats?.x ?? []) so a future field can't do
this again.
- AGENTS.md: note that the smoke test cannot catch this (clean profile, no
persisted cache).
Three improvements to /stats.
1. A relay outage must not read as real zeros. Every sub-query swallowed its
error, so a failed relay rendered a full board of 0s. The queryFn now counts
failures and returns 'partial'; the page shows 'Couldn't reach every relay —
these numbers may be incomplete'. Badge also gains 'updated Nm ago' + Refresh.
2. Surface more of the community: Marketplace (live listings, completed trades,
self-reported sats, cash/barter split) and Regions (top countries/cities from
each spot's own tags). Zap receipts are counted ONLY for the top contributors
and labelled as not-global-volume, because global zap volume isn't measurable
on a public relay.
3. Mobile/desktop: collapse the 13 Lab cards on mobile behind 'Show all' (a
display:contents wrapper keeps them grid items); widen max-w-6xl -> max-w-7xl;
StatCard gains a compact prop so big sats don't overflow.
Docs: AGENTS.md community-stats note.
- AppRouter wraps every route (except /amber-callback and the 404) in ONE
<Route element={<Layout/>}> with an <Outlet/>; the chrome now persists across
navigation instead of being torn down and rebuilt on every page.
- Layout: children optional (falls back to <Outlet/>); the map is full-bleed via
a route check (isFullBleed) instead of a per-page noPadding prop.
- All 59 pages + CraftProfileForm/ErrorState/NewsletterIssueView now render
fragments instead of <Layout> (structure-preserving), imports dropped.
Removes the whole remount class: chrome no longer re-runs its hooks per
navigation, Sasquatch chat state survives navigation, and the header can't
re-open Amber on every page change.
- Preserve the geo 'g' tag and paid 'featured' tag when editing any Lab
profile (all 7 craft roles via CraftProfileForm + 5 bespoke pages).
- Render previously collected-but-hidden fields on the Lab profile page:
judge 'events_judged', organizer 'events_hosted', editor hardware/software,
and spot references.
- Include Event Organizer (35013) in Community Stats + counts.
- Cap Title inputs at 100 (was 120) so profiles no longer fail the parser
and silently vanish; MIN_TITLE_LEN 2 -> 1 for 1-char display names.
- Normalize bare-domain URLs (example.com) to https:// before storing instead
of dropping/hiding them (new normalizeUrlInput helper; fixes extra url fields).
- No crew strip for judging/organizing (was wrongly showing Photo & Film);
scrollable Offer Services FAB; live bio counters.
- Flesh out SkateJudge as a full craft-based Lab profile (parseCraftBase),
with JUDGE_FORMAT / SPECIALTY / LEVEL label sets covering Game of S.K.A.T.E.,
best trick, jams, contests, am series, world tour stops, etc.
- Wire judge into useLabProfiles (union type, self-tag, parse case)
- Add "judging" ServiceType to LabPage: tab, accent, filter sections, empty
state, counts, profile card icon, and ?craft=judging deep link
- New BecomeJudgePage + /become-judge route + FAB entry
- CoachDetailPage renders judge profiles (accent, icon, labels, noun, title)
- Community Stats "Judges" card now links to /lab?craft=judging
- Document kind 35012 (skatejudge) in NIP.md
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- useGrowthStats now queries and exposes every Lab craft individually: artists,
graffiti, tattoo, trainers, musicians and judges (previously folded into an
opaque "other" bucket with no visible breakdown).
- CommunityStatsPage renders a card for all 11 Lab crafts, so the section matches
what /lab actually lists instead of only showing 5 categories.
- labTotal + the content-mix donut's "Lab" slice now sum every craft, keeping the
"total" badge and chart consistent.
Note on relay mismatch: the app dedupes addressable events (keeps newest per
pubkey+kind+d) and merges across the community relay + the user's relays, so
counts can legitimately differ from a raw single-relay browser that shows stale
duplicate versions.
Bump version to 1.2.1 (versionCode 10201).
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- useGrowthStats now queries kind 35004 (photographers) and 35005 (builders),
which were previously omitted; added to totals, monthly buckets, allContent,
and the content-mix donut (folded into the "Lab" slice)
- Community Stats page reorganized into clear sub-sections:
* "The Lab · Creators & Services" — Coaches, Filmers, Editors,
Photographers, Builders, with a total badge
* "Community" — Tours, Crews
This scales the growing role list cleanly instead of cramming one row.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Spot Chat:
- Compact TrustFilter dropdown in the chat header (All / Verified / Trusted / Following)
- Filters messages by author trust via useBatchTrustScores; own messages
always stay visible; shows a "N hidden by your trust filter" indicator
Community stats:
- useGrowthStats now queries kind 35006 crews (was missing entirely)
- totalCrews added to totals + monthly buckets + content-mix donut
- New "Crews" StatCard on the Community Stats page
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Fix iOS directions: Apple Maps first on Apple platforms, removed broken geo: URI,
added Waze option, fixed popover z-index clipping behind Sheet component
- Fix 'Me' bottom nav: now deep-links to /settings?section=profile for logged-in users
so tapping 'Me' shows your profile, not a generic settings menu
- Add SettingsPage URL param support: ?section=X opens sub-screen directly
- Add onboarding welcome banner: dismissible strip at top of map for logged-out users
with 'Get Started' CTA pointing to /onboarding, stored in localStorage
- Fix Lab empty state: FAQ link + login prompt for logged-out users
- Fix Events empty state: FAQ link added to all empty state instances
- Fix GearSwap empty state: actionable 'Clear Filters' button + FAQ link
- Community stats: add 'this month' delta badges (+N this month) on all stat cards
- Newsletter pill: hide on map page to eliminate bottom sheet overlap conflict
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
New useGrowthStats hook:
- Queries wss://goskatespots.nostr1.com directly (authoritative source)
- Fetches all 7 content types in parallel: spots, shops, events,
coaches, filmers, editors, tours
- Deduplicates addressable events (keep newest per pubkey+kind+d)
- Buckets events by month with gap-filling for continuous X axis
- Computes cumulative spots line chart data
- Extracts top 10 mappers by spot count
- 15 min stale time — stats don't need to be live
New useGeyserStats hook:
- Queries Geyser GraphQL for supporter count + total sats raised
- Reuses same API already used in SupportersPage
Rewritten CommunityStatsPage:
- Section 1: 8 hero stat cards (spots, shops, events, coaches,
filmers, editors, tours, mappers) with per-type accent colours
- Section 2: 4 charts (cumulative spots area, monthly stacked bar,
active mappers bar, content mix donut)
- Section 3: Top 10 mappers leaderboard with avatars + 🥇🥈🥉
- Section 4: Supporters banner with live Geyser count + sats raised
- Footer: data source + verifiability note
- Full skeleton loading for each section independently
- Error state for relay connectivity issues
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
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/pages/CommunityStatsPage.tsx (Browse further)