From 0bf96cab24555e24a9b97d1ec04e5e121efcdb1c Mon Sep 17 00:00:00 2001 From: chrisnojima Date: Thu, 2 Apr 2026 09:58:53 -0400 Subject: [PATCH 1/2] better route typing --- shared/constants/types/router.tsx | 6 ------ shared/router-v2/react-navigation.d.ts | 10 ++++++++++ shared/router-v2/route-params.tsx | 7 ------- 3 files changed, 10 insertions(+), 13 deletions(-) create mode 100644 shared/router-v2/react-navigation.d.ts diff --git a/shared/constants/types/router.tsx b/shared/constants/types/router.tsx index 5e320597d845..a31508557481 100644 --- a/shared/constants/types/router.tsx +++ b/shared/constants/types/router.tsx @@ -9,12 +9,6 @@ export type GetOptionsParams } -// Type for screen components that receive navigation props -export type ScreenProps = { - navigation: NativeStackNavigationProp - route: RouteProp -} - export type ScreenComponentProps = { route: {params: any} navigation: NativeStackNavigationProp diff --git a/shared/router-v2/react-navigation.d.ts b/shared/router-v2/react-navigation.d.ts new file mode 100644 index 000000000000..27cc0898d72b --- /dev/null +++ b/shared/router-v2/react-navigation.d.ts @@ -0,0 +1,10 @@ +import type {RootParamList as KBRootParamList} from './route-params' + +declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace ReactNavigation { + interface RootParamList extends KBRootParamList {} + } +} + +export {} diff --git a/shared/router-v2/route-params.tsx b/shared/router-v2/route-params.tsx index 6137c4343727..d2efa9baeeea 100644 --- a/shared/router-v2/route-params.tsx +++ b/shared/router-v2/route-params.tsx @@ -1,5 +1,4 @@ import type {RouteProp} from '@react-navigation/native' -import type {NativeStackNavigationProp} from '@react-navigation/native-stack' // import type {StaticParamList} from '@react-navigation/core' import type {routes, modalRoutes, loggedOutRoutes} from './routes' @@ -75,12 +74,6 @@ export type RootRouteProps = RouteName ex ? MaybeMissingParamsRouteProp : RouteProp -// Tab roots can mount before any params object exists, even when the screen prop type is an object. -export type RouteProps2 = { - route: RootRouteProps - navigation: NativeStackNavigationProp -} - export function getRouteParamsFromRoute( route: unknown ): RootParamList[T] | undefined { From ffe87ae409df6e59812bd5ab3bfc1fa9ef467743 Mon Sep 17 00:00:00 2001 From: chrisnojima Date: Thu, 2 Apr 2026 10:08:58 -0400 Subject: [PATCH 2/2] add skills --- .../skills/migrate-to-static-config/SKILL.md | 13 + .../agents/openai.yaml | 4 + .../references/react-navigation-7.md | 809 ++++++++++++++++++ .../references/react-navigation-8.md | 767 +++++++++++++++++ .../react-native-best-practices/README.md | 95 ++ .../react-native-best-practices/SKILL.md | 23 + .../react-native-best-practices | 1 + .../references/animations/SKILL.md | 28 + .../animations/animation-functions.md | 168 ++++ .../animations/animations-performance.md | 189 ++++ .../references/animations/animations.md | 285 ++++++ .../animations/canvas-animations.md | 467 ++++++++++ .../references/animations/canvas-atlas.md | 89 ++ .../references/animations/gpu-animations.md | 393 +++++++++ .../animations/layout-animations.md | 149 ++++ .../animations/scroll-and-events.md | 185 ++++ .../references/animations/svg-animations.md | 75 ++ .../references/audio/SKILL.md | 30 + .../references/audio/audio.md | 182 ++++ .../references/audio/effects-and-analysis.md | 358 ++++++++ .../references/audio/playback.md | 313 +++++++ .../references/audio/recording.md | 298 +++++++ .../audio/system-and-notifications.md | 418 +++++++++ .../references/audio/worklets.md | 236 +++++ .../references/gestures/SKILL.md | 111 +++ .../gestures/continuous-gestures.md | 278 ++++++ .../gestures/gesture-composition.md | 211 +++++ .../references/gestures/gestures.md | 240 ++++++ .../gestures/swipeable-and-drawer.md | 155 ++++ .../references/gestures/tap-handling.md | 145 ++++ .../references/gestures/testing.md | 149 ++++ .../references/multithreading/SKILL.md | 77 ++ .../multithreading/setup-and-advanced.md | 248 ++++++ .../multithreading/shared-memory.md | 182 ++++ .../multithreading/threading-api.md | 259 ++++++ .../references/on-device-ai/SKILL.md | 75 ++ .../references/on-device-ai/llm.md | 400 +++++++++ .../references/on-device-ai/setup.md | 312 +++++++ .../references/on-device-ai/speech.md | 322 +++++++ .../references/on-device-ai/vision.md | 440 ++++++++++ .../references/rich-text/SKILL.md | 302 +++++++ .../references/svg/SKILL.md | 17 + .../references/svg/svg.md | 190 ++++ .../references/svg/when-to-use.md | 45 + .../skills/upgrade-react-navigation/SKILL.md | 15 + .../agents/openai.yaml | 4 + .../references/upgrade-6-to-7.md | 321 +++++++ .../references/upgrade-7-to-8.md | 417 +++++++++ skill/migrate-to-static-config | 1 + skill/react-native-best-practices | 1 + skill/upgrade-react-navigation | 1 + skills-lock.json | 20 + 52 files changed, 10513 insertions(+) create mode 100644 .agents/skills/migrate-to-static-config/SKILL.md create mode 100644 .agents/skills/migrate-to-static-config/agents/openai.yaml create mode 100644 .agents/skills/migrate-to-static-config/references/react-navigation-7.md create mode 100644 .agents/skills/migrate-to-static-config/references/react-navigation-8.md create mode 100644 .agents/skills/react-native-best-practices/README.md create mode 100644 .agents/skills/react-native-best-practices/SKILL.md create mode 120000 .agents/skills/react-native-best-practices/react-native-best-practices create mode 100644 .agents/skills/react-native-best-practices/references/animations/SKILL.md create mode 100644 .agents/skills/react-native-best-practices/references/animations/animation-functions.md create mode 100644 .agents/skills/react-native-best-practices/references/animations/animations-performance.md create mode 100644 .agents/skills/react-native-best-practices/references/animations/animations.md create mode 100644 .agents/skills/react-native-best-practices/references/animations/canvas-animations.md create mode 100644 .agents/skills/react-native-best-practices/references/animations/canvas-atlas.md create mode 100644 .agents/skills/react-native-best-practices/references/animations/gpu-animations.md create mode 100644 .agents/skills/react-native-best-practices/references/animations/layout-animations.md create mode 100644 .agents/skills/react-native-best-practices/references/animations/scroll-and-events.md create mode 100644 .agents/skills/react-native-best-practices/references/animations/svg-animations.md create mode 100644 .agents/skills/react-native-best-practices/references/audio/SKILL.md create mode 100644 .agents/skills/react-native-best-practices/references/audio/audio.md create mode 100644 .agents/skills/react-native-best-practices/references/audio/effects-and-analysis.md create mode 100644 .agents/skills/react-native-best-practices/references/audio/playback.md create mode 100644 .agents/skills/react-native-best-practices/references/audio/recording.md create mode 100644 .agents/skills/react-native-best-practices/references/audio/system-and-notifications.md create mode 100644 .agents/skills/react-native-best-practices/references/audio/worklets.md create mode 100644 .agents/skills/react-native-best-practices/references/gestures/SKILL.md create mode 100644 .agents/skills/react-native-best-practices/references/gestures/continuous-gestures.md create mode 100644 .agents/skills/react-native-best-practices/references/gestures/gesture-composition.md create mode 100644 .agents/skills/react-native-best-practices/references/gestures/gestures.md create mode 100644 .agents/skills/react-native-best-practices/references/gestures/swipeable-and-drawer.md create mode 100644 .agents/skills/react-native-best-practices/references/gestures/tap-handling.md create mode 100644 .agents/skills/react-native-best-practices/references/gestures/testing.md create mode 100644 .agents/skills/react-native-best-practices/references/multithreading/SKILL.md create mode 100644 .agents/skills/react-native-best-practices/references/multithreading/setup-and-advanced.md create mode 100644 .agents/skills/react-native-best-practices/references/multithreading/shared-memory.md create mode 100644 .agents/skills/react-native-best-practices/references/multithreading/threading-api.md create mode 100644 .agents/skills/react-native-best-practices/references/on-device-ai/SKILL.md create mode 100644 .agents/skills/react-native-best-practices/references/on-device-ai/llm.md create mode 100644 .agents/skills/react-native-best-practices/references/on-device-ai/setup.md create mode 100644 .agents/skills/react-native-best-practices/references/on-device-ai/speech.md create mode 100644 .agents/skills/react-native-best-practices/references/on-device-ai/vision.md create mode 100644 .agents/skills/react-native-best-practices/references/rich-text/SKILL.md create mode 100644 .agents/skills/react-native-best-practices/references/svg/SKILL.md create mode 100644 .agents/skills/react-native-best-practices/references/svg/svg.md create mode 100644 .agents/skills/react-native-best-practices/references/svg/when-to-use.md create mode 100644 .agents/skills/upgrade-react-navigation/SKILL.md create mode 100644 .agents/skills/upgrade-react-navigation/agents/openai.yaml create mode 100644 .agents/skills/upgrade-react-navigation/references/upgrade-6-to-7.md create mode 100644 .agents/skills/upgrade-react-navigation/references/upgrade-7-to-8.md create mode 120000 skill/migrate-to-static-config create mode 120000 skill/react-native-best-practices create mode 120000 skill/upgrade-react-navigation create mode 100644 skills-lock.json diff --git a/.agents/skills/migrate-to-static-config/SKILL.md b/.agents/skills/migrate-to-static-config/SKILL.md new file mode 100644 index 000000000000..19a950fe24ae --- /dev/null +++ b/.agents/skills/migrate-to-static-config/SKILL.md @@ -0,0 +1,13 @@ +--- +name: migrate-to-static-config +description: Migrate React Navigation navigators from dynamic component based config to static object based config. +--- + +# Migrating to Static Config + +Check `@react-navigation/native` in `package.json` first. + +- If `7.x`, read `references/react-navigation-7.md` +- If `8.x`, read `references/react-navigation-8.md` + +Do not load both unless explicitly comparing versions. diff --git a/.agents/skills/migrate-to-static-config/agents/openai.yaml b/.agents/skills/migrate-to-static-config/agents/openai.yaml new file mode 100644 index 000000000000..523c537cc2d0 --- /dev/null +++ b/.agents/skills/migrate-to-static-config/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Migrate React Navigation to Static Config" + short_description: "Convert JSX navigators to static config" + default_prompt: "Use $migrate-to-static-config to migrate React Navigation navigators from dynamic JSX to static config." diff --git a/.agents/skills/migrate-to-static-config/references/react-navigation-7.md b/.agents/skills/migrate-to-static-config/references/react-navigation-7.md new file mode 100644 index 000000000000..60c90e14020d --- /dev/null +++ b/.agents/skills/migrate-to-static-config/references/react-navigation-7.md @@ -0,0 +1,809 @@ +# React Navigation 7.x Static Config Migration + +Use this file only when `@react-navigation/native` is on `7.x`. + +## Goal + +Convert React Navigation navigators from JSX-based dynamic setup to static configuration while preserving behavior, typing, and deep links. + +## When + +1. You are migrating screens to the static API in React Navigation 7.x. +2. The navigator's screen list is static and not built at runtime. +3. The navigator doesn't use dynamic variables or props that are not available in static config. + +## Prerequisites + +- The project is using React Navigation 7.x. +- The versions of `@react-navigation` packages are up-to-date with the published versions. + +## Structure + +1. Create a static navigator with `createXNavigator({ screens, groups, ... })`. +2. Each `screens` entry can be a component, a nested static navigator, or a screen config object. +3. `groups` define shared options and conditional rendering using `if`, and contain their own `screens`. +4. Screen config objects accept the same options as the dynamic `Screen` API, plus static-only additions such as `linking` and `if`. +5. When a screen needs a config object, use a plain screen config object. +6. A screen config `linking` can be a string path or an object with `path`, `parse`, `stringify`, and `exact`. + +## Workflow + +### 1. Identify static candidates + +A navigator is a static candidate if all its screens are known at build time. Look for: + +- **Convertible**: fixed `` elements, conditional rendering based on auth or feature flags (use `if` hooks), render callbacks passing extra props (use React context), navigators wrapped in providers or components using hooks for navigator-level props (use `.with()`) +- **Not convertible**: screen list built from runtime data such as mapping over an API response, screens added or removed based on values that can't be expressed as a hook returning a boolean. + +### 2. 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`. + +Before: + +```tsx +const Stack = createNativeStackNavigator(); + +function MyStack() { + return ( + + + + + ); +} +``` + +After: + +```tsx +const MyStack = createNativeStackNavigator({ + screenOptions: { headerShown: false }, + screens: { + Home: HomeScreen, + Profile: { + screen: ProfileScreen, + options: { title: 'My Profile' }, + }, + }, +}); +``` + +Full screen config shape: + +```tsx +const MyStack = createNativeStackNavigator({ + screens: { + Example: { + screen: ScreenComponent, + options: ({ route, navigation, theme }) => ({ + title: route.name, + }), + listeners: ({ route, navigation }) => ({ + focus: () => {}, + }), + initialParams: {}, + getId: ({ params }) => params.id, + linking: { + path: 'pattern/:id', + parse: { id: Number }, + stringify: { id: (value) => String(value) }, + exact: true, + }, + if: useConditionHook, + layout: ({ children }) => children, + }, + }, +}); +``` + +Shorthand (component only, no config): `ScreenName: ScreenComponent` + +Nested static navigator: `ScreenName: AnotherStaticNavigator` + +### 3. Convert nested navigators + +Nested dynamic navigators rendered as components become nested config objects. + +Before: + +```tsx +const Tab = createBottomTabNavigator(); + +function HomeTabs() { + return ( + + + + + ); +} + +function RootStack() { + return ( + + + + ); +} +``` + +After: + +```tsx +const HomeTabs = createBottomTabNavigator({ + screens: { + Groups: GroupsScreen, + Chats: ChatsScreen, + }, +}); + +const RootStack = createNativeStackNavigator({ + screens: { + Home: HomeTabs, + }, +}); +``` + +### 4. Convert groups + +Before: + +```tsx +function RootStack() { + return ( + + + + + + + + + + ); +} +``` + +After: + +```tsx +const RootStack = createNativeStackNavigator({ + groups: { + Card: { + screenOptions: { headerStyle: { backgroundColor: 'red' } }, + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, + }, + Modal: { + screenOptions: { presentation: 'modal' }, + screens: { + Settings: SettingsScreen, + }, + }, + }, +}); +``` + +Top-level `screens` and `screenOptions` handle the default group. + +Use `groups` when you need different shared options, conditional groups, grouped linking, or to logically group screens if the dynamic config already had such groups. + +### 5. Convert auth flows + +To migrate conditional screens from dynamic config, use static `if` hooks. The `if` property takes a user-defined hook that returns a boolean such as `useIsSignedIn` or `useIsSignedOut`. + +This prevents navigating to protected screens when signed out and unmounts auth screens after sign-in, so the back button cannot return to them. + +If you previously used `navigationKey` to reset a screen when auth state changes, duplicate the screen in both auth groups. The group name is used for the key, so switching groups resets the screen. For example, declare `Help` in both the signed-in and signed-out groups instead of using `navigationKey`. + +Loading UI should live outside the navigation tree, meaning outside `` / ``, not in a `Loading` screen or group. Keep `screens` and `groups` for actual navigable routes only. + +Use `.with()` for wrappers around a mounted navigator, not for boot or loading gates that should happen before rendering ``. + +```tsx +const App = () => { + const isLoading = useIsLoading(); + + if (isLoading) { + return ; + } + + return ; +}; +``` + +Before: + +```tsx +function App() { + const isSignedIn = useIsSignedIn(); + + return ( + + {isSignedIn ? ( + <> + + + + ) : ( + <> + + + + )} + + + ); +} +``` + +After: + +```tsx +const RootStack = createNativeStackNavigator({ + groups: { + SignedIn: { + if: useIsSignedIn, + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + Help: HelpScreen, + }, + }, + SignedOut: { + if: useIsSignedOut, + screens: { + SignIn: SignInScreen, + SignUp: SignUpScreen, + Help: HelpScreen, + }, + }, + }, +}); +``` + +### 6. Use `.with()` for wrappers, providers, and dynamic navigator props + +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 options + +Before: + +```tsx +function MyStack() { + const someValue = useSomeHook(); + + return ( + + + + + + ); +} +``` + +After: + +```tsx +const MyStack = createNativeStackNavigator({ + screens: { + Home: HomeScreen, + }, +}).with(({ Navigator }) => { + const someValue = useSomeHook(); + + return ( + + + + ); +}); +``` + +#### Per-screen dynamic options via `screenOptions` callback + +If each screen has different options, use a `screenOptions` callback and switch on `route.name`. + +Before: + +```tsx +function MyStack() { + const getSomething = useSomeHook(); + + return ( + + + + + + + ); +} +``` + +After: + +```tsx +const MyStack = createNativeStackNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}).with(({ Navigator }) => { + const getSomething = useSomeHook(); + + return ( + + { + switch (route.name) { + case 'Home': + return { + title: getSomething('First'), + }; + case 'Profile': + return { + title: getSomething('Second'), + }; + default: + return {}; + } + }} + /> + + ); +}); +``` + +#### Replacing render callbacks with context + +Static screens cannot receive extra props via render callbacks. Move the data to React context and provide it via `.with()`. + +Before: + +```tsx + + {(props) => } + +``` + +After: + +```tsx +const TokenContext = React.createContext(''); + +function ChatScreen() { + const token = React.useContext(TokenContext); + + return ; +} + +const MyStack = createNativeStackNavigator({ + screens: { + Chat: ChatScreen, + }, +}).with(({ Navigator }) => { + const token = useToken(); + + return ( + + + + ); +}); +``` + +### 7. Migrate screen-level linking + +Use screen-level `linking` to replace the old root `linking.config.screens` structure. + +Omit `linking` on a screen when the default kebab-case path is acceptable. If the path is identical to the auto path such as `Details` to `details`, remove the redundant `linking` entry. + +Add `linking` for custom paths or when you need path params with `parse` or `stringify`. + +Before: + +```tsx +const linking = { + prefixes: ['https://example.com'], + config: { + screens: { + Home: '', + Profile: { + path: 'user/:id', + parse: { id: Number }, + }, + Settings: 'settings', + }, + }, +}; +``` + +After: + +```tsx +const RootStack = createNativeStackNavigator({ + screens: { + Home: { + screen: HomeScreen, + linking: '', // explicit root path; omit if this is the first leaf screen or the initialRouteName + }, + Profile: { + screen: ProfileScreen, + linking: { + path: 'user/:id', + parse: { id: Number }, + }, + }, + Settings: SettingsScreen, + }, +}); +``` + +Linking paths are auto-generated for leaf screens using kebab-case of the screen name. The first leaf screen, or the `initialRouteName` if set, gets the path `/` unless you set an explicit empty path on another screen. + +To control auto-generated linking, pass `enabled` on the root `linking` prop: `enabled: 'auto'` generates paths for all leaf screens, and `enabled: false` disables linking entirely. + +If a screen previously had a custom path such as `linking: 'contacts'` and you remove it, the auto path becomes kebab-case of the screen name such as `TabContacts` to `tab-contacts`. This breaks existing URLs and deep links. Keep explicit `linking` when you need to preserve existing paths. + +If screens containing navigators have `linking` set to `''` or `'/'`, it is usually redundant and can be removed. + +Keep TypeScript param typing on the screen component with `StaticScreenProps`. Screen-level `linking` config is for URL parsing and serialization only. + +### 8. Update types + +#### Getting navigation and route access + +Use `StaticScreenProps` to type the screen's `route` prop. + +Use the default `useNavigation()` type provided through the global `ReactNavigation.RootParamList` augmentation for navigator-agnostic navigation calls. + +Prefer the screen `route` prop over `useRoute` when available. Use `useNavigationState` separately when you need navigation state. + +Before: + +```tsx +function ProfileScreen({ + navigation, + route, +}: NativeStackScreenProps) { + const id = route.params.id; + navigation.navigate('Home'); +} +``` + +After: + +```tsx +type ProfileScreenProps = StaticScreenProps<{ + id: string; +}>; + +function ProfileScreen({ route }: ProfileScreenProps) { + const navigation = useNavigation(); + const id = route.params.id; + + navigation.navigate('Home'); +} +``` + +Use `StaticScreenProps` to type the `route` prop. If you need navigator-specific APIs such as `push`, `pop`, `openDrawer`, or `setOptions`, you can manually annotate `useNavigation`, but this is not type-safe and should be kept to a minimum. + +If you need a navigator-specific navigation object: + +```tsx +type RootStackParamList = StaticParamList; + +type ProfileNavigationProp = NativeStackNavigationProp< + RootStackParamList, + 'Profile' +>; + +const navigation = useNavigation(); +``` + +#### Remove manual param lists + +Remove all hand-written param-list declarations created only to support dynamic typing. + +If a param list is absolutely necessary, derive it from the navigator type: + +```tsx +type SomeStackType = typeof SomeStack; +type SomeStackParamList = StaticParamList; +``` + +If a static navigator nests a dynamic navigator, annotate the dynamic navigator screen with `StaticScreenProps>` so the nesting is reflected in the root param list. + +For the root navigator, keep the single source of truth in the `RootParamList` augmentation shown below. + +Avoid circular dependencies by: + +- Using `StaticScreenProps` for screen params instead of shared hand-written param lists +- Using the default `useNavigation()` type where possible instead of navigator-specific aliases +- Deleting obsolete shared type files when they become empty + +#### Root type augmentation + +Place the global `RootParamList` augmentation next to the root static navigator. This is the single source of truth for the default types used by `useNavigation`, `Link`, refs, and related APIs. + +```tsx +const RootStack = createNativeStackNavigator({ + screens: { + // ... + }, +}); + +type RootStackParamList = StaticParamList; + +declare global { + namespace ReactNavigation { + interface RootParamList extends RootStackParamList {} + } +} +``` + +#### Typing params + +Use `StaticScreenProps` to annotate route params, including screens that use `linking`. + +A path such as `user/:userId` defines URL parsing and serialization. Keep the TypeScript param type on the screen component with `StaticScreenProps`. + +If the params are not strings, use `parse` and `stringify` in the `linking` config: + +```tsx +type ArticleScreenProps = StaticScreenProps<{ + date: Date; +}>; + +function ArticleScreen({ route }: ArticleScreenProps) { + return
; +} + +const RootStack = createNativeStackNavigator({ + screens: { + Article: { + screen: ArticleScreen, + linking: { + path: 'article/:date', + parse: { + date: (date: string) => new Date(date), + }, + stringify: { + date: (date: Date) => date.toISOString(), + }, + }, + }, + }, +}); +``` + +The runtime parsing comes from `linking`. The compile-time param type comes from `StaticScreenProps`. + +Avoid `any`, non-null assertions, and `as` assertions. + +#### Full before/after example + +Before: + +```tsx +type MyStackParamList = { + Article: { author: string }; + Albums: undefined; +}; + +const Stack = createNativeStackNavigator(); + +function ArticleScreen({ + navigation, + route, +}: NativeStackScreenProps) { + return