authorgravatar for dominic@sdko.orgDominic Roy <dominic@sdko.org> 2026-09-15 13:30:02-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-15 10:30:02-07:00
logd07ffbe351a10e1001f6d8eafa5b7533b8bd7e23
tree346647009210679bc925cef4d8e8c3ab85f45062
parentcaf8b5d798909780a7b197cb264e782888b919c1
signature Signed by PGP key B5690EEEBB952194

fix(mobile): keep iOS chat rows aligned after measurement (#11813)


1 files changed, 3 insertions(+), 14 deletions(-)

apps/mobile/src/features/threads/ThreadFeed.tsx+3-14
......@@ -83,7 +83,7 @@ import { isPdfFile } from "../../lib/filePreview";
8383import { flattenThemeColor } from "../../lib/mobileTheme";
8484import { PresentationSource } from "../../components/NativePresentation";
8585import { useSafeAreaInsets } from "react-native-safe-area-context";
86import Animated, { FadeIn, LinearTransition, type SharedValue } from "react-native-reanimated";
86import Animated, { FadeIn, type SharedValue } from "react-native-reanimated";
8787import { useUniwindTheme } from "../../lib/useUniwindTheme";
8888import { IOS_NAV_BAR_HEIGHT } from "../../lib/layoutMetrics";
8989import { useFontFamily } from "../../lib/useFontFamily";
......@@ -216,8 +216,6 @@ function formatMessageTime(input: string): string {
216216// Fixed heights mirror renderFeedEntry's classNames and are only used while
217217// text fits at the current font settings. Larger accessibility text is measured.
218218const TURN_FOLD_HEIGHT = 42; // min-h-11 (38.5) + mb-1 (3.5), with the mobile 14px rem
219const THREAD_FEED_LAYOUT_TRANSITION = LinearTransition.duration(THREAD_DISCLOSURE_TRANSITION_MS);
220const THREAD_FEED_IMMEDIATE_TRANSITION = LinearTransition.duration(0);
221219// Tailwind spacing on the mobile 14px rem: px-3.5 on the user bubble, px-1 on
222220// assistant rows. Images size their frame from these before their own layout.
223221const USER_BUBBLE_HORIZONTAL_PADDING = 3.5 * 3.5;
......@@ -2825,17 +2823,8 @@ export const ThreadFeed = memo(function ThreadFeed(props: ThreadFeedProps) {
28252823 entry.type === "message" ? `message:${entry.message.role}` : entry.type
28262824 }
28272825 getFixedItemSize={getFixedItemSize}
2828 // Android can retain stale native row positions when layout transitions
2829 // race the measurements arriving during sync, even with duration 0.
2830 // Keep its rows on LegendList's non-animated positioning path. On iOS,
2831 // keep a transition installed between disclosures so containers don't remount.
2832 itemLayoutAnimation={
2833 Platform.OS === "android"
2834 ? undefined
2835 : disclosureToggleSettling
2836 ? THREAD_FEED_LAYOUT_TRANSITION
2837 : THREAD_FEED_IMMEDIATE_TRANSITION
2838 }
2826 // Virtualized rows must move with their measurements. Native layout
2827 // transitions can retain stale positions during sync, even at duration 0.
28392828 onItemSizeChanged={handleItemSizeChanged}
28402829 // Measure rows well before they scroll into view so estimate→actual
28412830 // corrections land offscreen instead of under the user's finger.