From 985e9db6db12d3737c04996e37b0b49f096146c5 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Sun, 26 Oct 2025 15:08:52 +0100 Subject: [PATCH] fix(chat): maintain scroll position when keyboard appears MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the keyboard appeared, the chat content would get pushed up and the user would lose sight of what they were viewing. This happened because the outer container had keyboard-aware padding, but the ScrollView's content padding was static. Now the ScrollView's contentContainerStyle receives animated padding that matches the keyboard height, ensuring the content maintains its relative position and the user keeps seeing the same messages when the keyboard appears. Changes: - Pass keyboardHeight SharedValue to AgentStreamView - Add animated contentPaddingStyle that adjusts for keyboard - Apply dynamic padding to ScrollView contentContainerStyle - Content now smoothly adjusts as keyboard animates in/out 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- packages/app/src/app/agent/[id].tsx | 1 + .../app/src/components/agent-stream-view.tsx | 25 +++++++++++++++---- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/packages/app/src/app/agent/[id].tsx b/packages/app/src/app/agent/[id].tsx index 90d105e78..3fe5d7f5e 100644 --- a/packages/app/src/app/agent/[id].tsx +++ b/packages/app/src/app/agent/[id].tsx @@ -96,6 +96,7 @@ export default function AgentScreen() { onPermissionResponse={(requestId, optionId) => respondToPermission(requestId, id!, agent.sessionId || "", [optionId]) } + keyboardHeight={keyboardHeight} /> )} diff --git a/packages/app/src/components/agent-stream-view.tsx b/packages/app/src/components/agent-stream-view.tsx index 552bb7fbe..49466e1bf 100644 --- a/packages/app/src/components/agent-stream-view.tsx +++ b/packages/app/src/components/agent-stream-view.tsx @@ -3,7 +3,7 @@ import { View, Text, ScrollView, Pressable } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { StyleSheet, useUnistyles } from "react-native-unistyles"; import { BottomSheetModal } from "@gorhom/bottom-sheet"; -import Animated, { FadeIn, FadeOut } from "react-native-reanimated"; +import Animated, { FadeIn, FadeOut, useAnimatedStyle, SharedValue } from "react-native-reanimated"; import { ChevronDown } from "lucide-react-native"; import { AssistantMessage, @@ -25,6 +25,7 @@ export interface AgentStreamViewProps { streamItems: StreamItem[]; pendingPermissions: Map; onPermissionResponse: (requestId: string, optionId: string) => void; + keyboardHeight: SharedValue; } export function AgentStreamView({ @@ -33,6 +34,7 @@ export function AgentStreamView({ streamItems, pendingPermissions, onPermissionResponse, + keyboardHeight, }: AgentStreamViewProps) { const scrollViewRef = useRef(null); const bottomSheetRef = useRef(null); @@ -42,6 +44,17 @@ export function AgentStreamView({ const [isNearBottom, setIsNearBottom] = useState(true); const hasScrolledInitially = useRef(false); + // Animated content padding that responds to keyboard + const contentPaddingStyle = useAnimatedStyle(() => { + "worklet"; + const absoluteHeight = Math.abs(keyboardHeight.value); + const keyboardPadding = Math.max(0, absoluteHeight - insets.bottom); + const basePadding = Math.max(insets.bottom, 32); + return { + paddingBottom: basePadding + keyboardPadding, + }; + }, [insets.bottom]); + // Scroll to bottom immediately on initial load, then animate for new messages useEffect(() => { if (isNearBottom) { @@ -82,10 +95,12 @@ export function AgentStreamView({