Skip to content

CMM-150: Scroll to restored blog post after undoing block site#22532

Merged
nbradbury merged 4 commits intotrunkfrom
issue/CMM-150-reader-delete-blog
Jan 27, 2026
Merged

CMM-150: Scroll to restored blog post after undoing block site#22532
nbradbury merged 4 commits intotrunkfrom
issue/CMM-150-reader-delete-blog

Conversation

@nbradbury
Copy link
Copy Markdown
Contributor

@nbradbury nbradbury commented Jan 26, 2026

Description

Fixes CMM-150

When a user blocks a site in the Reader and then taps "Undo", the blog posts are restored but previously there was no visual indication that the action was undone. Now the list automatically scrolls to show the first post from the restored blog, providing a clear visual cue.

Changes:

  • Add scrollToSiteId LiveData to ReaderPostCardActionsHandler that emits the blog ID after undo
  • Forward the event through ReaderPostListViewModel
  • Observe in ReaderPostListFragment and scroll to the restored blog after data loads
  • Add getPositionOfFirstPostFromBlog() method to ReaderPostAdapter

Testing instructions

Block site undo scroll:

  1. Open the Reader
  2. Find a post from a blog and tap the overflow menu (three dots)
  3. Tap "Block this site"
  • Verify the posts from that blog disappear and a snackbar appears with "Undo"
  1. Tap "Undo" on the snackbar
  • Verify the posts are restored
  • Verify the list scrolls to show the first post from the restored blog
block.mp4

When a user blocks a site and then taps "Undo", the blog posts are
restored but previously there was no visual indication. Now the list
automatically scrolls to show the first post from the restored blog.

Changes:
- Add scrollToSiteId LiveData to ReaderPostCardActionsHandler
- Forward the event through ReaderPostListViewModel
- Observe in ReaderPostListFragment and scroll after data loads
- Add getPositionOfFirstPostFromBlog() to ReaderPostAdapter

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@dangermattic
Copy link
Copy Markdown
Collaborator

dangermattic commented Jan 26, 2026

1 Warning
⚠️ PR is not assigned to a milestone.

Generated by 🚫 Danger

@nbradbury
Copy link
Copy Markdown
Contributor Author

@claude

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Jan 26, 2026

Claude finished @nbradbury's task in 1m 23s —— View job


Code Review Complete

  • Read and analyze all changed files
  • Review code quality and architecture
  • Check for security concerns
  • Evaluate performance implications
  • Assess testing needs
  • Review documentation
  • Provide comprehensive feedback

Summary

This is a well-implemented feature that adds scroll-to-restored-blog functionality after undoing a site block action. The implementation follows established patterns in the codebase and integrates cleanly with the existing architecture. Overall, the code quality is good with no critical issues found.

Positive Aspects

Clean Architecture: The implementation properly follows the MVVM pattern with clear separation of concerns across the layers
Consistent Patterns: Uses LiveData and Event wrapper pattern consistently with the rest of the codebase
Good Naming: Variable and method names clearly communicate intent (scrollToSiteId, pendingScrollToBlogId, etc.)
No Breaking Changes: The changes are purely additive and don't modify existing behavior
Proper Event Handling: Uses the Event wrapper to prevent duplicate event consumption

Code Quality Issues

1. Potential Race Condition/Timing Issue

Location: ReaderPostListFragment.kt:2005-2008

There's a potential timing issue where pendingScrollToBlogId might be set but the data hasn't finished loading yet. The current implementation assumes data will be available when onDataLoaded() is called, but there's no guarantee the adapter has the blog's posts yet.

Suggestion: Consider adding a check to verify the blog's posts exist in the adapter:

pendingScrollToBlogId?.let { blogId ->
    val position = getPostAdapter().getPositionOfFirstPostFromBlog(blogId)
    if (position > -1) {
        scrollToFirstPostFromBlog(blogId)
    } else {
        // Posts not loaded yet, keep pending for next data load
        return@let
    }
    pendingScrollToBlogId = null
}

2. Missing Edge Case Handling

Location: ReaderPostAdapter.java:761-774

The getPositionOfFirstPostFromBlog() method doesn't handle the case where the blog posts might not have loaded yet after undo. While the method correctly returns -1 if not found, there's no retry mechanism or logging to help debug why a scroll might fail.

Suggestion: Add logging when the blog is not found to aid debugging:

public int getPositionOfFirstPostFromBlog(long blogId) {
    if (mPosts == null) {
        AppLog.w(T.READER, "getPositionOfFirstPostFromBlog: mPosts is null");
        return -1;
    }
    for (int i = 0; i < mPosts.size(); i++) {
        if (mPosts.get(i).blogId == blogId) {
            // ... existing code
        }
    }
    AppLog.d(T.READER, "getPositionOfFirstPostFromBlog: blog " + blogId + " not found");
    return -1;
}

3. Documentation

Location: ReaderPostCardActionsHandler.kt:128-130

While the comment is helpful, it could be more descriptive about when and why this event is emitted.

Suggestion: Expand the comment:

// Emits a blog ID to scroll to after undo block action. This provides visual feedback
// to the user that the undo was successful by scrolling to show the restored posts.
// The event is consumed by ReaderPostListFragment after posts are refreshed.
private val _scrollToSiteId = MediatorLiveData<Event<Long>>()

Performance Considerations

Efficient Search: The getPositionOfFirstPostFromBlog() method performs a linear search, but this is acceptable since:

  • It only runs after an undo action (infrequent operation)
  • The list is limited to MAX_ROWS = 200 posts
  • Early termination when blog is found

