77ae94e649
A rejected fetch in AuthenticatedImage set the error state, rendered nothing, and never asked again. The fetch effect only re-runs when its inputs change, and for a grid tile they never do — so a transient failure (a hiccup on cellular, or Safari cancelling loads when the tab goes to the background) was a permanently blank tile with no request in flight and nothing in any log. Grid passes no fallbackSrc, so there was not even a broken-image icon to point at. The retry is bounded and gated. Three attempts with a doubling delay (2 s, 4 s, 8 s), and an attempt fires only once the placeholder intersects the viewport and the document is visible, so a tile that failed while the user was away retries when they come back rather than while they are still gone. A new src gets a fresh budget. The fallbackSrc path is untouched: it already renders a plain <img> and should not loop. Two refinements from review. A final 4xx (anything but 408 and 429) exhausts the budget at once: an expired gallery token or a missing photo cannot be retried into existence, and on a 68-tile viewport three retries each would be ~200 requests that cannot succeed. And a 429's Retry-After is honoured as the minimum delay, because the backoff alone would spend every retry inside a 15-minute rate-limit window and leave the tile blank after the limit had lifted. Retry-After is not CORS-safelisted, so server.js now exposes it for split-origin deployments alongside Content-Disposition. The error branch now renders the same grey box as the loading state instead of null. That is what the retry effect observes, and it is also something the user can see. The empty-src branch now clears the error flag too, so a tile whose src is removed after a failure does not keep showing the failure box. Nine tests in AuthenticatedImage.retry.test.tsx; the retry cases fail against the previous version. Not presented as the fix for the iOS report. It closes the one gap that turns a transient failure into a permanent one, which the reporter asked for in the original issue, and it is worth having on any device. Relates to issue 1287