Commit graph

323 commits

Author SHA1 Message Date
SkateSpots
7611f3d266 feat(media): self-host Blossom at blossom.skate-spots.org (primary upload target)
Newlay's Blossom enabled on the relay box (blobs inside the data volume); Caddy + DNS + TLS wired. The app now uploads there first, keeping blossom.primal.net and blossom.band as mirrors, and includes it in BLOSSOM_MIRROR_HOSTS for read fallback.
2026-10-12 13:12:14 +07:00
SkateSpots
6efd6b8deb feat(relay): retire the legacy hosted relay from the app + servers
Verified the legacy relay held the same 115 events as the new one (0 diff) before removing it, so no data is lost. Drops wss://goskatespots.nostr1.com from the default pools, the DM/CVM/zap relay arrays, and the cvm-server + push-service defaults.
2026-10-12 13:06:02 +07:00
SkateSpots
6407310213 feat(relay): cut over community relay to self-hosted Newlay
Point SKATE_RELAY, the default relay pools, the DM/CVM/zap relay arrays, the cvm-server + push-service defaults, and the archive default at wss://relay.skate-spots.org, keeping the legacy hosted relay as a fallback during the client-update window. Restored 115/115 archived events; the relay runs behind Caddy with TLS. Docs + AGENTS.md updated.
2026-10-12 12:38:13 +07:00
SkateSpots
0b645d6d67 feat(lab): add Clothing Designer craft (kind 35015)
New data-driven Lab craft, mirroring the artist role: three skill
sections (What You Make [required] / How You Make It / Materials) plus
minimum order, turnaround, size range, samples and customisation extras,
inheriting the shared Lab extras (rate, payment, gallery, reel,
languages, availability, booking).

Wired through types.ts (kind, label/description maps, parse, collective-
capable), labelKeys, useLabProfiles (kind list + dispatch), becomeRoles
(role card), craftFields, LabPage (tab, filters, offer button, empty
state, counts, deep links), LabCrewsStrip, NIP.md, and en/es i18n
(labOptions, becomeRolesUi, labUi). /become/clothing added to the smoke
route list. tsc, eslint, check-i18n, vitest 101/101, build and smoke all
pass.
2026-10-10 19:30:47 +07:00
SkateSpots
b48f0e53ee feat(escrow): seller-advertised mint, preferred by the buyer
Finishes the escrow-mint work: advertiseEscrowKey now also publishes
cashu_mint (the seller's preferred mint from Wallet > Escrow) on kind 0,
documented in NIP.md. The buyer's EscrowRequestDialog reads the seller's
cashu_mint, includes it in the mint picker even if it is not in the buyer's
trust list, and prefers it (falling back to the buyer's preference, then
first trusted mint). Funds locked at a mint the seller actually uses are
the ones they can redeem. Still open: seller-side receive from a mint they
do not already have. New marketUi.escrowRequest.sellerMintHint (en/es).
2026-10-10 08:57:33 +07:00
SkateSpots
c71d176226 fix(marketplace): verify payment_hash and never pay fiat as sats
- usePayPaymentPointer now checks the seller's committed payment_hash (from
  the accepted offer) against the invoice actually paid; a swapped pointer
  is rejected before any payment.
- PayWithSats no longer offers an in-app sats Pay button for a non-SATS
  offer: a fiat amount is never paid as if it were sats. New
  marketUi.pay.nonSats (en/es).
- ListingOffersPanel passes paymentHash + currency through.
Docs updated (still open: NWC->WebLN/Cashu fallback, empty preimage).
2026-10-09 23:14:29 +07:00
SkateSpots
ac1b22674c fix(escrow): show and choose the mint, block non-P2PK mints
The escrow mint was silently the buyer's preferred/first trusted mint
(useCashuEscrow: prefs.preferredMintUrl || mints[0].url) with no UI, even
though the seller redeems there. Now:
- the escrow dialog shows the mint and lets the buyer pick from their
  trusted mints (defaults to their preference);
- a mint whose NUT-06 info advertises no NUT-11 (P2PK) support is blocked
  with a clear message before minting (cashuEscrow.mintSupportsP2PK);
- the create call uses the selected mint.
Still open (scoped separately): consult a seller-advertised mint and
seller-side receive from an unfamiliar mint. Recorded in the audit.
2026-10-09 23:07:24 +07:00
SkateSpots
077fb2d5d1 fix(marketplace): payment, attestation, and escrow safety
Next batch from MARKETPLACE_FLOW_AUDIT.md:
- bolt11.ts: getBolt11Expiry / isBolt11Expired; usePayPaymentPointer refuses
  an expired invoice before paying (a lapsed offer invoice used to fail deep
  in the wallet with a confusing error).
- PayWithSats shows unsupported pointer kinds (creq/clink) as unavailable up
  front instead of enabling a button that fails after the click.
- useTradeAttestations: verify events (onlyVerified) and keep the newest per
  (author, kind, d), so a newer s=revoked supersedes a stale valid copy;
  useTradeReputation no longer trusts unverified relay events.
- EscrowRequestDialog persists the minted token locally before publishing
  (lib/pendingEscrow.ts), so a failed send cannot strand the locked funds.
2026-10-09 23:00:35 +07:00
SkateSpots
4b92759913 fix(marketplace): close P0 trade/offer bugs + deep flow audit
Deep audit (MARKETPLACE_FLOW_AUDIT.md): full trade/escrow/offers lifecycle,
severity-ranked findings, and a state-coverage matrix.

Fixes:
- Grid Mark as sold now opens MarkSoldDialog, so the kind-31871 attestation
  (buyer, method) is recorded from the grid too, not just the detail page.
- Offers accept attaches a payment pointer only when the accepting user is the
  listing owner (seller); previously the buyer could publish their own lud16 as
  the payee and misdirect payment.
- advertiseEscrowKey refuses to publish kind 0 before the profile loads, so it
  cannot overwrite name/picture/lud16 with a cashu_escrow-only profile.
- useListingOffers passes requireAny, so the offers panel error state actually
  fires on an outage (previously an outage looked like no offers).
- Offer messages invalidate the offer queries, so accept/counter/decline update
  immediately instead of after the stale window.
2026-10-09 21:35:06 +07:00
SkateSpots
0d15a7ce3d fix(marketplace): close the P0 audit gaps + borrow the guidelines page
P0 fixes from UX_AUDIT_2026-10-09.md:
- useMarketplaceItem now filters admin-hidden listings and skips
  showcase-only (not-for-sale) items, so a direct /listing/... URL cannot
  surface removed or non-sale content.
- Listing detail hides Make an offer for logged-out users and for
  fixed-price listings (Buy is the single primary CTA there).

Borrowed from the Plebeian market review: a /marketplace-guidelines page
(peer-to-peer, buyer-beware, no checkout, prohibited items, soft
moderation), linked from the marketplace hub; new marketTerms en/es keys.
Records the remaining borrowables in the audit doc.
2026-10-09 20:21:04 +07:00
SkateSpots
db7f4a2707 feat(trust): confidence gate on the local ladder
A lone friend-of-a-friend no longer reads as trust. useViewerGraph now
counts distinct follows per friend-of-a-friend (fofCounts), and
deriveLocalScore returns verified only at FOF_MIN_RATERS (2) or more.
Provider assertions are unaffected; the full GrapeRank confidence model
stays provider-side. Docs updated.
2026-10-09 18:36:50 +07:00
SkateSpots
18a9e28f0b feat(trust): network alerts for reported accounts you follow
Fifth slice of TRUST_SCORE.md. useNetworkAlerts reads the viewer's NIP-85
assertions for the accounts they follow and surfaces those trusted people
reported or muted. New /alerts page lists them with Ignore / Unfollow /
Mute / Report (reusing useFollowActions, useMuteUser, ReportDialog) —
inform, never auto-act. Settings -> Trust links to it with a live count;
ignored accounts are device-local. /alerts added to the smoke list.
2026-10-09 18:32:47 +07:00
SkateSpots
e66888128f feat(trust): a real mute (NIP-51 kind 10000) that hides content
Completes the safety loop. useMuteList reads the viewer's kind-10000 mute
list plus optimistic local mutes (lib/localMutes.ts); useMuteUser
republishes the list with the target added, records it locally and
invalidates content queries. useAdminModeration carries a new
mutedPubkeys set, and the shared filterAdminHidden/filterAdminHiddenMixed
drop muted authors — so every list hook filters them with no per-hook
changes (admin tooling with includeHidden still sees them). The flagged
notice gains a Mute action.
2026-10-09 18:23:04 +07:00
SkateSpots
0f8c77908b perf(trust): compute the local WoT graph once per viewer
TrustBadge called useTrustScore per card, which fetched the viewer's
follow list plus up to 100 friends' follow lists for EVERY card. The
graph does not depend on the target, so useViewerGraph computes it once
per viewer (one shared React Query) and deriveLocalScore returns the same
100/90/60/0 ladder (the NIP-05 30 tier classified as unknown anyway).
TrustBadge now costs no per-card network work on top of the batched
assertion read. Behaviour preserved; logged-out users still use the house
graph. Also records the ANALYZE=1 bundle findings in AGENTS.md.
2026-10-09 18:04:16 +07:00
SkateSpots
3b06e90d15 feat(trust): let the viewer choose the trust provider (NIP-85 kind 10040)
Settings -> Trust is now functional, not read-only. useSetTrustProvider
publishes a kind-10040 designation for a custom provider (npub or 64-hex +
wss relay, the five 30382:<type> rows) and can clear it to fall back to the
community provider. The card validates input, prompts the signer, and
invalidates the provider + assertion queries so scores refresh. Docs
updated.
2026-10-09 17:55:00 +07:00
SkateSpots
fd9316331b feat(trust): Settings -> Trust and name AGPL on the License page
- Settings -> Trust section (?section=trust, TrustSettingsCard): a
  show/hide-marks toggle (useTrustDisplay; Flagged is never hidden) and the
  active provider + relay (useTrustProvider), linked to /trust.
