Appearance
Explorer could keep displaying code placeholders for entire blocks and sets, and blank card artwork, after a temporary image failure. This change reduces duplicate prefetch traffic and lets visible images recover without clearing the user's cache or leaving the screen.
Evidence
Read-only production inspection found 1,309 catalogue-image HTTP 429 responses at 2026-11-04 01:16 UTC, matching the screenshots' 02:16 Paris timestamp. The sanitized logs do not identify a device or distinguish individual image URLs, so they cannot establish that every request came from the reporting tester. Sample block and set logos subsequently returned HTTP 200 with valid WebP content and the existing five-minute public cache header.
Code inspection and regression tests established two independent defects:
- The backend shared one 1,200/minute/IP budget between card artwork and logos. Exhausting the artwork allowance also denied logos.
- The app kept native image failures until a source change/remount. Price-driven list rerenders resubmitted detail-image prefetches without in-flight deduplication.
These findings explain the observed failure pattern; they do not prove the origin of every request in the production burst.
Fix
Backend PR #654 separates logo and artwork budgets, each still limited to 1,200 requests per minute per client IP. Successful images retain public, max-age=300 caching. Failures, including 404/429/502, return no-store. Existing publication checks, private-path restrictions, size limits and signature validation remain intact. Tracking: backend issue #653.
Mobile PR #1014 adds at most three staggered retries to block/set logos and card-grid images. The delays are 2, 15 and 60 seconds, plus up to two seconds of jitter, measured after each failure; the last retry outlives a one-minute rate-limit window. Source changes and unmounts cancel timers; stale callbacks cannot hide a newer image. A permanently missing asset eventually keeps its fallback.
The set-detail prefetch scheduler retains at most two in-flight requests, replaces obsolete queued windows and remembers at most 256 recent URLs. It checks the native image cache, deduplicates successful warmups for five minutes and backs off failed prefetches for one minute. The original backend URLs are preserved: no random query parameters, cache purge, business metadata override or valuation-cache changes.
Validation
- Backend TypeScript build and full API suite: 1,678 passed, 42 skipped, zero failures. All 39 catalogue-media/set-logo tests pass, including independent logo limits, per-client isolation and uncached-error recovery.
- Mobile ESLint and TypeScript pass. Full Jest: 351 suites / 3,444 tests passed; 2 suites / 11 tests skipped. Native error callbacks on both affected image components are exercised, together with retry exhaustion, cleanup, cache reuse, prefetch concurrency and rapidly changing windows.
- Device/Maestro E2E was not run: Maestro and an iPhone are unavailable on the development VPS. No emulator, personal account or collection was modified.
GitHub mobile CI passed. Backend probe/build and both API/ingest unit/typecheck jobs passed. The two Docker-dependent CI jobs fail before running tests because the snag-k8s runner has no /var/run/docker.sock; no runner or workflow was changed in this fix.
As a fallback, the development VPS ran the ingest integration (passed) and the general API e2e.test.ts suite (40 passed, 1 failed), with fresh named containers bound only to localhost. Test assertions were unchanged. The failing session migration fixture attempts to add a card as an unverified guest and expects 201, but receives the current 403. The identical failure was reproduced on unmodified staging commit 70c379e; it is not caused by the image patch. Other specialized API E2E files were not run in this fallback. Temporary containers were removed.
Rollout, manual check and rollback
The PRs target staging. Creating or merging a PR does not deploy it. The API change can deploy independently and requires no migration or asset rewrite. The mobile recovery and scheduling improvements require a new app build.
After deployment/build distribution, on an iPhone and Android:
- Open several Pokemon and One Piece card series, scroll their grids and return to Explorer. Confirm block/set logos and card artwork stay visible in both themes.
- With a dedicated test installation, interrupt connectivity while images load, then restore it. Keep the same screen open and allow the bounded retries to complete; do not clear storage or add/remove collection items.
- Scroll quickly or leave a series while requests are pending. Confirm the app shows the current cards, does not reintroduce old imagery, and remains responsive.
- Use read-only aggregate response counts to check 429 frequency. Do not flood production merely to exhaust its limits.
Rollback the API to its previous image to restore the shared allowance. Roll back the app commit/build separately if needed. Neither rollback needs a data restore or changes to the user's account, collection or prices.