Repository navigation
fix(dashboard): keep rows loading when the split view re-adds Home - #1097
Merged
Merged
Conversation
On iPad, switching the sidebar back to Home (for example after reading a book opened from a read list) left the dashboard rows on stale cached items until pull-to-refresh. Selecting Home swaps the root of the split view's detail NavigationStack, and SwiftUI adds, removes (about 1 ms later), and re-adds the dashboard while keeping its state. Each row loaded in its view .task, which the removal cancelled mid-request; on re-add the task ran again but the row's load-once flag returned early, so the row kept its cache seed. Pinned rows re-ran their .task(id:), but their isLoading guard dropped the re-run while the cancelled one was unwinding, skipping their sync. Rows now load through view models they own (DashboardSectionViewModel, DashboardPinnedSectionViewModel). Loads run in tasks the model owns, so view cancellation cannot interrupt them; appearing starts a load only when none has completed or is running (pinned rows refresh on every appear but join a running refresh), and a reload supersedes a load in flight instead of being dropped by its guard. The reader's close refresh still has no mounted dashboard to reach while another sidebar page is showing; the dashboard created when Home is selected loads current data, so it no longer matters. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
everpcpc
added a commit
that referenced
this pull request
Sep 25, 2026
…anup (#1101) Follow-up nits from the review of #1097, no behavior change. DashboardPinnedSectionViewModel.refreshIfIdle is now refreshIfNeeded — when a refresh is running for a different server it cancels and restarts it, so the old name undersold the behavior. The pass-through computed properties in DashboardSectionView and DashboardPinnedSectionView are gone; the views read the view model directly. AGENTS.md's dashboard row rule now states that pinned rows join a running refresh for the same server, matching the guard the code implements.
spawvn
added a commit
to spawvn/KMReader
that referenced
this pull request
Sep 25, 2026
Resolves the conflict with kmworks#1097, which moved dashboard row loading into DashboardSectionViewModel: the On Deck read list merge now runs in the view model's loadPage (after the fetch, re-checking the load id after the merge) and the row view's snapshot observer calls reloadIfReadListsOutdated, which supersedes through startReload. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1096
Problem
On iPad, switching the sidebar back to Home leaves the dashboard rows on stale cached content until pull-to-refresh. It's most visible after reading a book opened from another page. Cause: SwiftUI adds, removes (about 1 ms later), and re-adds the dashboard while keeping its state. Each row's view
.taskis cancelled mid-request, and its load-once flag (pinned rows:isLoadingguard) then blocks the retry. Full trace in #1096.Approach
Rows load through view models they own, following the same direction as #1069's
OfflineViewbrowse models:DashboardSectionViewModelowns the row's pagination and its load task. The view's.taskis gone:onAppearcallsensureLoaded(), which starts a first-page load only when none has completed or is running. A load in flight survives the view being removed and re-added.reload()cancels a load in flight and restarts from the first page instead of being dropped by anisLoadingguard, and it returns once the newest load settles, so manual refreshes still acknowledge after content lands. Superseded loads end quietly (loadID check, no error alert). Loaded items stay on screen until the new first page replaces them, as before.DashboardPinnedSectionViewModeldoes the same for pinned collections and read lists. They keep their current behavior of refreshing (local items, then sync) on every appear, but join a refresh that's already running for the same server, and a reload command restarts it.DashboardRefreshCoordinator's register/acknowledge flow is unchanged.AGENTS.mddocuments the rule: rows load through view models, never in a view.task, with no load-once flags orisLoadingguards that drop a re-run.Why this shape, not another
NavigationStack's root underNavigationSplitView, and views get removed and re-added in other cases too, like tab switches mid-load on iPhone. Loading shouldn't depend on a view task surviving.DashboardView: each row already reloads through the coordinator's commands, so nothing needs to reach the models from above. Moving them up (and replacing the acknowledgement tracking with direct awaits) is possible later without changing the rows' behavior.DashboardSectionDetailViewis untouched: it's pushed onto the stack rather than swapped in as the root, so this trigger doesn't reach it.Validation
make buildpasses on iOS, macOS, and tvOS.onAppear→onDisappear1 ms later →onAppear19 ms later. Every row's request, plus the pinned read list/collection syncs, failed withRequest cancelled. Keep Reading stayed without the book just read.Synced readlists,Synced collections), and Keep Reading lists the book just read first, with no pull-to-refresh.Manual test plan
Note
#1095 also touches
DashboardSectionView; I'll rebase whichever of the two lands second.🤖 Generated with Claude Code