From ab1aaa38bb80ceb11c2294d04ea38f9dfe8668f3 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Thu, 22 Jan 2026 22:16:51 -0500 Subject: [PATCH] fix: reset viewport scroll on iOS keyboard dismiss On iOS Safari, when the virtual keyboard opens and closes, the visual viewport can remain scrolled even after the keyboard dismisses. This leaves fixed-position elements (like the mobile header) appearing offset from their intended position, making the hamburger menu unclickable. Add useMobileKeyboardFix hook that listens for visualViewport resize events and resets window.scrollTo(0, 0) when the viewport height increases (indicating keyboard closure). The hook is only active on mobile touch devices where this bug occurs. --- src/browser/App.tsx | 5 +++ src/browser/hooks/useMobileKeyboardFix.ts | 52 +++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 src/browser/hooks/useMobileKeyboardFix.ts diff --git a/src/browser/App.tsx b/src/browser/App.tsx index 013bb0a027..d8c8f8b992 100644 --- a/src/browser/App.tsx +++ b/src/browser/App.tsx @@ -65,6 +65,7 @@ import { getWorkspaceSidebarKey } from "./utils/workspace"; import { WindowsToolchainBanner } from "./components/WindowsToolchainBanner"; import { RosettaBanner } from "./components/RosettaBanner"; import { isDesktopMode } from "./hooks/useDesktopTitlebar"; +import { useMobileKeyboardFix } from "./hooks/useMobileKeyboardFix"; import { cn } from "@/common/lib/utils"; function AppInner() { @@ -102,6 +103,10 @@ function AppInner() { // Auto-collapse sidebar on mobile by default const isMobile = typeof window !== "undefined" && window.innerWidth <= 768; + + // Fix iOS Safari visual viewport scroll issue on keyboard dismiss + useMobileKeyboardFix(); + const [sidebarCollapsed, setSidebarCollapsed] = usePersistedState("sidebarCollapsed", isMobile, { listener: true, }); diff --git a/src/browser/hooks/useMobileKeyboardFix.ts b/src/browser/hooks/useMobileKeyboardFix.ts new file mode 100644 index 0000000000..c0ffb99f42 --- /dev/null +++ b/src/browser/hooks/useMobileKeyboardFix.ts @@ -0,0 +1,52 @@ +/** + * useMobileKeyboardFix + * + * iOS Safari has a known issue where the visual viewport can remain scrolled + * after the virtual keyboard dismisses. This leaves fixed-position elements + * (like our mobile header) appearing offset from their intended position. + * + * This hook listens for visual viewport resize events (which fire when the + * keyboard opens/closes) and resets window scroll when the viewport height + * increases (keyboard closing). + * + * Only active on mobile touch devices where this bug occurs. + */ +import { useEffect } from "react"; + +export function useMobileKeyboardFix(): void { + useEffect(() => { + // Only apply fix on mobile touch devices + const isMobileTouch = + typeof window !== "undefined" && + window.matchMedia("(max-width: 768px) and (pointer: coarse)").matches; + + if (!isMobileTouch || !window.visualViewport) { + return; + } + + const vv = window.visualViewport; + let lastHeight = vv.height; + + const handleResize = () => { + const currentHeight = vv.height; + + // When viewport height increases significantly (keyboard closing), + // reset scroll position to fix the offset header issue + if (currentHeight > lastHeight + 50) { + // Use requestAnimationFrame to ensure DOM has settled + requestAnimationFrame(() => { + // Scroll the window to top to reset any viewport offset + window.scrollTo(0, 0); + }); + } + + lastHeight = currentHeight; + }; + + vv.addEventListener("resize", handleResize); + + return () => { + vv.removeEventListener("resize", handleResize); + }; + }, []); +}