Version: react-resizable-panels@4.12.2
Platform: iPadOS Safari (reproduces both as regular Safari UA and as the "Macintosh" desktop-site UA iPadOS sometimes reports)
Summary
After a user manually resizes a Panel inside a Group (drag), backgrounding the Safari tab (e.g. switching to another app via the App Switcher) and then returning to it can leave the panel's live layout visually corrupted — a Panel with a pixel minSize ends up much larger than the user set, and does not shrink back even though the container has returned to its original width. A full page reload restores the correct layout, which confirms only the live layout state is affected, not anything persisted via useDefaultLayout/localStorage.
What we found
On-device console logging (attaching listeners for resize, orientationchange, visibilitychange, and a ResizeObserver on the Group element) showed:
- Right as the app starts moving to the background (well before
visibilitychange fires "hidden"), a burst of resize/orientationchange events fires with transiently wrong container widths — in our case the sequence was roughly 1180px → 820px → 585px → 1180px within ~700ms (this looks like it's coming from the OS-level app-switch transition animation, not a real resize).
- During the transient narrow reading (e.g. 540px), a
Panel with minSize={380} no longer fits at its current percentage, so the library recomputes a much larger percentage to satisfy the pixel minSize (e.g. jumping from ~49% to ~72%).
- Once the container returns to its real width (1180px again), that skewed percentage is not re-derived — because the default
groupResizeBehavior is "preserve-relative-size", the percentage (not the pixel size) is what's preserved across the resize, so the panel stays pinned at the inflated percentage even though nothing about the current width requires it anymore.
- In our case this all happens before
visibilitychange turns "hidden" — in one capture, the last resize event and visibilitychange:"hidden" were only ~15ms apart. We were unable to observe onLayoutChanged firing at all for this transient burst in that scenario (it's possible the notification is scheduled via requestAnimationFrame internally and never gets a chance to run before rAF is suspended for the hidden document) — so consumers can't reliably detect the corruption via the public callback API either.
- Once the tab becomes
"visible" again, no further resize/layout event fires at all — the container's width is already correct at that point but the skewed percentage from step 3 is simply left as-is, with nothing to trigger a recompute.
We were able to reproduce this consistently by: dragging a Separator to resize a Panel with a pixel minSize, then switching to another app via iPadOS's App Switcher and back to Safari.
Minimal repro
import { Group, Panel, Separator, useDefaultLayout } from "react-resizable-panels";
function App() {
const { defaultLayout, onLayoutChanged } = useDefaultLayout({
id: "demo",
panelIds: ["sidebar", "main"],
storage: localStorage,
onlySaveAfterUserInteractions: true,
});
return (
<div style={{ display: "flex", height: "100vh" }}>
<Group
defaultLayout={defaultLayout}
onLayoutChanged={onLayoutChanged}
orientation="horizontal"
style={{ flex: 1 }}
>
<Panel id="sidebar" minSize={380} defaultSize={380} style={{ background: "#eef" }}>
sidebar
</Panel>
<Separator style={{ width: 8, cursor: "col-resize", background: "#ccc" }} />
<Panel id="main" minSize={150} style={{ background: "#fee" }}>
main
</Panel>
</Group>
</div>
);
}
Steps on an iPad (Safari):
- Open the page above.
- Drag the separator to make the sidebar noticeably narrower or wider than its default 380px.
- Swipe up to the App Switcher, open a different app, then switch back to Safari.
- The sidebar's width has changed significantly from what was set in step 2 (usually much wider); a full page reload restores the correct width.
Workaround (application level)
We worked around this by listening for document.visibilitychange turning "visible" and imperatively calling the Group's setLayout() back to the last user-set layout (tracked via onLayoutChanged's isUserInteraction flag) if it differs from the panel's current getLayout(). This isn't something library consumers should have to do, though — a library-level fix (e.g. ignoring/debouncing resize readings that are followed almost immediately by a very different one, or treating a minSize-driven percentage bump as provisional until the resize settles) would remove the need for it.
Happy to share the full console log timeline if useful.
Version: react-resizable-panels@4.12.2
Platform: iPadOS Safari (reproduces both as regular Safari UA and as the "Macintosh" desktop-site UA iPadOS sometimes reports)
Summary
After a user manually resizes a
Panelinside aGroup(drag), backgrounding the Safari tab (e.g. switching to another app via the App Switcher) and then returning to it can leave the panel's live layout visually corrupted — aPanelwith a pixelminSizeends up much larger than the user set, and does not shrink back even though the container has returned to its original width. A full page reload restores the correct layout, which confirms only the live layout state is affected, not anything persisted viauseDefaultLayout/localStorage.What we found
On-device console logging (attaching listeners for
resize,orientationchange,visibilitychange, and aResizeObserveron theGroupelement) showed:visibilitychangefires"hidden"), a burst ofresize/orientationchangeevents fires with transiently wrong container widths — in our case the sequence was roughly1180px → 820px → 585px → 1180pxwithin ~700ms (this looks like it's coming from the OS-level app-switch transition animation, not a real resize).PanelwithminSize={380}no longer fits at its current percentage, so the library recomputes a much larger percentage to satisfy the pixelminSize(e.g. jumping from ~49% to ~72%).groupResizeBehavioris"preserve-relative-size", the percentage (not the pixel size) is what's preserved across the resize, so the panel stays pinned at the inflated percentage even though nothing about the current width requires it anymore.visibilitychangeturns"hidden"— in one capture, the lastresizeevent andvisibilitychange:"hidden"were only ~15ms apart. We were unable to observeonLayoutChangedfiring at all for this transient burst in that scenario (it's possible the notification is scheduled viarequestAnimationFrameinternally and never gets a chance to run before rAF is suspended for the hidden document) — so consumers can't reliably detect the corruption via the public callback API either."visible"again, no further resize/layout event fires at all — the container's width is already correct at that point but the skewed percentage from step 3 is simply left as-is, with nothing to trigger a recompute.We were able to reproduce this consistently by: dragging a
Separatorto resize aPanelwith a pixelminSize, then switching to another app via iPadOS's App Switcher and back to Safari.Minimal repro
Steps on an iPad (Safari):
Workaround (application level)
We worked around this by listening for
document.visibilitychangeturning"visible"and imperatively calling theGroup'ssetLayout()back to the last user-set layout (tracked viaonLayoutChanged'sisUserInteractionflag) if it differs from the panel's currentgetLayout(). This isn't something library consumers should have to do, though — a library-level fix (e.g. ignoring/debouncing resize readings that are followed almost immediately by a very different one, or treating aminSize-driven percentage bump as provisional until the resize settles) would remove the need for it.Happy to share the full console log timeline if useful.