fix: prevent spontaneous scroll jumps when reading old messages#668
fix: prevent spontaneous scroll jumps when reading old messages#668torlando-tech wants to merge 10 commits intomainfrom
Conversation
Remove _messagesRefreshTrigger from the paging flow's combine(). Every 30s (and on each delivery receipt), the trigger incremented, causing flatMapLatest to create a brand-new PagingSource — discarding all loaded pages. With enablePlaceholders=false, the rebuilt source could only approximately restore scroll position, jumping the user back toward recent messages. Room's PagingSource already auto-invalidates on DB writes, preserving scroll position via key-based anchoring within the existing Pager. formatTimestamp() is a pure function that recomputes relative times on each recomposition, so no data-layer refresh was ever needed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Greptile SummaryThis PR removes Confidence Score: 5/5Safe to merge — the fix is correct and all remaining findings are style-level. The root cause (PagingSource recreation on every tick/delivery receipt) is accurately identified and properly fixed. Room's PagingSource reliably auto-invalidates on DB writes; the old workaround comment about cachedIn() blocking propagation was incorrect. The Compose-level timestampTick clock pattern (passing an unused-but-tracked parameter to force recomposition) is a well-established technique. Import reordering and test-mock additions are cosmetic/correctness improvements with no risk. No files require special attention. Important Files Changed
Sequence DiagramsequenceDiagram
participant DB as Room DB
participant PS as PagingSource
participant VM as MessagingViewModel
participant SC as MessagingScreen
participant MB as MessageBubble
note over VM,SC: BEFORE (causes scroll jumps)
DB->>PS: DB write (delivery receipt)
PS-->>VM: invalidate (auto)
VM->>VM: _messagesRefreshTrigger++
VM->>PS: new PagingSource created
note over SC: All loaded pages discarded → scroll jumps
note over VM,SC: AFTER (this PR)
DB->>PS: DB write (delivery receipt)
PS-->>VM: invalidate (auto, key-based)
note over SC: Scroll position preserved
loop Every 30 s (RESUMED lifecycle)
SC->>SC: rememberLifecycleTickerMillis tick
SC->>MB: timestampTick (new value)
MB->>MB: recompose → formatTimestamp() re-runs
note over MB: Only visible bubbles recompose
end
Reviews (9): Last reviewed commit: "Stub mapStylePreferenceFlow in MapViewMo..." | Re-trigger Greptile |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
…ption Use a Compose-level clock state (timestampTick) that triggers recomposition of visible message items every 30s and on ON_RESUME, instead of the old _messagesRefreshTrigger which recreated the entire PagingData pipeline. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Keep enrichSentInterfaceOnDelivery and sentInterface UI/DB/AIDL additions from main; drop re-added _messagesRefreshTrigger (scroll-jump cause, replaced by Compose-level timestampTick in prior commit). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
6af99ad to
a16dcc1
Compare
The original scroll-fix commit over-removed code: enrichSentInterfaceOnDelivery(), the getNextHopInterfaceName query in handleSendSuccess(), and the merge missed main's format_interface_name fix in reticulum_wrapper.py. Restore all of these — they are independent of the scroll-position bug (which was caused solely by _messagesRefreshTrigger recreating PagingData). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The branch had stale GitHub Actions versions from before main upgraded them. Take main's workflows verbatim to resolve version downgrades and restore logcat timeout guards. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move the snapshot dependency from a bare read in the items lambda to an explicit parameter on MessageBubble. This ensures recomposition of formatTimestamp() even if MessageUi is stabilised in the future (the previous approach relied on ByteArray making MessageUi unstable). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…lling # Conflicts: # app/src/main/java/network/columba/app/ui/screens/MessagingScreen.kt
|
@greptileai review |
- Remove unused mutableLongStateOf import; ticker is produceState-backed via rememberLifecycleTickerMillis - Suppress UNUSED_PARAMETER for MessageBubble.timestampTick — the param is intentionally unread; its sole purpose is to drive recomposition so formatTimestamp() re-evaluates with the current wall clock Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Same regression as fixed in PR #840: after merging main with the dark-mode picker (PR #839), MapViewModel reads mapStylePreferenceFlow at construction time but MapViewModelTest's SettingsRepository mock doesn't stub it. Without this stub the entire MapViewModelTest suite fails with MockKException on the shard-2 CI shard. The fix is a single-line addition returning the production default (AUTO). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
_messagesRefreshTriggerfrom the paging flow'scombine()— it was creating a brand-newPagingSourceevery 30 seconds (and on each delivery receipt), discarding all loaded pages and causing the viewport to jump back toward recent messagesPagingSourcealready auto-invalidates on DB writes with proper key-based scroll position preservationformatTimestamp()is a pure function that recomputes relative times on each recomposition, so no data-layer refresh was ever needed for timestamp updatesTest plan
MessagingViewModelTest— all passMessagingViewModelImageLoadingTest— all pass🤖 Generated with Claude Code