From b38a68ad75e15bc5d1d5da16b202b1ccb0953798 Mon Sep 17 00:00:00 2001 From: Michael Lynch Date: Mon, 26 Jan 2026 21:29:33 -0500 Subject: [PATCH] shelley: Fix Android keyboard covering chat input on Firefox (#48) * Fix Android keyboard covering chat input on Firefox When the virtual keyboard appears on Android Firefox, it can cover the chat input field, making it impossible to see what you're typing. This fix adds two mechanisms to ensure the input stays visible: 1. On focus: scroll the input into view with a small delay to let the keyboard animation start 2. On viewport resize: use the visualViewport API to detect when the keyboard changes the viewport size and scroll the input into view if it's focused Fixes #47 Co-authored-by: Shelley * Drop the onFocus handler The visualViewport API should be sufficient * Restore previous comment --------- Co-authored-by: Shelley --- ui/src/components/MessageInput.tsx | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/ui/src/components/MessageInput.tsx b/ui/src/components/MessageInput.tsx index ae7c7cea7566e70ecac9a02ecf1588315e555e9b..c00049bd66b8bc75beb9e35ce1e28367b91812d5 100644 --- a/ui/src/components/MessageInput.tsx +++ b/ui/src/components/MessageInput.tsx @@ -381,6 +381,29 @@ function MessageInput({ } }, [autoFocus]); + // Handle virtual keyboard appearance on mobile (especially Android Firefox) + // The visualViewport API lets us detect when the keyboard shrinks the viewport + useEffect(() => { + if (typeof window === "undefined" || !window.visualViewport) { + return; + } + + const handleViewportResize = () => { + // Only scroll if our textarea is focused (keyboard is for us) + if (document.activeElement === textareaRef.current) { + // Small delay to let the viewport settle after resize + requestAnimationFrame(() => { + textareaRef.current?.scrollIntoView({ behavior: "smooth", block: "center" }); + }); + } + }; + + window.visualViewport.addEventListener("resize", handleViewportResize); + return () => { + window.visualViewport?.removeEventListener("resize", handleViewportResize); + }; + }, []); + const isDisabled = disabled || uploadsInProgress > 0; const canSubmit = message.trim() && !isDisabled && !submitting;