Skip to content

Reader: never leave the post detail stuck on an endless spinner (CMM-2254) - #23184

Open
jkmassel wants to merge 3 commits into
trunkfrom
jkmassel/reader-changes-27-0
Open

Reader: never leave the post detail stuck on an endless spinner (CMM-2254)#23184
jkmassel wants to merge 3 commits into
trunkfrom
jkmassel/reader-changes-27-0

Conversation

@jkmassel

@jkmassel jkmassel commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

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

  • The post detail now always resolves to content or an error — never a permanent spinner.
  • Root cause: fetch failures were silently swallowed whenever any cached post object existed, even a header-only one with no body.
  • Hardens ReaderFetchPostUseCase with 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 on post != 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 — ReaderPostDetailViewModel

  • Gate the optimistic render on post?.hasText() (a renderable body) instead of post != null. A header-only post now shows the managed progress_loading spinner — which the existing ErrorUiState path already tears down — instead of the unmanaged WebView progress bar.
  • Surface an error on every terminal non-success outcome that leaves us without renderable content (NoNetwork, RequestFailed, NotAuthorised, PostNotFound).
  • Apply the same hasText() test on Success: 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.
  • Posts that already have a body are unchanged — they render immediately and a background-refresh failure stays silent.

2. Harden the fetch — ReaderFetchPostUseCase

  • Wrap the request in withTimeoutOrNull (30s) so a stalled connection that never calls back resolves to RequestFailed instead of suspending forever.
  • Replace the nullable continuations map + resume-by-key with an in-flight Set released in a finally, so a request cancelled mid-flight (user backs out) or timed out no longer leaves a sticky AlreadyRunning. The callback now resumes its own continuation guarded by isActive, 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; Success with a missing post or an empty body shows an error; an empty background refetch keeps the rendered content; a concurrent AlreadyRunning fetch holds the loading state.
  • ReaderFetchPostUseCaseTest (10 cases pass) — a request that never responds resolves to RequestFailed (timeout); a second request for the same post returns AlreadyRunning.

Related issues

  • CMM-2254

🤖 Generated with Claude Code

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
@wpmobilebot

wpmobilebot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

App Icon📲 You can test the changes from this Pull Request in WordPress Android by scanning the QR code below to install the corresponding build.

App NameWordPress Android
Build TypeDebug
Versionpr23184-4092cc6
Build Number1498
Application IDorg.wordpress.android.prealpha
Commit4092cc6
Installation URL1h46jrp0fjrb0
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

@wpmobilebot

wpmobilebot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

App Icon📲 You can test the changes from this Pull Request in Jetpack Android by scanning the QR code below to install the corresponding build.

App NameJetpack Android
Build TypeDebug
Versionpr23184-4092cc6
Build Number1498
Application IDcom.jetpack.android.prealpha
Commit4092cc6
Installation URL16ti5nbb6ovbo
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

@wpmobilebot

Copy link
Copy Markdown
Contributor

🤖 Build Failure Analysis

This 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.
@jkmassel
jkmassel requested a review from nbradbury August 7, 2026 21:12
@codecov

codecov Bot commented Aug 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 86.36364% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 37.86%. Comparing base (5ab4806) to head (4092cc6).
⚠️ Report is 1 commits behind head on trunk.

Files with missing lines Patch % Lines
.../ui/reader/viewmodels/ReaderPostDetailViewModel.kt 84.61% 1 Missing and 3 partials ⚠️
...droid/ui/reader/usecases/ReaderFetchPostUseCase.kt 88.88% 0 Missing and 2 partials ⚠️
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@jkmassel jkmassel added this to the 27.1 milestone Aug 7, 2026
@jkmassel
jkmassel marked this pull request as ready for review August 7, 2026 21:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants