From 628c13c644f729a9e7e4facfa17e78ab708ce0bf Mon Sep 17 00:00:00 2001 From: Padreug Date: Sun, 3 May 2026 09:55:44 +0200 Subject: [PATCH] fix(market): resolve stall_id from a-tag when content omits it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NIP-15 lists stall_id inside the JSON content of kind-30018 product events, but some publishers (older nostrmarket builds, third-party clients) omit the field and only emit the parent reference via the a-tag of the form ["a", "30017::"]. Adds resolveStallId(event, productData) which: 1. Reads productData.stall_id when present (the spec-canonical path) 2. Falls back to the a-tag prefixed "30017:" when content omits it 3. Returns 'unknown' as a sentinel that won't match any real stall Both code paths in useMarket.ts (loadProducts batch and handleProductEvent live-update) now use it. Combined with the addStall sweep from eb3393f, products eventually link to their parent stall regardless of order or which form the publisher used. This DOES NOT fix orphan products whose referenced stall genuinely isn't on the relay — those still render "Unknown Stall" because no stall exists to link to. Investigating that separately. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/modules/market/composables/useMarket.ts | 30 +++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/modules/market/composables/useMarket.ts b/src/modules/market/composables/useMarket.ts index 47ccaa0..66af02c 100644 --- a/src/modules/market/composables/useMarket.ts +++ b/src/modules/market/composables/useMarket.ts @@ -14,6 +14,32 @@ const MARKET_EVENT_KINDS = { PRODUCT: 30018 } as const +/** + * Resolve a product's parent stall id from the event. + * + * NIP-15 lists `stall_id` inside the JSON `content`, but some publishers + * (older nostrmarket builds, third-party clients) only emit the parent + * reference via an `a` tag of the form + * ["a", "30017::"] + * + * Read content first, then fall back to the tag, then a sentinel that won't + * match any real stall. Returning the tag form prevents "Unknown Stall" + * from sticking when the JSON omits the field. + */ +function resolveStallId(event: any, productData: any): string { + if (productData?.stall_id && typeof productData.stall_id === 'string') { + return productData.stall_id + } + const aTag = event.tags?.find( + (t: any) => Array.isArray(t) && t[0] === 'a' && typeof t[1] === 'string' && t[1].startsWith(`${MARKET_EVENT_KINDS.STALL}:`) + ) + if (aTag) { + const parts = aTag[1].split(':') + if (parts[2]) return parts[2] + } + return 'unknown' +} + export function useMarket() { const marketStore = useMarketStore() const relayHub = injectService(SERVICE_TOKENS.RELAY_HUB) as any @@ -315,7 +341,7 @@ export function useMarket() { try { const productData = JSON.parse(latestEvent.content) - const stallId = productData.stall_id || 'unknown' + const stallId = resolveStallId(latestEvent, productData) // Extract categories from Nostr event tags (standard approach) const categories = latestEvent.tags @@ -515,7 +541,7 @@ export function useMarket() { const productId = event.tags.find((tag: any) => tag[0] === 'd')?.[1] if (productId) { const productData = JSON.parse(event.content) - const stallId = productData.stall_id || 'unknown' + const stallId = resolveStallId(event, productData) // Extract categories from Nostr event tags (standard approach) const categories = event.tags