From c367b12f77614cf08e2edeb154918baba0f997e3 Mon Sep 17 00:00:00 2001 From: JD Date: Tue, 8 Sep 2026 01:39:07 -0400 Subject: [PATCH] fix(mobile): lift the iOS Safari toolbar by the measured chrome overlap, not 100vh minus the visual height The phone block lifted the toolbar (and padded .main) by (100vh - --app-height) on iOS Safari to clear a bottom bar that position: fixed elements were assumed to sit behind. On iPhone Safari fixed elements already stop above the bar, and 100vh is the large viewport with the bar collapsed while --app-height is the visual viewport with it expanded, so the expression measures the bar's collapsible height and shows up as an empty band between the toolbar and the bar whenever the bar is expanded. The terminal was padded by the same amount. The lift is now --chrome-overlap, set in updateAppHeight() as innerHeight minus the visual viewport height: the distance the layout viewport that anchors fixed elements extends past the visible area. That is 0 on iPhone Safari, so the toolbar meets the bar, and it is the overlap itself on any browser where fixed elements really do land behind the chrome, so those keep the lift. The keyboard-visible rules, which already override the toolbar offset, are unchanged. --- src/web/public/mobile-handlers.js | 7 +++++++ src/web/public/mobile.css | 19 +++++++++++-------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/web/public/mobile-handlers.js b/src/web/public/mobile-handlers.js index 393180b36..fddf5926d 100644 --- a/src/web/public/mobile-handlers.js +++ b/src/web/public/mobile-handlers.js @@ -148,6 +148,13 @@ const MobileDetection = { if (typeof KeyboardHandler !== 'undefined' && KeyboardHandler.keyboardVisible) return; const vh = window.visualViewport?.height || window.innerHeight; document.documentElement.style.setProperty('--app-height', `${vh}px`); + // How far the layout viewport (which anchors position: fixed) extends below + // the visual viewport, i.e. behind the browser's bottom bar. 0 on iPhone + // Safari, where fixed elements already stop above the bar; the overlap + // where they do not. mobile.css lifts the toolbar by this rather than by + // (100vh - --app-height), which on iPhone measures the collapsible chrome + // instead and left an empty band between the toolbar and the bar. + document.documentElement.style.setProperty('--chrome-overlap', `${Math.max(0, window.innerHeight - vh)}px`); }, /** Initialize mobile detection and set up resize listener */ diff --git a/src/web/public/mobile.css b/src/web/public/mobile.css index b313f0978..64036a181 100644 --- a/src/web/public/mobile.css +++ b/src/web/public/mobile.css @@ -471,11 +471,11 @@ html.mobile-init .file-browser-panel { padding-bottom: calc(40px + var(--safe-area-bottom)); } - /* iOS Safari: toolbar is pushed up by (100vh - --app-height) to clear the - browser's bottom bar. Match that offset in main's padding so the terminal - doesn't extend behind the toolbar. */ + /* iOS Safari: the toolbar is lifted by --chrome-overlap where the browser's + bottom bar would otherwise cover it. Match that offset in main's padding so + the terminal doesn't extend behind the toolbar. */ .ios-device.safari-browser .main { - padding-bottom: calc(40px + var(--safe-area-bottom) + (100vh - var(--app-height, 100vh))); + padding-bottom: calc(40px + var(--safe-area-bottom) + var(--chrome-overlap, 0px)); } .header-right { @@ -780,11 +780,14 @@ html.mobile-init .file-browser-panel { will-change: transform; } - /* iOS Safari with tab bar: position: fixed uses the layout viewport which - extends behind the browser chrome. Offset the toolbar upward by the delta - between 100vh (layout) and --app-height (visual). */ + /* iOS Safari: where position: fixed anchors to a layout viewport that + extends behind the browser's bottom bar, lift the toolbar by that overlap. + --chrome-overlap is innerHeight minus the visual viewport height, set in + mobile-handlers.js. On iPhone Safari it is 0 because fixed elements already + stop above the bar; the previous (100vh - --app-height) lift measured the + collapsible chrome instead and left an empty band above the bar. */ .ios-device.safari-browser .toolbar { - bottom: calc(var(--safe-area-bottom) + (100vh - var(--app-height, 100vh))); + bottom: calc(var(--safe-area-bottom) + var(--chrome-overlap, 0px)); } /* When keyboard is visible the JS translateY already accounts for the full