diff --git a/docs/expo-router.md b/docs/expo-router.md
index af5ce6792..14344a3f1 100644
--- a/docs/expo-router.md
+++ b/docs/expo-router.md
@@ -96,6 +96,18 @@ Keep workspace identity and retention outside native-stack `getId` and
Navigation `getId`, and `getId` has broken Android native-stack/Fabric by
reordering an already-mounted workspace screen.
+Use `ThemedStack` from `packages/app/src/navigation/themed-stack.tsx` for every
+Expo Router stack. React Navigation otherwise paints each native stack screen
+with its light default background. A screen-level wrapper can hide that surface
+while settled, but Android may expose it for one frame when navigation crosses
+from a nested stack to its parent stack. This is especially visible when an
+app-wide route such as `/new` opens from a dark workspace.
+
+Do not read the active theme with `useUnistyles()` in a layout to build
+`screenOptions`. `ThemedStack` keeps that third-party prop theme-reactive through
+a small `withUnistyles` boundary without subscribing the route tree itself to
+every Unistyles runtime update.
+
## Regression Shape
Pure helper tests are useful but not enough. The failure mode here is native
diff --git a/packages/app/src/app/_layout.tsx b/packages/app/src/app/_layout.tsx
index e397affd5..db6db10e5 100644
--- a/packages/app/src/app/_layout.tsx
+++ b/packages/app/src/app/_layout.tsx
@@ -52,6 +52,7 @@ import {
type StartupBlocker,
} from "@/navigation/host-runtime-bootstrap";
import { registerWorkspaceRouteNavigationRef } from "@/navigation/workspace-route-navigation";
+import { ThemedStack } from "@/navigation/themed-stack";
import { shouldUseDesktopDaemon } from "@/desktop/daemon/desktop-daemon";
import { listenToDesktopEvent } from "@/desktop/electron/events";
import { updateDesktopWindowControls } from "@/desktop/electron/window";
@@ -784,21 +785,15 @@ function FaviconStatusSync() {
return null;
}
+const ROOT_STACK_SCREEN_OPTIONS = {
+ headerShown: false,
+ animation: "none" as const,
+};
+
function RootStack() {
const storeReady = useStoreReady();
- const { theme } = useUnistyles();
- const stackScreenOptions = useMemo(
- () => ({
- headerShown: false,
- animation: "none" as const,
- contentStyle: {
- backgroundColor: theme.colors.surface0,
- },
- }),
- [theme.colors.surface0],
- );
return (
-
+
@@ -815,7 +810,7 @@ function RootStack() {
-
+
);
}
diff --git a/packages/app/src/app/h/[serverId]/_layout.tsx b/packages/app/src/app/h/[serverId]/_layout.tsx
index 70f7fcf55..4cd3428e0 100644
--- a/packages/app/src/app/h/[serverId]/_layout.tsx
+++ b/packages/app/src/app/h/[serverId]/_layout.tsx
@@ -2,6 +2,7 @@ import { Redirect, Stack, useLocalSearchParams } from "expo-router";
import { useHostRuntimeBootstrapState } from "@/app/_layout";
import { HostRouteProvider } from "@/navigation/host-route-context";
import { resolveStartupRoute } from "@/navigation/host-runtime-bootstrap";
+import { ThemedStack } from "@/navigation/themed-stack";
import { useHostRegistryStatus, useHosts } from "@/runtime/host-runtime";
const HOST_STACK_SCREEN_OPTIONS = {
@@ -33,14 +34,14 @@ function KnownHostRoute() {
}
const stack = (
-
+
-
+
);
if (!routeServerId) {
diff --git a/packages/app/src/navigation/themed-stack.tsx b/packages/app/src/navigation/themed-stack.tsx
new file mode 100644
index 000000000..cf83f701c
--- /dev/null
+++ b/packages/app/src/navigation/themed-stack.tsx
@@ -0,0 +1,26 @@
+import type { NativeStackNavigationOptions } from "@react-navigation/native-stack";
+import { Stack } from "expo-router";
+import { type ReactNode, useMemo } from "react";
+import { withUnistyles } from "react-native-unistyles";
+
+interface ThemedStackBaseProps {
+ backgroundColor: string;
+ children?: ReactNode;
+ screenOptions?: NativeStackNavigationOptions;
+}
+
+function ThemedStackBase({ backgroundColor, children, screenOptions }: ThemedStackBaseProps) {
+ const themedScreenOptions = useMemo(
+ () => ({
+ ...screenOptions,
+ contentStyle: [{ backgroundColor }, screenOptions?.contentStyle],
+ }),
+ [backgroundColor, screenOptions],
+ );
+
+ return {children};
+}
+
+export const ThemedStack = withUnistyles(ThemedStackBase, (theme) => ({
+ backgroundColor: theme.colors.surface0,
+}));