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;
}