From 6ad112dea37643bc50af55a21201c5dd65d9352b Mon Sep 17 00:00:00 2001 From: "shakespeare.diy" Date: Tue, 8 Sep 2026 00:19:10 +0700 Subject: [PATCH] Spot detail audit: dedup claim provenance tags (1.2.40) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Audited the spot detail surface — status reports, ownership, and community contributions. It's well-built: STATUS (useSpotStatus) — a proper trust×recency consensus: each report weighted by recencyWeight (30-day half-life) × trustWeight (0.2 floor so everyone counts, trusted users dominate), only the newest report per reporter counts (no ballot stuffing), reports past 180 days are dropped, and the parser rejects any unrecognised status value so garbage can't poison the tally. Single + batch paths both invalidate on publish. CONTRIBUTIONS — photos (kind 1063) and tag suggestions (kind 1111) are SEPARATE events referencing the spot via #a, so contributors never touch the owner's addressable spot event (no clobber). Tag votes dedupe per contributor. The detail gallery spreads owner images first so the hero is always the owner's photo when present; community shots are badged. OWNERSHIP — transfer/claim model is intentional (claiming republishes under the claimer's own key = their own coordinate; the original survives, as the UI states). One real rough edge: handleClaim copied ALL original tags then appended claim-from/claim-reason, so claiming a spot that was itself a prior claim would STACK multiple provenance pairs on the event. Fixed by filtering out any existing claim-from/claim-reason before appending a single fresh pair pointing at the immediate prior owner. No functional/authorization change. Co-authored-by: shakespeare.diy --- package.json | 2 +- src/components/SpotOwnership.tsx | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index ce4575b..3d07202 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "skate-spots", "private": true, - "version": "1.2.39", + "version": "1.2.40", "type": "module", "homepage": "https://skate-spots.com", "scripts": { diff --git a/src/components/SpotOwnership.tsx b/src/components/SpotOwnership.tsx index 9e7f55b..fde9421 100644 --- a/src/components/SpotOwnership.tsx +++ b/src/components/SpotOwnership.tsx @@ -148,12 +148,15 @@ export function SpotOwnership({ spot }: SpotOwnershipProps) { // This creates a new addressable event with the same d-tag but new owner const eventTags: string[][] = []; - // Copy all original tags except the ones we're changing + // Copy all original tags EXCEPT provenance markers we're about to (re)set. + // Without this filter, claiming a spot that was itself a prior claim would + // stack multiple claim-from/claim-reason pairs on the event. for (const tag of spot.event.tags) { + if (tag[0] === 'claim-from' || tag[0] === 'claim-reason') continue; eventTags.push([...tag]); } - // Add claim reference + // Add a single, fresh claim reference pointing at the immediate prior owner. eventTags.push(['claim-from', spot.pubkey]); eventTags.push(['claim-reason', isAbandoned ? 'abandoned' : 'transfer']);