From 56e2b17990f8f749be2194712a64994983f2cb9b Mon Sep 17 00:00:00 2001 From: "shakespeare.diy" Date: Tue, 8 Sep 2026 00:09:00 +0700 Subject: [PATCH] Lab/Marketplace/Stash audit: useLabProfile picks newest revision (1.2.38) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Audited all three remaining surfaces. Two of them needed no changes: MARKETPLACE / GEARSWAP — sound. useMarkListingSold preserves every tag and flips only `status` (author-gated, right comment explaining why it doesn't rebuild). Queries validate + dedupe, exclude showcase-only Stash items from the sellable market, and GearSwap filters out expired/sold listings and separates WTB from WTS. No bugs. STASH — sound, and notably careful. The private list is one NIP-44 encrypted-to-self blob; all three mutations (add/update/remove) use fetchLatestStash() (fetch-before-write) which falls back to non-empty cache and THROWS rather than risk clobbering the user's gear, and is Amber-gated (staleTime: Infinity). The public/private showcase reconciliation in StashPage is complete: going public publishes the kind-30402 showcase event, and going private / deleting / converting to a wishlist item all call unpublishItem — so a public event can't linger after an item is made private (no privacy leak). No bugs. THE LAB — the collective join-request clobber was already fixed in 1.2.37. One robustness gap remained: useLabProfile fetched the profile with limit:1 and trusted whatever came back. Relays can hold multiple revisions of an addressable event, so a stale revision could win — showing an old bio or, worse, a roster missing recently-approved members. Now fetches a few (limit:10) and reduces to the newest created_at, matching the pickMostRecent discipline already in useCrew. On detail pages an author filter is always set, so newest-of-one-author is exactly right. Co-authored-by: shakespeare.diy --- package.json | 2 +- src/hooks/useLabProfiles.ts | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index b75227e..b60f59e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "skate-spots", "private": true, - "version": "1.2.37", + "version": "1.2.38", "type": "module", "homepage": "https://skate-spots.com", "scripts": { diff --git a/src/hooks/useLabProfiles.ts b/src/hooks/useLabProfiles.ts index 2470d95..2f93173 100644 --- a/src/hooks/useLabProfiles.ts +++ b/src/hooks/useLabProfiles.ts @@ -149,7 +149,10 @@ export function useLabProfile(identifier: string, pubkey?: string) { } = { kinds: LAB_KINDS, '#d': [identifier], - limit: 1, + // Relays can hold multiple revisions of an addressable event; fetch a + // few and pick the newest rather than trusting whatever `limit: 1` + // returns (which may be a stale revision, dropping members/edits). + limit: 10, }; if (pubkey) filter.authors = [pubkey]; @@ -158,7 +161,12 @@ export function useLabProfile(identifier: string, pubkey?: string) { if (events.length === 0) return null; - return parseLabProfile(events[0]); + // Newest revision wins. + const newest = events.reduce((latest, current) => + current.created_at > latest.created_at ? current : latest, + ); + + return parseLabProfile(newest); }, enabled: !!identifier, staleTime: 30000,