diff --git a/frontend/src/hooks/usePreloadOrchestrator.ts b/frontend/src/hooks/usePreloadOrchestrator.ts index 8faaf4a..650fe2b 100644 --- a/frontend/src/hooks/usePreloadOrchestrator.ts +++ b/frontend/src/hooks/usePreloadOrchestrator.ts @@ -257,6 +257,8 @@ export function usePreloadOrchestrator( // Map of original URL → decoded blob URL (ready to display instantly) const readyBlobUrlsRef = useRef>(new Map()); + // Set of URLs that failed cache lookup (prevents infinite retry loops) + const failedCacheLookupRef = useRef>(new Set()); // Use neighbor graph for determining what to preload const neighborGraph = useNeighborGraph({ @@ -278,11 +280,40 @@ export function usePreloadOrchestrator( const createReadyBlobUrl = useCallback( async (url: string, storageKey?: string): Promise => { try { - // Get blob from Cache API - const blob = await StorageManager.getAsset(url); + // Skip if we already know this URL is not in cache (prevents infinite loops) + if (failedCacheLookupRef.current.has(url)) { + return; + } + if (storageKey && failedCacheLookupRef.current.has(storageKey)) { + return; + } + + // Try multiple URL formats to handle key mismatches + // (presigned URL vs proxy URL vs storage key) + let blob = await StorageManager.getAsset(url); + + // If not found and we have a storage key, try that too + if (!blob && storageKey && storageKey !== url) { + blob = await StorageManager.getAsset(storageKey); + } + + // Also try with the proxy URL format as fallback + if (!blob && storageKey) { + const proxyUrl = `${baseURLApi}/file/download?privateUrl=${encodeURIComponent(storageKey.replace(/^\/+/, ''))}`; + if (proxyUrl !== url) { + blob = await StorageManager.getAsset(proxyUrl); + } + } + if (!blob) { + // Mark as failed to prevent repeated lookups + failedCacheLookupRef.current.add(url); + if (storageKey) { + failedCacheLookupRef.current.add(storageKey); + } logger.info('[PRELOAD] No blob found in cache', { url: url.slice(-50), + storageKey: storageKey?.slice(-50), }); return; } @@ -370,6 +401,11 @@ export function usePreloadOrchestrator( logger.info('[PRELOAD] Download complete', { url: item.url.slice(-50), }); + // Clear failed cache lookup status since we just downloaded fresh + failedCacheLookupRef.current.delete(item.url); + if (item.storageKey) { + failedCacheLookupRef.current.delete(item.storageKey); + } await createReadyBlobUrl(item.url, item.storageKey); if (isPresignedUrl(item.url)) { markPresignedUrlsVerified();