- TrustBadge respects the display preference.
- Name AGPL-3.0 on the License page by updating its existing copy
  (licenseLabel + body) in en/es.
- Settings + card i18n keys; /settings?section=trust added to the smoke
  list. Docs updated.
2026-10-09 17:45:56 +07:00
SkateSpots
2d1144bb7e feat(trust): batch provider reads, neutral direct follows, flagged notice
Second pass of TRUST_SCORE.md.

- useTrustAssertions now batches: every TrustBadge registers its subject into
  a shared per-viewer union and ONE query fetches the union (no per-card
  query), with a debounced refetch when a list grows. Same hook API.
- A direct follow (or self) is no longer shown as Verified - only a
  friend-of-friend or a provider assertion counts; Flagged is never
  suppressed.
- useUserReports reads public NIP-56 reports for an account, and
  TrustFlaggedNotice shows a flagged counterparty with the reporters grouped
  by the viewer's network plus Report / Dismiss. Wired into the listing
  seller card and the coach detail page.
- New walletUi.trust.* keys (en/es).
2026-10-09 17:17:11 +07:00
SkateSpots
fead9ca781 feat(trust): spec-correct NIP-85 consumer + Verified/Unknown/Flagged badge
First slice of TRUST_SCORE.md.

- lib/nip85.ts: parse kind-10040 provider designations, resolve a provider
  with a house fallback, parse kind-30382 assertions (rank/followers/
  reporters/muters/hops; absent hops means no path, absence is not
  distrust), and author-check the provider.
- hooks/useTrustAssertions.ts: batch read from the resolved provider,
  signature-verified via onlyVerified + the author check.
- lib/trustLadder.ts: three buckets and thresholds (flagged = trusted
  reporters >= 2; verified rank >= 2).
- hooks/useTrustBucket.ts: prefers a NIP-85 assertion (verify + flag),
  falls back to the local WoT ladder (verify only).
- TrustBadge.tsx: now renders Verified / Unknown / Flagged with glyph,
  colour and tooltip; Unknown hidden by default, Flagged always shown.
  Reaches the map/tour/event cards, marketplace card, listing detail and
  coach detail through the shared component.

