Fix auto-resize destroying horizontal scroll positions (#567)

* Fix auto-resize destroying horizontal scroll positions

The setupSizeChangedNotifications method temporarily sets
html.style.width to 'fit-content' to measure intrinsic content width.
For responsive apps that derive width from their container (width: 100%,
flex: 1, etc.), fit-content resolves to 0px. The synchronous reflow at
0px causes browsers to clamp scrollLeft on all horizontal scroll
containers to 0, permanently destroying their scroll positions.

Replace the fit-content width measurement with window.innerWidth. The
height measurement (max-content) is preserved since hosts rely on it.
Neither the iOS nor web host uses the width value from size-changed
notifications, so this change has no functional impact on hosts while
fixing scroll position clamping for all MCP apps with horizontal scroll
views.

* Skip size-changed measurement during fullscreen

Hosts determine the container size in fullscreen mode, so size-changed
notifications are not needed. The temporary max-content height override
used for measurement causes visible vertical jitter during animated
fullscreen transitions by briefly reflowing the document on every frame.
This commit is contained in:
Mel Ludowise 2026-03-26 03:08:18 -07:00 • committed by GitHub
commit a7273bbf42
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1287,26 +1287,31 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
scheduled = true;
requestAnimationFrame(() => {
scheduled = false;
// Hosts determine the container size in fullscreen, so size-changed
// notifications are not needed.
if (this._hostContext?.displayMode === "fullscreen") {
return;
}
const html = document.documentElement;
// Measure actual content size by temporarily overriding html sizing.
// Width uses fit-content so content wraps at the host-provided width.
// Measure actual content height by temporarily overriding html sizing.
// Height uses max-content because fit-content would clamp to the viewport
// height when content is taller than the iframe, causing internal scrolling.
const originalWidth = html.style.width;
//
// Width uses window.innerWidth instead of measuring via fit-content.
// Setting html.style.width to fit-content forces a synchronous reflow at
// 0px width for responsive apps (whose content derives width from the
// container rather than having intrinsic width). This causes the browser
// to clamp scrollLeft on any horizontal scroll containers to 0, permanently
// destroying their scroll positions.
const originalHeight = html.style.height;
html.style.width = "fit-content";
html.style.height = "max-content";
const rect = html.getBoundingClientRect();
html.style.width = originalWidth;
const height = Math.ceil(html.getBoundingClientRect().height);
html.style.height = originalHeight;
// Compensate for scrollbar width on Linux/Windows where scrollbars consume space.
// On systems with overlay scrollbars (macOS), this will be 0.
const scrollbarWidth = window.innerWidth - html.clientWidth;
const width = Math.ceil(rect.width + scrollbarWidth);
const height = Math.ceil(rect.height);
const width = Math.ceil(window.innerWidth);
// Only send if size actually changed (prevents feedback loops from style changes)
if (width !== lastWidth || height !== lastHeight) {