Skip to content

fix: debounce Group ResizeObserver to prevent iPadOS transient resize corruption - #733

Open
waterWang wants to merge 1 commit into
bvaughn:mainfrom
waterWang:fix/issue-731-ipados-resize-burst
Open

waterWang wants to merge 1 commit into
bvaughn:mainfrom
waterWang:fix/issue-731-ipados-resize-burst

Conversation

@waterWang

Copy link
Copy Markdown
Contributor

Summary

Fixes #731 — iPadOS Safari layout corruption after backgrounding the app.

Root Cause

On iPadOS Safari, backgrounding the app (via the App Switcher) fires a rapid sequence of ResizeObserver events with transiently wrong container widths. In our capture the sequence was roughly 1180px → 820px → 585px → 1180px within ~700ms.

During the transient narrow reading (e.g. 585px), a Panel with a pixel minSize (e.g. 380px) no longer fits at its current percentage, so validatePanelGroupLayout recomputes a much larger percentage to satisfy the pixel minSize. Once the container returns to its real width (1180px), the preserve-relative-size behavior keeps the inflated percentage — nothing triggers a re-derive.

This all happens before visibilitychange fires "hidden", so a visibilitychange-based workaround would miss it.

Fix

Debounce the Group element's ResizeObserver callback with a short (50ms) timeout. When multiple resize events fire in rapid succession, only the final settled size is applied. The transient narrow readings are discarded, preventing the minSize-driven percentage inflation from ever being committed.

  • Panel onResize notifications are not debounced (they fire immediately for each ResizeObserver entry).
  • The debounce timer is properly cleaned up in the unmountGroup function.

Testing

  • This fix cannot be tested with a unit test because the mock ResizeObserver fires synchronously and does not simulate iPadOS's transient burst pattern.
  • On an iPad, the visual corruption (steps in the issue) no longer reproduces after this change.
  • All existing pointer/keyboard resize tests continue to pass (the 50ms debounce does not affect user-initiated resize interactions, which flow through the pointer/keyboard event handlers, not the ResizeObserver).

Related

…e corruption (bvaughn#731)

On iPadOS Safari, backgrounding the app (App Switcher) fires a rapid
sequence of ResizeObserver events with transiently wrong container
widths (e.g. 1180px -> 820px -> 585px -> 1180px within ~700ms).  During
the transient narrow reading, a Panel with a pixel minSize triggers a
percentage inflation that is never re-derived when the container returns
to its real width, because the preserve-relative-size behavior keeps the
inflated percentage.

The fix adds a short (50ms) debounce to the Group element's ResizeObserver
callback.  Only the final settled size in a burst is applied, so the
transient narrow readings never corrupt the layout.

Fixes bvaughn#731
@vercel

vercel Bot commented Aug 11, 2026

Copy link
Copy Markdown

@waterWang is attempting to deploy a commit to the Brian Vaughn's projects Team on Vercel.

A member of the Team first needs to authorize it.

Comment thread lib/global/mountGroup.ts
if (resizeTimer !== null) {
clearTimeout(resizeTimer);
}
resizeTimer = setTimeout(handleGroupResize, 50);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

How did you determine 50ms to be a good debounce interval?

@bvaughn bvaughn left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

I am uneasy about adding debounce in the way that's proposed here. I think it's too broad a change and I can see it breaking use cases other than the one reported in #731.

I would prefer to listen for "visibilitychange" events and just ignore ResizeObserver when document.visibilityState === "hidden" (see#735). Unfortunately this seems trickier than I assumed, because iOS Safari runs the ResizeObserver callbacks right before the "visibilitychange" event fires. Same for other events we could potentially listen to, like "blur".

I've also checked projects like Chrome's page-lifecycle package and confirmed the sequencing is similarly "broken" for those.

I'm not sure yet of the best way to proceed. I think if we need to add some sort of debounce to cover this case, I'd prefer to scope it to iOS devices only.

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.

Panel layout gets permanently corrupted on iPadOS Safari after backgrounding the app (transient resize burst skews percentage, doesn't self-correct)

2 participants