diff --git a/apps/mobile/src/native/createV5StackNavigator.ios.tsx b/apps/mobile/src/native/createV5StackNavigator.ios.tsx index 4ef229495c664e4201e002d7e05b3117afca4e8e..413bd8712704c632f497758151ed0bd0b1c047fd 100644 --- a/apps/mobile/src/native/createV5StackNavigator.ios.tsx +++ b/apps/mobile/src/native/createV5StackNavigator.ios.tsx @@ -181,7 +181,14 @@ export function V5CardStackView(props: V5StackViewProps) { > - 0} /> + current.key === route.key) > 0 + : index > 0 + } + /> {descriptor.render()} diff --git a/apps/mobile/src/native/workspace-stack-projection.test.ts b/apps/mobile/src/native/workspace-stack-projection.test.ts index 7f0537653ebd683c0443e091445b0bfcf6530706..1f5c00c655519cb68c0c7e6f4caacba88ad4e112 100644 --- a/apps/mobile/src/native/workspace-stack-projection.test.ts +++ b/apps/mobile/src/native/workspace-stack-projection.test.ts @@ -143,21 +143,41 @@ describe("v5 stack handoff", () => { }); it("still retains an unfinished JS pop beside a completed native pop", () => { expect(reconcileStackScreens([home, thread, files], [home], new Set([files.key]))).toEqual([ - thread, home, + thread, ]); }); it("retains removed native screens through a pop followed immediately by a push", () => { const popped = reconcileStackScreens([home, thread, files], [home, thread]); const next = { ...files, key: "new-files" }; const screens = reconcileStackScreens(popped, [home, thread, next]); - expect(screens.map((route) => route.key)).toEqual(["files", "home", "thread", "new-files"]); + expect(screens.map((route) => route.key)).toEqual(["home", "thread", "files", "new-files"]); expect(screens.filter((route) => [home, thread, next].includes(route))).toEqual([ home, thread, next, ]); - expect(screens[0]).toBe(files); + expect(screens[2]).toBe(files); + }); + it("keeps the root and outgoing settings page in place until the JS pop finishes", () => { + const environments = { ...files, key: "environments", name: "SettingsEnvironments" }; + const popped = reconcileStackScreens([settings, environments], [settings]); + expect(popped).toEqual([settings, environments]); + expect(reconcileStackScreens(popped, [settings])).toEqual(popped); + const next = { ...environments, key: "next-environments" }; + expect(reconcileStackScreens(popped, [settings, next])).toEqual([settings, environments, next]); + expect(reconcileStackScreens(popped, [settings], new Set([environments.key]))).toEqual([ + settings, + ]); + }); + it("retains a multi-page pop in order without changing the active router order", () => { + const replacement = { ...thread, key: "replacement" }; + const screens = reconcileStackScreens([home, thread, files], [home, replacement]); + expect(screens).toEqual([home, thread, files, replacement]); + expect(screens.filter((route) => route === home || route === replacement)).toEqual([ + home, + replacement, + ]); }); it("updates params without retaining duplicate copies of the same screen", () => { const updated = { ...thread, params: { threadId: "another-thread" } }; diff --git a/apps/mobile/src/native/workspace-stack-projection.ts b/apps/mobile/src/native/workspace-stack-projection.ts index 48f196cd3f18019f88d88d7296205b6e60a28698..79e66290fe6f8c340a1a06fb70d18797d485a073 100644 --- a/apps/mobile/src/native/workspace-stack-projection.ts +++ b/apps/mobile/src/native/workspace-stack-projection.ts @@ -57,17 +57,18 @@ export function partitionStackPresentations( return groups; } -/** Preserve UIKit's outgoing screens, before newly pushed screens, until dismissal. */ +/** Keep outgoing screens in place until dismissal; Fabric moves enqueue native pop/push operations. */ export function reconcileStackScreens( previous: readonly T[], current: readonly T[], completedNativeDismissals: ReadonlySet = new Set(), ): T[] { const active = new Set(current.map((route) => route.key)); - return [ - ...previous.filter( - (route) => !active.has(route.key) && !completedNativeDismissals.has(route.key), - ), - ...current, - ]; + const screens = [...current]; + previous.forEach((route, index) => { + if (!active.has(route.key) && !completedNativeDismissals.has(route.key)) { + screens.splice(Math.min(index, screens.length), 0, route); + } + }); + return screens; }