diff --git a/src/hooks/useArticleLoader.ts b/src/hooks/useArticleLoader.ts index dcd2d85e..159e1f93 100644 --- a/src/hooks/useArticleLoader.ts +++ b/src/hooks/useArticleLoader.ts @@ -72,15 +72,26 @@ export function useArticleLoader({ useEffect(() => { mountedRef.current = true - if (!relayPool || !naddr) return + if (!relayPool || !naddr) { + console.log('[article-loader] Skipping load - missing relayPool or naddr', { hasRelayPool: !!relayPool, hasNaddr: !!naddr }) + return + } + + console.log('[article-loader] Starting load for naddr:', naddr) // Synchronously check cache sources BEFORE starting async loading // This prevents showing loading skeletons when content is immediately available // Do this outside the async function for immediate execution try { + console.log('[article-loader] Checking localStorage cache...') // Check localStorage cache first (synchronous) const cachedArticle = getFromCache(naddr) if (cachedArticle) { + console.log('[article-loader] ✅ Cache HIT - loading from localStorage', { + title: cachedArticle.title, + hasMarkdown: !!cachedArticle.markdown, + markdownLength: cachedArticle.markdown?.length + }) const title = cachedArticle.title || 'Untitled Article' setCurrentTitle(title) setReaderContent({ @@ -146,16 +157,24 @@ export function useArticleLoader({ } // Return early - we have cached content, no need to query relays + console.log('[article-loader] Returning early with cached content') return + } else { + console.log('[article-loader] ❌ Cache MISS - not found in localStorage') } } catch (err) { // If cache check fails, fall through to async loading - console.warn('Cache check failed:', err) + console.warn('[article-loader] Cache check failed:', err) } const loadArticle = async () => { const requestId = ++currentRequestIdRef.current - if (!mountedRef.current) return + console.log('[article-loader] Starting async loadArticle function', { requestId }) + + if (!mountedRef.current) { + console.log('[article-loader] Component unmounted, aborting') + return + } setSelectedUrl(`nostr:${naddr}`) setIsCollapsed(true) @@ -167,14 +186,22 @@ export function useArticleLoader({ // Check eventStore for instant load (from bookmark cards, explore, etc.) // Cache was already checked synchronously above, so this only handles EventStore if (eventStore) { + console.log('[article-loader] Checking EventStore...') try { // Decode naddr to get the coordinate const decoded = nip19.decode(naddr) if (decoded.type === 'naddr') { const pointer = decoded.data as AddressPointer const coordinate = `${pointer.kind}:${pointer.pubkey}:${pointer.identifier}` + console.log('[article-loader] Looking for event with coordinate:', coordinate) const storedEvent = eventStore.getEvent?.(coordinate) if (storedEvent) { + console.log('[article-loader] ✅ EventStore HIT - found event', { + id: storedEvent.id, + kind: storedEvent.kind, + hasContent: !!storedEvent.content, + contentLength: storedEvent.content?.length + }) const title = Helpers.getArticleTitle(storedEvent) || 'Untitled Article' setCurrentTitle(title) const image = Helpers.getArticleImage(storedEvent) @@ -197,17 +224,24 @@ export function useArticleLoader({ // If we found the content in EventStore, we can return early // This prevents unnecessary relay queries when offline + console.log('[article-loader] Returning early with EventStore content') return + } else { + console.log('[article-loader] ❌ EventStore MISS - no event found for coordinate:', coordinate) } } } catch (err) { // Ignore store errors, fall through to relay query + console.warn('[article-loader] EventStore check failed:', err) } + } else { + console.log('[article-loader] No EventStore available, skipping check') } // At this point, we've checked EventStore and cache - neither had content // Only show loading skeleton if we also don't have preview data if (previewData) { + console.log('[article-loader] Using preview data (no skeleton)', { title: previewData.title }) // If we have preview data from navigation, show it immediately (no skeleton!) setCurrentTitle(previewData.title) setReaderContent({ @@ -221,11 +255,13 @@ export function useArticleLoader({ setReaderLoading(false) // Turn off loading immediately - we have the preview! } else { // No cache, no EventStore, no preview data - need to load from relays + console.log('[article-loader] ⚠️ No cache, EventStore, or preview - showing loading skeleton and querying relays') setReaderLoading(true) setReaderContent(undefined) } try { + console.log('[article-loader] Querying relays for article...') // Decode naddr to filter const decoded = nip19.decode(naddr) if (decoded.type !== 'naddr') { @@ -237,15 +273,34 @@ export function useArticleLoader({ authors: [pointer.pubkey], '#d': [pointer.identifier] } + console.log('[article-loader] Relay query filter:', filter) let firstEmitted = false let latestEvent: NostrEvent | null = null // Stream local-first via queryEvents; rely on EOSE (no timeouts) + console.log('[article-loader] Starting queryEvents...') const events = await queryEvents(relayPool, filter, { onEvent: (evt) => { - if (!mountedRef.current) return - if (currentRequestIdRef.current !== requestId) return + if (!mountedRef.current) { + console.log('[article-loader] Component unmounted during event stream, ignoring') + return + } + if (currentRequestIdRef.current !== requestId) { + console.log('[article-loader] Request ID mismatch, ignoring event', { + currentRequestId: currentRequestIdRef.current, + eventRequestId: requestId + }) + return + } + + console.log('[article-loader] 📨 Received event from relay', { + id: evt.id, + kind: evt.kind, + created_at: evt.created_at, + contentLength: evt.content?.length, + isFirst: !firstEmitted + }) // Store in event store for future local reads try { @@ -262,6 +317,7 @@ export function useArticleLoader({ // Emit immediately on first event if (!firstEmitted) { + console.log('[article-loader] ✅ First event received - updating UI immediately') firstEmitted = true const title = Helpers.getArticleTitle(evt) || 'Untitled Article' setCurrentTitle(title) @@ -282,15 +338,31 @@ export function useArticleLoader({ setCurrentArticleEventId(evt.id) setCurrentArticle?.(evt) setReaderLoading(false) + console.log('[article-loader] UI updated with first event') } } }) - if (!mountedRef.current || currentRequestIdRef.current !== requestId) return + console.log('[article-loader] QueryEvents completed', { + eventCount: events.length, + hasLatestEvent: !!latestEvent, + mounted: mountedRef.current, + requestIdMatch: currentRequestIdRef.current === requestId + }) + + if (!mountedRef.current || currentRequestIdRef.current !== requestId) { + console.log('[article-loader] Component unmounted or request ID changed, aborting') + return + } // Finalize with newest version if it's newer than what we first rendered const finalEvent = (events.sort((a, b) => b.created_at - a.created_at)[0]) || latestEvent if (finalEvent) { + console.log('[article-loader] ✅ Finalizing with event', { + id: finalEvent.id, + created_at: finalEvent.created_at, + wasFirstEmitted: firstEmitted + }) const title = Helpers.getArticleTitle(finalEvent) || 'Untitled Article' setCurrentTitle(title) const image = Helpers.getArticleImage(finalEvent) @@ -310,9 +382,16 @@ export function useArticleLoader({ setCurrentArticleCoordinate(articleCoordinate) setCurrentArticleEventId(finalEvent.id) setCurrentArticle?.(finalEvent) + console.log('[article-loader] ✅ Finalized with event from relays') } else { // As a last resort, fall back to the legacy helper (which includes cache) + console.log('[article-loader] ⚠️ No events from relays, falling back to fetchArticleByNaddr') const article = await fetchArticleByNaddr(relayPool, naddr, false, settingsRef.current) + console.log('[article-loader] fetchArticleByNaddr result:', { + hasArticle: !!article, + title: article?.title, + hasMarkdown: !!article?.markdown + }) if (!mountedRef.current || currentRequestIdRef.current !== requestId) return setCurrentTitle(article.title) setReaderContent({ diff --git a/src/services/articleService.ts b/src/services/articleService.ts index a279885d..f210fc2d 100644 --- a/src/services/articleService.ts +++ b/src/services/articleService.ts @@ -37,19 +37,36 @@ function getCacheKey(naddr: string): string { export function getFromCache(naddr: string): ArticleContent | null { try { const cacheKey = getCacheKey(naddr) + console.log('[article-cache] Checking cache with key:', cacheKey) const cached = localStorage.getItem(cacheKey) - if (!cached) return null + if (!cached) { + console.log('[article-cache] ❌ No cached entry found') + return null + } const { content, timestamp }: CachedArticle = JSON.parse(cached) const age = Date.now() - timestamp + console.log('[article-cache] Found cached entry', { + age: age, + ageDays: Math.floor(age / (24 * 60 * 60 * 1000)), + ttlDays: Math.floor(CACHE_TTL / (24 * 60 * 60 * 1000)), + isExpired: age > CACHE_TTL + }) if (age > CACHE_TTL) { + console.log('[article-cache] ⚠️ Cache expired, removing') localStorage.removeItem(cacheKey) return null } + console.log('[article-cache] ✅ Cache valid, returning content', { + title: content.title, + hasMarkdown: !!content.markdown, + markdownLength: content.markdown?.length + }) return content - } catch { + } catch (err) { + console.warn('[article-cache] Error reading cache:', err) return null } }