From e61e1c2013ff899c05c2a54252f9cc6771501e61 Mon Sep 17 00:00:00 2001 From: Mel Ludowise Date: Wed, 25 Mar 2026 13:17:31 -0700 Subject: [PATCH 1/2] 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. --- src/app.ts | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/app.ts b/src/app.ts index 3f2c9e43..fa3bf23f 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1289,24 +1289,22 @@ export class App extends Protocol { scheduled = false; 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) { From 0d1e17cbb4bf0f7470fd9a9fe6425eecfac93e6d Mon Sep 17 00:00:00 2001 From: Mel Ludowise Date: Wed, 25 Mar 2026 16:52:35 -0700 Subject: [PATCH 2/2] 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. --- src/app.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/app.ts b/src/app.ts index fa3bf23f..5d4879e8 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1287,6 +1287,13 @@ export class App extends Protocol { 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 height by temporarily overriding html sizing.