Feeds: fix pull-to-refresh cancellation
Created by: jedmund
Summary
Pull-to-refresh on Tag feed, Bookmarks tab, and pushed Profile was surfacing URLError.cancelled ("Failed to Load / Network error: cancelled"). Home worked because it's the nav-stack root; the cancellation only reproduced on pushed/tab-root hosting controllers. SwiftUI was cancelling the .refreshable action Task before the network round-trip completed, and the cancellation propagated into URLSession.data(for:).
Changes
-
IllustFeedViewModel.refreshruns the fetch insideTask.detachedso caller-task cancellation can't reachURLSession.data(for:). Stillawaitsfetch.valueto keep the spinner up through the round-trip. Also keeps existingillustsvisible during the fetch and swaps atomically on success — matches Mail / Twitter UX and avoids a mid-gesture view-tree collapse. -
ProfileView.refreshno longer re-fetchesuserDetail. Profile metadata is stable; blanking it and re-awaitingloadUserDetailreproduced the same cancellation against a different endpoint and flashed "Failed to Load" over a perfectly valid header. -
TagFeedView/ProfileViewnow keep their scroll-reactivescrollOffset/showsCenterTitlestate in a nested body subview, so scroll ticks no longer re-render the view that owns.refreshable. (Precautionary refactor; reduces body re-evaluation on scroll regardless.)
Test plan
-
Home tab: pull-to-refresh still works (regression check). -
Bookmarks tab (iPhone + iPad): pull-to-refresh; grid reloads, no error banner. -
Bookmarks: toggle Public ↔ Private, then pull-to-refresh on the new feed. -
Search → tap a tag → pull-to-refresh TagFeedView; grid reloads. -
Detail → tap user row → pushed ProfileView→ pull-to-refresh; feed reloads, header stays intact. -
Scroll-reactive chrome still animates: TagFeedViewtitle pill on scroll; pushedProfileViewcenter pill reveals past ~185pt. -
Pagination still works on all feeds (scroll to tail loads more pages).