Skip to content

fix(navigation): stop wrap-content sheets spinning in measure - #1929

Merged
bmc08gt merged 2 commits into
code/cashfrom
fix/group-info-host-hang
Nov 1, 2026
Merged

bmc08gt merged 2 commits into
code/cashfrom
fix/group-info-host-hang

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Oct 31, 2026

Copy link
Copy Markdown
Collaborator

Fixes the host Invite sheet ANR (Bugsnag 6ae543e9). Tapping Share on a private group's info page froze the app. A host's bugreport had 11 ANR dumps, and in 10 of them the main thread was in compose-unstyled's Sheet measure (updateContentHeight, shouldUseContentHeightForLayout).

Cause. WrapContent returned the sheetHeight that compose-unstyled passes in. That puts the sheet in content-height mode, where measure writes the content's height into the sheet state and reads it back. Scenes sit in a SharedTransitionLayout, so the sheet is measured twice a frame. The Invite sheet's Approve crossfade (SizeTransform) reports its target height to the lookahead pass and the animated height to the main pass. The two writes keep invalidating each other.

Change. WrapContentDetent never reads sheetHeight:

  • A layout modifier measures the content at its natural height, capped at 92.5% of the window.
  • It records that height after layout, in onSizeChanged.
  • Track calls invalidateDetents() when the height changes.

Modal wrap-content sheets and InWindowBottomSheet (swipe pay) both use it.

WrapContentSheetLayoutLoopTest flips an AnimatedContent with a SizeTransform while a wrap-content sheet opens inside a SharedTransitionLayout. On code/cash it spins at full CPU in the same frames as the device dumps. With this change it passes.

The host Invite sheet froze the app (Bugsnag 6ae543e9; 10 of 11 ANR dumps
in a host's bugreport). Every main thread was inside compose-unstyled's
Sheet measure, in updateContentHeight and shouldUseContentHeightForLayout.

The wrap-content detent returned the sheetHeight compose-unstyled passes
in, which puts the sheet in its content-height mode: measure writes the
content's height into the sheet state and reads it back in the same pass.
Scenes sit in a SharedTransitionLayout, so the sheet is measured twice a
frame. The Invite sheet's Approve crossfade uses a SizeTransform, so it
reports its target height to the lookahead pass and the animated height to
the main pass. Each write invalidates the other, and the frame never ends.

WrapContentDetent never reads sheetHeight. A layout modifier measures the
content at its natural height (up to 92.5% of the window) and records it
after layout, and the sheet's detents are re-resolved when it changes.
Modal wrap-content sheets and InWindowBottomSheet both use it.

WrapContentSheetLayoutLoopTest flips an AnimatedContent with a
SizeTransform while a wrap-content sheet opens inside a
SharedTransitionLayout. Before this change it spun at full CPU in the same
frames as the device dumps; now it passes.
@bmc08gt bmc08gt self-assigned this Oct 31, 2026
@github-actions github-actions Bot added type: fix Bug fix area: ui Compose UI, theme, components, resources labels Oct 31, 2026
…asures

The loop spins inside the sheet's own measure, so the counter in the test's layout modifier
never ran. A 10 minute test task timeout makes a regression fail. Also treat a zero content
height as unmeasured.
@bmc08gt
bmc08gt merged commit 6cb326a into code/cash Nov 1, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: ui Compose UI, theme, components, resources type: fix Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant