From 65232215c6a5381457b758039136b2110c641b18 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Wed, 11 Nov 2026 00:43:49 +0000 Subject: [PATCH 1/3] fix(compose): Detect lazy layout scrolls on Compose foundation 1.10+ Compose foundation 1.10 replaced ScrollingContainerElement with ScrollableAreaElement, so the gesture target locator no longer found LazyColumn/LazyRow/LazyGrid/Pager as scroll targets and dropped the scroll breadcrumb. Match ScrollableAreaElement and keep its class name under R8. Fixes #6547 Co-Authored-By: Gene Wright --- sentry-compose/proguard-rules.pro | 1 + .../gestures/ComposeGestureTargetLocator.kt | 5 +++- .../foundation/GestureModifierStubs.kt | 2 ++ .../ComposeGestureTargetLocatorTest.kt | 25 +++++++++++++++++++ 4 files changed, 32 insertions(+), 1 deletion(-) diff --git a/sentry-compose/proguard-rules.pro b/sentry-compose/proguard-rules.pro index ea5984aa88e..30473497a5f 100644 --- a/sentry-compose/proguard-rules.pro +++ b/sentry-compose/proguard-rules.pro @@ -13,6 +13,7 @@ -keepnames class androidx.compose.foundation.CombinedClickableElement -keepnames class androidx.compose.foundation.ScrollingLayoutElement -keepnames class androidx.compose.foundation.ScrollingContainerElement +-keepnames class androidx.compose.foundation.ScrollableAreaElement -keepnames class androidx.compose.ui.platform.TestTagElement { *; } -keepnames class io.sentry.compose.SentryModifier$SentryTagModifierNodeElement { *; } diff --git a/sentry-compose/src/androidMain/kotlin/io/sentry/compose/gestures/ComposeGestureTargetLocator.kt b/sentry-compose/src/androidMain/kotlin/io/sentry/compose/gestures/ComposeGestureTargetLocator.kt index 47dda6eda9c..90ee0409a3d 100644 --- a/sentry-compose/src/androidMain/kotlin/io/sentry/compose/gestures/ComposeGestureTargetLocator.kt +++ b/sentry-compose/src/androidMain/kotlin/io/sentry/compose/gestures/ComposeGestureTargetLocator.kt @@ -85,7 +85,10 @@ public class ComposeGestureTargetLocator(private val logger: ILogger) : GestureT } else if ( targetType == UiElement.Type.SCROLLABLE && ("androidx.compose.foundation.ScrollingLayoutElement" == type || - "androidx.compose.foundation.ScrollingContainerElement" == type) + "androidx.compose.foundation.ScrollingContainerElement" == type || + // Compose foundation 1.10+: lazy layouts and pagers use ScrollableAreaElement + // in place of ScrollingContainerElement + "androidx.compose.foundation.ScrollableAreaElement" == type) ) { return UiElement(null, null, null, tag, ORIGIN) } diff --git a/sentry-compose/src/androidUnitTest/kotlin/androidx/compose/foundation/GestureModifierStubs.kt b/sentry-compose/src/androidUnitTest/kotlin/androidx/compose/foundation/GestureModifierStubs.kt index 43b3e53bc67..052158c1243 100644 --- a/sentry-compose/src/androidUnitTest/kotlin/androidx/compose/foundation/GestureModifierStubs.kt +++ b/sentry-compose/src/androidUnitTest/kotlin/androidx/compose/foundation/GestureModifierStubs.kt @@ -13,3 +13,5 @@ internal open class CombinedClickableElement : Modifier.Element internal open class ScrollingLayoutElement : Modifier.Element internal open class ScrollingContainerElement : Modifier.Element + +internal open class ScrollableAreaElement : Modifier.Element diff --git a/sentry-compose/src/androidUnitTest/kotlin/io/sentry/compose/gestures/ComposeGestureTargetLocatorTest.kt b/sentry-compose/src/androidUnitTest/kotlin/io/sentry/compose/gestures/ComposeGestureTargetLocatorTest.kt index 8efdf3ebe30..407c5d2f748 100644 --- a/sentry-compose/src/androidUnitTest/kotlin/io/sentry/compose/gestures/ComposeGestureTargetLocatorTest.kt +++ b/sentry-compose/src/androidUnitTest/kotlin/io/sentry/compose/gestures/ComposeGestureTargetLocatorTest.kt @@ -217,6 +217,31 @@ class ComposeGestureTargetLocatorTest { assertEquals("scroll", result!!.tag) } + @Test + fun `detects scrollable via ScrollableAreaElement modifier`() { + val scrollableChild = + mockLayoutNode( + isPlaced = true, + tag = "scroll", + width = 50, + height = 50, + nodeModifierClassName = "androidx.compose.foundation.ScrollableAreaElement", + ) + val root = + mockLayoutNode( + isPlaced = true, + tag = null, + width = 100, + height = 100, + children = listOf(scrollableChild), + ) + val owner = mockOwner(root) + + val result = locator.locate(owner, 5f, 5f, UiElement.Type.SCROLLABLE) + assertNotNull(result) + assertEquals("scroll", result!!.tag) + } + @Test fun `ignores clickable when looking for scrollable`() { val clickableChild = From d883e021972a75adabe9a6a8ef9fa1fef4c1c1ed Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Wed, 11 Nov 2026 00:44:17 +0000 Subject: [PATCH 2/3] changelog Co-Authored-By: Gene Wright --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50319e4d8cd..bded2b64d93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ ### Fixes +- Capture scroll breadcrumbs for `LazyColumn`, `LazyRow`, lazy grids, and `Pager` on Jetpack Compose foundation 1.10+ ([#6548](https://github.com/getsentry/sentry-java/pull/6548)) - Prevent the main thread from potentially hanging during app start if Android is slow to register the SDK's network or telephony listeners. `Sentry.init`, Session Replay, and capturing events no longer wait for that registration ([#6537](https://github.com/getsentry/sentry-java/pull/6537)) - Add opt-in support for matching GraphQL ignored error types against classification specifications via `sentry.graphql.enable-specification-error-type-filtering` ([#6092](https://github.com/getsentry/sentry-java/pull/6092)) - Session Replay: Fix `SentryOkHttpInterceptor` hanging forever on responses whose body has no known length, such as Server-Sent Events or a gzipped response, when network details are enabled for the URL ([#6231](https://github.com/getsentry/sentry-java/pull/6231)) From e87aa935e5a872aef30eba06e19181ee24e604df Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Wed, 11 Nov 2026 00:47:51 +0000 Subject: [PATCH 3/3] fix(compose): Satisfy detekt ComplexCondition in scroll target check --- .../gestures/ComposeGestureTargetLocator.kt | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/sentry-compose/src/androidMain/kotlin/io/sentry/compose/gestures/ComposeGestureTargetLocator.kt b/sentry-compose/src/androidMain/kotlin/io/sentry/compose/gestures/ComposeGestureTargetLocator.kt index 90ee0409a3d..ba011e455ab 100644 --- a/sentry-compose/src/androidMain/kotlin/io/sentry/compose/gestures/ComposeGestureTargetLocator.kt +++ b/sentry-compose/src/androidMain/kotlin/io/sentry/compose/gestures/ComposeGestureTargetLocator.kt @@ -83,12 +83,7 @@ public class ComposeGestureTargetLocator(private val logger: ILogger) : GestureT ) { targetTag = tag } else if ( - targetType == UiElement.Type.SCROLLABLE && - ("androidx.compose.foundation.ScrollingLayoutElement" == type || - "androidx.compose.foundation.ScrollingContainerElement" == type || - // Compose foundation 1.10+: lazy layouts and pagers use ScrollableAreaElement - // in place of ScrollingContainerElement - "androidx.compose.foundation.ScrollableAreaElement" == type) + targetType == UiElement.Type.SCROLLABLE && type in SCROLLABLE_ELEMENT_TYPES ) { return UiElement(null, null, null, tag, ORIGIN) } @@ -133,5 +128,14 @@ public class ComposeGestureTargetLocator(private val logger: ILogger) : GestureT public companion object { private const val ORIGIN = "jetpack_compose" + + private val SCROLLABLE_ELEMENT_TYPES = + setOf( + "androidx.compose.foundation.ScrollingLayoutElement", + "androidx.compose.foundation.ScrollingContainerElement", + // Compose foundation 1.10+: lazy layouts and pagers use ScrollableAreaElement + // in place of ScrollingContainerElement + "androidx.compose.foundation.ScrollableAreaElement", + ) } }