Reader: never leave the post detail stuck on an endless spinner (CMM-2254) - #23184
Open
jkmassel wants to merge 3 commits into
Open
Reader: never leave the post detail stuck on an endless spinner (CMM-2254)#23184jkmassel wants to merge 3 commits into
jkmassel wants to merge 3 commits into
Conversation
Opening a Reader post could hang on a spinner (or a blank body) indefinitely. `getOrFetchReaderPost()` decided whether to show cached content — and whether to surface a fetch error — based on `post != null`. A post cached from the feed is often header-only (its body hasn't been fetched), so a failed or stalled body fetch was silently swallowed and the screen was left spinning with no error. Gate the optimistic render on `post?.hasText()` (a renderable body) so a header-only post shows the managed loading spinner instead of the unmanaged WebView progress bar, and surface an error on every non-success outcome — including a `Success` that yields no post. The load now always terminates in content or an error. CMM-2254
Back the detail-load fix with a fetch that can't hang: wrap the request in `withTimeoutOrNull` (30s) so a stalled connection resolves to RequestFailed instead of suspending forever. Track in-flight requests in a Set released in a `finally` so a request that is cancelled mid-flight (user backs out) or times out no longer leaves a sticky `AlreadyRunning`. The request callback now resumes its own continuation guarded by `isActive`, so a late callback can't resume a later retry of the same post. CMM-2254
Contributor
|
|
Contributor
|
|
Contributor
🤖 Build Failure AnalysisThis build has failures. Claude has analyzed them - check the build annotations for details. |
Follow-ups from reviewing the CMM-2254 fetch changes: - The `Success` path now applies the same `hasText()` renderability test as the loading gate. A fetch that succeeds but leaves no post, or a post with an empty body, surfaces a retryable error instead of a blank article; content already on screen is kept, not clobbered. - `AlreadyRunning` with nothing rendered stays on `LoadingUiState` instead of `ErrorUiState(null)`, which blanked the screen entirely on a concurrent re-entry (e.g. rotation mid-fetch). Folds the per-branch `!hasRenderableContent` guard into `renderFetchedPost` / `showFetchOutcomeWithoutContent` (dropping the `CyclomaticComplexMethod` suppress) and suppresses `ReturnCount` on the `fetchPost` guard clauses.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## trunk #23184 +/- ##
=======================================
Coverage 37.85% 37.86%
=======================================
Files 2347 2347
Lines 127701 127704 +3
Branches 17733 17734 +1
=======================================
+ Hits 48346 48352 +6
+ Misses 75401 75400 -1
+ Partials 3954 3952 -2 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
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.


Fixes a bug where opening a Reader post could hang on a spinner (or a blank article body) indefinitely, with no error and no way forward. Reported in CMM-2254 for private P2 posts on 27.0.
Summary
ReaderFetchPostUseCasewith a fetch timeout and leak-free in-flight tracking so the fetch itself can't hang.Root Cause
ReaderPostDetailViewModel.getOrFetchReaderPost()gated both its "show cached content vs. loading" decision and every fetch-failure branch onpost != null. A post cached from the feed is often header-only — its body hasn't been fetched yet. So when the body fetch then failed or stalled, all error branches (if (!hasCachedPost)) were skipped,updatePostDetailsUi()had already shown the (unmanaged) WebView progress bar, and the screen was left showing a header over a blank body with a spinner that never stopped. Confirmed against the CMM-2254 screenshots: header rendered, body blank/spinner.Fix
1. Resolve the detail load to a terminal state —
ReaderPostDetailViewModelpost?.hasText()(a renderable body) instead ofpost != null. A header-only post now shows the managedprogress_loadingspinner — which the existingErrorUiStatepath already tears down — instead of the unmanaged WebView progress bar.NoNetwork,RequestFailed,NotAuthorised,PostNotFound).hasText()test onSuccess: a fetch that yields no renderable body — a missing post or an empty body — surfaces an error rather than a blank article or a stuck spinner, while any content already on screen is kept, not clobbered.AlreadyRunning— a concurrent fetch for the same post (e.g. a re-entry after rotation) — holds the loading state and lets the in-flight request resolve the UI, rather than blanking the screen.2. Harden the fetch —
ReaderFetchPostUseCasewithTimeoutOrNull(30s) so a stalled connection that never calls back resolves toRequestFailedinstead of suspending forever.continuationsmap + resume-by-key with an in-flightSetreleased in afinally, so a request cancelled mid-flight (user backs out) or timed out no longer leaves a stickyAlreadyRunning. The callback now resumes its own continuation guarded byisActive, so a late callback can't resume a later retry of the same post.The server-side degradation that triggered the report — the private-P2 content fetch failing for some regions — is not addressed here; this change makes the client fail gracefully instead of hanging.
Test plan
ReaderPostDetailViewModelTest(63 cases pass) — body-less cached post shows loading; body-less + fetch failure shows an error;Successwith a missing post or an empty body shows an error; an empty background refetch keeps the rendered content; a concurrentAlreadyRunningfetch holds the loading state.ReaderFetchPostUseCaseTest(10 cases pass) — a request that never responds resolves toRequestFailed(timeout); a second request for the same post returnsAlreadyRunning.Related issues
🤖 Generated with Claude Code