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() {
5454 cloudEnvironments: null,
5555 });
5656
57 // Pull-to-refresh tracks its own spinner instead of discovery's refreshing
58 // flag, so background refreshes (e.g. the sign-in one) don't yank the
59 // content down.
60 const [isPullRefreshing, setIsPullRefreshing] = useState(false);
61 const handlePullRefresh = useCallback(() => {
57 const [isRefreshing, setIsRefreshing] = useState(false);
58 const handleRefresh = useCallback(() => {
6259 void (async () => {
63 setIsPullRefreshing(true);
64 await refreshRelayEnvironments();
65 setIsPullRefreshing(false);
60 setIsRefreshing(true);
61 try {
62 await refreshRelayEnvironments();
63 } finally {
64 setIsRefreshing(false);
65 }
6666 })();
6767 }, [refreshRelayEnvironments]);
6868
......@@ -87,7 +87,16 @@ function ConfiguredConnectOnboardingRouteScreen() {
8787 {Platform.OS === "android" ? (
8888 <AndroidSheetHeader
8989 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 ]}
91100 />
92101 ) : (
93102 <NativeHeaderToolbar placement="right">
......@@ -95,6 +104,7 @@ function ConfiguredConnectOnboardingRouteScreen() {
95104 </NativeHeaderToolbar>
96105 )}
97106 <ScrollView
107 nestedScrollEnabled
98108 alwaysBounceVertical
99109 contentInsetAdjustmentBehavior="automatic"
100110 showsVerticalScrollIndicator={false}
......@@ -106,7 +116,9 @@ function ConfiguredConnectOnboardingRouteScreen() {
106116 paddingTop: 16,
107117 }}
108118 refreshControl={
109 <RefreshControl refreshing={isPullRefreshing} onRefresh={handlePullRefresh} />
119 Platform.OS === "ios" ? (
120 <RefreshControl refreshing={isRefreshing} onRefresh={handleRefresh} />
121 ) : undefined
110122 }
111123 >
112124 {isSignedIn ? (
apps/mobile/src/features/connection/ConnectionsNewRouteScreen.tsx+1
......@@ -212,6 +212,7 @@ export function ConnectionsNewRouteScreen({
212212 ]}
213213 >
214214 <ScrollView
215 nestedScrollEnabled
215216 contentInsetAdjustmentBehavior="automatic"
216217 keyboardShouldPersistTaps="handled"
217218 showsVerticalScrollIndicator={false}
apps/mobile/src/features/threads/ThreadAgentsSheet.tsx+1
......@@ -56,6 +56,7 @@ export function ThreadAgentsSheet({ route }: StaticScreenProps<AgentsTarget>) {
5656
5757 const content = (
5858 <ScrollView
59 nestedScrollEnabled
5960 className="flex-1"
6061 // The iOS header is translucent and floats over this view; UIKit has to
6162 // 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>) {
179179 queuedRuns.map(({ run }) => ({ id: run.id, ...rowLayouts.current.get(run.id) }));
180180 const content = (
181181 <ScrollView
182 nestedScrollEnabled
182183 className="flex-1"
183184 scrollEnabled={draggedRunId === null}
184185 // 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) {
8484 ) : null}
8585 <MaterialScreenContent fitToContents>
8686 <ScrollView
87 nestedScrollEnabled
8788 className="android:shrink android:grow-0 ios:flex-1"
8889 keyboardShouldPersistTaps="handled"
8990 showsVerticalScrollIndicator={false}
apps/mobile/src/features/threads/git/GitCommitSheet.tsx+1
......@@ -103,6 +103,7 @@ export function GitCommitSheet(_props: GitCommitSheetProps) {
103103 ) : null}
104104 <MaterialScreenContent fitToContents>
105105 <ScrollView
106 nestedScrollEnabled
106107 className="android:shrink android:grow-0 ios:flex-1"
107108 keyboardShouldPersistTaps="handled"
108109 showsVerticalScrollIndicator={false}
apps/mobile/src/features/threads/git/GitConfirmSheet.tsx+1
......@@ -141,6 +141,7 @@ export function GitConfirmSheet(props: GitConfirmSheetProps) {
141141
142142 <MaterialScreenContent fitToContents>
143143 <ScrollView
144 nestedScrollEnabled
144145 className="android:shrink android:grow-0 ios:flex-1"
145146 showsVerticalScrollIndicator={false}
146147 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) {
252252
253253 const behindCount = gitStatus.data?.behindCount ?? 0;
254254
255 // Deterministic pull-to-refresh state. Tying RefreshControl to the query's
255 // Deterministic refresh state. Tying RefreshControl to the query's
256256 // isPending flag left the spinner stuck (the status query reports pending
257257 // during quiet background refreshes too).
258 const [isPullRefreshing, setIsPullRefreshing] = useState(false);
259 const handlePullRefresh = useCallback(async () => {
260 setIsPullRefreshing(true);
258 const [isRefreshing, setIsRefreshing] = useState(false);
259 const handleRefresh = useCallback(async () => {
260 setIsRefreshing(true);
261261 try {
262262 await gitActions.refreshSelectedThreadGitStatus();
263263 } finally {
264 setIsPullRefreshing(false);
264 setIsRefreshing(false);
265265 }
266266 }, [gitActions]);
267267
268268 const content = (
269269 <ScrollView
270 nestedScrollEnabled
270271 alwaysBounceVertical
271272 className="flex-1 android:bg-sheet-solid ios:bg-screen"
272273 contentInsetAdjustmentBehavior={Platform.OS === "ios" ? "automatic" : "never"}
......@@ -278,7 +279,9 @@ function GitOverviewSheetContent(props: GitOverviewSheetProps) {
278279 gap: Platform.OS === "android" ? 8 : 14,
279280 }}
280281 refreshControl={
281 <RefreshControl refreshing={isPullRefreshing} onRefresh={() => void handlePullRefresh()} />
282 Platform.OS === "ios" || isInspector ? (
283 <RefreshControl refreshing={isRefreshing} onRefresh={() => void handleRefresh()} />
284 ) : undefined
282285 }
283286 >
284287 <View
......@@ -474,11 +477,11 @@ function GitOverviewSheetContent(props: GitOverviewSheetProps) {
474477 {
475478 id: "refresh",
476479 title: "Refresh repository status",
477 attributes: { disabled: busy || isPullRefreshing },
480 attributes: { disabled: busy || isRefreshing },
478481 },
479482 ]}
480483 onPressAction={({ nativeEvent }) => {
481 if (nativeEvent.event === "refresh") void handlePullRefresh();
484 if (nativeEvent.event === "refresh") void handleRefresh();
482485 }}
483486 >
484487 {(open) => (