fix: scroll-to-bottom on restart, keyboard avoiding in project picker
- MessageList fires 3 scroll attempts (100/300/700ms) for bulk loads
to catch lazy FlatList rendering on app restart
- SessionDrawer wrapped in KeyboardAvoidingView so custom path input
stays visible when keyboard opens
| .. | .. |
|---|
| 9 | 9 | Animated, |
|---|
| 10 | 10 | Dimensions, |
|---|
| 11 | 11 | Keyboard, |
|---|
| 12 | + KeyboardAvoidingView, |
|---|
| 12 | 13 | LayoutAnimation, |
|---|
| 14 | + Platform, |
|---|
| 13 | 15 | Pressable, |
|---|
| 14 | 16 | ScrollView, |
|---|
| 15 | 17 | StyleSheet, |
|---|
| .. | .. |
|---|
| 471 | 473 | elevation: 20, |
|---|
| 472 | 474 | }} |
|---|
| 473 | 475 | > |
|---|
| 476 | + <KeyboardAvoidingView |
|---|
| 477 | + style={{ flex: 1 }} |
|---|
| 478 | + behavior={Platform.OS === "ios" ? "padding" : undefined} |
|---|
| 479 | + > |
|---|
| 474 | 480 | <GestureHandlerRootView style={{ flex: 1 }}> |
|---|
| 475 | 481 | {/* Header */} |
|---|
| 476 | 482 | <View |
|---|
| .. | .. |
|---|
| 673 | 679 | </Text> |
|---|
| 674 | 680 | </View> |
|---|
| 675 | 681 | </GestureHandlerRootView> |
|---|
| 682 | + </KeyboardAvoidingView> |
|---|
| 676 | 683 | </Animated.View> |
|---|
| 677 | 684 | </View> |
|---|
| 678 | 685 | </View> |
|---|
| .. | .. |
|---|
| 20 | 20 | |
|---|
| 21 | 21 | useEffect(() => { |
|---|
| 22 | 22 | if (messages.length > 0) { |
|---|
| 23 | | - // If the message count changed by more than 1, it's a session switch or |
|---|
| 24 | | - // initial load — snap to bottom instantly instead of visibly scrolling. |
|---|
| 25 | 23 | const delta = Math.abs(messages.length - prevLengthRef.current); |
|---|
| 26 | | - const animated = delta === 1; |
|---|
| 27 | | - const delay = delta > 1 ? 200 : 50; |
|---|
| 28 | | - setTimeout(() => { |
|---|
| 29 | | - listRef.current?.scrollToEnd({ animated }); |
|---|
| 30 | | - }, delay); |
|---|
| 24 | + if (delta > 1) { |
|---|
| 25 | + // Bulk load (restart, session switch) — FlatList renders lazily, |
|---|
| 26 | + // so fire multiple scroll attempts to catch late renders. |
|---|
| 27 | + for (const delay of [100, 300, 700]) { |
|---|
| 28 | + setTimeout(() => { |
|---|
| 29 | + listRef.current?.scrollToEnd({ animated: false }); |
|---|
| 30 | + }, delay); |
|---|
| 31 | + } |
|---|
| 32 | + } else { |
|---|
| 33 | + // Single new message — smooth scroll |
|---|
| 34 | + setTimeout(() => { |
|---|
| 35 | + listRef.current?.scrollToEnd({ animated: true }); |
|---|
| 36 | + }, 50); |
|---|
| 37 | + } |
|---|
| 31 | 38 | } |
|---|
| 32 | 39 | prevLengthRef.current = messages.length; |
|---|
| 33 | 40 | }, [messages.length, isTyping, lastContent]); |
|---|