Layouts nest the way folders do. BookNest's root layout is a stack whose first screen is the whole (tabs) group, so the tab navigator lives inside the stack. The file's default export wraps this stack in SessionProvider and a StatusBar: the root layout is where app-wide providers go, since every screen renders inside it.
function RootStack() {
const { user } = useSession();
const path = usePathname();
useEffect(() => console.log('path:', path), [path]);
return (
<Stack screenOptions={{ headerTintColor: '#1565c0' }}>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="book/[id]" options={{ title: '' }} />
<Stack.Screen name="notes/[id]"
options={{ presentation: 'modal', title: 'Reading notes' }} />
<Stack.Protected guard={user !== null}>
<Stack.Screen name="checkout" options={{ title: 'Checkout' }} />
</Stack.Protected>
<Stack.Screen name="sign-in" options={{ title: 'Sign in' }} />
</Stack>
);
}Where a screen sits decides what surrounds it. book/[id] belongs to the root stack, so it covers the tab bar; inside (tabs), under a stack layout of its own, it would keep the tab bar visible. The (tabs) screen hides the stack's header because the tabs draw their own. presentation: 'modal' makes reading notes a sheet on iOS; on Android it is a full screen, as the emulator showed.
A navigator's options reach its direct children only: headerTintColor here does not color the tab headers. And a linked screen needs something underneath it. Cold-starting BookNest from booknest://book/3 and pressing Back left the app. One more line in the root layout, export const unstable_settings = { anchor: '(tabs)' };, names the screen the router places at the bottom of the stack (anchor replaced the deprecated initialRouteName). With it, the same test logged path: / after Back and showed the catalog.