From 0ae8c6466d07db58b4c778efdb1eea7b067c9fee Mon Sep 17 00:00:00 2001 From: tris203 Date: Tue, 8 Sep 2026 22:24:17 +0100 Subject: [PATCH 1/4] fix(mobile): respect code word break in file viewer --- apps/mobile/src/features/files/SourceFileSurface.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/mobile/src/features/files/SourceFileSurface.tsx b/apps/mobile/src/features/files/SourceFileSurface.tsx index 2eabce998e8e..d01b4c1cee38 100644 --- a/apps/mobile/src/features/files/SourceFileSurface.tsx +++ b/apps/mobile/src/features/files/SourceFileSurface.tsx @@ -281,8 +281,9 @@ function JavaScriptSourceFileSurface(props: SourceFileSurfaceProps) { } export function SourceFileSurface(props: SourceFileSurfaceProps) { + const { appearance } = useAppearancePreferences(); const NativeView = resolveNativeReviewDiffView(); - return NativeView ? ( + return NativeView && !appearance.codeWordBreak ? ( ) : ( From bb109232ffdffc838619d4c784c4c44e9e385fa5 Mon Sep 17 00:00:00 2001 From: tris203 Date: Tue, 8 Sep 2026 22:28:47 +0100 Subject: [PATCH 2/4] fix(mobile): preserve file refresh when wrapping --- .../src/features/files/SourceFileSurface.tsx | 41 ++++++++++++------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/apps/mobile/src/features/files/SourceFileSurface.tsx b/apps/mobile/src/features/files/SourceFileSurface.tsx index d01b4c1cee38..d6ec5b21db01 100644 --- a/apps/mobile/src/features/files/SourceFileSurface.tsx +++ b/apps/mobile/src/features/files/SourceFileSurface.tsx @@ -36,6 +36,23 @@ interface SourceFileSurfaceProps { type SourceHighlightStatus = "highlighting" | "ready" | "error"; +function useSourcePullToRefresh(onRefresh: SourceFileSurfaceProps["onRefresh"]) { + const [isRefreshing, setIsRefreshing] = useState(false); + const handleRefresh = useCallback(async () => { + if (!onRefresh) { + return; + } + setIsRefreshing(true); + try { + await onRefresh(); + } finally { + setIsRefreshing(false); + } + }, [onRefresh]); + + return { handleRefresh, isRefreshing }; +} + const HighlightedSourceLine = memo(function HighlightedSourceLine(props: { readonly codeSurface: ResolvedMobileCodeSurface; readonly index: number; @@ -157,18 +174,7 @@ function NativeSourceFileSurface( const appTheme = useUniwindTheme(); const { width: viewportWidth } = useWindowDimensions(); const { rowsJson, status, targetIndex, tokens } = useSourceFileModel(props); - const [isPullRefreshing, setIsPullRefreshing] = useState(false); - const handlePullToRefresh = useCallback(async () => { - if (!onRefresh) { - return; - } - setIsPullRefreshing(true); - try { - await onRefresh(); - } finally { - setIsPullRefreshing(false); - } - }, [onRefresh]); + const { handleRefresh, isRefreshing } = useSourcePullToRefresh(onRefresh); const tokensJson = useMemo(() => JSON.stringify(buildNativeSourceTokens(tokens)), [tokens]); const selectedRowIdsJson = useMemo( () => JSON.stringify(targetIndex === null ? [] : [nativeSourceRowId(targetIndex)]), @@ -202,8 +208,8 @@ function NativeSourceFileSurface( tokensJson={tokensJson} {...(onRefresh ? { - refreshing: isPullRefreshing, - onPullToRefresh: () => void handlePullToRefresh(), + refreshing: isRefreshing, + onPullToRefresh: () => void handleRefresh(), } : {})} /> @@ -214,6 +220,7 @@ function NativeSourceFileSurface( function JavaScriptSourceFileSurface(props: SourceFileSurfaceProps) { const { codeSurface, codeWordBreak } = useAppearanceCodeSurface(); const { lines, status, targetIndex, tokens } = useSourceFileModel(props); + const { handleRefresh, isRefreshing } = useSourcePullToRefresh(props.onRefresh); const listRef = useRef>(null); useEffect(() => { @@ -248,6 +255,12 @@ function JavaScriptSourceFileSurface(props: SourceFileSurfaceProps) { initialNumToRender={80} maxToRenderPerBatch={80} windowSize={12} + {...(props.onRefresh + ? { + refreshing: isRefreshing, + onRefresh: () => void handleRefresh(), + } + : {})} {...(codeWordBreak ? {} : { From ed33a7725f84430cd046bff64fbc566e3df8ea91 Mon Sep 17 00:00:00 2001 From: tris203 Date: Tue, 8 Sep 2026 22:36:56 +0100 Subject: [PATCH 3/4] fix(mobile): recover wrapped file navigation --- .../src/features/files/SourceFileSurface.tsx | 44 +++++++++++++++++-- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/apps/mobile/src/features/files/SourceFileSurface.tsx b/apps/mobile/src/features/files/SourceFileSurface.tsx index d6ec5b21db01..76c079c09a5c 100644 --- a/apps/mobile/src/features/files/SourceFileSurface.tsx +++ b/apps/mobile/src/features/files/SourceFileSurface.tsx @@ -36,6 +36,9 @@ interface SourceFileSurfaceProps { type SourceHighlightStatus = "highlighting" | "ready" | "error"; +const SOURCE_SCROLL_RETRY_DELAY_MS = 100; +const SOURCE_SCROLL_MAX_RETRIES = 3; + function useSourcePullToRefresh(onRefresh: SourceFileSurfaceProps["onRefresh"]) { const [isRefreshing, setIsRefreshing] = useState(false); const handleRefresh = useCallback(async () => { @@ -222,16 +225,50 @@ function JavaScriptSourceFileSurface(props: SourceFileSurfaceProps) { const { lines, status, targetIndex, tokens } = useSourceFileModel(props); const { handleRefresh, isRefreshing } = useSourcePullToRefresh(props.onRefresh); const listRef = useRef>(null); + const scrollRetryCountRef = useRef(0); + const scrollRetryTimeoutRef = useRef | null>(null); + + const scrollToTargetIndex = useCallback((index: number) => { + listRef.current?.scrollToIndex({ index, animated: false, viewPosition: 0.3 }); + }, []); useEffect(() => { if (targetIndex === null) { return; } + scrollRetryCountRef.current = 0; const frame = requestAnimationFrame(() => { - listRef.current?.scrollToIndex({ index: targetIndex, animated: false, viewPosition: 0.3 }); + scrollToTargetIndex(targetIndex); }); - return () => cancelAnimationFrame(frame); - }, [props.path, targetIndex]); + return () => { + cancelAnimationFrame(frame); + if (scrollRetryTimeoutRef.current !== null) { + clearTimeout(scrollRetryTimeoutRef.current); + scrollRetryTimeoutRef.current = null; + } + }; + }, [props.path, scrollToTargetIndex, targetIndex]); + + const handleScrollToIndexFailed = useCallback( + (failure: { readonly averageItemLength: number; readonly index: number }) => { + listRef.current?.scrollToOffset({ + animated: false, + offset: failure.averageItemLength * failure.index, + }); + if (scrollRetryCountRef.current >= SOURCE_SCROLL_MAX_RETRIES) { + return; + } + scrollRetryCountRef.current += 1; + if (scrollRetryTimeoutRef.current !== null) { + clearTimeout(scrollRetryTimeoutRef.current); + } + scrollRetryTimeoutRef.current = setTimeout(() => { + scrollRetryTimeoutRef.current = null; + scrollToTargetIndex(failure.index); + }, SOURCE_SCROLL_RETRY_DELAY_MS); + }, + [scrollToTargetIndex], + ); const renderLine = useCallback( ({ item, index }: { item: string; index: number }) => ( @@ -255,6 +292,7 @@ function JavaScriptSourceFileSurface(props: SourceFileSurfaceProps) { initialNumToRender={80} maxToRenderPerBatch={80} windowSize={12} + onScrollToIndexFailed={handleScrollToIndexFailed} {...(props.onRefresh ? { refreshing: isRefreshing, From 730e341abb0609c081e1e081f91b317b089b630f Mon Sep 17 00:00:00 2001 From: tris203 Date: Tue, 8 Sep 2026 22:53:40 +0100 Subject: [PATCH 4/4] chore: rerun ci