# AGENTS.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Overview

This package is a **type checker for React Native's JS/Native boundary**. It detects backwards-incompatible changes between JavaScript and Native code to prevent crashes, particularly useful for:
- Local development (detecting when native rebuild is needed)
- Over-the-air (OTA) updates
- Server Components with React Native

The tool operates on JSON schema files generated by `@react-native/codegen`, making it agnostic to TypeScript/Flow.

## Architecture: Three-Stage Pipeline

The compatibility check flows through three distinct stages:

```
Schema (new) ──┐
               ├──▶ TypeDiffing ──▶ VersionDiffing ──▶ ErrorFormatting ──▶ Output
Schema (old) ──┘
```

### Stage 1: TypeDiffing (`TypeDiffing.js`)
**Pure type comparison** - Compares two type annotations and returns all structural differences.
- Reports ALL differences between types (added/removed properties, union changes, etc.)
- Returns `ComparisonResult` with status: `matching`, `skipped`, `properties`, `members`, `unionMembers`, `functionChange`, `positionalTypeChange`, `nullableChange`, or `error`
- **Must remain pure** - no React Native-specific logic belongs here

### Stage 2: VersionDiffing (`VersionDiffing.js`)
**Semantic safety analysis** - Interprets TypeDiffing results in the context of React Native's boundary.
- Determines if changes are safe based on **data flow direction**:
  - `toNative`: Data flows from JS to Native (method parameters, component props)
  - `fromNative`: Data flows from Native to JS (return values, getConstants)
  - `both`: Bidirectional flow
- Encodes compatibility rules:
  - Adding to a union sent TO native = **UNSAFE** (native won't expect it)
  - Removing from a union received FROM native = **UNSAFE** (JS won't handle it)
  - Adding optional properties = **SAFE**
  - Making required properties optional when sending TO native = **UNSAFE**

### Stage 3: ErrorFormatting (`ErrorFormatting.js`)
**Human-readable output** - Converts deep error objects into formatted strings.
- **Must remain pure** - no business logic

### Supporting Files

- **`ComparisonResult.js`**: Type definitions for all comparison result shapes
- **`DiffResults.js`**: Type definitions for schema diff results, error codes, and summary types
- **`SortTypeAnnotations.js`**: Sorting utilities for comparing type annotations in a stable order
- **`convertPropToBasicTypes.js`**: Converts Component prop types to standard type annotations for comparison
- **`index.js`**: Public API - exports `compareSchemas()` returning a `CompatCheckResult`

## Key Type Definitions

```javascript
// Main comparison statuses
type ComparisonResult =
  | {status: 'matching'}           // Types are identical
  | {status: 'skipped'}            // No old type to compare
  | {status: 'properties', ...}    // Object property changes
  | {status: 'members', ...}       // Enum member changes
  | {status: 'unionMembers', ...}  // Union member changes
  | {status: 'functionChange', ...}// Function signature changes
  | {status: 'error', ...}         // Incompatible type change

// Summary statuses
type DiffSummary = {
  status: 'ok' | 'patchable' | 'incompatible',
  incompatibilityReport: {...}
}
```

## Commands

Run tests from the react-native-compatibility-check directory:
```bash
cd packages/react-native-compatibility-check

# Run all tests
yarn test

# Run a specific test file
yarn test src/__tests__/TypeDiffing-test.js

# Run tests matching a pattern
yarn test --testNamePattern="compareTypes on unions"
```

**Meta employees**: Use `js1 test SUBPATH` instead (e.g., `js1 test react-native-compatibility-check`).

## Testing Patterns

### Test Fixtures
Tests use Flow files in `__tests__/__fixtures__/` parsed by `@react-native/codegen`:
- **Native Modules**: `native-module-*/NativeModule.js.flow`
- **Native Components**: `native-component-*/NativeComponent.js.flow`

The `getTestSchema()` utility parses these fixtures into schema objects.

### Test Structure
- **TypeDiffing-test.js**: Tests pure type comparison logic
- **VersionDiffing-test.js**: Tests safety analysis with boundary direction
- **ErrorFormatting-test.js**: Tests error message generation (uses snapshots)

### Adding Test Cases
1. Create a new fixture directory under `__tests__/__fixtures__/`
2. Add a `.js.flow` file defining a Native Module or Component
3. Load it in tests using `getTestSchema(__dirname, '__fixtures__', 'fixture-name', 'FileName.js.flow')`

## Design Principles

### Separation of Concerns
- **TypeDiffing**: Pure type comparison. Should work for ANY JavaScript types.
- **VersionDiffing**: React Native boundary semantics. Only place for RN-specific logic.
- **ErrorFormatting**: Presentation only. No business logic.

### Module-scope Type Registries
`TypeDiffing.js` uses module-scope variables (`_newerTypesReg`, `_olderTypesReg`, `_newerEnumMap`, `_olderEnumMap`) to avoid threading lookups through all recursive calls. This is acceptable because the logic is serial.

### Structural Type Comparison
Types are compared structurally, not nominally. Two different type aliases with identical structure are considered matching.

## Compatibility Rules Reference

### Data Flowing TO Native (parameters, props)
| Change | Safe? |
|--------|-------|
| Add optional property | ✅ |
| Add required property | ❌ |
| Remove property | ✅ |
| Make property optional | ❌ |
| Add union member | ❌ |
| Remove union member | ✅ |
| Add enum member | ❌ |
| Remove enum member | ✅ |

### Data Flowing FROM Native (return values, constants)
| Change | Safe? |
|--------|-------|
| Add optional property | ✅ |
| Add required property | ❌ |
| Remove property | ✅ |
| Make property required | ❌ |
| Add union member | ✅ |
| Remove union member | ❌ |
| Add enum member | ✅ |
| Remove enum member | ❌ |

## Common Gotchas

1. **Component Commands**: Adding/removing commands is intentionally allowed even though it could cause OTA issues, because there's no feature detection mechanism for commands.

2. **Union ordering**: Unions are sorted before comparison, so `'a' | 'b'` equals `'b' | 'a'`.

3. **Nullable vs Optional**: These are distinct concepts:
   - Optional: Property may be absent (`prop?: T`)
   - Nullable: Value may be null/undefined (`prop: ?T`)

4. **Type Aliases**: Resolved during comparison. Different alias names with identical structure are treated as matching.

5. **Component Props with Defaults**: `WithDefault` types are stripped during comparison - only the underlying type matters for compatibility.

6. **Int32EnumTypeAnnotation**: Currently converted to `AnyTypeAnnotation` because the tool lacks support for number literal unions.

## Adding New Type Support

1. Add the type case to `compareTypeAnnotation()` in `TypeDiffing.js`
2. Add sorting logic in `SortTypeAnnotations.js` (`compareTypeAnnotationForSorting`)
3. Add formatting in `ErrorFormatting.js` (`formatTypeAnnotation`)
4. Add test fixtures and tests covering the new type
5. If it affects safety analysis, update `VersionDiffing.js` checks

## Code Style

- All source files use `@flow strict-local` or `@flow strict`
- All source files require `@format` pragma for Prettier
- Tests use `@noflow` or `@flow` (not strict)