Open follow-ups (in TRUST_SCORE.md): batch the bucket at list level, a
'who reported' breakdown, a provider-choice setting, alerts, confidence
scoring.
2026-10-09 17:07:34 +07:00
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
dd2062dc74 i18n(es): translate status presets, SetStatusDialog, nutzap + wallet toasts 2026-10-07 17:04:04 +07:00
SkateSpots
69458e1ae6 i18n(es): translate the Sasquatch onboarding guide script 2026-10-07 16:58:43 +07:00
SkateSpots
97c90c506a i18n(es): translate zap/NWC/delete/stash toasts + reuse feesUi keys for fee metadata 2026-10-07 16:55:33 +07:00
SkateSpots
b01bd22e96 i18n(es): translate SupportSkateSpots + hook toasts (follow, report, offline, publish) 2026-10-07 16:50:00 +07:00
SkateSpots
efd7e9608d feat(create-spot): pin map, progressive disclosure, mandatory photo+description, inline errors, draft autosave 2026-10-07 10:29:01 +07:00
SkateSpots
39572998a0 feat(clink): real bech32+TLV decode/validation (lib/clink.ts), spec-correct raw QR/copy, show decoded pricing; validate scanner 2026-10-06 11:16:20 +07:00
SkateSpots
2f6c6230f3 fix(audit P2 data): NaN guards (events/tours/zaps), query-key fixes, no array mutation, pure preference updater, zap limit 2026-10-05 21:37:01 +07:00
SkateSpots
84f80d8330 fix(audit P2 money/auth): invoice amount checks (Pro/fee/pointer), verify embedded zap req, harden NIP-05 proof + Pro grant, sanitize cordn link 2026-10-05 21:32:58 +07:00
SkateSpots
5728c226d5 fix(audit P1): comment cache key, ticket verify fails closed, featured amount check, no empty-object persist, sticky sub-bars 2026-10-05 21:07:07 +07:00
SkateSpots
04774bc180 fix(stats): validate kind-30017 with parseSkateShop so foreign shops leave Top Shops 2026-10-05 20:00:44 +07:00
SkateSpots
a9dcb5719c feat(supporters): per-row dates + 'updated', dedicated /donate page, extract shared support cards 2026-10-05 19:54:49 +07:00
SkateSpots
24e1369f6c feat(supporters): unified ranked wall, verifiable zaps, tier legend, impact link, pay-for detail, anonymous flag, map chip, OG meta 2026-10-05 18:55:16 +07:00
SkateSpots
87aca51248 feat(supporters): pay-for list + impact stats; scope zaps to the project 2026-10-05 18:46:49 +07:00
SkateSpots
ee55669625 Deck cost calculator: convert + a searchable multi-currency selector
- getBtcRates()/useBtcRates() read every BTC→fiat rate from the ONE Coinbase
  exchange-rates call the app already made, plus sats (1e8). No new endpoint.
- The calculator's currency control is now a searchable Popover: sats + every
  fiat Coinbase reports, with a search box. Selecting a currency CONVERTS every
  money field (labour, rent, utilities, subscriptions, equipment cost, material
  order/shipping, packaging materials) by the live ratio; unitless fields
  (overhead factor, board counts, minutes, margins) are untouched. Falls back to
  relabel-only if rates aren't loaded.
- Currency symbols/formats for the common fiats.
2026-10-05 10:16:50 +07:00
SkateSpots
84fde55ddd Add the DIY section (resources for boards, ramps & concrete)
New /diy hub: curated resources for making your own gear, with cross-links into
the rest of the app.

- src/lib/diyGuides.ts: seed data — categories (Boards, Ramps & wooden
  obstacles, Concrete & DIY spots, Repairs & tuning, Tools & plans), resource
  cards (Open Source Skateboards / SK8CAD / Instructables / YouTube), and an
  actions block (find a Builder, find a DIY crew, map a DIY spot, sell builds,
  find a coach).
- src/pages/DiyPage.tsx renders it (category chips + card grid + actions);
  external links go through sanitizeUrl and credit the source.
- Route /diy (lazyWithRetry) + a nav-catalog entry (id diy) so it appears in the
  desktop "More" menu and the mobile burger; also linked from the site footer
  and the map links strip. Smoke test walks /diy.
