diff --git a/skills/migrate-to-static-config/SKILL.md b/skills/migrate-to-static-config/SKILL.md
index 1cd54a8..17675b5 100644
--- a/skills/migrate-to-static-config/SKILL.md
+++ b/skills/migrate-to-static-config/SKILL.md
@@ -1,6 +1,7 @@
---
name: migrate-to-static-config
-description: Migrate React Navigation navigators from dynamic component based config to static object based config.
+description: "Use when converting React Navigation navigators from JSX-based dynamic config to the static object-based config API. Covers createXNavigator({screens}), .with() wrappers, custom navigators using useNavigationBuilder, lazy loading migration, auth flows, linking, and type updates."
+compatibility: "React Navigation 7.x+"
---
# Migrating to Static Config
@@ -13,6 +14,24 @@ Convert React Navigation navigators from JSX-based dynamic setup to static confi
You are migrating screens from Dynamic API to the Static API in React Navigation.
+Triggering symptoms:
+
+- Navigator files use ``, ``, or similar JSX patterns with `` children
+- The codebase has a `NavigationContainer` wrapping a component-based root navigator
+- Hand-written `ParamList` types are maintained alongside navigator definitions
+- A centralized `linking.config.screens` object duplicates the navigator tree structure
+- `getComponent` or render callbacks are used on `Screen` elements
+
+## Safety principle
+
+**Do not break working code.** If you are uncertain whether a change preserves behavior, do NOT apply it. Instead:
+
+1. Leave the code unchanged.
+2. Add a comment or report to the user describing the suggested change, the uncertainty, and the risk.
+3. Let the user decide whether to proceed.
+
+This applies to: type changes that may break call sites, wrapper removal that may lose behavior, linking changes that may break deep links, and any transformation where the before/after equivalence is not obvious. When in doubt, leave it and explain.
+
## Adaptation policy
Treat the patterns in this skill as canonical starting points, not an exhaustive list. The examples are meant to illustrate the core patterns.
@@ -52,6 +71,123 @@ Ask for clarification when:
- Migrating would require assumptions about which behavior is intentional.
- It is unclear whether related helpers should be updated as part of the same change.
+## Environment conflicts
+
+Project-level instructions (CLAUDE.md, .cursorrules, etc.) may restrict operations this skill requires. Handle conflicts as follows:
+
+- **Package updates blocked** -- If project instructions prohibit modifying package.json, skip the prerequisite version check. Note which packages may need updating and inform the user, but proceed with migration guidance using current versions.
+- **Code modification restricted** -- If instructions restrict editing navigation files, produce the migration as a diff or structured plan instead of applying changes directly. Explain what would change and why.
+- **Tool execution restricted** -- If instructions prohibit running npm/yarn commands, skip automated checks. Document which checks the user should run manually.
+- **Conflicting conventions** -- If project conventions conflict with this skill's patterns (e.g., "always use dynamic config"), flag the conflict to the user and ask how to proceed. Do not silently follow either instruction.
+
+When in doubt, inform the user of the conflict and let them decide. Do not silently skip steps or silently override project instructions.
+
+## Common Mistakes
+
+These are patterns that commonly cause migration failures. Check for them proactively.
+
+### 1. Bottom-up migration breaks parent types
+
+`.with()` changes the return type from `React.ComponentType` to `TypedNavigatorStaticDecorated`. If the parent navigator is dynamic and uses `component={MyNavigator}`, it will produce a type error.
+
+**Check:** Before migrating any navigator, run:
+```bash
+grep -rn 'component={YourNavigatorName}\|component: YourNavigatorName' src/
+```
+If any match is in a dynamic parent, migrate the parent first or use `.getComponent()` on the static child.
+
+### 2. Using `React.lazy` for `getComponent` migration
+
+`React.lazy` requires async `import()` and adds Suspense fallback flashes for screens that previously loaded instantly via synchronous `require()`. Use the synchronous `lazyScreen` utility from the reference file instead.
+
+**Check:** After migration, run:
+```bash
+grep -rn 'React\.lazy' src/ --include='*.tsx' --include='*.ts'
+```
+Any match in a navigator file is likely wrong.
+
+### 3. Hooks called directly in static config callbacks
+
+Static navigator config is created at module load time, not during render. Hooks in `options`, `listeners`, or `screenOptions` callbacks will crash.
+
+**Check:** Review every `options:` and `screenOptions:` in the static config. If any calls a hook (`use*`), move it to `.with()` or a `screenOptions` callback passed via `.with()`.
+
+### 4. Missing `linking` on screens with custom paths
+
+Auto-generated linking uses kebab-case of the screen name. Removing an explicit `linking` entry that had a custom path (e.g., `linking: 'contacts'`) silently changes the URL to the auto-generated path (e.g., `tab-contacts`), breaking existing deep links.
+
+**Check:** Compare old linking config entries against new screen-level `linking`. Every screen that had a custom path in the old `linking.config.screens` must have an explicit `linking` in static config.
+
+### 5. Outdated `@react-navigation/*` packages
+
+`.with()` was added in later 7.x versions. If packages are outdated, `.with()` does not exist and the migration fails.
+
+**Check:** Before starting, run:
+```bash
+npm ls @react-navigation/core @react-navigation/native @react-navigation/native-stack 2>/dev/null | grep -E '@react-navigation'
+```
+Then compare against latest published versions:
+```bash
+npm view @react-navigation/core@latest version
+npm view @react-navigation/native@latest version
+```
+
+### 6. Test files rendering navigator as JSX
+
+Test files that render `` break when the export changes from a component to a static config object.
+
+**Check:** After migration, run:
+```bash
+grep -rn '
+ ```
+ Should return zero matches for files that were migrated to static config.
+
+3. **No hand-written ParamList types remain (unless derived):**
+ ```bash
+ grep -rn 'ParamList\s*=' src/
+ ```
+ Every match should use `StaticParamList`, not hand-written types.
+
+4. **No old screen prop types remain in migrated screens:**
+ ```bash
+ grep -rn 'NativeStackScreenProps\|BottomTabScreenProps\|DrawerScreenProps' src/
+ ```
+ Migrated screens should use `StaticScreenProps` instead.
+
+5. **Root type augmentation exists:**
+ ```bash
+ grep -rn 'RootParamList extends' src/
+ ```
+ Exactly one `ReactNavigation.RootParamList` augmentation next to the root static navigator.
+
+6. **No `NavigationContainer` remains (if root was migrated):**
+ ```bash
+ grep -rn 'NavigationContainer' src/
+ ```
+ Should be replaced with `createStaticNavigation()`.
+
+7. **Linking preserved:** Compare old `linking.config.screens` paths against new screen-level `linking` entries. Every custom path must be explicitly present.
+
+8. **Wrapper behavior preserved:** For each migrated navigator that had wrapper JSX (providers, View wrappers, accessibility attributes, event handlers), verify the `.with()` callback reproduces the same DOM/component structure. Compare the old render output with the new one — every wrapper, style, and accessibility prop (`accessibilityViewIsModal`, `aria-modal`, `role`, etc.) must be present.
+
+9. **App runs and navigates correctly:** Manual smoke test — navigate to every migrated screen, test deep links, test back button behavior.
+
## References
Check `@react-navigation/native` in `package.json` first.
diff --git a/skills/migrate-to-static-config/agents/openai.yaml b/skills/migrate-to-static-config/agents/openai.yaml
index 523c537..a35766e 100644
--- a/skills/migrate-to-static-config/agents/openai.yaml
+++ b/skills/migrate-to-static-config/agents/openai.yaml
@@ -1,4 +1,4 @@
interface:
display_name: "Migrate React Navigation to Static Config"
- short_description: "Convert JSX navigators to static config"
+ short_description: "Convert JSX navigators to static object-based config"
default_prompt: "Use $migrate-to-static-config to migrate React Navigation navigators from dynamic JSX to static config."
diff --git a/skills/migrate-to-static-config/references/react-navigation-7.md b/skills/migrate-to-static-config/references/react-navigation-7.md
index a8faa5b..218b7fa 100644
--- a/skills/migrate-to-static-config/references/react-navigation-7.md
+++ b/skills/migrate-to-static-config/references/react-navigation-7.md
@@ -5,15 +5,18 @@ Use this file only when `@react-navigation/native` is on `7.x`.
## Prerequisites
- The project is using React Navigation 7.x.
-- Before migrating any navigator, ensure `@react-navigation/*` packages in `package.json` are updated to the latest published 7.x version:
- - Run `npm view package-name@latest version` for each `@react-navigation` package in `package.json` to check the latest version, for example `npm view @react-navigation/native@latest version`.
- - If the versions are not up-to-date, stop and ask whether to update them.
- - Once confirmed, update the versions in `package.json` and install them.
- - Do not proceed with the migration unless versions are updated.
+- Before migrating, check if `@react-navigation/*` packages are on the latest published 7.x version:
+ - Run `npm view package-name@latest version` for each `@react-navigation` package to check.
+ - If versions are outdated, recommend updating and explain why (newer 7.x versions added static config support and fixes). If the user declines or updates are blocked by project constraints, proceed with current versions and note any features that may be unavailable.
## Official reference
-Fetch [llms.txt](https://reactnavigation.org/llms.txt) for a list of documentation links. During the migration, find the relevant link based on the topic and refer to the official docs when needed.
+Fetch [llms.txt](https://reactnavigation.org/llms.txt) for a table of contents of all React Navigation documentation. During the migration, when you encounter an API, pattern, or type not fully covered in this reference, find the relevant link in llms.txt and fetch that specific page. Common mappings:
+
+- Custom navigators or routers -- fetch "Custom navigators" and "Custom routers" pages
+- Static config API details -- fetch "Static configuration" page
+- Type errors or TypeScript issues -- fetch "TypeScript" page
+- Screen options for a specific navigator -- fetch that navigator's API page (e.g., "Stack Navigator")
## Structure
@@ -26,7 +29,29 @@ Fetch [llms.txt](https://reactnavigation.org/llms.txt) for a list of documentati
## Workflow
-### Identify static candidates
+### Step 0: Check the parent navigator and navigator origin
+
+**Before classifying or editing any navigator, run these two prerequisite checks.** Do not skip them.
+
+**Check A — Parent navigator:**
+
+1. Find the parent navigator that renders this navigator as a `` component.
+2. If the parent uses JSX-based dynamic config (`` with `` children, hooks for screenOptions, conditional rendering via JSX expressions), it is dynamic.
+3. **If the parent is dynamic, classify this navigator as "keep dynamic"** unless the user explicitly wants to migrate bottom-up. Static navigators nested inside dynamic ones lose automatic linking and TypeScript type inference at the boundary (see "Mixing Static and Dynamic APIs" below).
+4. If the user wants to proceed anyway, use the "Dynamic root navigator, static nested navigator" pattern: call `.getComponent()` on the static child and `createPathConfigForStaticNavigation()` for linking.
+5. If you are migrating the root navigator, skip Check A — proceed to Check B.
+
+**Check B — Navigator origin:**
+
+1. Check if the navigator is produced by a **factory function** (a function that takes a screen list or config and returns a navigator component). Run:
+ ```bash
+ grep -rn 'function create.*Navigator\|const create.*Navigator' src/ --include='*.ts' --include='*.tsx'
+ ```
+2. If the navigator comes from a factory that generates **10+ navigators**, default to "keep dynamic" unless the user explicitly requests migration. Migrating factory-generated navigators requires duplicating the factory's shared wrapper logic (hooks, View wrappers, accessibility attributes) into individual `.with()` calls, which scales poorly and is error-prone.
+3. If the factory generates **fewer than 10 navigators**, it is a candidate for the "Migrating navigator factories" pattern (see below). Proceed to classification.
+4. If the navigator is NOT factory-generated, proceed to classification.
+
+### Classify the navigator
A navigator is a static candidate if all its screens are known at build time. Classify it before editing code:
@@ -45,6 +70,8 @@ A navigator is a static candidate if all its screens are known at build time. Cl
- Navigation structure that depends on async data before the full route tree can be known
- Child navigators whose parent navigator must stay dynamic and cannot represent the child as a static screen entry
+When a navigator matches both "adaptation" and "keep dynamic" criteria, "keep dynamic" takes precedence.
+
Start the migration from the root navigator and work downwards. If the root navigator is not a static candidate, abort the migration unless the user explicitly wants to keep the root dynamic and migrate only nested navigators.
### Identify custom navigators
@@ -52,7 +79,7 @@ Start the migration from the root navigator and work downwards. If the root navi
A custom navigator uses the `useNavigationBuilder` hook. Before migration, ensure it uses same patterns for its types as official docs:
```tsx
-import * as React from 'react';
+import * as React from "react";
import {
View,
Text,
@@ -60,7 +87,7 @@ import {
type StyleProp,
type ViewStyle,
StyleSheet,
-} from 'react-native';
+} from "react-native";
import {
createNavigatorFactory,
CommonActions,
@@ -75,7 +102,7 @@ import {
type TypedNavigator,
useNavigationBuilder,
type NavigationProp,
-} from '@react-navigation/native';
+} from "@react-navigation/native";
type MyNavigationConfig = {
// Additional props accepted by the view
@@ -165,7 +192,7 @@ const MyNavigator = createMyNavigator({
Home: HomeScreen,
Profile: {
screen: ProfileScreen,
- options: { title: 'My Profile' },
+ options: { title: "My Profile" },
},
},
});
@@ -175,6 +202,20 @@ The actual implementation of the navigator is not relevant to the migration. The
If it doesn't accept a config object, update it to use the `createNavigatorFactory` and navigator API patterns shown above before migration. If it already uses the same patterns, there are no changes needed to the navigator implementation for static config migration.
+#### Custom router options in static config
+
+If the custom navigator accepts additional router options beyond the standard set (e.g., `sidebarScreen`, `persistentScreens`, custom layout modes), these options cannot be expressed in the static config object directly. The static config API passes `screens`, `groups`, `screenOptions`, `screenListeners`, and `initialRouteName` to the navigator -- not custom router options.
+
+Options for handling custom router options:
+
+1. **Static values** -- Set them as default values in the navigator implementation itself.
+2. **Dynamic values via `.with()`** -- Pass them as navigator props inside a `.with()` wrapper.
+3. **Keep dynamic** -- If the custom router options must be computed from hooks or parent context that cannot be accessed via `.with()` + `useRoute()`, keep the navigator dynamic.
+
+When using `.with()` to pass custom router options, the flow is: the `.with()` wrapper passes props to `` → the navigator component receives them → it forwards them to `useNavigationBuilder` options → `useNavigationBuilder` passes non-standard options to the router factory. This means any prop you pass to `` inside `.with()` will reach the custom router, as long as the navigator component forwards it.
+
+If the navigator component does NOT forward the prop to `useNavigationBuilder` (e.g., it consumes it for rendering only), you may need to modify the navigator to thread it through. If neither `.with()` nor navigator modification can deliver the option to the router, keep the navigator dynamic.
+
### Convert navigator JSX to static config
Convert the existing navigator first, then introduce screen config objects only where a screen needs options, listeners, params, IDs, linking, or `if`.
@@ -191,7 +232,7 @@ function MyStack() {
);
@@ -207,7 +248,7 @@ const MyStack = createNativeStackNavigator({
Home: HomeScreen,
Profile: {
screen: ProfileScreen,
- options: { title: 'My Profile' },
+ options: { title: "My Profile" },
},
},
});
@@ -229,7 +270,7 @@ const MyStack = createNativeStackNavigator({
initialParams: {},
getId: ({ params }) => params.id,
linking: {
- path: 'pattern/:id',
+ path: "pattern/:id",
parse: { id: Number },
stringify: { id: (value) => String(value) },
exact: true,
@@ -297,11 +338,11 @@ Before:
function RootStack() {
return (
-
+
-
+
@@ -315,14 +356,14 @@ After:
const RootStack = createNativeStackNavigator({
groups: {
Card: {
- screenOptions: { headerStyle: { backgroundColor: 'red' } },
+ screenOptions: { headerStyle: { backgroundColor: "red" } },
screens: {
Home: HomeScreen,
Profile: ProfileScreen,
},
},
Modal: {
- screenOptions: { presentation: 'modal' },
+ screenOptions: { presentation: "modal" },
screens: {
Settings: SettingsScreen,
},
@@ -379,7 +420,7 @@ function App() {
>
)}
@@ -415,6 +456,8 @@ const RootStack = createNativeStackNavigator({
### Use `.with()` for wrappers, providers, and dynamic navigator props
+`.with()` renders a regular React component. It can use any React hooks (including `useEffect`, `useCallback`, custom hooks), return early (e.g., `return null` for loading guards in nested navigators), and render arbitrary JSX (multiple nested wrappers, event handlers, refs, animated views). The `{ Navigator }` argument is the configured navigator component — render it wherever you would render ``.
+
If the dynamic navigator is rendered in a component that uses hooks for navigator-level behavior, or has wrappers around the mounted navigator, use `.with()` to provide this wrapper. This applies to navigator-level props such as `initialRouteName`, `backBehavior`, `screenOptions`, and `screenListeners` that are derived dynamically.
#### Wrapping with a provider and dynamic props and options
@@ -497,12 +540,12 @@ function MyStack() {
@@ -526,13 +569,13 @@ const MyStack = createNativeStackNavigator({
{
switch (route.name) {
- case 'Home':
+ case "Home":
return {
- title: getSomething('First'),
+ title: getSomething("First"),
};
- case 'Profile':
+ case "Profile":
return {
- title: getSomething('Second'),
+ title: getSomething("Second"),
};
default:
return {};
@@ -544,6 +587,73 @@ const MyStack = createNativeStackNavigator({
});
```
+#### Dynamic `initialParams` from hooks
+
+If a screen's `initialParams` are computed from hooks or URL state at mount time, use `.with()` to compute them and pass via the `screenOptions` callback or a context provider.
+
+Before:
+
+```tsx
+function MyStack() {
+ const computedId = useComputedId();
+
+ return (
+
+
+
+ );
+}
+```
+
+After:
+
+```tsx
+const MyStack = createNativeStackNavigator({
+ screens: {
+ Detail: DetailScreen,
+ },
+}).with(({ Navigator }) => {
+ const computedId = useComputedId();
+
+ return ;
+});
+```
+
+If different screens need different computed `initialParams`, use a context provider in `.with()` and read the context inside each screen component.
+
+#### Merging a static per-screen options map with dynamic base options
+
+If the codebase defines a static map of per-screen options and merges them with hook-derived base options at runtime, use a `screenOptions` callback inside `.with()`:
+
+```tsx
+const OPTIONS_PER_SCREEN: Record = {
+ Settings: { animationTypeForReplace: "pop" },
+ Profile: { gestureDirection: "vertical" },
+};
+
+const MyStack = createNativeStackNavigator({
+ screens: {
+ Settings: SettingsScreen,
+ Profile: ProfileScreen,
+ },
+}).with(({ Navigator }) => {
+ const baseOptions = useBaseScreenOptions();
+
+ return (
+ ({
+ ...baseOptions,
+ ...OPTIONS_PER_SCREEN[route.name],
+ })}
+ />
+ );
+});
+```
+
#### Convert render callbacks for screens
Static config doesn't support render callbacks on screens.
@@ -563,7 +673,7 @@ Before:
After:
```tsx
-const TokenContext = React.createContext('');
+const TokenContext = React.createContext("");
function ChatScreen() {
const token = React.useContext(TokenContext);
@@ -649,6 +759,49 @@ const MyStack = createNativeStackNavigator({
If multiple of these patterns are used on the same screen, use appropriate combinations of context and layout.
+#### Migrating navigator factories
+
+A factory function that generates multiple navigators from a shared template (e.g., a function that takes a screen map and returns a configured navigator component) is not a custom navigator in the `useNavigationBuilder` sense — it is a wrapper-generator.
+
+To migrate a factory to static config:
+
+1. Convert each factory invocation to a separate `createXNavigator({ screens: ... })` call.
+2. Extract shared wrapper logic into a reusable `.with()` callback.
+
+```tsx
+// Shared wrapper for all factory-generated navigators
+function withModalWrapper({
+ Navigator,
+}: {
+ Navigator: React.ComponentType;
+}) {
+ const screenOptions = useModalScreenOptions();
+
+ return (
+
+
+
+ );
+}
+
+// Each factory call becomes a static navigator with the shared wrapper
+const SettingsModal = createNativeStackNavigator({
+ screens: {
+ Profile: ProfileScreen,
+ Preferences: PreferencesScreen,
+ },
+}).with(withModalWrapper);
+
+const ReportsModal = createNativeStackNavigator({
+ screens: {
+ ReportList: ReportListScreen,
+ ReportDetail: ReportDetailScreen,
+ },
+}).with(withModalWrapper);
+```
+
+If the factory iterates over a static object to build screens (e.g., `Object.keys(screenMap)`), the screens ARE known at build time — this is a static candidate despite the iteration pattern.
+
#### Migrating `getComponent` lazy loading
Static config uses a `screen` component and doesn't support `getComponent`. Use a custom utility to lazily render the screen:
@@ -667,6 +820,8 @@ const lazyScreen = >(
Place this utility in a shared file such as `utils/lazyScreen.ts` following the pattern of other shared utilities in the codebase.
+This utility preserves synchronous loading semantics. Do not use `React.lazy(() => import(...))` for `getComponent` migration -- `React.lazy` requires async `import()` and introduces Suspense fallback flashes for screens that previously loaded instantly via synchronous `require()`. The `lazyScreen` utility above avoids this by calling the factory synchronously during render.
+
Then, replace `getComponent` with the lazy screen:
Before:
@@ -674,7 +829,7 @@ Before:
```tsx
require('./SettingsScreen').default}
+ getComponent={() => require("./SettingsScreen").default}
/>
```
@@ -684,8 +839,8 @@ After:
const MyStack = createNativeStackNavigator({
screens: {
Settings: {
- screen: lazyScreen(
- () => require('./SettingsScreen').default,
+ screen: lazyScreen(
+ () => require("./SettingsScreen").default,
),
},
},
@@ -704,15 +859,15 @@ Before:
```tsx
const linking = {
- prefixes: ['https://example.com'],
+ prefixes: ["https://example.com"],
config: {
screens: {
- Home: '',
+ Home: "",
Profile: {
- path: 'user/:id',
+ path: "user/:id",
parse: { id: Number },
},
- Settings: 'settings',
+ Settings: "settings",
},
},
};
@@ -725,12 +880,12 @@ const RootStack = createNativeStackNavigator({
screens: {
Home: {
screen: HomeScreen,
- linking: '', // explicit root path; omit if this is the first leaf screen or the initialRouteName
+ linking: "", // explicit root path; omit if this is the first leaf screen or the initialRouteName
},
Profile: {
screen: ProfileScreen,
linking: {
- path: 'user/:id',
+ path: "user/:id",
parse: { id: Number },
},
},
@@ -765,9 +920,9 @@ Before:
function ProfileScreen({
navigation,
route,
-}: NativeStackScreenProps) {
+}: NativeStackScreenProps) {
const id = route.params.id;
- navigation.navigate('Home');
+ navigation.navigate("Home");
}
```
@@ -782,7 +937,7 @@ function ProfileScreen({ route }: ProfileScreenProps) {
const navigation = useNavigation();
const id = route.params.id;
- navigation.navigate('Home');
+ navigation.navigate("Home");
}
```
@@ -795,7 +950,7 @@ type RootStackParamList = StaticParamList;
type ProfileNavigationProp = NativeStackNavigationProp<
RootStackParamList,
- 'Profile'
+ "Profile"
>;
const navigation = useNavigation();
@@ -816,6 +971,16 @@ If a static navigator nests a dynamic navigator, annotate the dynamic navigator
For the root navigator, keep the single source of truth in the `RootParamList` augmentation shown below.
+#### Custom screen prop types
+
+If the codebase uses custom screen prop types from a custom navigator (e.g., `PlatformStackScreenProps` instead of `NativeStackScreenProps`), migrate them alongside the standard types:
+
+1. Replace the custom screen prop type with `StaticScreenProps` for param typing.
+2. If the custom type provides navigator-specific navigation methods, use `useNavigation()` with a manual type annotation as a temporary bridge.
+3. Update all screen files that reference the old custom type.
+
+If many screen files (10+) reference the custom type, consider a codemod or find-and-replace to update them in bulk rather than manually.
+
Avoid circular dependencies by:
- Using `StaticScreenProps` for screen params instead of shared hand-written param lists
@@ -864,7 +1029,7 @@ const RootStack = createNativeStackNavigator({
Article: {
screen: ArticleScreen,
linking: {
- path: 'article/:date',
+ path: "article/:date",
parse: {
date: (date: string) => new Date(date),
},
@@ -894,8 +1059,8 @@ const Stack = createNativeStackNavigator();
function ArticleScreen({
navigation,
route,
-}: NativeStackScreenProps) {
- return
@@ -927,7 +1092,7 @@ import {
type StaticParamList,
type StaticScreenProps,
useNavigation,
-} from '@react-navigation/native';
+} from "@react-navigation/native";
type ArticleScreenProps = StaticScreenProps<{
author: string;
@@ -939,7 +1104,7 @@ function ArticleScreen({ route }: ArticleScreenProps) {
return (
navigation.navigate('Albums')}
+ onPress={() => navigation.navigate("Albums")}
/>
);
}
@@ -953,8 +1118,8 @@ const RootStack = createNativeStackNavigator({
Article: {
screen: ArticleScreen,
options: ({ route }) => ({ title: route.params.author }),
- initialParams: { author: 'Gandalf' },
- linking: 'article/:author',
+ initialParams: { author: "Gandalf" },
+ linking: "article/:author",
},
Albums: AlbumsScreen,
},
@@ -983,11 +1148,11 @@ Before:
```tsx
const linking = {
- prefixes: ['https://example.com', 'example://'],
+ prefixes: ["https://example.com", "example://"],
config: {
screens: {
- Home: '',
- Profile: 'profile/:id',
+ Home: "",
+ Profile: "profile/:id",
},
},
};
@@ -1004,14 +1169,14 @@ function App() {
After:
```tsx
-import { createStaticNavigation } from '@react-navigation/native';
+import { createStaticNavigation } from "@react-navigation/native";
const RootStack = createNativeStackNavigator({
screens: {
Home: HomeScreen,
Profile: {
screen: ProfileScreen,
- linking: 'profile/:id',
+ linking: "profile/:id",
},
},
});
@@ -1022,8 +1187,8 @@ function App() {
return (
@@ -1060,9 +1225,7 @@ type FeedParamList = {
Popular: undefined;
};
-type FeedScreenProps = StaticScreenProps<
- NavigatorScreenParams
->;
+type FeedScreenProps = StaticScreenProps>;
function FeedScreen(_: FeedScreenProps) {
return (
@@ -1079,10 +1242,10 @@ const RootStack = createNativeStackNavigator({
Feed: createNativeStackScreen({
screen: FeedScreen,
linking: {
- path: 'feed',
+ path: "feed",
screens: {
- Latest: 'latest',
- Popular: 'popular',
+ Latest: "latest",
+ Popular: "popular",
},
},
}),
@@ -1121,12 +1284,12 @@ type RootStackParamList = {
const feedScreens = createPathConfigForStaticNavigation(FeedTabs);
const linking = {
- prefixes: ['https://example.com', 'example://'],
+ prefixes: ["https://example.com", "example://"],
config: {
screens: {
- Home: '',
+ Home: "",
Feed: {
- path: 'feed',
+ path: "feed",
screens: feedScreens,
},
},
@@ -1136,6 +1299,10 @@ const linking = {
### Gotchas
+#### Platform-specific option keys
+
+If the codebase uses custom navigator types that extend screen options with platform-specific keys (e.g., a `web:` or `native:` key inside `screenOptions`), these are not standard React Navigation options. They will work in static config as long as the custom navigator's TypeScript types accept them. When passing platform-specific options via `.with()`'s `screenOptions` prop, ensure the custom navigator component handles the platform-specific keys the same way it does in dynamic config — the `.with()` wrapper passes `screenOptions` through to the navigator unchanged.
+
#### Module-load timing
Static navigator config is created at module load time, not during component render.
@@ -1150,7 +1317,7 @@ If the value should resolve later, wrap it in a callback:
```tsx
options: () => ({
- tabBarLabel: translate('tabs:home'),
+ tabBarLabel: translate("tabs:home"),
});
```
@@ -1162,16 +1329,4 @@ options: () => ({
## Review
-1. All `@react-navigation/*` packages were updated to the latest published 7.x versions before migration.
-2. No `NativeStackScreenProps`, `BottomTabScreenProps`, or custom screen-prop aliases remain. Use `useNavigation()` for access, the screen `route` prop when available, and `StaticScreenProps` for params.
-3. `RootParamList` augmentation in `ReactNavigation` lives next to the root static navigator.
-4. `createStaticNavigation` replaces `NavigationContainer`.
-5. Root `linking` contains container-level settings such as `prefixes` and `enabled`. Screen paths live in screen-level `linking`.
-6. Linking config is present only where custom paths or params are required. Defaults are kebab-case.
-7. Every screen with params uses `StaticScreenProps`. Screen-level `linking` is used for URL parsing and serialization.
-8. No render callbacks remain on screens. Extra props use React context and wrappers use `layout`.
-9. Any previous `getComponent` screens now use custom utility
-10. No hand-written param lists remain unless derived via `StaticParamList`.
-11. No hooks are called directly in `screenOptions`, `options`, or `listeners` callbacks.
-12. Loading or boot UI lives outside ``.
-13. No circular type references or obsolete shared type files remain from the old dynamic setup.
+Use the **Definition of Done** checklist in `SKILL.md`. It contains all verification commands with expected outputs. This reference file intentionally does not duplicate those checks to avoid context bloat — both files load together during migration.
diff --git a/skills/upgrade-react-navigation/SKILL.md b/skills/upgrade-react-navigation/SKILL.md
index 9790e2b..d1bc016 100644
--- a/skills/upgrade-react-navigation/SKILL.md
+++ b/skills/upgrade-react-navigation/SKILL.md
@@ -1,13 +1,184 @@
---
name: upgrade-react-navigation
-description: Upgrade React Navigation from 6.x to 7.x or from 7.x to 8.x.
+description: Use when upgrading React Navigation across major versions (6.x to 7.x or 7.x to 8.x). Covers breaking changes in TypeScript setup, navigator options, bottom tabs, linking config, custom navigators with useNavigationBuilder, deprecated API removal, and peer dependency updates.
+compatibility: Requires React Navigation 6.x or 7.x as starting version.
---
# Upgrade React Navigation
+## Goal
+
+Upgrade React Navigation to the next major version and handle all required breaking changes while preserving existing navigation behavior.
+
+## When to use
+
+You are upgrading React Navigation from one major version to the next (6.x -> 7.x or 7.x -> 8.x).
+
+## Safety principle
+
+**Do not break working code.** If you are uncertain whether a change preserves behavior, do NOT apply it. Instead:
+
+1. Leave the code unchanged.
+2. Add a comment or report to the user describing the suggested change, the uncertainty, and the risk.
+3. Let the user decide whether to proceed.
+
+This applies to: removing APIs whose replacements are unclear, updating custom navigator internals where the new type signature is ambiguous, changing `inactiveBehavior` on screens with custom persistence logic, and any transformation where the before/after equivalence is not obvious. When in doubt, leave it and explain.
+
+## Adaptation policy
+
+Treat the migration steps in this skill as canonical starting points, not an exhaustive list.
+
+When applying this skill to a codebase:
+
+- Follow the migration order specified in the reference file.
+- If the codebase uses custom navigators, custom routers, or direct rendering of internal components (e.g., `StackView`), the reference may not cover every affected API. Use llms.txt to fetch the relevant official documentation pages and verify what changed.
+- Do not require an exact matching example in the skill before proceeding. Infer the closest equivalent pattern and adapt it.
+- Keep changes minimal -- upgrade only what the new version requires. Do not refactor unrelated code.
+
+## Scope rule
+
+Do not treat the absence of an explicit example as a blocker. If the reference covers the general pattern (e.g., "custom navigators need updated overloads"), use the guidance plus official docs to derive the specific changes needed for the local code.
+
+## When to ask for clarification
+
+Inspect the local code first.
+
+If, after reading the relevant navigator and the upgrade reference, you cannot determine with high confidence whether an API changed or how to update it, pause and ask the user before editing code.
+
+Ask for clarification when:
+
+- A custom navigator uses internal APIs not covered by the reference or official docs.
+- The upgrade would require assumptions about which behavior changes are acceptable.
+- It is unclear whether related helpers, tests, or configuration should be updated as part of the same change.
+
+## Environment conflicts
+
+Project-level instructions (CLAUDE.md, .cursorrules, etc.) may restrict operations this skill requires. Handle conflicts as follows:
+
+- **Package updates blocked** -- If project instructions prohibit modifying package.json, document the required version changes and inform the user. Do not proceed with code changes until dependencies are updated, as the new APIs may not be available.
+- **Code modification restricted** -- If instructions restrict editing navigation files, produce the upgrade as a diff or structured plan instead of applying changes directly.
+- **Tool execution restricted** -- If instructions prohibit running npm/yarn commands, skip automated checks. Document which checks the user should run manually.
+- **Conflicting conventions** -- If project conventions conflict with the upgrade requirements, flag the conflict to the user and ask how to proceed.
+
+When in doubt, inform the user of the conflict and let them decide. Do not silently skip steps or silently override project instructions.
+
+## Common Mistakes
+
+These are patterns that commonly cause upgrade failures. Check proactively.
+
+### 1. Not upgrading all `@react-navigation/*` packages together
+
+Mixing major versions (e.g., `@react-navigation/core` 8.x with `@react-navigation/native-stack` 7.x) causes type errors and runtime crashes.
+
+**Check:** After upgrading, run:
+```bash
+npm ls 2>/dev/null | grep '@react-navigation' | grep -v 'deduped'
+```
+All packages must be on the same major version.
+
+### 2. Stale `NavigatorID` generic in custom navigator types
+
+Custom navigators using `useNavigationBuilder` typically have a `NavigatorID extends string | undefined` generic. This generic is removed in 8.x but easy to miss.
+
+**Check:** Run:
+```bash
+grep -rn 'NavigatorID' src/ --include='*.ts' --include='*.tsx'
+```
+Every match needs updating — remove the generic parameter and its usage in `NavigationProp`, `DefaultNavigatorOptions`, and factory overloads.
+
+### 3. Using removed `freezeOnBlur`/`detachInactiveScreens` instead of `inactiveBehavior`
+
+These options are removed silently (no runtime error), so the app appears to work but inactive screens behave differently.
+
+**Check:** Run:
+```bash
+grep -rn 'freezeOnBlur\|detachInactiveScreens\|detachPreviousScreen' src/ --include='*.ts' --include='*.tsx'
+```
+Replace every match with the appropriate `inactiveBehavior` value.
+
+### 4. Missing `moduleResolution: 'bundler'` in tsconfig
+
+8.x requires `moduleResolution: 'bundler'`. Without it, TypeScript cannot resolve the new package exports.
+
+**Check:** Run:
+```bash
+grep -n 'moduleResolution' tsconfig.json
+```
+Must show `"bundler"`.
+
+### 5. Replacing `setParams` with `pushParams` globally
+
+Only call sites inside tab/drawer navigators with `backBehavior="fullHistory"` that NEED history push should use `pushParams`. All other `setParams` calls remain unchanged.
+
+**Check:** Before replacing, identify which navigators use `backBehavior="fullHistory"`:
+```bash
+grep -rn 'backBehavior.*fullHistory\|fullHistory.*backBehavior' src/
+```
+Only `setParams` calls on screens INSIDE those navigators are candidates.
+
+### 6. Bottom tabs missing `headerShown: true`
+
+8.x changes the default to `headerShown: false`. Existing tab navigators lose their headers silently.
+
+**Check:** Run:
+```bash
+grep -rn 'createBottomTabNavigator' src/ --include='*.ts' --include='*.tsx'
+```
+For each match, verify `headerShown: true` is in `screenOptions` if headers were previously visible.
+
+## Definition of Done
+
+Upgrade is complete when ALL of the following pass:
+
+1. **TypeScript compiles cleanly:**
+ ```bash
+ npx tsc --noEmit
+ ```
+ Zero errors related to navigation types.
+
+2. **No removed APIs remain:**
+ ```bash
+ grep -rn 'navigateDeprecated\|navigationInChildEnabled\|freezeOnBlur\|detachInactiveScreens\|detachPreviousScreen\|headerBackImage\b\|headerBackImageSource\|\.getId\b\|UNSTABLE_router\|UNSTABLE_routeNamesChangeBehavior' src/ --include='*.ts' --include='*.tsx'
+ ```
+ Zero matches.
+
+3. **No stale `NavigatorID` generics remain:**
+ ```bash
+ grep -rn 'NavigatorID' src/ --include='*.ts' --include='*.tsx'
+ ```
+ Zero matches (unless the codebase defines its own unrelated `NavigatorID`).
+
+4. **No navigator `id` props remain:**
+ ```bash
+ grep -rn '<.*Navigator.*\bid=' src/ --include='*.tsx'
+ ```
+ Zero matches. All `navigation.getParent(id)` calls rewritten to use screen names.
+
+5. **Peer dependencies correct:**
+ ```bash
+ npm ls react-native-screens react-native-safe-area-context react-native-reanimated react-native-pager-view @callstack/liquid-glass 2>/dev/null
+ ```
+ All installed at required minimum versions.
+
+6. **Root type registration updated:**
+ ```bash
+ grep -rn 'namespace ReactNavigation' src/
+ ```
+ Should show `RootNavigator` module augmentation (8.x), not `RootParamList` (7.x).
+
+7. **Hook generics removed:**
+ ```bash
+ grep -rn 'useNavigation<\|useRoute<\|useNavigationState<' src/ --include='*.ts' --include='*.tsx'
+ ```
+ Zero matches. Hooks no longer accept generics in 8.x.
+
+8. **App runs and navigates correctly:** Manual smoke test — navigate to every major screen, test deep links, test back button, verify tab headers visible.
+
+## References
+
Check `@react-navigation/native` in `package.json` first.
-- If `6.x`, read [`references/upgrade-6-to-7.md`](./references/upgrade-6-to-7.md).
-- If `7.x`, read [`references/upgrade-7-to-8.md`](./references/upgrade-7-to-8.md).
+- If `6.x`, read [`references/upgrade-6-to-7.md`](./references/upgrade-6-to-7.md)
+- If `7.x`, read [`references/upgrade-7-to-8.md`](./references/upgrade-7-to-8.md)
Load exactly one reference file unless explicitly comparing versions.
diff --git a/skills/upgrade-react-navigation/agents/openai.yaml b/skills/upgrade-react-navigation/agents/openai.yaml
index d7ea6bd..f78bce5 100644
--- a/skills/upgrade-react-navigation/agents/openai.yaml
+++ b/skills/upgrade-react-navigation/agents/openai.yaml
@@ -1,4 +1,4 @@
interface:
display_name: "Upgrade React Navigation"
- short_description: "Migrate React Navigation across major versions"
- default_prompt: "Use $upgrade-react-navigation to upgrade React Navigation from 6.x to 7.x or from 7.x to 8.x."
+ short_description: "Upgrade React Navigation across major versions (6→7, 7→8)"
+ default_prompt: "Use $upgrade-react-navigation to upgrade React Navigation to the next major version."
diff --git a/skills/upgrade-react-navigation/references/upgrade-7-to-8.md b/skills/upgrade-react-navigation/references/upgrade-7-to-8.md
index 4a14ed8..5bee5f5 100644
--- a/skills/upgrade-react-navigation/references/upgrade-7-to-8.md
+++ b/skills/upgrade-react-navigation/references/upgrade-7-to-8.md
@@ -26,7 +26,13 @@ Upgrade React Navigation to 8.x and handle the required breaking changes.
## Official reference
-Fetch [llms.txt](https://reactnavigation.org/llms-8.x.txt) for a list of documentation links. During the migration, refer to the official documentation for API reference for the latest React Navigation 8.x versions.
+Fetch [llms.txt](https://reactnavigation.org/llms-8.x.txt) for a table of contents of all React Navigation 8.x documentation. During the upgrade, when you encounter an API not fully covered in this reference, find the relevant link in llms.txt and fetch that specific page. Common mappings:
+- Stack navigator APIs -> fetch "Stack Navigator" page
+- Custom navigators or routers -> fetch "Custom navigators" and "Custom routers" pages
+- Screen API changes -> fetch "Screen" page
+- TypeScript changes -> fetch "TypeScript" page
+
+**Note:** The 8.x documentation pages from llms.txt may still reference removed APIs (e.g., `freezeOnBlur` in option lists, `NavigatorID` in type examples) if the docs have not been fully updated. When the skill reference and the fetched docs disagree, follow the skill reference for breaking changes — it is based on the actual 8.x source code. Use the fetched docs primarily to confirm API stability (i.e., that an API is still present), not to confirm removal.
## Areas to review
@@ -34,6 +40,7 @@ Fetch [llms.txt](https://reactnavigation.org/llms-8.x.txt) for a list of documen
- Bottom tabs, custom tab bars, tab headers, and image-based tab icons
- Navigation APIs affected by removed navigator ids, `getParent(id)`, `navigateDeprecated`, `navigationInChildEnabled`, `setParams` with `backBehavior="fullHistory"`, and `getId` behavior changes
- Removed lifecycle and layout props such as detach/freeze options, `Header` layout props, and `getDefaultHeaderHeight`
+- `getComponent` lazy loading: still supported in 8.x for both static and dynamic config. No migration needed for `getComponent` usage itself. If the codebase wraps `getComponent` in a custom factory, run `npx tsc --noEmit` after upgrading to check for type mismatches in the wrapper's screen type generics.
- Linking and static navigation config, including `prefixes`, `enabled`, `UNSTABLE_router`, and `UNSTABLE_routeNamesChangeBehavior`
- Direct `@react-navigation/elements` usage, removed exports, and direct `Header` rendering
@@ -126,7 +133,135 @@ const Stack = createNativeStackNavigator({
#### Custom navigators need updated overloads
-If the repo defines a custom navigator, update its `createNavigatorFactory` overloads and any local navigator wrapper typings.
+If the repo defines custom navigators using `useNavigationBuilder` and `createNavigatorFactory`, update them for 8.x.
+
+**Finding custom navigators through abstraction layers:** Large codebases often wrap `useNavigationBuilder` and `createNavigatorFactory` inside intermediate factory functions (e.g., `createPlatformNavigator` that internally calls `createNavigatorFactory`). These wrappers inherit the same upgrade requirements. Find all layers:
+```bash
+grep -rn 'useNavigationBuilder\|createNavigatorFactory' src/ --include='*.ts' --include='*.tsx'
+```
+Trace each match to its exported API. If file A calls `createNavigatorFactory` and file B imports from file A to create navigators, BOTH files may need updates. Update from the inside out: fix the innermost `useNavigationBuilder` call first, then fix each wrapper layer's generic parameters and type exports.
+
+**`NavigatorID` generic removal:**
+The `NavigatorID` generic type parameter is removed from navigator types. Update `createNavigatorFactory` overloads, `NavigationProp` types, and `DefaultNavigatorOptions` to remove the `NavigatorID` generic (or the `string | undefined` slot it occupied).
+
+Before (7.x):
+
+```tsx
+type MyNavigationProp<
+ ParamList extends ParamListBase,
+ RouteName extends keyof ParamList = keyof ParamList,
+ NavigatorID extends string | undefined = undefined,
+> = NavigationProp<
+ ParamList,
+ RouteName,
+ NavigatorID,
+ StackNavigationState,
+ MyNavigationOptions,
+ MyNavigationEventMap
+>;
+
+type Props = DefaultNavigatorOptions<
+ ParamListBase,
+ string | undefined,
+ StackNavigationState,
+ MyNavigationOptions,
+ MyNavigationEventMap,
+ MyNavigationProp
+> & StackRouterOptions;
+```
+
+After (8.x):
+
+```tsx
+type MyNavigationProp<
+ ParamList extends ParamListBase,
+ RouteName extends keyof ParamList = keyof ParamList,
+> = NavigationProp<
+ ParamList,
+ RouteName,
+ StackNavigationState,
+ MyNavigationOptions,
+ MyNavigationEventMap
+>;
+
+type Props = DefaultNavigatorOptions<
+ ParamListBase,
+ StackNavigationState,
+ MyNavigationOptions,
+ MyNavigationEventMap,
+ MyNavigationProp
+> & StackRouterOptions;
+```
+
+Remove the `NavigatorID` generic parameter and the `string | undefined` slot it occupied from `NavigationProp`, `DefaultNavigatorOptions`, and the factory function's generic parameter list. Update `NavigatorTypeBagBase`, `TypedNavigator`, and `StaticConfig` usages similarly — drop any generic slot that previously held `NavigatorID`.
+
+If the codebase has a standalone `NavigationProp` type alias with 3+ generics (e.g., `NavigationProp`), update it to 2 generics: `NavigationProp`. The same applies to `RouteProp` and screen prop utility types.
+
+**`id` in `useNavigationBuilder` options:**
+If the navigator passes `id` to `useNavigationBuilder` options, remove it. Navigator `id` is no longer supported.
+
+**`useNavigationBuilder` return values:**
+Check if the navigator destructures return values from `useNavigationBuilder`. The `describe` function and `NavigationContent` wrapper may have changed signatures.
+
+Find all destructured return values:
+```bash
+grep -rn 'useNavigationBuilder' src/ --include='*.ts' --include='*.tsx' -A 3 | grep -E 'const \{|describe|NavigationContent'
+```
+
+After upgrading, compare against the new return type:
+```bash
+cat node_modules/@react-navigation/core/lib/typescript/commonjs/src/useNavigationBuilder.d.ts 2>/dev/null | grep -A 20 'return'
+```
+
+If `describe` or `NavigationContent` signatures changed, `npx tsc --noEmit` will show the exact type mismatch.
+
+**Generic parameter count:** The number of generic type parameters on `useNavigationBuilder` may have changed between 7.x and 8.x. If the codebase passes 6+ generics to `useNavigationBuilder`, compare against the 8.x type definition in `node_modules/@react-navigation/core` after upgrading. Remove or update any generic slots that no longer exist (typically the `NavigatorID` slot).
+
+**Screen options transform (3rd argument):** If the codebase passes a third argument to `useNavigationBuilder` (a screen options transform function, e.g., `convertToWebNavigationOptions`), check that this overload still exists in 8.x.
+
+Find all 3-argument `useNavigationBuilder` calls:
+```bash
+grep -rn 'useNavigationBuilder' src/ --include='*.ts' --include='*.tsx' -A 5 | grep -B 2 'convertTo\|transform\|, ('
+```
+
+After upgrading, inspect the type definition:
+```bash
+cat node_modules/@react-navigation/core/lib/typescript/commonjs/src/useNavigationBuilder.d.ts 2>/dev/null | head -40
+```
+
+If the 3rd argument overload was removed, inline the screen options transform into the navigator's render logic.
+
+**Direct `StackView` rendering:**
+If a custom navigator renders `StackView` (or other view components) directly with `state`, `descriptors`, `navigation`, and `describe` props, check their types after upgrading.
+
+Find all direct `StackView` usages:
+```bash
+grep -rn 'StackView' src/ --include='*.ts' --include='*.tsx'
+```
+
+`StackView` is not a documented public API. After upgrading, read its type definition directly:
+```bash
+cat node_modules/@react-navigation/stack/lib/typescript/commonjs/src/views/Stack/StackView.d.ts 2>/dev/null || cat node_modules/@react-navigation/stack/src/views/Stack/StackView.tsx
+```
+
+Compare the prop interface against what your code passes. Pay attention to the `describe` prop — it may have changed signature or been renamed. Run `npx tsc --noEmit` to surface any prop type mismatches.
+
+**Custom router method signatures:**
+If the codebase extends `StackRouter`, `TabRouter`, or other built-in routers, check method signatures after upgrading.
+
+Find all custom router extensions:
+```bash
+grep -rn 'extends StackRouter\|extends TabRouter\|extends DrawerRouter\|extends BaseRouter' src/ --include='*.ts' --include='*.tsx'
+```
+
+For each match, compare the overridden method signatures against the 8.x base router types:
+```bash
+cat node_modules/@react-navigation/routers/lib/typescript/commonjs/src/StackRouter.d.ts 2>/dev/null | grep -A 3 'getStateForAction\|getInitialState\|getRehydratedState'
+```
+
+Run `npx tsc --noEmit` after upgrading — type errors on custom routers surface as mismatches in `getStateForAction`, `getInitialState`, `getRehydratedState`, or `RouterConfigOptions`.
+
+When the reference does not provide enough detail for a specific custom navigator pattern, use llms.txt to fetch the relevant official documentation page and derive the changes.
### 3. Update navigator behavior and option APIs
@@ -260,6 +395,19 @@ Use the new screen option instead:
Default behavior is now `pause`.
+If the codebase implements custom detach or screen-persistence logic, check for conflicts with `inactiveBehavior`:
+
+```bash
+grep -rn 'persistentScreens\|dontDetach\|shouldDetach\|freezeNonTop\|keepMounted' src/ --include='*.ts' --include='*.tsx'
+```
+
+For each match, determine how the custom logic interacts with `inactiveBehavior`:
+- If the custom logic **prevents unmounting** certain screens: set `inactiveBehavior: 'none'` on those screens to preserve the behavior. The new default `pause` may freeze effects that persistent screens relied on.
+- If the custom logic **forces unmounting** non-focused screens: set `inactiveBehavior: 'unmount'` (stack/native-stack only) to match the old behavior.
+- If the custom logic reads `detachInactiveScreens` or `freezeOnBlur` from navigator options: update it to read `inactiveBehavior` instead and map the values accordingly.
+
+After updating, run the app and verify: (1) persistent screens remain interactive when unfocused, (2) non-persistent screens are correctly paused/unmounted, (3) effects on persistent screens (timers, subscriptions) continue running.
+
#### Rename and remove the affected navigator options
Apply these direct updates: