authorgravatar for me@pixp.ccAlex <me@pixp.cc> 2026-10-11 11:27:08-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-11 11:27:08-07:00
log4bd0ecca19934dce38e16cd42eba5bf81c170d91
treedd4f68e9e6a88dd31f1d2bdec4fc5bb512b7d810
parent328dc27d63dd4b164d0211b56c8bc9100b74f3fe
signature Signed by PGP key B5690EEEBB952194

fix(mobile): let sheet lists scroll before dismissal (#17177)


8 files changed, 39 insertions(+), 18 deletions(-)

apps/mobile/src/features/cloud/ConnectOnboardingRouteScreen.tsx+22-10
...@@ -54,15 +54,15 @@ function ConfiguredConnectOnboardingRouteScreen() {...@@ -54,15 +54,15 @@ function ConfiguredConnectOnboardingRouteScreen() {
54 cloudEnvironments: null,54 cloudEnvironments: null,
55 });55 });
5656
57 // Pull-to-refresh tracks its own spinner instead of discovery's refreshing57 const [isRefreshing, setIsRefreshing] = useState(false);
58 // flag, so background refreshes (e.g. the sign-in one) don't yank the58 const handleRefresh = useCallback(() => {
59 // content down.
60 const [isPullRefreshing, setIsPullRefreshing] = useState(false);
61 const handlePullRefresh = useCallback(() => {
62 void (async () => {59 void (async () => {
63 setIsPullRefreshing(true);60 setIsRefreshing(true);
64 await refreshRelayEnvironments();61 try {
65 setIsPullRefreshing(false);62 await refreshRelayEnvironments();
63 } finally {
64 setIsRefreshing(false);
65 }
66 })();66 })();
67 }, [refreshRelayEnvironments]);67 }, [refreshRelayEnvironments]);
6868
...@@ -87,7 +87,16 @@ function ConfiguredConnectOnboardingRouteScreen() {...@@ -87,7 +87,16 @@ function ConfiguredConnectOnboardingRouteScreen() {
87 {Platform.OS === "android" ? (87 {Platform.OS === "android" ? (
88 <AndroidSheetHeader88 <AndroidSheetHeader
89 title="Set up T3 Connect"89 title="Set up T3 Connect"
90 actions={[{ accessibilityLabel: "Close", icon: "xmark", onPress: handleClose }]}90 actions={[
91 {
92 accessibilityLabel: "Refresh environments",
93 disabled: isRefreshing,
94 icon: "arrow.clockwise",
95 loading: isRefreshing,
96 onPress: handleRefresh,
97 },
98 { accessibilityLabel: "Close", icon: "xmark", onPress: handleClose },
99 ]}
91 />100 />
92 ) : (101 ) : (
93 <NativeHeaderToolbar placement="right">102 <NativeHeaderToolbar placement="right">
...@@ -95,6 +104,7 @@ function ConfiguredConnectOnboardingRouteScreen() {...@@ -95,6 +104,7 @@ function ConfiguredConnectOnboardingRouteScreen() {
95 </NativeHeaderToolbar>104 </NativeHeaderToolbar>
96 )}105 )}
97 <ScrollView106 <ScrollView
107 nestedScrollEnabled
98 alwaysBounceVertical108 alwaysBounceVertical
99 contentInsetAdjustmentBehavior="automatic"109 contentInsetAdjustmentBehavior="automatic"
100 showsVerticalScrollIndicator={false}110 showsVerticalScrollIndicator={false}
...@@ -106,7 +116,9 @@ function ConfiguredConnectOnboardingRouteScreen() {...@@ -106,7 +116,9 @@ function ConfiguredConnectOnboardingRouteScreen() {
106 paddingTop: 16,116 paddingTop: 16,
107 }}117 }}
108 refreshControl={118 refreshControl={
109 <RefreshControl refreshing={isPullRefreshing} onRefresh={handlePullRefresh} />119 Platform.OS === "ios" ? (
120 <RefreshControl refreshing={isRefreshing} onRefresh={handleRefresh} />
121 ) : undefined
110 }122 }
111 >123 >
112 {isSignedIn ? (124 {isSignedIn ? (
apps/mobile/src/features/connection/ConnectionsNewRouteScreen.tsx+1
...@@ -212,6 +212,7 @@ export function ConnectionsNewRouteScreen({...@@ -212,6 +212,7 @@ export function ConnectionsNewRouteScreen({
212 ]}212 ]}
213 >213 >
214 <ScrollView214 <ScrollView
215 nestedScrollEnabled
215 contentInsetAdjustmentBehavior="automatic"216 contentInsetAdjustmentBehavior="automatic"
216 keyboardShouldPersistTaps="handled"217 keyboardShouldPersistTaps="handled"
217 showsVerticalScrollIndicator={false}218 showsVerticalScrollIndicator={false}
apps/mobile/src/features/threads/ThreadAgentsSheet.tsx+1
...@@ -56,6 +56,7 @@ export function ThreadAgentsSheet({ route }: StaticScreenProps<AgentsTarget>) {...@@ -56,6 +56,7 @@ export function ThreadAgentsSheet({ route }: StaticScreenProps<AgentsTarget>) {
5656
57 const content = (57 const content = (
58 <ScrollView58 <ScrollView
59 nestedScrollEnabled
59 className="flex-1"60 className="flex-1"
60 // The iOS header is translucent and floats over this view; UIKit has to61 // The iOS header is translucent and floats over this view; UIKit has to
61 // inset the content or the first row sits underneath the title.62 // inset the content or the first row sits underneath the title.
apps/mobile/src/features/threads/ThreadQueueControl.tsx+1
...@@ -179,6 +179,7 @@ export function ThreadQueueSheet({ route }: StaticScreenProps<QueueTarget>) {...@@ -179,6 +179,7 @@ export function ThreadQueueSheet({ route }: StaticScreenProps<QueueTarget>) {
179 queuedRuns.map(({ run }) => ({ id: run.id, ...rowLayouts.current.get(run.id) }));179 queuedRuns.map(({ run }) => ({ id: run.id, ...rowLayouts.current.get(run.id) }));
180 const content = (180 const content = (
181 <ScrollView181 <ScrollView
182 nestedScrollEnabled
182 className="flex-1"183 className="flex-1"
183 scrollEnabled={draggedRunId === null}184 scrollEnabled={draggedRunId === null}
184 // The iOS header is translucent and floats over this view; UIKit has to185 // The iOS header is translucent and floats over this view; UIKit has to
apps/mobile/src/features/threads/git/GitBranchesSheet.tsx+1
...@@ -84,6 +84,7 @@ export function GitBranchesSheet(_props: GitBranchesSheetProps) {...@@ -84,6 +84,7 @@ export function GitBranchesSheet(_props: GitBranchesSheetProps) {
84 ) : null}84 ) : null}
85 <MaterialScreenContent fitToContents>85 <MaterialScreenContent fitToContents>
86 <ScrollView86 <ScrollView
87 nestedScrollEnabled
87 className="android:shrink android:grow-0 ios:flex-1"88 className="android:shrink android:grow-0 ios:flex-1"
88 keyboardShouldPersistTaps="handled"89 keyboardShouldPersistTaps="handled"
89 showsVerticalScrollIndicator={false}90 showsVerticalScrollIndicator={false}
apps/mobile/src/features/threads/git/GitCommitSheet.tsx+1
...@@ -103,6 +103,7 @@ export function GitCommitSheet(_props: GitCommitSheetProps) {...@@ -103,6 +103,7 @@ export function GitCommitSheet(_props: GitCommitSheetProps) {
103 ) : null}103 ) : null}
104 <MaterialScreenContent fitToContents>104 <MaterialScreenContent fitToContents>
105 <ScrollView105 <ScrollView
106 nestedScrollEnabled
106 className="android:shrink android:grow-0 ios:flex-1"107 className="android:shrink android:grow-0 ios:flex-1"
107 keyboardShouldPersistTaps="handled"108 keyboardShouldPersistTaps="handled"
108 showsVerticalScrollIndicator={false}109 showsVerticalScrollIndicator={false}
apps/mobile/src/features/threads/git/GitConfirmSheet.tsx+1
...@@ -141,6 +141,7 @@ export function GitConfirmSheet(props: GitConfirmSheetProps) {...@@ -141,6 +141,7 @@ export function GitConfirmSheet(props: GitConfirmSheetProps) {
141141
142 <MaterialScreenContent fitToContents>142 <MaterialScreenContent fitToContents>
143 <ScrollView143 <ScrollView
144 nestedScrollEnabled
144 className="android:shrink android:grow-0 ios:flex-1"145 className="android:shrink android:grow-0 ios:flex-1"
145 showsVerticalScrollIndicator={false}146 showsVerticalScrollIndicator={false}
146 contentContainerClassName="android:gap-2 android:p-2"147 contentContainerClassName="android:gap-2 android:p-2"
apps/mobile/src/features/threads/git/GitOverviewSheet.tsx+11-8
...@@ -252,21 +252,22 @@ function GitOverviewSheetContent(props: GitOverviewSheetProps) {...@@ -252,21 +252,22 @@ function GitOverviewSheetContent(props: GitOverviewSheetProps) {
252252
253 const behindCount = gitStatus.data?.behindCount ?? 0;253 const behindCount = gitStatus.data?.behindCount ?? 0;
254254
255 // Deterministic pull-to-refresh state. Tying RefreshControl to the query's255 // Deterministic refresh state. Tying RefreshControl to the query's
256 // isPending flag left the spinner stuck (the status query reports pending256 // isPending flag left the spinner stuck (the status query reports pending
257 // during quiet background refreshes too).257 // during quiet background refreshes too).
258 const [isPullRefreshing, setIsPullRefreshing] = useState(false);258 const [isRefreshing, setIsRefreshing] = useState(false);
259 const handlePullRefresh = useCallback(async () => {259 const handleRefresh = useCallback(async () => {
260 setIsPullRefreshing(true);260 setIsRefreshing(true);
261 try {261 try {
262 await gitActions.refreshSelectedThreadGitStatus();262 await gitActions.refreshSelectedThreadGitStatus();
263 } finally {263 } finally {
264 setIsPullRefreshing(false);264 setIsRefreshing(false);
265 }265 }
266 }, [gitActions]);266 }, [gitActions]);
267267
268 const content = (268 const content = (
269 <ScrollView269 <ScrollView
270 nestedScrollEnabled
270 alwaysBounceVertical271 alwaysBounceVertical
271 className="flex-1 android:bg-sheet-solid ios:bg-screen"272 className="flex-1 android:bg-sheet-solid ios:bg-screen"
272 contentInsetAdjustmentBehavior={Platform.OS === "ios" ? "automatic" : "never"}273 contentInsetAdjustmentBehavior={Platform.OS === "ios" ? "automatic" : "never"}
...@@ -278,7 +279,9 @@ function GitOverviewSheetContent(props: GitOverviewSheetProps) {...@@ -278,7 +279,9 @@ function GitOverviewSheetContent(props: GitOverviewSheetProps) {
278 gap: Platform.OS === "android" ? 8 : 14,279 gap: Platform.OS === "android" ? 8 : 14,
279 }}280 }}
280 refreshControl={281 refreshControl={
281 <RefreshControl refreshing={isPullRefreshing} onRefresh={() => void handlePullRefresh()} />282 Platform.OS === "ios" || isInspector ? (
283 <RefreshControl refreshing={isRefreshing} onRefresh={() => void handleRefresh()} />
284 ) : undefined
282 }285 }
283 >286 >
284 <View287 <View
...@@ -474,11 +477,11 @@ function GitOverviewSheetContent(props: GitOverviewSheetProps) {...@@ -474,11 +477,11 @@ function GitOverviewSheetContent(props: GitOverviewSheetProps) {
474 {477 {
475 id: "refresh",478 id: "refresh",
476 title: "Refresh repository status",479 title: "Refresh repository status",
477 attributes: { disabled: busy || isPullRefreshing },480 attributes: { disabled: busy || isRefreshing },
478 },481 },
479 ]}482 ]}
480 onPressAction={({ nativeEvent }) => {483 onPressAction={({ nativeEvent }) => {
481 if (nativeEvent.event === "refresh") void handlePullRefresh();484 if (nativeEvent.event === "refresh") void handleRefresh();
482 }}485 }}
483 >486 >
484 {(open) => (487 {(open) => (