2026-10-05 09:55:26 +07:00
SkateSpots
b37d627138 Add a slim, dismissible site-links strip to the map
The desktop footer is hidden on full-bleed map routes, leaving the
Newsletter/License/Privacy/Collaborators links (and the "not sponsored by
OpenSats" note) unreachable from the homepage. MapFooterStrip overlays them
inside the map container without stealing map height:

- Desktop: a slim full-width bottom bar.
- Mobile: a tiny collapsed "Links" chip that expands to a compact, scrollable
  row.
- Dismissible (useMapFooterHidden, device-local, same-tab synced) and
  restorable from Settings -> Appearance.
- The map's OSM attribution moved up (md:bottom-7) so the bar doesn't cover it.
2026-10-05 09:15:51 +07:00
SkateSpots
084c002c45 Fix mobile grid overflow + add a Top Shops leaderboard
The /lab profile card and /stats Top Mappers were overflowing off the right
edge: Tailwind `grid md:grid-cols-2` (no base column) renders one implicit
`auto` track on mobile that can exceed the container when a name is long, so the
whole card got pushed off-screen. Added the base `grid-cols-1` repo-wide (plus
`min-w-0` on truncating flex/grid children).

/stats also gained a "Top Shops — most shops added" leaderboard (kind 30017)
beside Top Mappers (spots): `topShops` in useGrowthStats, a generalized
MapperRow (count + unit), and EN/ES copy. PERSIST_BUSTER → v6 (new persisted
field).
2026-10-04 18:01:49 +07:00
SkateSpots
fb42f4f0d1 Add Cashu escrow settings + a "require escrow" listing flag
- useEscrowPrefs (device-local): default lock window, preferred escrow mint,
  require-escrow default for new listings.
- EscrowSettingsCard on the Wallet (native-only, beside EscrowKeyCard).
- EscrowRequestDialog now defaults its lock window + funding mint from the prefs.
- Listings gain an optional ['escrow','required'] tag: a toggle on
  CreateListingPage (defaulted from the pref) and a 🔒 badge on the card.
2026-10-04 17:54:42 +07:00
SkateSpots
8cdc41cc15 Quieter Sasquatch bubble: no perpetual pulse, hideable, map-aware
Addresses "the floating Sasquatch flashing is annoying":

- Pulse is now state-driven: the animate-ping halo shows only when there's an
  unread tip (message), never perpetually; the unread dot matches.
- Honors prefers-reduced-motion (motion-reduce:hidden).
- Map-aware: Layout passes isFullBleed, so on the map the bubble runs compact
  (smaller, no glow, no pulse) instead of competing with map controls.
- Hideable: a ✕ on the FAB hides it (useSasquatchHidden, localStorage
  sasquatch-hidden, same-tab synced via an event), with a "Show Sasquatch
  assistant" toggle in Settings → Appearance to bring it back.
2026-10-04 14:20:46 +07:00
SkateSpots
3e84310384 Autosave Lab profile drafts so long forms survive nav/reload
- useLabDraft persists the form snapshot to localStorage (content-debounced)
  and only offers a saved draft as resumable when it differs from the current
  state, so opening an existing profile never shows a spurious resume banner and
  a draft can't silently clobber a freshly loaded profile.
- LabDraftBanner offers Resume / Discard; wired into CraftProfileForm (8 crafts)
  and the coach + filmer/editor/photographer/builder pages.
- The draft clears on a successful publish.
2026-10-04 11:35:41 +07:00
SkateSpots
c794d5fd18 Give Lab profiles a per-craft URL: /lab/<craft>/<pubkey>/<id>
Every provider lived at /coach/<pubkey>/<id> regardless of craft (a barber got
a "coach" URL). New src/lib/labRoutes.ts maps craft kind → segment and builds
the canonical path; the router adds /lab/:craft/:pubkey/:identifier (the craft
segment is cosmetic — the page still resolves by pubkey + identifier) and keeps
/coach/... working for old links. All generated links updated: offer-form
redirects, Lab cards + "Your Lab profiles", cross-links, spot/event/admin links,
the CVM client URL, the share path, and the SPA clean-link shim.
2026-10-04 11:32:15 +07:00
SkateSpots
b285db907d feat(map): show community confidence on spot cards
- New `useBatchSpotCommunityScores(spots)` — ONE query for many spots (same
  signals/weights as the single-spot hook; counts distinct accounts, drops the
  mapper, caps per account; skip WoT/activity weighting at this scale).
- SpotCard takes an optional `community` prop and shows a small "Community"
  (green) or "Seen" chip in the footer.
- MapPage scores the visible spot cards (shops excluded — different kind) and
  passes each card its score.
2026-10-03 19:28:28 +07:00
SkateSpots
77a3d592ea feat(spots): crews + Lab links count as strong signals; add a verify nudge
- useSpotCommunityScore now also reads crews (35006, `a` ref, weight 3) and Lab
  profile links (35001-35014, `a` ref, weight 2) as effortful signals — a whole
  crew listing the spot, or a Lab pro linking it, is strong evidence. A crew
  ref alone reaches "community". (Tours/events use geohash, not a spot coord,
  so they're not counted.)
- Spot page: on an unconfirmed spot, a logged-in non-owner now sees a "Be the
  first to confirm this spot" nudge with Add-photo + "I've skated here".
- Breakdown line/tooltip include crew + Lab counts.
2026-10-03 18:48:19 +07:00
SkateSpots
0e4e8c548a feat(spots): aggregate community confidence from all engagement signals
New `useSpotCommunityScore(spot)` — ONE batched relay query covering photos,
status reports, comments, reactions, zaps, collections and tag suggestions for
a spot, then a distinct-account, tier-weighted score:

- counts DISTINCT accounts and never the mapper's own signals;
- weights costly signals higher (zap 3, photo/status 2, comment/reaction/
  collection/tag 1) and caps each account's contribution at 3;
- multiplies by the actor's web-of-trust + real-activity (reusing
  useBatchTrustScores/useBatchActivity) so a ring of empty accounts can't move it;
- returns level (unverified | seen | community) + a breakdown.

SpotDetailPage now derives the Community-verified badge from this (not just
photos+status) and shows a small "why" line (e.g. "2 photos · 1 zap · 3
comments") under the badges. No new event kinds.
2026-10-03 18:39:54 +07:00
SkateSpots
127819e3da feat(nip05): multiple handles for profiles, crews and shops
A NIP-05 name maps to one key, so "who owns it" decides where it lives:
- Crew: repeatable nip05 tags (up to 5), verified against the founder key.
- Shop: same on kind 30017, verified against the owner key (new field).
- Profile: kind-0 has ONE standard nip05; extra personal handles live in a
  separate NIP-78 record (kind 30078, d=skatespots:profile-handles) so another
  client re-saving the profile can't wipe them.

Shared pieces:
- components/Nip05Badges.tsx — generic verify-and-render chip row (any owner).
- components/Nip05HandlesEditor.tsx — shared add/remove editor with live
  verified/mismatch status; used by profile, crew and shop forms.
- CrewNip05Badge now delegates to Nip05Badges; CrewForm uses the shared editor.
- Profile page shows primary + extra handles, and each crew/shop chips its own
  verified handle.
- lib/profileHandles.ts + hooks/useProfileHandles.ts (read/publish the record).
- types: SkateShop gains nip05/nip05s; parseSkateShop parses repeatable nip05s.
- NIP.md + AGENTS.md + i18n (EN/ES) updated.
2026-10-03 17:26:59 +07:00
SkateSpots
d566af851e fix: H14 double-pay guard + H1 claim provenance
H14 (NWC double-pay): after the 15s pay() timeout, useNWC now polls
lookupInvoice to confirm settlement before reporting failure; if the status
stays unknown it throws a coded error (PAYMENT_STATUS_UNKNOWN) and the callers
that used to fall through to another rail (useZaps, usePurchasePro,
PlatformFeePayment) now STOP and tell the user to check their wallet — no
automatic second payment.

H1 (spot claim): claim-from now stores the ORIGINAL COORDINATE
(30818:pubkey:d) rather than an ambiguous bare pubkey; SkateSpot parses
claimedFrom/claimReason; a claimed listing shows a 'Claimed listing' notice so
the duplicate is understandable. NIP.md updated. Full reader-side supersede is
documented as the remaining design item (a wrong rule could hide real spots).
2026-10-03 09:34:00 +07:00
SkateSpots
a745bdbbcf fix(trust): C5b — featured ranking requires a verified provider receipt
A ['featured', expiry] tag is self-asserted, so anyone could mint one and buy
top placement (the badge verified it, ordering didn't).

- New src/lib/featuredVerification.ts resolves the provider pubkey (cached) and
  batch-checks provider-authored kind-9735 zap receipts for the featured coords.
- useTours / useSkateEvents / useUpcomingSkateEvents / useLabProfiles now rank
  only receipts-backed featured items first.
- Availability fallback: if the provider can't be resolved or the query fails,
  the self-asserted ordering is kept, so a provider outage never blanks paid
  placement.
2026-10-02 23:28:34 +07:00
SkateSpots
caa5f8c56e fix: tier 4 — offer role trust, proof replay, crew alerts, error/nav i18n
- H13 offer threads: the seller is ALWAYS the listing author (derived from the
  event pubkey), never a self-declared role tag — a non-owner can no longer
  appear as the seller with a payment pointer.
- H12 trade reputation: a preimage/deposit proof counts at most once, so a real
  payment can't be reused across attestations to inflate payment-verified.
- Crew alerts: listed crews are filtered against the user's own membership
  confirmation labels (one batched query), so a confirmed member no longer gets
  a phantom 'confirm to join' badge/notification when the crew is edited.
- Error boundaries (app + route) and DesktopNavManager are translated; the
  class boundary renders a translated functional ErrorFallback. New batch4
  namespaces errorsUi.* / desktopNav.*.
2026-10-02 23:22:13 +07:00
SkateSpots
e016ee20bd fix: tier 3 — medium bugs & hardening
- Tour sponsor URL/logo sanitized before href/src (SponsorSlot) — same XSS
  guard EventSponsors already uses.
- Official SkateSpots badge only shows when the NIP-05 actually verifies
  (NIP19Page), not on a self-asserted handle.
- Community stats reads are null-guarded on persisted-cache fields
  (monthly/cumulativeSpots/topMappers) — no more crash on an old cache.
- Overpass response no longer assumes .elements (food layer).
- user-listings query key is memoized (was Date.now() → refetch storm).
- Pro grant requires the l=pro label (was accepted with none).
- Verify card RSVP maps accepted/tentative (was going/interested → showed
  'No RSVP' on a valid ticket).
- Expired listings can't be bought via a stale/shared URL.
- Batch spot photo/status hooks include the relay suffix and use
  querySkateContent (relay-policy parity with useSpotReports).
- Offline queue requeues rows stuck in 'publishing' after a crash.
- querySkateContent/queryCanonical dedupe replaceable events by author+kind
  (not id) so a stale profile/relay list can't win.
- Route offline cache key includes the origin (was destination+mode only).
- Internal links use <Link> (MarketplacePage); EventCountdown reacts to
  language changes.
2026-10-02 22:26:46 +07:00
SkateSpots
64ba187ad4 fix(trust): H15 — relay outage must not read as 'not found'/'no events'
- useMarketplaceItem / useSkateEvents now pass requireAny, so a total outage
  throws (isError) instead of silently returning [].
- ListingDetailPage and EventsPage render the shared ErrorState with a retry
  instead of a false 'Listing not found' / 'No upcoming events'.
2026-10-02 21:36:44 +07:00
SkateSpots
9189fc6584 fix(trust): tier 2 — moderation, labels & data integrity (H2–H17)
- H2 useAdminModeration now fetches the 'cleared' label too, so an admin Undo
  actually propagates (newest-verdict-wins) instead of only working on-device.
- H3 admin takedowns publish the NIP-32 'hidden' label (useAdminHide) instead of
  an author-only kind-5 relays ignore; the cross-app hide is real now.
- H5 the /stats outage banner works: queryCanonical reports relay failures via
  onError, so 'partial' can be true and zeros are never presented as truth.
- H6 useShops got its own cache namespace (was colliding with useSkateShops).
- H7 listing NIP-05 is verified against the user's key, not merely self-asserted.
- H8 NIP-22 spot comments/transfer now use uppercase root tags (A/K/P); readers
  accept legacy #a; NIP.md corrected.
- H9 Lab profile edits preserve foreign  spot refs (crew link = 35006 only).
- H10 collection edits preserve foreign NIP-51 tags.
- H11 LAB_KINDS = all 13 provider kinds (was 35001-35005).
- H16 skip-to-content is a button that focuses <main> (was href=#main, which
  HashRouter treated as a route → 404).
- H17 CustomizeNavSheet is translated; useLocalStorage re-reads on key change and
  keeps stateRef in sync cross-tab.
- Backup 'confirmed' flag is now per-account (backup-confirmed:<pubkey>).
2026-10-02 21:30:01 +07:00