No Memory Leaks: Properly uses LiveData observers that are lifecycle-aware

Security

✅ No security concerns identified. The feature only deals with UI state and doesn't involve:

  • User input validation
  • Network requests
  • Data persistence
  • Authentication/authorization

Testing

⚠️ No Unit Tests: This PR adds new functionality without corresponding unit tests.

Recommendation: Add tests for:

  1. ReaderPostCardActionsHandler: Verify scrollToSiteId emits correct blog ID after undo
  2. ReaderPostListViewModel: Verify event is forwarded correctly from handler to fragment
  3. ReaderPostAdapter.getPositionOfFirstPostFromBlog(): Test various scenarios:
    • Blog found at start of list
    • Blog found at end of list
    • Blog found with gap marker present
    • Blog not found
    • Null posts list

Example test structure:

@Test
fun `scrollToSiteId emits blog ID after undo block action`() = runTest {
    // Given a blocked blog
    val blogId = 123L
    // When undo is triggered
    // Then scrollToSiteId should emit the blog ID
}

Documentation

✅ PR description clearly explains the changes and provides testing instructions
✅ Code comments are adequate
⚠️ Consider adding a comment in ReaderPostListFragment explaining the pendingScrollToBlogId flow

Minor Observations

  1. Consistency: The implementation correctly follows the existing pattern used for refreshPosts LiveData
  2. Thread Safety: All LiveData updates use postValue() when called from background threads, which is correct
  3. Nullability: Properly handles nullable blog ID with Kotlin's safe call operator

Recommendations Summary

Must Fix: None - the code is functional as-is

Should Fix:

  • Add unit tests for the new functionality
  • Add defensive logging in getPositionOfFirstPostFromBlog()
  • Consider handling the race condition where posts might not be loaded yet

Nice to Have:

  • Expand inline documentation
  • Add a comment explaining the pending scroll flow in the fragment

Overall Assessment: ✅ Approved - This is a solid implementation that adds valuable user feedback. The code is clean, follows established patterns, and doesn't introduce any critical issues. The main gap is test coverage.


nbradbury and others added 2 commits January 26, 2026 15:05
Verifies that scrollToSiteId LiveData emits the blog ID when user
clicks undo after blocking a site, enabling the UI to scroll to
the restored post.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@nbradbury
Copy link
Copy Markdown
Contributor Author

⚠️ No Unit Tests: This PR adds new functionality without corresponding unit tests.

Added in bdccdf4.

Suggestion: Expand the comment:

Done in fa92d9a.

@wpmobilebot
Copy link
Copy Markdown
Contributor

wpmobilebot commented Jan 26, 2026

App Icon📲 You can test the changes from this Pull Request in WordPress by scanning the QR code below to install the corresponding build.
App NameWordPress
FlavorJalapeno
Build TypeDebug
Versionpr22532-44fc46c
Commit44fc46c
Direct Downloadwordpress-prototype-build-pr22532-44fc46c.apk
Note: Google Login is not supported on these builds.

@wpmobilebot
Copy link
Copy Markdown
Contributor

wpmobilebot commented Jan 26, 2026

App Icon📲 You can test the changes from this Pull Request in Jetpack by scanning the QR code below to install the corresponding build.
App NameJetpack
FlavorJalapeno
Build TypeDebug
Versionpr22532-44fc46c
Commit44fc46c
Direct Downloadjetpack-prototype-build-pr22532-44fc46c.apk
Note: Google Login is not supported on these builds.

@codecov
Copy link
Copy Markdown

codecov Bot commented Jan 26, 2026

Codecov Report

❌ Patch coverage is 80.00000% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 38.87%. Comparing base (8152646) to head (44fc46c).
⚠️ Report is 4 commits behind head on trunk.

Files with missing lines Patch % Lines
...id/ui/reader/viewmodels/ReaderPostListViewModel.kt 0.00% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##            trunk   #22532   +/-   ##
=======================================
  Coverage   38.87%   38.87%           
=======================================
  Files        2202     2202           
  Lines      107052   107057    +5     
  Branches    15103    15103           
=======================================
+ Hits        41618    41623    +5     
  Misses      61972    61972           
  Partials     3462     3462           

☔ View full report in Codecov by Sentry.
📢 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.

@nbradbury nbradbury marked this pull request as ready for review January 26, 2026 21:01
@nbradbury nbradbury requested a review from adalpari January 26, 2026 21:02
Comment on lines +131 to +132
private val _scrollToSiteId = MediatorLiveData<Event<Long>>()
val scrollToSiteId: LiveData<Event<Long>> = _scrollToSiteId
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Can we use a Flow Instead of a LiveData here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed in 44fc46c


private val _scrollToSiteId = MediatorLiveData<Event<Long>>()
val scrollToSiteId: LiveData<Event<Long>> = _scrollToSiteId

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same questions about not using LiveData

Refactored _scrollToSiteId in ReaderPostCardActionsHandler to use
MutableSharedFlow instead of MediatorLiveData. Updated the ViewModel
to directly expose the handler's flow and updated the Fragment to
collect the flow using repeatOnLifecycle. Updated tests to use
backgroundScope for flow collection.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown
Contributor

@adalpari adalpari left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! :shipit:

@nbradbury nbradbury merged commit 95028dd into trunk Jan 27, 2026
24 checks passed
@nbradbury nbradbury deleted the issue/CMM-150-reader-delete-blog branch January 27, 2026 14:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants