# Multi Tv Builder

> Complete knowledge base for building production-ready cross-platform TV applications with React Native - supporting Android TV, Apple TV, Fire TV (Fire OS & Vega OS), and Web TV platforms with a single codebase. Use when implementing TV apps, monorepo architecture, spatial navigation, video playback, platform-specific optimizations, or migrating from Vega to multi-platform.

- Skill: `majiayu000/multi-tv-builder` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds add majiayu000/multi-tv-builder`
- Raw SKILL.md: https://api.skillmd.com/api/skills/majiayu000/multi-tv-builder/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: majiayu000 (https://skillmd.com/u/majiayu000)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/majiayu000/multi-tv-builder

---


# Multi-TV Builder - Complete Knowledge Base

Build production-ready cross-platform TV applications with React Native, supporting Android TV, Apple TV, Fire TV (Fire OS & Vega OS), and Web TV platforms with a single codebase.

## Quick Reference

- **Sample Repo**: https://github.com/AmazonAppDev/react-native-multi-tv-app-sample
- **Slash Commands**: `/multi-tv-help`, `/multi-tv-setup`, `/multi-tv-build`, `/multi-tv-platform`
- **Skills**: `multi-tv-builder`, `apple-tv-troubleshooter`, `tmdb-integration`
- **Author**: Giovanni Laquidara

## Core Capabilities

- **Cross-platform TV support** with single codebase for 5+ platforms
- **Monorepo architecture** with Yarn workspaces for apps and shared packages
- **Spatial navigation** optimized for TV remote controls and D-pad input
- **Video playback** with custom controls and remote integration
- **Platform-specific optimizations** using automatic file resolution
- **Shared UI library** with reusable TV-optimized components
- **TypeScript-first** with strict type safety across all packages

## Supported Platforms

| Platform | Technology | Output |
|----------|-----------|--------|
| Android TV | react-native-tvos | APK/AAB |
| Apple TV (tvOS) | react-native-tvos + Expo | IPA |
| Fire TV (Fire OS) | react-native-tvos | APK |
| Fire TV (Vega OS) | Amazon Vega SDK | Optimized VPKG |
| Web TV | React Native Web | HTML/JS Bundle |

---

# Product Overview

## React Native Multi-TV App Sample

A production-ready TV application template for building cross-platform TV apps with a single React Native codebase.

### Core Features

1. **Video Playback**
   - Integrated video player with react-native-video (universal) or W3C Media APIs (Vega)
   - Custom overlay controls with play/pause, seek bar, and time display
   - Remote control integration (play/pause, seek ±10s, exit)
   - Auto-hide controls after 5s of inactivity
   - Buffering indicators during loading

2. **Spatial Navigation**
   - React TV Space Navigation for focus management
   - D-pad/arrow key navigation optimized for TV remotes
   - Platform-specific remote control managers
   - Focus restoration when returning from player

3. **Content Discovery**
   - Dynamic content loading from external catalog API
   - Grid layouts optimized for TV screens
   - Detail screens with rich metadata (genre, rating, duration, year)
   - Dynamic hero banner based on focused content
   - Trending flags and content ratings

4. **Navigation**
   - Drawer navigation with left-side menu
   - Stack navigation for detail screens
   - Platform-specific navigation optimizations
   - Deep linking support (optional)

5. **Responsive Design**
   - Adaptive layouts for different TV screen sizes
   - Responsive scaling with useScale hook
   - Platform-specific layout adjustments
   - Safe zone handling for TV overscan

### App Variants

1. **expo-multi-tv** - Universal app supporting Android TV, Apple TV, Fire TV (Fire OS), and Web
2. **vega** - Fire TV Vega OS optimized build using Amazon Vega SDK

### Key Use Cases

- Streaming video applications
- Content discovery and browsing
- TV-optimized user interfaces
- Cross-platform TV development reference

---

# Architecture & Structure

## Monorepo Organization

```
project-root/
├── apps/                    # Application packages
│   ├── expo-multi-tv/      # Universal TV app
│   └── vega/               # Fire TV Vega optimized
├── packages/               # Shared packages
│   └── shared-ui/          # Shared components
├── package.json            # Workspace configuration
├── tsconfig.base.json      # Shared TypeScript config
├── babel.config.js         # Root babel configuration
└── yarn.lock               # Dependency lock file
```

## Apps Directory

### `apps/expo-multi-tv/`
Universal TV application built with Expo SDK.

**Key Files:**
- `App.tsx` - Main entry point
- `app.json` - Expo configuration
- `package.json` - App-specific dependencies
- `tsconfig.json` - TypeScript config extending base
- `metro.config.js` - Metro bundler configuration
- `ios/` - iOS/tvOS native code
- `android/` - Android TV native code

**Platforms Supported:**
- Android TV
- Apple TV (tvOS)
- Fire TV (Fire OS)
- Web (LG WebOS, Samsung Tizen)

### `apps/vega/`
Fire TV Vega OS optimized application using Amazon Vega SDK.

**Key Files:**
- `App.tsx` - Main entry point
- `package.json` - Vega-specific dependencies
- `tsconfig.json` - TypeScript config
- `metro.config.js` - Metro bundler configuration
- `android/` - Fire TV Vega native code

**Platform Supported:**
- Fire TV (Vega OS)

## Shared UI Package Structure

```
packages/shared-ui/src/
├── app/                           # App-level configuration
│   └── remote-control/           # Platform-specific remote managers
│       ├── RemoteControlManager.android.ts
│       ├── RemoteControlManager.ios.ts
│       └── RemoteControlManager.kepler.ts
├── assets/                        # Static assets (fonts, images, icons)
├── components/                    # Reusable UI components
│   ├── player/                   # Video player components
│   ├── CustomDrawerContent.tsx   # Drawer menu
│   ├── FocusablePressable.tsx    # TV-optimized pressable
│   ├── LoadingIndicator.tsx      # Loading spinner
│   ├── MenuContext.tsx           # Menu state management
│   └── PlatformLinearGradient.tsx # Cross-platform gradient
├── data/                          # Data and API
│   └── moviesData.ts             # Movie catalog API
├── hooks/                         # Custom React hooks
│   └── useScale.ts               # Responsive scaling
├── navigation/                    # Navigation configuration
│   ├── AppNavigator.tsx          # Main app navigator
│   ├── DrawerNavigator.tsx       # Drawer navigation
│   ├── RootNavigator.tsx         # Root wrapper
│   └── types.ts                  # Navigation types
├── screens/                       # Screen components
│   ├── DetailsScreen.tsx         # Content detail
│   ├── ExploreScreen.tsx         # Content exploration
│   ├── HomeScreen.tsx            # Home/landing
│   ├── PlayerScreen.tsx          # Video player
│   ├── PlayerScreen.vega.tsx     # Vega-specific player
│   ├── SettingsScreen.tsx        # Settings
│   └── TVScreen.tsx              # TV content
├── theme/                         # Design system
│   ├── colors.ts                 # Color palette
│   ├── spacing.ts                # Spacing scale
│   ├── typography.ts             # Font styles
│   ├── safeZones.ts              # TV safe zones
│   └── index.ts                  # Theme exports
├── utils/                         # Utility functions
└── index.ts                       # Package exports
```

## Platform-Specific File Resolution

Metro bundler automatically resolves platform files:
- `.kepler.ts/tsx` - Fire TV Vega OS (highest priority for Vega builds)
- `.android.ts/tsx` - Android TV & Fire TV Fire OS
- `.ios.ts/tsx` - Apple TV (tvOS)
- `.vega.tsx` - Vega-specific screens
- `.web.ts/tsx` - Web platforms (LG WebOS, Samsung Tizen)
- `.ts/tsx` - Default/shared implementation (lowest priority)

**Example:**
```
RemoteControlManager.android.ts  → Android TV
RemoteControlManager.ios.ts      → Apple TV
RemoteControlManager.kepler.ts   → Fire TV Vega
```

### Key Architectural Patterns

#### Shared UI Library Pattern
- All reusable components live in `@project/shared-ui` package
- Apps import from shared package, never local files
- Platform-specific implementations use file extensions
- Single source of truth for UI components and screens

#### TV-First Design
- **10-foot UI**: Larger touch targets, high-contrast colors, readable typography
- **Safe zones**: Content positioned within TV safe areas (see theme/safeZones.ts)
- **Focus management**: Clear visual focus indicators for all interactive elements
- **Remote control**: D-pad navigation with platform-specific key mappings

---

# Technology Stack

## Build System

- **Yarn Workspaces v4+**: Monorepo management
- **Metro Bundler**: Module bundling with platform-specific resolution
- **TypeScript v5.3+**: Strict mode enabled across all packages

## Core Technologies

- **React Native**: v0.74+ (react-native-tvos fork)
- **Expo SDK**: v51+ (for universal app)
- **React**: v18.2+
- **TypeScript**: v5.3+ with strict mode

## Navigation & Focus

- **React Navigation v6**: Drawer and stack navigators
- **React TV Space Navigation**: Spatial navigation for TV remotes
- **Platform Remote Managers**: Android (react-native-keyevent), iOS (TVEventHandler), Vega (Kepler SDK)

## Video Playback

- **react-native-video v6+**: Universal app video player
- **W3C Media APIs**: Native video for Vega OS
- Custom overlay controls with spatial navigation

## Platform-Specific SDKs

- **Amazon Vega SDK**: Fire TV Vega OS builds
- **@amazon-devices packages**: Kepler SDK components
- **react-native-tvos**: TV platform support (Android TV, Apple TV, Fire TV FOS)

## Development Tools

- **ESLint**: Code quality with TypeScript and React plugins
- **Prettier**: Code formatting (120 char width, single quotes)
- **Husky**: Git hooks for pre-commit checks
- **Commitlint**: Conventional commit enforcement
- **Jest**: Unit testing

---

# Implementation Patterns

## Workspace Configuration

**Root `package.json`:**
```json
{
  "name": "tv-app-project",
  "private": true,
  "workspaces": ["apps/*", "packages/*"],
  "packageManager": "yarn@4.5.0",
  "scripts": {
    "dev": "yarn workspace @project/expo-multi-tv start",
    "dev:android": "yarn workspace @project/expo-multi-tv android",
    "dev:ios": "yarn workspace @project/expo-multi-tv ios",
    "dev:web": "yarn workspace @project/expo-multi-tv web",
    "dev:vega": "yarn workspace @project/vega start",
    "build:vega": "yarn workspace @project/vega run build",
    "test:all": "yarn workspaces foreach -pt run test",
    "lint:all": "yarn workspaces foreach -pt run lint",
    "clean:all": "yarn workspaces foreach run clean"
  }
}
```

## Theme System

Centralized design tokens in `packages/shared-ui/src/theme/`:

**colors.ts:**
```typescript
export const colors = {
  primary: '#E50914',      // Netflix-style red
  background: '#141414',   // Dark background
  card: '#2F2F2F',        // Card background
  text: '#FFFFFF',        // Primary text
  textSecondary: '#B3B3B3' // Secondary text
};
```

**spacing.ts:**
```typescript
export const spacing = {
  xs: 4,
  sm: 8,
  md: 16,
  lg: 24,
  xl: 32,
  xxl: 48
};
```

**safeZones.ts:**
```typescript
export const safeZones = {
  horizontal: 48,  // Left/right margins for TV overscan
  vertical: 27     // Top/bottom margins
};
```

## Responsive Scaling

**useScale hook:**
```typescript
import { Dimensions } from 'react-native';

const BASE_WIDTH = 1920;
const BASE_HEIGHT = 1080;

export const useScale = () => {
  const { width, height } = Dimensions.get('window');

  const scaleWidth = width / BASE_WIDTH;
  const scaleHeight = height / BASE_HEIGHT;
  const scale = Math.min(scaleWidth, scaleHeight);

  return {
    scale: (size: number) => size * scale,
    width,
    height
  };
};
```

## Remote Control Manager Interface

All platform managers implement this interface:

```typescript
export interface RemoteControlManagerInterface {
  initialize(): void;
  cleanup(): void;
}
```

**Android/Fire TV FOS (`RemoteControlManager.android.ts`):**
```typescript
import ReactNativeKeyEvent from 'react-native-keyevent';

export class RemoteControlManager implements RemoteControlManagerInterface {
  initialize() {
    ReactNativeKeyEvent.onKeyDownListener((keyEvent) => {
      // Handle Android TV remote keys
    });
  }

  cleanup() {
    ReactNativeKeyEvent.removeKeyDownListener();
  }
}
```

**Apple TV (`RemoteControlManager.ios.ts`):**
```typescript
import { TVEventHandler } from 'react-native';

export class RemoteControlManager implements RemoteControlManagerInterface {
  private tvEventHandler: TVEventHandler | null = null;

  initialize() {
    this.tvEventHandler = new TVEventHandler();
    this.tvEventHandler.enable(this, (component, evt) => {
      // Handle tvOS remote events
    });
  }

  cleanup() {
    this.tvEventHandler?.disable();
  }
}
```

**Fire TV Vega (`RemoteControlManager.kepler.ts`):**
```typescript
import { TVEventHandler } from '@amazon-devices/react-native-kepler';

export class RemoteControlManager implements RemoteControlManagerInterface {
  private tvEventHandler: TVEventHandler | null = null;

  initialize() {
    this.tvEventHandler = new TVEventHandler();
    this.tvEventHandler.enable(this, (component, evt) => {
      // Handle Vega remote events
    });
  }

  cleanup() {
    this.tvEventHandler?.disable();
  }
}
```

## Spatial Navigation Setup

**Integration with React TV Space Navigation:**
```typescript
import { SpatialNavigation } from 'react-tv-space-navigation';

// Initialize in App.tsx
SpatialNavigation.init({
  debug: __DEV__,
  visualDebug: __DEV__
});

// Use in components
import { useFocusable } from 'react-tv-space-navigation';

const MyComponent = () => {
  const { ref, focused } = useFocusable();

  return (
    <View ref={ref} style={focused ? styles.focused : styles.default}>
      {/* Content */}
    </View>
  );
};
```

## Video Player Implementation

**Universal App (expo-multi-tv):**
```typescript
import Video from 'react-native-video';

<Video
  source={{ uri: videoUrl }}
  style={styles.video}
  controls={Platform.OS === 'ios'} // Native controls on iOS
  resizeMode="contain"
  onLoad={handleLoad}
  onProgress={handleProgress}
  ref={videoRef}
/>
```

**Vega App:**
```typescript
// Use W3C Media APIs through Kepler SDK
import { VideoView } from '@amazon-devices/react-native-kepler';

<VideoView
  source={{ uri: videoUrl }}
  style={styles.video}
  onLoad={handleLoad}
  ref={videoRef}
/>
```

## Dynamic Content Loading

**Catalog API Integration:**
```typescript
// packages/shared-ui/src/data/moviesData.ts
export interface Movie {
  id: string;
  title: string;
  genre: string;
  rating: number;
  contentRating: string;
  year: number;
  duration: number;
  videoUrl: string;
  imageUrl: string;
  trending: boolean;
}

export const fetchMovies = async (): Promise<Movie[]> => {
  const response = await fetch('https://api.example.com/catalog.json');
  const data = await response.json();
  return transformCatalogData(data);
};
```

---

# Common Commands

## Development

```bash
# Install all dependencies
yarn install

# Start Metro bundler for expo-multi-tv
yarn dev

# Run on specific platforms
yarn dev:android    # Android TV & Fire TV FOS
yarn dev:ios        # Apple TV
yarn dev:web        # Web TV platforms

# Start Metro bundler for vega
yarn dev:vega
```

## Building

**Expo Multi-TV App:**
```bash
# Build for all platforms
cd apps/expo-multi-tv

# Android TV / Fire TV (Fire OS)
npx expo build:android
# or: npx expo run:android

# Apple TV (tvOS)
npx expo build:ios
# or: npx expo run:ios

# Web TV platforms
npx expo export --platform web
```

**Fire TV Vega App:**
```bash
# From project root
yarn build:vega         # Build release VPKG
yarn build:vega:debug   # Build debug VPKG

# From apps/vega directory
cd apps/vega

# Build with npm (default from Vega CLI)
npm run build:app       # Builds for all architectures

# Build with Vega CLI (more control)
vega build --arch armv7 --buildType release    # Fire TV Stick
vega build --arch aarch64 --buildType release  # M-series Mac virtual devices
vega build --arch x86_64 --buildType release   # Intel virtual devices

# Clean build
rm -rf build && npm run build:app
```

## Testing & Linting

```bash
# Run tests across all workspaces
yarn test:all

# Lint all workspaces
yarn lint:all

# Type check
yarn typecheck

# Format code
yarn format
```

## Vega CLI Commands

**Project Management:**
```bash
# List available project templates
vega project list-templates

# Generate new Vega project (files are created in current directory)
# First create and cd into target directory, then generate:
mkdir -p myapp && cd myapp
vega project generate -n myapp --template helloWorld

# Verify Vega SDK installation
vega --version
```

**Building:**
```bash
# Build for specific architecture and build type
vega build --arch <armv7|aarch64|x86_64> --buildType <debug|release>

# Examples:
vega build --arch armv7 --buildType release     # Production Fire TV Stick build
vega build --arch aarch64 --buildType debug     # Debug build for M-series Mac
vega build --arch x86_64 --buildType debug      # Debug build for Intel virtual device
```

**Device Management:**
```bash
# List connected devices
vega device list

# Install VPKG on device
vega install --vpkg <path-to-vpkg-file>

# Run app on device
vega run --packageId com.company.myapp
```

---

# Getting Started

## Quick Start (Recommended)

The fastest way to get started is to clone the official React Native Multi-TV App Sample repository.

**Clone the Repository:**
```bash
# Clone the official sample repository
git clone https://github.com/AmazonAppDev/react-native-multi-tv-app-sample.git
cd react-native-multi-tv-app-sample

# Install dependencies
yarn install

# Bootstrap the monorepo
yarn bootstrap
```

**What's Included:**
- ✅ Pre-configured monorepo with Yarn workspaces
- ✅ expo-multi-tv app - Universal app for Android TV, Apple TV, Fire TV (Fire OS), and Web
- ✅ vega app - Fire TV Vega OS optimized app
- ✅ shared-ui package - Complete component library
- ✅ All configuration files

**Next Steps After Cloning:**
1. Review the codebase
2. Set up for Expo TV - Follow Expo TV documentation
3. Set up for Vega (optional) - Install Vega SDK
4. Run on platforms
5. Customize for your needs

## Alternative: Build from Scratch

### 1. Initialize Project Structure

```bash
# Create project directory
mkdir my-tv-app && cd my-tv-app

# Initialize package.json
yarn init -y

# Create directory structure
mkdir -p apps packages/shared-ui
```

### 2. Configure Shared TypeScript

Create `tsconfig.base.json`:
```json
{
  "compilerOptions": {
    "target": "ESNext",
    "module": "CommonJS",
    "lib": ["ES2021"],
    "jsx": "react-native",
    "strict": true,
    "moduleResolution": "node",
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  }
}
```

### 3. Set Up Shared UI Package

```bash
cd packages/shared-ui
yarn init -y

# Install dependencies
yarn add react react-native
yarn add -D typescript @types/react @types/react-native

# Create src directory structure
mkdir -p src/{components,screens,navigation,hooks,theme,app/remote-control,data,utils,assets}
```

### 4. Create Universal App (expo-multi-tv)

**IMPORTANT:** Follow the official Expo TV setup guide.

Option 1: Start with the basic TV template:
```bash
cd apps
npx create-expo-app@latest expo-multi-tv -e with-tv
```

Option 2: Start with the TV Router template (recommended):
```bash
cd apps
npx create-expo-app@latest expo-multi-tv -e with-router-tv
```

**After creating the app:**

1. Link shared-ui package by editing `apps/expo-multi-tv/package.json`
2. Install additional TV dependencies
3. Configure Metro bundler for monorepo

### 5. Create Vega App (Optional - Fire TV Vega OS Only)

**Prerequisites: Install Vega SDK**

**Required Dependencies (macOS):**
```bash
# For Apple Silicon Macs (includes Rosetta 2)
[[ $(arch) == "arm64" ]] && softwareupdate --install-rosetta --agree-to-license; brew update && brew install binutils coreutils gawk findutils grep jq lz4 gnu-sed watchman

# For Intel Macs
brew update && brew install binutils coreutils gawk findutils grep jq lz4 gnu-sed watchman
```

**Install Vega SDK:**
1. Close VS Code before installing
2. Download and run installer
3. Add export commands to shell config
4. Verify: `kepler --version`

**Create Vega Project:**
```bash
# Files are generated in current directory, so create and cd into target first
mkdir -p apps/vega
cd apps/vega

vega project generate -n vega --template helloWorld
```

### 6. Configure Metro Bundler

**metro.config.js** (in each app):
```javascript
const { getDefaultConfig } = require('expo/metro-config');
const path = require('path');

const config = getDefaultConfig(__dirname);

// Add monorepo support
const projectRoot = __dirname;
const workspaceRoot = path.resolve(projectRoot, '../..');

config.watchFolders = [workspaceRoot];
config.resolver.nodeModulesPaths = [
  path.resolve(projectRoot, 'node_modules'),
  path.resolve(workspaceRoot, 'node_modules'),
];

module.exports = config;
```

---

# Vega to Multi-TV Migration Guide

## Prerequisites

- Node.js 18+
- Yarn 4.x (Berry)
- Java 17 (for Android builds)
- Android SDK with TV emulator
- Xcode (for Apple TV builds)

## Phase 1: Monorepo Setup

### 1.1 Initialize Yarn Workspaces

Create root `package.json`:

```json
{
  "name": "@mycompany/multi-tv",
  "version": "1.0.0",
  "private": true,
  "packageManager": "yarn@4.5.0",
  "workspaces": [
    "apps/*",
    "packages/*"
  ],
  "resolutions": {
    "metro-source-map": "0.80.12"
  }
}
```

**CRITICAL**: The `metro-source-map` resolution is required to fix a babel plugin error. Without it, you'll get:
```
.plugins[0] must be a string, object, function
```

### 1.2 Create Root Babel Config

**CRITICAL**: Create `babel.config.js` at monorepo root. Metro looks for babel config starting from the project root.

```javascript
const path = require('path');

module.exports = function (api) {
  api.cache(true);

  // Try to resolve reanimated plugin from the universal TV app
  let reanimatedPlugin;
  try {
    reanimatedPlugin = require.resolve('react-native-reanimated/plugin', {
      paths: [path.join(__dirname, 'apps/universal-tv/node_modules')]
    });
  } catch (e) {
    try {
      reanimatedPlugin = require.resolve('react-native-reanimated/plugin');
    } catch (e2) {
      reanimatedPlugin = null;
    }
  }

  const plugins = reanimatedPlugin ? [reanimatedPlugin] : [];

  return {
    presets: ['babel-preset-expo'],
    plugins,
  };
};
```

## Phase 2: Shared UI Package Setup

### Platform-Specific File Resolution

| Extension | Platform | Description |
|-----------|----------|-------------|
| `.ts` / `.tsx` | All | Base/shared implementation |
| `.native.ts` | React Native | Android TV, Apple TV, Fire TV FOS |
| `.android.ts` | Android | Android TV, Fire TV FOS |
| `.ios.ts` | iOS/tvOS | Apple TV |
| `.kepler.ts` | Vega | Fire TV Vega OS specific |
| `.web.ts` | Web | Web TV specific |

**CRITICAL - Avoiding Circular Dependencies**:

When you have a base class that platform-specific files extend, Metro's platform resolution can create a circular dependency.

```
# BAD - Creates circular dependency
RemoteControlManager.ts        # Base class
RemoteControlManager.android.ts # Imports from RemoteControlManager.ts
```

**FIX**: Rename base files to use `.base.ts` extension:

```
# GOOD - No circular dependency
RemoteControlManager.base.ts    # Base class
RemoteControlManager.android.ts # Imports from RemoteControlManager.base.ts
RemoteControlManager.ios.ts     # Imports from RemoteControlManager.base.ts
```

## Phase 3: Universal TV App Setup (Expo)

### Fix HMRClient Error (CRITICAL)

**CRITICAL**: The `react-native-tvos` fork has an HMR (Hot Module Replacement) incompatibility.

**Fix**: Add HMRClient polyfill to `apps/universal-tv/index.js`:

```javascript
import { registerRootComponent } from 'expo';
import { AppRegistry } from 'react-native';
import App from './App';

// Polyfill for HMRClient to prevent "Module has not been registered as callable" error
if (!AppRegistry.getRunnable('HMRClient')) {
  AppRegistry.registerCallableModule('HMRClient', {
    setup: () => {},
    enable: () => {},
    disable: () => {},
  });
}

registerRootComponent(App);
```

### Configure app.json

```json
{
  "expo": {
    "name": "MyTVApp",
    "slug": "my-tv-app",
    "version": "1.0.0",
    "orientation": "landscape",
    "userInterfaceStyle": "dark",
    "plugins": [
      "@react-native-tvos/config-tv"
    ]
  }
}
```

## Phase 4: Platform Abstraction Layer

### Remote Control Manager Pattern

Create base class in `packages/shared-ui/src/app/remote-control/RemoteControlManager.base.ts`:

```typescript
export type RemoteKey =
  | 'up' | 'down' | 'left' | 'right'
  | 'select' | 'back'
  | 'play' | 'pause' | 'playPause'
  | 'fastForward' | 'rewind'
  | 'menu';

export interface RemoteKeyEvent {
  key: RemoteKey;
  action: 'keyDown' | 'keyUp';
  timestamp: number;
}

export type RemoteKeyHandler = (event: RemoteKeyEvent) => void;

export interface RemoteControlManagerInterface {
  initialize(): void;
  cleanup(): void;
  addKeyListener(handler: RemoteKeyHandler): () => void;
  removeKeyListener(handler: RemoteKeyHandler): void;
}

export class BaseRemoteControlManager implements RemoteControlManagerInterface {
  protected listeners: Set<RemoteKeyHandler> = new Set();

  initialize(): void {
    // Override in platform-specific implementation
  }

  cleanup(): void {
    this.listeners.clear();
  }

  addKeyListener(handler: RemoteKeyHandler): () => void {
    this.listeners.add(handler);
    return () => this.removeKeyListener(handler);
  }

  removeKeyListener(handler: RemoteKeyHandler): void {
    this.listeners.delete(handler);
  }

  protected notifyListeners(event: RemoteKeyEvent): void {
    this.listeners.forEach((handler) => handler(event));
  }
}
```

### Video Player Service Pattern

Create interface in `packages/shared-ui/src/services/videoPlayer/VideoPlayerService.ts`:

```typescript
export interface VideoSource {
  uri: string;
  type?: 'hls' | 'dash' | 'mp4';
  headers?: Record<string, string>;
  drm?: DRMConfig;
}

export interface DRMConfig {
  type: 'widevine' | 'fairplay' | 'playready';
  licenseServer: string;
  headers?: Record<string, string>;
}

export interface PlaybackState {
  isPlaying: boolean;
  isPaused: boolean;
  isBuffering: boolean;
  currentTime: number;
  duration: number;
}

export interface VideoPlayerService {
  initialize(): Promise<void>;
  load(source: VideoSource): Promise<void>;
  play(): Promise<void>;
  pause(): Promise<void>;
  seek(timeInSeconds: number): Promise<void>;
  destroy(): Promise<void>;
  getPlaybackState(): PlaybackState;
}

