diff --git a/src/web/public/mobile-handlers.js b/src/web/public/mobile-handlers.js index 3add60fd..3e0c7950 100644 --- a/src/web/public/mobile-handlers.js +++ b/src/web/public/mobile-handlers.js @@ -320,9 +320,10 @@ const KeyboardHandler = { // Shrink main content area so terminal doesn't extend behind keyboard. // Use stable keyboard height (not scroll-dependent) for padding. + // 84px = toolbar (40px) + accessory bar (44px). const keyboardHeight = this.initialViewportHeight - (window.visualViewport.height || window.innerHeight); if (main && keyboardHeight > 0) { - main.style.paddingBottom = `${keyboardHeight + 94}px`; + main.style.paddingBottom = `${keyboardHeight + 84}px`; } } else { this.resetLayout(); @@ -369,6 +370,11 @@ const KeyboardHandler = { try { app.fitAddon.fit(); } catch {} + // Eliminate terminal row quantization gap: xterm can only show whole + // rows, so leftover pixels create dead space below the last row. + // Shrink .main's paddingBottom by the gap so the terminal fills flush + // to the accessory bar. + this._shrinkPaddingToFit(); app.terminal.scrollToBottom(); // Send resize to server so PTY dimensions match xterm this._sendTerminalResize(); @@ -424,6 +430,31 @@ const KeyboardHandler = { } catch {} }, + /** + * Shrink .main paddingBottom to eliminate the terminal row quantization gap. + * xterm can only render whole rows, so fractional-row pixels create dead + * space below the last row. After fitAddon.fit(), measure the gap and + * reduce padding by that amount so the terminal sits flush against the bars. + */ + _shrinkPaddingToFit() { + try { + const container = document.getElementById('terminalContainer'); + const main = document.querySelector('.main'); + if (!container || !main || typeof app === 'undefined' || !app.terminal) return; + const cellH = app.terminal._core?._renderService?.dimensions?.css?.cell?.height; + if (!cellH) return; + const gap = container.clientHeight - app.terminal.rows * cellH; + if (gap > 0 && gap < cellH) { + const currentPadding = parseInt(main.style.paddingBottom) || 0; + main.style.paddingBottom = currentPadding + gap + 'px'; + if (app.fitAddon) + try { + app.fitAddon.fit(); + } catch {} + } + } catch {} + }, + /** Check if element is an input that triggers keyboard (excludes terminal) */ isInputElement(el) { if (!el) return false; diff --git a/src/web/public/mobile.css b/src/web/public/mobile.css index 2ad77ed5..a1a61658 100644 --- a/src/web/public/mobile.css +++ b/src/web/public/mobile.css @@ -327,15 +327,27 @@ html.mobile-init .file-browser-panel { max-height: calc(36px + var(--safe-area-top)); } - /* Add top margin to main content to account for fixed header */ + /* Add top margin to main content to account for fixed header, + and bottom padding for the fixed toolbar (40px) so terminal + content doesn't extend behind it. JS overrides paddingBottom + when keyboard is visible, and resetLayout() clears the inline + style to re-expose this CSS value. */ .main { margin-top: 42px; + padding-bottom: calc(40px + var(--safe-area-bottom)); } .ios-device .main { margin-top: calc(42px + var(--safe-area-top)); } + /* 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-device.safari-browser .main { + padding-bottom: calc(40px + var(--safe-area-bottom) + (100vh - var(--app-height, 100vh))); + } + .header-right { padding-left: 0.2rem; gap: 0.1rem; @@ -1586,11 +1598,9 @@ html.mobile-init .file-browser-panel { scroll-margin-top: 80px; } - /* When keyboard is visible, adjust content to account for moved toolbar */ - .keyboard-visible .terminal-container { - /* Reduce terminal height when keyboard is up so it doesn't overlap toolbar */ - padding-bottom: 50px; - } + /* When keyboard is visible, the JS paddingBottom on .main already reserves + space for toolbar + accessory bar. No extra padding needed here — it was + double-counting and creating dead space between terminal and the bars. */ /* Ensure modals scroll properly when keyboard is visible */ .keyboard-visible .modal-body {