The previous fix rebuilt the quality chain when converting persisted
models back to videos, but many consumers discarded it again by
rendering bestThumbnail (usually a maxresdefault.jpg that 404s for
older videos) as a single URL with no fallback.
Extract the retry logic from VideoThumbnailView into FallbackLazyImage
and use it at every in-app site that can iterate: player thumbnails
(mini bar, expanded sheet loaders, autoplay previews, tvOS audio-mode
artwork - previously a silent black screen), video info card and tvOS
header, and the tvOS playlist cover.
Sites that fetch or send exactly one URL are rewritten to the
always-available hqdefault variant via Thumbnail.reliableURL (exposed
as Video.reliableThumbnailURL): Now Playing artwork, Top Shelf
snapshots, remote control state, the frozen transition thumbnail,
blurred info background, navigation covers, and playlist covers
derived from a video's first thumbnail in Invidious/Yattee Server
responses.
Also invert RecentPlaylist's upgrade helper, which rewrote covers *to*
maxresdefault, walk the quality chain when caching download thumbnails
for offline artwork instead of giving up after one 404, and expand the
remaining single-thumbnail Piped conversions into full chains.
yattee-server returns direct YouTube CDN URLs in the storyboard `url`
and `templateUrl` fields instead of an Invidious-style VTT proxy path.
Two resulting issues:
- `Storyboard.directSheetURL` was replacing the whole `M$M` token with
just the index, producing `.../0.jpg` (404) instead of `.../M0.jpg`.
Replace `M$M` with `M\(index)` to preserve the literal `M` prefix;
matching the full token also avoids clobbering `$M` sequences that
may appear in `sigh=rs$...` query params.
- The download code fetched `proxyUrl` as if it were a WebVTT file;
with yattee-server that downloads a JPEG that fails UTF-8 parsing.
Skip the VTT round-trip when `proxyUrl` obviously points at an image.
Also align the on-disk filename with the local-playback template
(`sb_M$M.jpg` → `sb_M{N}.jpg`) so offline seek-bar previews resolve,
and add [Storyboard] debug logs at each decision point so future
failures can be diagnosed without guessing.
- Fix BGTaskScheduler assertion crash on Mac Catalyst by guarding all
iOS background task APIs with isMacCatalystApp check
- Fix iPad popover crash in UIPopoverPresentationController by adding
.presentationCompactAdaptation(.sheet) to all 27 confirmationDialogs
- Fix SwiftData assertion crash when accessing deleted Bookmark model
properties during SwiftUI hit testing in BookmarkRowView
- Fix UICollectionView invalid item count crash on queue swipe-to-delete
by using ID-based removal with withAnimation instead of stale index
- Fix Range crash in storyboard download when storyboardCount is zero