Description
The notification detail image cell (NoteBlockImageTableViewCell) never retries a remote image download that fails on its first attempt. If the request fails — flaky network, transient 5xx, offline — the cell is left blank for the rest of that viewing session, even after connectivity is restored. There is no failure: handler wired up and no retry at this layer (the retry loop in NotificationMediaDownloader is a separate code path this cell does not use).
- Expected: a failed image load recovers — retried when connectivity returns or the cell is reconfigured, or at minimum not permanently stuck for the session.
- Actual: the image area stays empty until the cell is reused or bound to a different URL.
Root Cause
NoteBlockImageTableViewCell.downloadImage(_:) sets imageURL optimistically, before the download resolves, and never resets it on failure:
@objc func downloadImage(_ url: URL?) {
guard imageURL != url else {
return // ← short-circuits a same-URL re-bind
}
imageURL = url // ← set before the download completes; never cleared on failure
blockImageView.downloadImage(from: url)
}
The imageURL != url guard exists to skip redundant re-downloads of an image we're already showing. But because imageURL is assigned up front, the guard cannot distinguish "already showing this image" from "attempted this image and failed." Once a download fails, any later setupImageCell call with the same URL hits the guard and returns early — the failed download is never re-attempted.
UIImageView.downloadImage(from:) does call failure?(error) on the failure path, but the cell passes no failure: closure, so there is no hook to reset state. Recovery only happens when the cell is genuinely reused (prepareForReuse resets imageURL = nil) — e.g. navigating away and back — or when it is bound to a different URL.
Step-by-step reproduction instructions
- Enable Airplane Mode (or a Network Link Conditioner profile with 100% loss).
- Open a notification whose detail view contains a remote image (e.g. an achievement / milestone notification).
- Observe the image area is blank — the download failed.
- Disable Airplane Mode and wait.
- The image never appears, despite connectivity being restored, as long as you stay on the screen.
- Navigate away and back — the image now loads (the cell was reused).
Screenshots, screen recording, code snippet
See the snippet under Root Cause. File: WordPress/Classes/ViewRelated/Notifications/Views/NoteBlockImageTableViewCell.swift.
Scope / notes
- Pre-existing. Surfaced during code review of the notification image spring-animation removal (branch
bugfix/CMM-2157-notification-animation-v1); not introduced by that change.
- Confirmed by code inspection; not yet reproduced on-device.
- Fix sketch: pass a
failure: closure to blockImageView.downloadImage(from:) that resets imageURL = nil, so a later re-bind retries. Keep the optimistic assignment for in-flight dedupe — the image-view layer already calls cancelImageDownload() on re-entry. Moving the assignment entirely into the success path would fix retries but lose cell-level dedupe of concurrent same-URL calls, so the failure:-reset approach is the narrower fix.
Description
The notification detail image cell (
NoteBlockImageTableViewCell) never retries a remote image download that fails on its first attempt. If the request fails — flaky network, transient 5xx, offline — the cell is left blank for the rest of that viewing session, even after connectivity is restored. There is nofailure:handler wired up and no retry at this layer (the retry loop inNotificationMediaDownloaderis a separate code path this cell does not use).Root Cause
NoteBlockImageTableViewCell.downloadImage(_:)setsimageURLoptimistically, before the download resolves, and never resets it on failure:The
imageURL != urlguard exists to skip redundant re-downloads of an image we're already showing. But becauseimageURLis assigned up front, the guard cannot distinguish "already showing this image" from "attempted this image and failed." Once a download fails, any latersetupImageCellcall with the same URL hits the guard and returns early — the failed download is never re-attempted.UIImageView.downloadImage(from:)does callfailure?(error)on the failure path, but the cell passes nofailure:closure, so there is no hook to reset state. Recovery only happens when the cell is genuinely reused (prepareForReuseresetsimageURL = nil) — e.g. navigating away and back — or when it is bound to a different URL.Step-by-step reproduction instructions
Screenshots, screen recording, code snippet
See the snippet under Root Cause. File:
WordPress/Classes/ViewRelated/Notifications/Views/NoteBlockImageTableViewCell.swift.Scope / notes
bugfix/CMM-2157-notification-animation-v1); not introduced by that change.failure:closure toblockImageView.downloadImage(from:)that resetsimageURL = nil, so a later re-bind retries. Keep the optimistic assignment for in-flight dedupe — the image-view layer already callscancelImageDownload()on re-entry. Moving the assignment entirely into thesuccesspath would fix retries but lose cell-level dedupe of concurrent same-URL calls, so thefailure:-reset approach is the narrower fix.