diff --git a/apps/mobile/src/app/_layout.tsx b/apps/mobile/src/app/_layout.tsx
index 968be6c14a8..382a7100757 100644
--- a/apps/mobile/src/app/_layout.tsx
+++ b/apps/mobile/src/app/_layout.tsx
@@ -26,6 +26,8 @@ import {
useClerkSettingsSheetDetent,
} from "../features/cloud/ClerkSettingsSheetDetent";
import { useAgentNotificationNavigation } from "../features/agent-awareness/notificationNavigation";
+import { pushScreenAnimation } from "../lib/pushScreenAnimation";
+import { useHeaderBlurEffect } from "../lib/useHeaderBlurEffect";
import { useThemeColor } from "../lib/useThemeColor";
function AppNavigator() {
@@ -45,7 +47,9 @@ function AppNavigatorContent() {
const { collapse, isExpanded } = useClerkSettingsSheetDetent();
const colorScheme = useColorScheme();
const statusBarBg = useThemeColor("--color-status-bar");
+ const screenColor = useThemeColor("--color-screen");
const sheetStyle = useResolveClassNames("bg-sheet");
+ const headerBlurEffect = useHeaderBlurEffect();
useAgentNotificationNavigation();
useThreadOutboxDrain();
@@ -96,9 +100,10 @@ function AppNavigatorContent() {
@@ -112,9 +117,10 @@ function AppNavigatorContent() {
diff --git a/apps/mobile/src/app/connections/_layout.tsx b/apps/mobile/src/app/connections/_layout.tsx
index 902b53cb15a..4bf840a0b75 100644
--- a/apps/mobile/src/app/connections/_layout.tsx
+++ b/apps/mobile/src/app/connections/_layout.tsx
@@ -1,5 +1,6 @@
import Stack from "expo-router/stack";
import { useResolveClassNames } from "uniwind";
+import { pushScreenAnimation } from "../../lib/pushScreenAnimation";
import { useThemeColor } from "../../lib/useThemeColor";
export const unstable_settings = {
@@ -23,7 +24,7 @@ export default function ConnectionsLayout() {
}}
>
-
+
);
}
diff --git a/apps/mobile/src/app/new/_layout.tsx b/apps/mobile/src/app/new/_layout.tsx
index 2113b13311c..6960aaae2bc 100644
--- a/apps/mobile/src/app/new/_layout.tsx
+++ b/apps/mobile/src/app/new/_layout.tsx
@@ -2,6 +2,7 @@ import Stack from "expo-router/stack";
import { useResolveClassNames } from "uniwind";
import { NewTaskFlowProvider } from "../../features/threads/new-task-flow-provider";
+import { pushScreenAnimation } from "../../lib/pushScreenAnimation";
import { useThemeColor } from "../../lib/useThemeColor";
export const unstable_settings = {
@@ -29,21 +30,24 @@ export default function NewTaskLayout() {
+
-
);
diff --git a/apps/mobile/src/app/settings/_layout.tsx b/apps/mobile/src/app/settings/_layout.tsx
index 2607c2cd1f1..193ba4b90a4 100644
--- a/apps/mobile/src/app/settings/_layout.tsx
+++ b/apps/mobile/src/app/settings/_layout.tsx
@@ -3,6 +3,7 @@ import { useCallback } from "react";
import { useResolveClassNames } from "uniwind";
import { useClerkSettingsSheetDetent } from "../../features/cloud/ClerkSettingsSheetDetent";
+import { pushScreenAnimation } from "../../lib/pushScreenAnimation";
import { useThemeColor } from "../../lib/useThemeColor";
export const unstable_settings = {
@@ -37,25 +38,25 @@ export default function SettingsLayout() {
);
diff --git a/apps/mobile/src/app/threads/[environmentId]/[threadId]/_layout.tsx b/apps/mobile/src/app/threads/[environmentId]/[threadId]/_layout.tsx
index 92e90920e2d..dcab393b216 100644
--- a/apps/mobile/src/app/threads/[environmentId]/[threadId]/_layout.tsx
+++ b/apps/mobile/src/app/threads/[environmentId]/[threadId]/_layout.tsx
@@ -2,7 +2,11 @@ import Stack from "expo-router/stack";
import { StyleSheet } from "react-native";
import { useResolveClassNames } from "uniwind";
+import { pushScreenAnimation } from "../../../../lib/pushScreenAnimation";
+import { useHeaderBlurEffect } from "../../../../lib/useHeaderBlurEffect";
+
export default function ThreadLayout() {
+ const headerBlurEffect = useHeaderBlurEffect();
const sheetStyle = StyleSheet.flatten(useResolveClassNames("bg-sheet"));
const headerBg = {
backgroundColor: (sheetStyle as { backgroundColor?: string })?.backgroundColor,
@@ -16,6 +20,7 @@ export default function ThreadLayout() {
contentStyle: { backgroundColor: "transparent" },
headerShown: true,
headerTransparent: true,
+ headerBlurEffect,
headerShadowVisible: false,
headerTitle: "",
}}
@@ -45,8 +50,9 @@ export default function ThreadLayout() {
,
diff --git a/apps/mobile/src/features/home/HomeHeader.tsx b/apps/mobile/src/features/home/HomeHeader.tsx
index 9757d5fbf91..18b0e78b2df 100644
--- a/apps/mobile/src/features/home/HomeHeader.tsx
+++ b/apps/mobile/src/features/home/HomeHeader.tsx
@@ -11,6 +11,7 @@ import {
import { Stack } from "expo-router";
import { Text as RNText, View } from "react-native";
+import { useHeaderBlurEffect } from "../../lib/useHeaderBlurEffect";
import { useThemeColor } from "../../lib/useThemeColor";
import { MOBILE_TYPOGRAPHY } from "../../lib/typography";
import type { HomeProjectSortOrder } from "./homeThreadList";
@@ -72,6 +73,7 @@ export function HomeHeader(props: {
readonly onOpenSettings: () => void;
readonly onStartNewTask: () => void;
}) {
+ const headerBlurEffect = useHeaderBlurEffect();
const iconColor = useThemeColor("--color-icon");
const mutedColor = useThemeColor("--color-foreground-muted");
const subtleColor = useThemeColor("--color-subtle");
@@ -88,6 +90,7 @@ export function HomeHeader(props: {
headerShown: true,
headerTransparent: true,
headerStyle: { backgroundColor: "transparent" },
+ headerBlurEffect,
headerShadowVisible: false,
headerTintColor: iconColor,
headerTitle: "",
diff --git a/apps/mobile/src/features/review/ReviewSheet.tsx b/apps/mobile/src/features/review/ReviewSheet.tsx
index 92203c0ed4e..44bba904a80 100644
--- a/apps/mobile/src/features/review/ReviewSheet.tsx
+++ b/apps/mobile/src/features/review/ReviewSheet.tsx
@@ -19,6 +19,7 @@ import { AppText as Text } from "../../components/AppText";
import { environmentCatalog } from "../../connection/catalog";
import { useEnvironmentPresentation } from "../../state/presentation";
import { useAtomCommand } from "../../state/use-atom-command";
+import { useHeaderBlurEffect } from "../../lib/useHeaderBlurEffect";
import { useThemeColor } from "../../lib/useThemeColor";
import { MOBILE_TYPOGRAPHY } from "../../lib/typography";
import { useThreadDraftForThread } from "../../state/use-thread-composer-state";
@@ -113,6 +114,7 @@ function ReviewSelectionActionBar(props: {
export function ReviewSheet() {
const insets = useSafeAreaInsets();
const colorScheme = useColorScheme();
+ const headerBlurEffect = useHeaderBlurEffect();
const headerForeground = String(useThemeColor("--color-foreground"));
const headerMuted = String(useThemeColor("--color-foreground-muted"));
const headerIcon = String(useThemeColor("--color-icon"));
@@ -241,6 +243,7 @@ export function ReviewSheet() {
Gesture.Pan()
.enabled(!isSplitLayout)
- .hitSlop({ left: 0, width: 40 })
+ // Confine the drawer pan to the top-left corner where it completes
+ // (see the `event.y` check below). Covering the full left edge stole
+ // the touch from the native swipe-back gesture, leaving back
+ // navigation dead below the header.
+ .hitSlop({ left: 0, width: 40, top: 0, height: drawerGestureThreshold })
.activeOffsetX([10, 999])
.failOffsetY([-24, 24])
.onEnd((event) => {
diff --git a/apps/mobile/src/features/threads/ThreadRouteScreen.tsx b/apps/mobile/src/features/threads/ThreadRouteScreen.tsx
index f8c916974e5..c16460731ba 100644
--- a/apps/mobile/src/features/threads/ThreadRouteScreen.tsx
+++ b/apps/mobile/src/features/threads/ThreadRouteScreen.tsx
@@ -5,6 +5,7 @@ import { EnvironmentId, type ProjectScript } from "@t3tools/contracts";
import { projectScriptCwd, projectScriptRuntimeEnv } from "@t3tools/shared/projectScripts";
import { Pressable, ScrollView, Text as RNText, View } from "react-native";
import { useWorkspaceState } from "../../state/workspace";
+import { useHeaderBlurEffect } from "../../lib/useHeaderBlurEffect";
import { useThemeColor } from "../../lib/useThemeColor";
import { useEnvironmentQuery } from "../../state/query";
import { dismissGitActionResult, useGitActionProgress } from "../../state/use-vcs-action-state";
@@ -104,6 +105,7 @@ export function ThreadRouteScreen() {
const iconColor = String(useThemeColor("--color-icon"));
const foregroundColor = String(useThemeColor("--color-foreground"));
const secondaryFg = String(useThemeColor("--color-foreground-secondary"));
+ const headerBlurEffect = useHeaderBlurEffect();
/* ─── Git status for native header trigger ───────────────────────── */
const gitStatus = useEnvironmentQuery(
@@ -326,6 +328,7 @@ export function ThreadRouteScreen() {
headerShown: true,
headerTransparent: true,
headerStyle: { backgroundColor: "transparent" },
+ headerBlurEffect,
headerShadowVisible: false,
headerTintColor: iconColor,
headerBackTitle: "",
diff --git a/apps/mobile/src/lib/pushScreenAnimation.ts b/apps/mobile/src/lib/pushScreenAnimation.ts
new file mode 100644
index 00000000000..cb953e7c2f5
--- /dev/null
+++ b/apps/mobile/src/lib/pushScreenAnimation.ts
@@ -0,0 +1,12 @@
+import { Platform } from "react-native";
+
+/**
+ * Stack animation for pushed (non-sheet) screens.
+ *
+ * iOS keeps the default push animation: forcing `slide_from_right` switches
+ * react-native-screens to its custom swipe animator, which paints a black
+ * void behind the outgoing screen during interactive swipe-back. The native
+ * default is visually the same slide with proper parallax over the previous
+ * screen. Android has no interactive pop, so it keeps `slide_from_right`.
+ */
+export const pushScreenAnimation = Platform.OS === "ios" ? "default" : "slide_from_right";
diff --git a/apps/mobile/src/lib/useHeaderBlurEffect.ts b/apps/mobile/src/lib/useHeaderBlurEffect.ts
new file mode 100644
index 00000000000..94d091479dc
--- /dev/null
+++ b/apps/mobile/src/lib/useHeaderBlurEffect.ts
@@ -0,0 +1,15 @@
+import { useColorScheme } from "react-native";
+
+/**
+ * Blur effect for transparent navigation headers.
+ *
+ * Recent iOS betas stopped drawing the implicit material behind transparent
+ * navigation bars, and the trait-adaptive `systemChromeMaterial` resolves to
+ * its light variant there even while the app renders in dark mode — so pick
+ * the light/dark variant explicitly from the app color scheme.
+ */
+export function useHeaderBlurEffect() {
+ return useColorScheme() === "dark"
+ ? ("systemChromeMaterialDark" as const)
+ : ("systemChromeMaterialLight" as const);
+}