From 7d69229cd76447b92ee66f472f760994d00817ae Mon Sep 17 00:00:00 2001
From: Matthias Nott <mnott@mnsoft.org>
Date: Sat, 07 Mar 2026 14:14:56 +0100
Subject: [PATCH] fix: reliable scroll-to-bottom, keyboard-aware project picker
---
components/SessionDrawer.tsx | 25 +++++++++++++++++--------
components/chat/MessageList.tsx | 21 ++++++++++++++-------
2 files changed, 31 insertions(+), 15 deletions(-)
diff --git a/components/SessionDrawer.tsx b/components/SessionDrawer.tsx
index d7a1998..be58cef 100644
--- a/components/SessionDrawer.tsx
+++ b/components/SessionDrawer.tsx
@@ -9,9 +9,7 @@
Animated,
Dimensions,
Keyboard,
- KeyboardAvoidingView,
LayoutAnimation,
- Platform,
Pressable,
ScrollView,
StyleSheet,
@@ -276,6 +274,18 @@
const slideAnim = useRef(new Animated.Value(-DRAWER_WIDTH)).current;
const fadeAnim = useRef(new Animated.Value(0)).current;
const [rendered, setRendered] = useState(false);
+ const [keyboardHeight, setKeyboardHeight] = useState(0);
+ const pickerScrollRef = useRef<ScrollView>(null);
+
+ useEffect(() => {
+ const showSub = Keyboard.addListener("keyboardWillShow", (e) => {
+ setKeyboardHeight(e.endCoordinates.height);
+ });
+ const hideSub = Keyboard.addListener("keyboardWillHide", () => {
+ setKeyboardHeight(0);
+ });
+ return () => { showSub.remove(); hideSub.remove(); };
+ }, []);
// Local ordering: merge server sessions while preserving user's drag order
const [orderedSessions, setOrderedSessions] = useState<WsSession[]>([]);
@@ -473,11 +483,7 @@
elevation: 20,
}}
>
- <KeyboardAvoidingView
- style={{ flex: 1 }}
- behavior={Platform.OS === "ios" ? "padding" : undefined}
- >
- <GestureHandlerRootView style={{ flex: 1 }}>
+ <GestureHandlerRootView style={{ flex: 1, paddingBottom: keyboardHeight }}>
{/* Header */}
<View
style={{
@@ -564,6 +570,7 @@
{showProjectPicker && (
<ScrollView
+ ref={pickerScrollRef}
style={{
marginHorizontal: 12,
borderRadius: 12,
@@ -630,6 +637,9 @@
autoCapitalize="none"
autoCorrect={false}
returnKeyType="go"
+ onFocus={() => {
+ setTimeout(() => pickerScrollRef.current?.scrollToEnd({ animated: true }), 100);
+ }}
onSubmitEditing={() => {
if (customPath.trim()) launchSession({ path: customPath.trim() });
}}
@@ -679,7 +689,6 @@
</Text>
</View>
</GestureHandlerRootView>
- </KeyboardAvoidingView>
</Animated.View>
</View>
</View>
diff --git a/components/chat/MessageList.tsx b/components/chat/MessageList.tsx
index 5ff7aea..c3eb13e 100644
--- a/components/chat/MessageList.tsx
+++ b/components/chat/MessageList.tsx
@@ -18,17 +18,17 @@
// Track the last message's content so transcript reflections trigger a scroll
const lastContent = messages.length > 0 ? messages[messages.length - 1].content : "";
+ // Flag: when true, every content size change triggers a scroll to bottom.
+ // Used for bulk loads (restart, session switch) where FlatList renders lazily.
+ const bulkScrollRef = useRef(false);
+
useEffect(() => {
if (messages.length > 0) {
const delta = Math.abs(messages.length - prevLengthRef.current);
if (delta > 1) {
- // Bulk load (restart, session switch) — FlatList renders lazily,
- // so fire multiple scroll attempts to catch late renders.
- for (const delay of [100, 300, 700]) {
- setTimeout(() => {
- listRef.current?.scrollToEnd({ animated: false });
- }, delay);
- }
+ // Bulk load — let onContentSizeChange handle scrolling
+ bulkScrollRef.current = true;
+ setTimeout(() => { bulkScrollRef.current = false; }, 3000);
} else {
// Single new message — smooth scroll
setTimeout(() => {
@@ -38,6 +38,12 @@
}
prevLengthRef.current = messages.length;
}, [messages.length, isTyping, lastContent]);
+
+ const handleContentSizeChange = useCallback(() => {
+ if (bulkScrollRef.current) {
+ listRef.current?.scrollToEnd({ animated: false });
+ }
+ }, []);
// Play from a voice message and auto-chain all consecutive assistant voice messages after it
const handlePlayVoice = useCallback(async (messageId: string) => {
@@ -77,6 +83,7 @@
onPlayVoice={handlePlayVoice}
/>
)}
+ onContentSizeChange={handleContentSizeChange}
contentContainerStyle={{ paddingVertical: 12 }}
showsVerticalScrollIndicator={false}
ListFooterComponent={
--
Gitblit v1.3.1