export const DEFAULT_SEEK_SECONDS = 10;
```

Create native implementation in `VideoPlayerService.native.ts` (uses react-native-video).
Create Vega implementation in `VideoPlayerService.kepler.ts` (uses Shaka/W3C Media).

## Migration Troubleshooting

### Error: `.plugins[0] must be a string, object, function`

**Cause**: `metro-source-map` version mismatch in monorepo
**Fix**: Add resolution in root `package.json`:
```json
"resolutions": {
  "metro-source-map": "0.80.12"
}
```

### Error: `Super expression must either be null or a function`

**Cause**: Circular dependency in class inheritance due to Metro's platform resolution
**Fix**: Rename base class files from `ClassName.ts` to `ClassName.base.ts`

### Error: `HMRClient.setup() Module has not been registered`

**Cause**: react-native-tvos HMR incompatibility with Expo
**Fix**: Add HMRClient polyfill in `index.js`

### Error: `Cannot find module '@mycompany/shared-ui'`

**Cause**: Metro not configured for monorepo workspace resolution
**Fix**:
1. Add `watchFolders` in metro.config.js
2. Add `extraNodeModules` mapping
3. Run `yarn install` from root

### Warning: `Require cycle: X.ts -> X.ts`

**Cause**: Platform-specific file importing itself due to Metro resolution
**Fix**: Rename base file to use `.base.ts` extension

## Version Compatibility Matrix

| Package | Version | Notes |
|---------|---------|-------|
| expo | ~51.0.0 | Required for TV support |
| react | 18.2.0 | Must match expo requirement |
| react-native-tvos | ~0.74.3-0 | Use instead of react-native |
| metro-source-map | 0.80.12 | Force via resolution |
| react-native-reanimated | ~3.10.1 | For animations |
| @react-native-tvos/config-tv | ^0.0.12 | Expo plugin for TV |
| react-tv-space-navigation | ^3.6.1 | Spatial navigation |
| Java | 17 | Required for Android Gradle |
| Node.js | 18+ | Required for Expo |
| Yarn | 4.x | Berry with workspaces |

---

# Best Practices

## TV-Specific Design

- Always test on actual TV hardware, not just emulators
- Maintain 10-foot UI standards (large text, high contrast)
- Position critical UI within safe zones (48px horizontal, 27px vertical)
- Provide clear focus indicators (borders, scale, highlights)
- Design for remote control first, touch second

## Focus Management

- Set default focus on screen mount
- Restore focus when returning from navigation
- Prevent focus traps (ensure all UI is reachable)
- Test navigation flow with D-pad only
- Handle edge cases (first/last item in grid)

## Performance

- Optimize image sizes for TV resolution (1920x1080)
- Use FlatList for long scrolling lists
- Implement image caching strategy
- Lazy load video content
- Monitor memory usage on low-end devices

## Platform-Specific Code

- Use file extensions for platform variants
- Keep shared logic in base files
- Document platform differences
- Test each platform independently
- Abstract platform APIs behind common interfaces

## Video Player

- Always show buffering state
- Handle network failures gracefully
- Implement playback controls with remote support
- Test with various video formats and codecs
- Support multiple video qualities/bitrates

## Content Loading

- Implement retry logic for failed requests
- Show loading states during fetch
- Cache catalog data locally
- Handle empty states gracefully
- Validate API response structure

---

# Testing

## Unit Tests

- Test business logic in shared-ui package
- Mock platform-specific modules
- Test remote control event handlers
- Validate navigation flows
- Test data transformations

## Integration Tests

- Test cross-platform file resolution
- Validate navigation between screens
- Test video player controls
- Verify focus management
- Test catalog API integration

## Platform Testing

- Test on Android TV emulator and device
- Test on Apple TV simulator and device
- Test on Fire TV device
- Test on web browser with keyboard navigation
- Verify remote control functionality per platform

---

# Troubleshooting

## Metro Bundler Issues

- Clear cache: `yarn start --reset-cache`
- Check for port conflicts (default 8081)
- Verify workspace resolution in metro.config.js

## Platform-Specific Build Failures

- **iOS/tvOS**: Check Node binary path in `.xcode.env.local`
- **Android TV**: Verify Android SDK and emulator setup
- **Vega**: See Vega-specific troubleshooting below

## Vega SDK Issues

### Installation Problems

**'Electron quit unexpectedly' during installation**
- This is expected if VS Code is running during installation
- Restart VS Code after installation completes

**Vega SDK not found / kepler command not available**
- Verify: `kepler --version`
- Check environment variables:
  ```bash
  echo $KEPLER_SDK_PATH
  echo $PATH | grep kepler
  ```
- Add exports to shell config if missing

**Development utilities missing (macOS)**
```bash
brew update && brew install binutils coreutils gawk findutils grep jq lz4 gnu-sed watchman
```

### Build Failures

**npm run build:app fails**
- Ensure dependencies installed: `npm install`
- Check Node.js version: `node --version` (requires 16.x or later)
- Verify package ID format (reverse DNS, no `com.amazon`)
- Check Metro not already running on port 8081

**VPKG file not generated**
- Check build output: `build/<architecture>-<buildType>/`
- Try clean build: `rm -rf build && npm run build:app`

### Runtime Issues

**App crashes on Vega device**
- Check device logs for errors
- Verify VPKG architecture matches device
- Test with debug build first
- Ensure native dependencies compatible with Vega SDK

**Metro bundler connection issues**
- Verify Metro is running: `yarn start`
- Check firewall settings
- Ensure device and dev machine on same network

## Navigation Issues

- Verify React Navigation installation
- Check focus restoration logic
- Test with D-pad/arrow keys only
- Enable spatial navigation debug mode

## Video Playback Issues

- Verify video URL accessibility
- Check video format compatibility (MP4 recommended)
- Test with different video codecs
- Monitor network bandwidth

---

# Additional Resources

## Sample Repositories

**React Native Multi-TV App Sample**
- Repository: https://github.com/AmazonAppDev/react-native-multi-tv-app-sample
- Complete monorepo setup
- Working implementations for all platforms
- Reference for best practices

## Official Documentation

**Expo TV Documentation**
- System requirements and prerequisites
- Quick start with TV templates
- EAS Build profiles for TV
- Platform-specific build instructions

**Vega SDK Documentation**
- Installation guide
- Vega CLI guide
- Device setup
- Deployment to Amazon Appstore

## External Resources

- [React Native TV (react-native-tvos)](https://github.com/react-native-tvos/react-native-tvos)
- [React TV Space Navigation](https://github.com/bamlab/react-tv-space-navigation)
- [Expo Examples - TV](https://github.com/expo/examples/tree/master/with-tv)
- [Expo Examples - TV Router](https://github.com/expo/examples/tree/master/with-router-tv)
- [React Navigation](https://reactnavigation.org/)
- [React Native Directory](https://reactnative.directory/?tvos=true)

---

# Adding New Features

## New Shared Component

1. Create in `packages/shared-ui/src/components/ComponentName.tsx`
2. Export from `packages/shared-ui/src/index.ts`
3. Import in apps: `import { ComponentName } from '@project/shared-ui'`

## Platform-Specific Implementation

1. Create base file: `Feature.base.ts`
2. Create platform files: `Feature.android.ts`, `Feature.ios.ts`, `Feature.kepler.ts`
3. Metro automatically resolves correct file at build time

## New Screen

1. Create in `packages/shared-ui/src/screens/ScreenName.tsx`
2. Add to navigation in `navigation/AppNavigator.tsx`
3. Export from `packages/shared-ui/src/index.ts`
4. Use in app's navigation configuration

---

# Vega App in Monorepo

## Critical Configuration

### 1. Prevent Dependency Hoisting

Add to `apps/vega/package.json`:
```json
{
  "installConfig": {
    "hoistingLimits": "dependencies"
  }
}
```

### 2. Fix Metro Source Map

Create `apps/vega/fix-metro.js`:
```javascript
const fs = require('fs');
const path = require('path');

const metroSourceMapPath = path.join(__dirname, 'node_modules/metro-source-map');
const packageJsonPath = path.join(metroSourceMapPath, 'package.json');

if (!fs.existsSync(packageJsonPath)) {
  console.log('⚠ metro-source-map not found');
  process.exit(0);
}

const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
pkg.exports = {
  ".": "./src/source-map.js",
  "./package.json": "./package.json",
  "./private/*": "./src/*.js",
  "./src/*": "./src/*.js"
};

fs.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2));
console.log('✓ Patched metro-source-map exports for Vega');
```

### 3. Add Postinstall Script

In `apps/vega/package.json`:
```json
{
  "scripts": {
    "postinstall": "node fix-metro.js",
    "build:debug": "react-native build-kepler --build-type Debug",
    "build:release": "react-native build-kepler --build-type Release"
  }
}
```

**Note:** Use `build-kepler` command, not `build-vega`.

### 4. Manifest Configuration (CRITICAL)

The `manifest.toml` **MUST** include a `[processes]` section or the app will crash immediately on launch with no error message.

`apps/vega/manifest.toml`:
```toml
schema-version = 1

