Skip to content

fix(details): hoist markdown height so scroll doesn't snap back#614

Merged
rainxchzed merged 3 commits into
mainfrom
fix/details-readme-scroll-reset
May 16, 2026
Merged

fix(details): hoist markdown height so scroll doesn't snap back#614
rainxchzed merged 3 commits into
mainfrom
fix/details-readme-scroll-reset

Conversation

@rainxchzed
Copy link
Copy Markdown
Member

@rainxchzed rainxchzed commented May 16, 2026

README / What's-new sections snapped to the top after scrolling past and back. Cause: ExpandableMarkdownContent held measured height in remember, which LazyColumn disposed when the item left the viewport buffer. On re-entry the composable started with contentHeightPx = 0fneedsExpansion = false → full-height render → onGloballyPositioned measured → flipped to clipped → layout reflow under the user's finger.

Fix:

  • aboutMeasuredHeightPx + whatsNewMeasuredHeightPx on DetailsState.
  • OnAboutMeasured / OnWhatsNewMeasured actions, monotonic update in VM.
  • about() / whatsNew() take measured + callback as parameters; ExpandableMarkdownContent reads measured from props instead of local remember.
  • Stable item(key = ...) for both markdown blocks so LazyColumn restores them deterministically.
  • Removed the wrapping AnimatedContent (was recomposing the whole subtree on every translation toggle).
  • Reset whatsNew measurement on release switch (content changes).

Test plan

  • Android compile clean
  • Device: open Details with long README, scroll past, scroll back — no jump to section start. Toggle translation — no flash.

Summary by CodeRabbit

  • Bug Fixes
    • README and release notes no longer snap back to the top after navigating away and returning — scroll position and measured height are preserved.
    • Pinned variant labels in details now refresh correctly between releases (stale beta/rc numbers removed).
    • Improved detection of Magisk / KernelSU / APatch on Android 13+ so probe results are not obscured.

Review Change Stack

Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

rainxchzed has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 16, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3ea16ca0-42a8-4c49-ae8e-15b64fd8f9b5

📥 Commits

Reviewing files that changed from the base of the PR and between ba67292 and c32b34d.

📒 Files selected for processing (19)
  • core/presentation/src/commonMain/composeResources/files/whatsnew/18.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/ar/18.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/bn/18.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/es/18.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/fr/18.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/hi/18.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/it/18.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/ja/18.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/ko/18.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/pl/18.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/ru/18.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/tr/18.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/zh-CN/18.json
  • feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/DetailsAction.kt
  • feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/DetailsRoot.kt
  • feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/DetailsState.kt
  • feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/DetailsViewModel.kt
  • feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/sections/About.kt
  • feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/sections/WhatsNew.kt

Walkthrough

Details screen hoists measured heights for About and WhatsNew into DetailsState and reports measurements via new DetailsAction variants; DetailsViewModel stores the maximum observed heights and DetailsRoot wires state and callbacks into refactored About/WhatsNew composables. Localized whatsnew JSON files add a FIXED bullet about scroll-position persistence.

Changes

Markdown Height Measurement State Hoisting

Layer / File(s) Summary
Measurement state contracts
feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/DetailsState.kt, feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/DetailsAction.kt
DetailsState gains aboutMeasuredHeightPx and whatsNewMeasuredHeightPx nullable Float properties. DetailsAction adds OnAboutMeasured(heightPx: Float) and OnWhatsNewMeasured(heightPx: Float) variants.
View model measurement handling
feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/DetailsViewModel.kt
onAction clears whatsNewMeasuredHeightPx on SelectRelease and handles OnAboutMeasured / OnWhatsNewMeasured by storing the maximum observed height.
Root composition wiring
feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/DetailsRoot.kt
DetailsRoot passes hoisted measuredHeightPx props and onMeasured callbacks (dispatching the new actions) into both about and whatsNew section composables across UI branches.
About section refactoring
feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/sections/About.kt
LazyListScope.about and ExpandableMarkdownContent accept measuredHeightPx and onMeasured; internal size state and animateContentSize removed; measurement uses Modifier.onSizeChanged and reports via onMeasured when appropriate; markdown item keyed.
WhatsNew section refactoring
feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/sections/WhatsNew.kt
LazyListScope.whatsNew and ExpandableMarkdownContent accept measuredHeightPx and onMeasured; remove AnimatedContent and internal height state; compute needsExpansion from hoisted height; measure via Modifier.onSizeChanged and report larger measurements; markdown item keyed; bring-into-view preserved.
Release notes documentation
core/presentation/src/commonMain/composeResources/files/whatsnew/*/18.json (14 localized files)
Add FIXED bullet documenting that README/release-notes no longer snap back to the top when revisited after scrolling because measured viewport height is preserved; existing Android 13+ Magisk/KernelSU/APatch detection note retained.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 I measured each markdown hill and hollow,

Heights hoisted high, no more scroll to follow.
No snapping back where readers roam,
State holds steady — a cozy home.
✨ Scrolls preserved, the readers are mellow.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main fix: hoisting markdown height state to prevent scroll snap-back when navigating away and returning to README/What's-New sections.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/details-readme-scroll-reset

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/DetailsViewModel.kt (1)

339-353: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Reset whatsNewMeasuredHeightPx for all release-switch paths.

Line 351 clears measurement in SelectRelease, but selectReleaseCategory(), retryReleases(), and refresh() can also change selectedRelease without clearing this field. That can carry stale height into a different release and produce incorrect expand/collapse behavior.

Suggested fix
 private fun selectReleaseCategory(action: DetailsAction.SelectReleaseCategory) {
   ...
   _state.update {
     it.copy(
       selectedReleaseCategory = newCategory,
       selectedRelease = newSelected,
       installableAssets = installable,
       primaryAsset = primary,
       whatsNewTranslation = TranslationState(),
+      whatsNewMeasuredHeightPx = null,
     )
   }
 }
// In retryReleases()/refresh(), clear when selected release identity changes
val oldId = _state.value.selectedRelease?.id
val newId = selected?.id // or selectedRelease?.id
...
whatsNewMeasuredHeightPx = if (oldId != newId) null else it.whatsNewMeasuredHeightPx
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/DetailsViewModel.kt`
around lines 339 - 353, The state field whatsNewMeasuredHeightPx is only cleared
in the DetailsAction.SelectRelease branch but not when selectedRelease is
changed via selectReleaseCategory(), retryReleases(), or refresh(), which can
leave a stale height; update those code paths (selectReleaseCategory,
retryReleases, refresh) to compare previous selectedRelease?.id with the new
selected release id and set whatsNewMeasuredHeightPx = null when the id changed
(otherwise keep the existing value), mirroring the behavior in the SelectRelease
handling where you call recomputeAssetsForRelease and update
selectedRelease/primaryAsset/etc.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/DetailsViewModel.kt`:
- Around line 339-353: The state field whatsNewMeasuredHeightPx is only cleared
in the DetailsAction.SelectRelease branch but not when selectedRelease is
changed via selectReleaseCategory(), retryReleases(), or refresh(), which can
leave a stale height; update those code paths (selectReleaseCategory,
retryReleases, refresh) to compare previous selectedRelease?.id with the new
selected release id and set whatsNewMeasuredHeightPx = null when the id changed
(otherwise keep the existing value), mirroring the behavior in the SelectRelease
handling where you call recomputeAssetsForRelease and update
selectedRelease/primaryAsset/etc.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: eabdbc83-7795-490c-a1ce-a985ef51373a

📥 Commits

Reviewing files that changed from the base of the PR and between a98ac82 and f286aee.

📒 Files selected for processing (19)
  • core/presentation/src/commonMain/composeResources/files/whatsnew/18.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/ar/18.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/bn/18.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/es/18.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/fr/18.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/hi/18.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/it/18.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/ja/18.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/ko/18.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/pl/18.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/ru/18.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/tr/18.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/zh-CN/18.json
  • feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/DetailsAction.kt
  • feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/DetailsRoot.kt
  • feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/DetailsState.kt
  • feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/DetailsViewModel.kt
  • feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/sections/About.kt
  • feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/sections/WhatsNew.kt

Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

rainxchzed has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

rainxchzed has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@rainxchzed rainxchzed force-pushed the fix/details-readme-scroll-reset branch from ba67292 to c32b34d Compare May 16, 2026 16:12
@rainxchzed rainxchzed merged commit bf2271a into main May 16, 2026
1 check was pending
@rainxchzed rainxchzed deleted the fix/details-readme-scroll-reset branch May 16, 2026 16:12
Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

rainxchzed has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant