7367595e71
Final slice of the per-item rendering pass. Wraps every tile widget in an AnimatedSwitcher between the skeleton placeholder and the real card. 220ms cross-fade with easeOut: tiles "settle into place" rather than hard-cutting from shimmer to content. Since each tile fades independently as its data lands — and the HydrationQueue's concurrency cap drains in a natural cascade — the overall feel is the staged "page builds piece by piece" effect we wanted, with no per-tile position math required. Bumps CachedNetworkImage fadeInDuration from zero to 120ms (server_ image.dart, discover_screen.dart). Imperceptible on cache hits since the image decodes synchronously; on cache misses the bytes fade in smoothly instead of popping. Slice 1's "zero fade" call was right about the 500ms default being a regression, but 120ms threads the needle. Playlist detail wraps its body in the same AnimatedSwitcher so the cold-load skeleton page cross-fades into the real track list. Tiles affected: home _AlbumTile / _ArtistTile / _TrackTile + liked _LikedAlbumTile / _LikedArtistTile / _LikedTrackRow + playlist _SkeletonBody / _Body. All keyed via ValueKey so AnimatedSwitcher detects the transition. End of the per-item pass. Net behavior: cold visits paint shaped pages instantly with skeletons, content cascades in as hydration lands; warm visits paint fully from drift in the first frame.