authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-08-31 11:40:53-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-08-31 11:40:53-07:00
log746c932e164d49ddb4ac98a42176194b11bf0699
tree295b2810a1381b9514bb7423e776dc2d2e6b8c19
parentbba79cc254b65969bde6b6bfc3032c3b5b9316ae
signature Signed by PGP key B5690EEEBB952194

fix(mobile): defer draft navigation until submission completes (#8914)


1 files changed, 23 insertions(+), 7 deletions(-)

apps/mobile/src/features/threads/NewTaskDraftScreen.tsx+23-7
...@@ -1,9 +1,11 @@...@@ -1,9 +1,11 @@
1import { NativeHeaderToolbar, NativeStackScreenOptions } from "../../native/StackHeader";1import { NativeHeaderToolbar, NativeStackScreenOptions } from "../../native/StackHeader";
2import {2import {
3 CommonActions,
3 StackActions,4 StackActions,
4 useFocusEffect,5 useFocusEffect,
5 useNavigation,6 useNavigation,
6 usePreventRemove,7 usePreventRemove,
8 type NavigationAction,
7} from "@react-navigation/native";9} from "@react-navigation/native";
8import { useCallback, useEffect, useRef, useState } from "react";10import { useCallback, useEffect, useRef, useState } from "react";
9import { Alert, Platform, Pressable, ScrollView, View } from "react-native";11import { Alert, Platform, Pressable, ScrollView, View } from "react-native";
...@@ -212,6 +214,9 @@ export function NewTaskDraftScreen(props: {...@@ -212,6 +214,9 @@ export function NewTaskDraftScreen(props: {
212 const [isCancellingShareImport, setIsCancellingShareImport] = useState(false);214 const [isCancellingShareImport, setIsCancellingShareImport] = useState(false);
213 const [cancelledIncomingShareId, setCancelledIncomingShareId] = useState<string | null>(null);215 const [cancelledIncomingShareId, setCancelledIncomingShareId] = useState<string | null>(null);
214 const [isReturningToProjectPicker, setIsReturningToProjectPicker] = useState(false);216 const [isReturningToProjectPicker, setIsReturningToProjectPicker] = useState(false);
217 const [submitNavigationAction, setSubmitNavigationAction] = useState<NavigationAction | null>(
218 null,
219 );
215 const [shareImportAttempt, setShareImportAttempt] = useState(0);220 const [shareImportAttempt, setShareImportAttempt] = useState(0);
216 const startedShareImportKeyRef = useRef<string | null>(null);221 const startedShareImportKeyRef = useRef<string | null>(null);
217 const cancellingShareImportKeyRef = useRef<string | null>(null);222 const cancellingShareImportKeyRef = useRef<string | null>(null);
...@@ -275,12 +280,23 @@ export function NewTaskDraftScreen(props: {...@@ -275,12 +280,23 @@ export function NewTaskDraftScreen(props: {
275 voiceInput.elapsedSeconds,280 voiceInput.elapsedSeconds,
276 );281 );
277 const isVoiceInputPresented = voicePresentation.statusLabel !== null;282 const isVoiceInputPresented = voicePresentation.statusLabel !== null;
278 usePreventRemove(283 const preventRemove =
279 (isIncomingShareTransferPending && !isProjectPickerReturnActive) ||284 (isIncomingShareTransferPending && !isProjectPickerReturnActive) ||
280 isCancellingShareImport ||285 isCancellingShareImport ||
281 flow.submitting,286 flow.submitting;
282 () => undefined,287 usePreventRemove(preventRemove, () => undefined);
283 );288 useEffect(() => {
289 if (preventRemove || submitNavigationAction === null) {
290 return;
291 }
292 // Give the guard update a frame to reach the parent sheet before navigating,
293 // just like the project-picker fallback below.
294 const frame = requestAnimationFrame(() => {
295 setSubmitNavigationAction(null);
296 (navigation.getParent() ?? navigation).dispatch(submitNavigationAction);
297 });
298 return () => cancelAnimationFrame(frame);
299 }, [navigation, preventRemove, submitNavigationAction]);
284 const hasImportedIncomingShare = Boolean(300 const hasImportedIncomingShare = Boolean(
285 props.incomingShareId &&301 props.incomingShareId &&
286 flow.draftKey &&302 flow.draftKey &&
...@@ -872,7 +888,7 @@ export function NewTaskDraftScreen(props: {...@@ -872,7 +888,7 @@ export function NewTaskDraftScreen(props: {
872 clearWorkspaceSelection: true,888 clearWorkspaceSelection: true,
873 });889 });
874 }890 }
875 navigation.getParent()?.goBack();891 setSubmitNavigationAction(CommonActions.goBack());
876 return;892 return;
877 }893 }
878894
...@@ -943,7 +959,7 @@ export function NewTaskDraftScreen(props: {...@@ -943,7 +959,7 @@ export function NewTaskDraftScreen(props: {
943 clearWorkspaceSelection: true,959 clearWorkspaceSelection: true,
944 });960 });
945 }961 }
946 navigation.dispatch(962 setSubmitNavigationAction(
947 StackActions.replace("Thread", {963 StackActions.replace("Thread", {
948 environmentId: String(result.value.environmentId),964 environmentId: String(result.value.environmentId),
949 threadId: String(result.value.threadId),965 threadId: String(result.value.threadId),