From e9773032750c0148c4541218cd01b07dc4ebee39 Mon Sep 17 00:00:00 2001 From: Nick Misasi Date: Tue, 3 Mar 2026 11:03:31 -0500 Subject: [PATCH] Fix iOS Sources panel expansion in agent citations (#9567) * Fix iOS citations panel toggle reliability Remove the animated maxHeight/opacity accordion from agent citations so the Sources section expands and collapses consistently on iOS without hidden or underlapping content. Made-with: Cursor * Restore citations panel animation with stable transitions Use Reanimated enter/exit and layout transitions for the Sources panel so opening and closing stay animated while preserving the iOS reliability fix. Made-with: Cursor * Fix citations panel collapse animation overlapping content below Replace entering/exiting animations with shared-value-driven height and opacity transitions. Reanimated's exiting animations pull the view out of layout flow as an absolute overlay, causing the fading content to overlap the regenerate button during collapse. Using an always-mounted Animated.View with measured content height and overflow clipping ensures smooth expand/collapse without overlap. Made-with: Cursor --- .../components/citations_list/index.tsx | 59 ++++++++++++------- 1 file changed, 38 insertions(+), 21 deletions(-) diff --git a/app/products/agents/components/citations_list/index.tsx b/app/products/agents/components/citations_list/index.tsx index 137b1489a..d990ced72 100644 --- a/app/products/agents/components/citations_list/index.tsx +++ b/app/products/agents/components/citations_list/index.tsx @@ -2,8 +2,8 @@ // See LICENSE.txt for license information. import {TOUCH_TARGET_SIZE} from '@agents/constants'; -import React, {useCallback, useState} from 'react'; -import {Text, TouchableOpacity, View} from 'react-native'; +import React, {useCallback, useEffect, useState} from 'react'; +import {type LayoutChangeEvent, Text, TouchableOpacity, View} from 'react-native'; import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; import CompassIcon from '@components/compass_icon'; @@ -41,7 +41,11 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { marginLeft: 8, }, citationsList: { - marginTop: 4, + overflow: 'hidden', + }, + citationsContentWrapper: { + position: 'absolute', + width: '100%', }, citationItem: { flexDirection: 'row', @@ -90,15 +94,22 @@ const CitationsList = ({annotations}: CitationsListProps) => { const theme = useTheme(); const styles = getStyleSheet(theme); const [isExpanded, setIsExpanded] = useState(false); - const animatedHeight = useSharedValue(0); - const opacity = useSharedValue(0); + const progress = useSharedValue(0); + const contentHeight = useSharedValue(0); + + useEffect(() => { + progress.value = withTiming(isExpanded ? 1 : 0, {duration: 250}); + // eslint-disable-next-line react-hooks/exhaustive-deps -- progress is a stable shared value ref + }, [isExpanded]); const handleToggle = useCallback(() => { - const newExpanded = !isExpanded; - setIsExpanded(newExpanded); - animatedHeight.value = withTiming(newExpanded ? 1 : 0, {duration: 250}); - opacity.value = withTiming(newExpanded ? 1 : 0, {duration: 250}); - }, [isExpanded, animatedHeight, opacity]); + setIsExpanded((prev) => !prev); + }, []); + + const handleContentLayout = useCallback((e: LayoutChangeEvent) => { + contentHeight.value = e.nativeEvent.layout.height; + // eslint-disable-next-line react-hooks/exhaustive-deps -- contentHeight is a stable shared value ref + }, []); const handleCitationPress = useCallback((url: string) => { if (url) { @@ -106,11 +117,14 @@ const CitationsList = ({annotations}: CitationsListProps) => { } }, []); - const listAnimatedStyle = useAnimatedStyle(() => ({ - opacity: opacity.value, - maxHeight: animatedHeight.value === 0 ? 0 : undefined, - overflow: 'hidden', - })); + const collapsibleStyle = useAnimatedStyle(() => { + const p = progress.value; + return { + height: contentHeight.value > 0 ? p * contentHeight.value : 0, + opacity: p, + marginTop: p * 4, + }; + }); return ( @@ -139,11 +153,14 @@ const CitationsList = ({annotations}: CitationsListProps) => { /> - {isExpanded && ( - - {annotations.map((annotation, idx) => ( + + + {annotations.map((annotation) => ( handleCitationPress(annotation.url)} style={styles.citationItem} testID={`citations.list.item.${annotation.index}`} @@ -176,8 +193,8 @@ const CitationsList = ({annotations}: CitationsListProps) => { /> ))} - - )} + + ); };