[package]
title = "My Vega App"
version = "0.1.0"
id = "com.mycompany.myapp"

[components]
[[components.interactive]]
id = "com.mycompany.myapp.main"
runtime-module = "/com.amazon.kepler.keplerscript.runtime.loader_2@IKeplerScript_2_0"
launch-type = "singleton"
categories = ["com.amazon.category.main"]

# CRITICAL: This section is REQUIRED or the app will crash on launch
[processes]
[[processes.group]]
component-ids = ["com.mycompany.myapp.main"]
```

**Common Symptom:** App installs successfully, shows "Successfully launched the app", but immediately crashes (black screen) with no visible error. The fix is adding the `[processes]` section.

### 5. Metro Configuration

`apps/vega/metro.config.js`:
```javascript
const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config');
const path = require('path');

const config = {
  watchFolders: [
    path.resolve(__dirname, '../../packages/shared-ui'),
  ],
  resolver: {
    unstable_enableSymlinks: true,
    sourceExts: ['kepler.tsx', 'kepler.ts', 'kepler.js', 'tsx', 'ts', 'jsx', 'js', 'json'],
    nodeModulesPaths: [
      path.resolve(__dirname, 'node_modules'),
      path.resolve(__dirname, '../../node_modules'),
    ],
  },
};

module.exports = mergeConfig(getDefaultConfig(__dirname), config);
```

### 5. Required Dependencies

In `apps/vega/package.json`:
```json
{
  "dependencies": {
    "@amazon-devices/react-native-kepler": "^2.0.0",
    "@babel/runtime": "^7.28.6",
    "react": "18.2.0",
    "react-native": "0.72.0",
    "react-tv-space-navigation": "^6.0.0-beta1"
  },
  "devDependencies": {
    "@amazon-devices/kepler-cli-platform": "^0",
    "@amazon-devices/kepler-module-manifest-builder": "^0.1.0",
    "@react-native/metro-config": "^0.72.6",
    "jest-worker": "^28.0.0"
  }
}
```

### 6. Spatial Navigation with Vega (react-tv-space-navigation)

`react-tv-space-navigation` is compatible with Vega but requires a remote control configuration to map Vega's TVEventHandler to spatial navigation directions.

**Create `packages/shared-ui/src/app/remote-control/SupportedKeys.ts`:**
```typescript
export enum SupportedKeys {
  Up = 'up',
  Down = 'down',
  Left = 'left',
  Right = 'right',
  Enter = 'enter',
  Back = 'back',
  PlayPause = 'playPause',
  Rewind = 'rewind',
  FastForward = 'fastForward',
}
```

**Create `packages/shared-ui/src/app/remote-control/RemoteControlManager.kepler.ts`:**
```typescript
import { SupportedKeys } from './SupportedKeys';

type Listener = (event: SupportedKeys) => void;

const EVENT_TYPE_MAPPING: Record<string, SupportedKeys> = {
  left: SupportedKeys.Left,
  right: SupportedKeys.Right,
  down: SupportedKeys.Down,
  up: SupportedKeys.Up,
  select: SupportedKeys.Enter,
  back: SupportedKeys.Back,
  playpause: SupportedKeys.PlayPause,
  skip_backward: SupportedKeys.Rewind,
  skip_forward: SupportedKeys.FastForward,
};

class RemoteControlManager {
  private listeners: Set<Listener> = new Set();
  private tvEventHandler: any;

  constructor() {
    const { TVEventHandler } = require('react-native');
    this.tvEventHandler = new TVEventHandler();
    this.tvEventHandler.enable(this, this.handleHWEvent);
  }

  private handleHWEvent = (_component: any, event: any): void => {
    if (event.eventKeyAction === 0 || event.eventKeyAction === undefined) {
      const mappedKey = EVENT_TYPE_MAPPING[event.eventType];
      if (mappedKey) {
        this.listeners.forEach(listener => listener(mappedKey));
      }
    }
  };

  addKeydownListener = (listener: Listener) => {
    this.listeners.add(listener);
    return listener;
  };

  removeKeydownListener = (listener: Listener) => {
    this.listeners.delete(listener);
  };
}

export default new RemoteControlManager();
```

**Create `packages/shared-ui/src/app/configureRemoteControl.ts`:**
```typescript
import { Directions, SpatialNavigation } from 'react-tv-space-navigation';
import { SupportedKeys } from './remote-control/SupportedKeys';
import RemoteControlManager from './remote-control/RemoteControlManager';

SpatialNavigation.configureRemoteControl({
  remoteControlSubscriber: (callback) => {
    const mapping: { [key in SupportedKeys]: Directions | null } = {
      [SupportedKeys.Right]: Directions.RIGHT,
      [SupportedKeys.Left]: Directions.LEFT,
      [SupportedKeys.Up]: Directions.UP,
      [SupportedKeys.Down]: Directions.DOWN,
      [SupportedKeys.Enter]: Directions.ENTER,
      [SupportedKeys.Back]: null,
      [SupportedKeys.PlayPause]: null,
      [SupportedKeys.Rewind]: null,
      [SupportedKeys.FastForward]: null,
    };

    const remoteControlListener = (keyE

…(truncated)
