← Back home

Getting Started with Kiro as a Developer

Getting Started with Kiro as a Developer

Kiro is an agentic AI IDE and CLI that helps you go from prototype to production with spec‑driven development—turning natural‑language prompts into structured requirements, architecture, and executable tasks that generate code, tests, and docs aligned with your intent.

First thing to do: add Agent Steering & Skills

In each Kiro workspace, you can define Agent Steering & Skills so that your agents behave the way you expect and follow your team’s engineering practices.

  1. In your Kiro workspace, click the Agent Steering & Skills icon in the main navigation.

    Agent Steering & Skills icon

  2. Click Add steering & skills.

  3. Create a new steering document for your workspace and paste in the content below (or an adapted version of it).

Here’s the steering document I use for Lifeway frontend development, focused on React Native, TypeScript, Expo, and TV/mobile best practices:

---
inclusion: always
name: lifeway-frontend-development
description: Comprehensive guide for Lifeway's React Native development with TypeScript, Expo, and mobile/TV UI best practices. Includes testing strategies, state management patterns, and production-proven practices from mobile and TV codebases.
fileMatchPattern: ["**/*.ts", "**/*.tsx"]
---
<!------------------------------------------------------------------------------------
  Lifeway Front End Development - Complete Guide
  
  Expert guidance for TypeScript, React Native, Expo, and Mobile/TV UI development.
  This guide is based on analysis of the mobile and TV codebases and reflects
  actual patterns used in production at Lifeway.
  
  Learn about inclusion modes: https://kiro.dev/docs/steering/#inclusion-modes
------------------------------------------------------------------------------------->

# Code Style and Structure

## General Principles
- Write concise, technical TypeScript code with accurate examples
- Use functional and declarative programming patterns; avoid classes
- Prefer iteration and modularization over code duplication
- Use descriptive variable names with auxiliary verbs (e.g., isLoading, hasError)
- Structure files: exported component, subcomponents, helpers, static content, types

## Naming Conventions
- Use snakeCases for directories (e.g., `components/formElements`)
- Favor named exports for components
- Use PascalCase for component names
- Use camelCase for functions and variables

## TypeScript Usage
- Use TypeScript for all code; prefer types over interfaces
- Avoid enums; use maps or const objects instead
- Use functional components with TypeScript types
- Use strict mode in TypeScript for better type safety
- Prefer const assertions and `as const` for literal types to improve type inference
- Use discriminated unions for handling different states and variants in components
- Avoid using `any` type; use `unknown` and proper type guards when type is unclear
- Leverage React Native's built-in types (e.g., ViewStyle, TextStyle, ImageStyle) for styling props

```typescript
// Good - discriminated union
type LoadingState = { status: "loading" }
type SuccessState = { status: "success"; data: Data }
type ErrorState = { status: "error"; error: Error }
type State = LoadingState | SuccessState | ErrorState

// Good - const assertion
const COLORS = {
  primary: "#007AFF",
  secondary: "#5856D6"
} as const

// Avoid - enum
enum Colors {
  Primary = "#007AFF",
  Secondary = "#5856D6"
}
```

</details>

## Syntax and Formatting
- Use the "function" keyword for pure functions
- Avoid unnecessary curly braces in conditionals; use concise syntax for simple statements
- Use declarative JSX
- Use Biome for mobile/TV apps, Prettier for other packages
- Format on save and before commits

---

# Testing Practices

## Testing Framework Configuration

### Mobile & TV Apps (Jest)
- Use Jest with `jest-expo` preset for React Native testing
- Configuration: `.jest/spec/config.ts`
- Test files: `**/__tests__/**/*.spec.{ts,tsx}` or `**/*.spec.{ts,tsx}`
- Run tests: `yarn test` (from frontend/ directory)
- Watch mode: `yarn test:watch`

### Shared Packages (Vitest)
- Use Vitest for `@lifeway/react-native-shared` and other shared packages
- Faster execution and better ESM support
- Configuration in `vitest.config.ts`

## Test Structure & Organization

### File Organization
```
feature/
├── components/
│   ├── MyComponent.tsx
│   └── __tests__/
│       └── MyComponent.spec.tsx
├── state/
│   ├── myState.ts
│   └── __tests__/
│       └── myState.spec.ts
└── utils/
    ├── myUtil.ts
    └── __tests__/
        └── myUtil.spec.ts
```

### Test Naming Conventions
- Use `describe()` blocks to group related tests
- Use `it()` or `test()` for individual test cases
- Use descriptive names that explain the behavior being tested
- Follow pattern: "when [condition], should [expected behavior]"

```typescript
describe("MyComponent", () => {
  describe("when user is authenticated", () => {
    it("should display user profile", () => {
      // test implementation
    })
  })

  describe("when user is not authenticated", () => {
    it("should redirect to login", () => {
      // test implementation
    })
  })
})
```

## Testing Utilities

### Test Renderer Pattern
Use the `testRenderer` helper for consistent test setup with all required providers:

```typescript
import { testRenderer } from "@/src/test/testRenderer"

const renderPage = testRenderer(<MyComponent />)

it("should render correctly", () => {
  const { getByText } = renderPage()
  expect(getByText("Hello")).toBeTruthy()
})
```

The `testRenderer` automatically wraps components with:
- `SafeAreaProvider`
- `UserPreferenceContextProvider`
- `ThemeContextProvider`
- `AlertProvider`
- `CustomApolloClientProvider` (with mock support)
- `DeviceProvider`
- `Subscribe` (for RxJS state)
- Other global providers

### GraphQL Mocking
Override GraphQL responses in tests:

```typescript
import type { MockedResponse } from "@apollo/client/testing"

const mockResponse: MockedResponse[] = [
  {
    request: {
      query: GET_STUDY_BY_ID,
      variables: { id: "test-id" }
    },
    result: {
      data: {
        studyByID: { id: "test-id", title: "Test Study" }
      }
    }
  }
]

const { getByText } = testRenderer(<MyComponent />)(mockResponse)
```

## Component Testing

### React Testing Library
- Use `@testing-library/react-native` for component testing
- Query by accessibility properties (role, label) over test IDs when possible
- Use `waitFor` for async operations
- Use `fireEvent` for user interactions

```typescript
import { fireEvent, waitFor } from "@testing-library/react-native"

it("should handle button press", async () => {
  const onPress = jest.fn()
  const { getByRole } = testRenderer(<Button onPress={onPress} />)()
  
  fireEvent.press(getByRole("button"))
  
  await waitFor(() => {
    expect(onPress).toHaveBeenCalled()
  })
})
```

### Accessibility Testing
- Query by `getByRole()` and `getByLabelText()` to ensure accessibility
- Test that components have proper accessibility labels
- Verify screen reader behavior when relevant

```typescript
it("should have accessible button", () => {
  const { getByRole } = testRenderer(<MyButton />)()
  const button = getByRole("button")
  expect(button).toHaveAccessibilityLabel("Submit form")
})
```

## Hook Testing

### Testing Custom Hooks
Use `renderHook` from React Testing Library:

```typescript
import { renderHook, act } from "@testing-library/react-native"

it("should update state", () => {
  const { result } = renderHook(() => useMyHook())
  
  act(() => {
    result.current.updateValue("new value")
  })
  
  expect(result.current.value).toBe("new value")
})
```

### Testing Hooks with Context
Provide wrapper for hooks that need context:

```typescript
import { rxjsWrapper } from "@/__testutils__/rxjsTesting"

const { result } = renderHook(() => useCurrentStudy(), {
  wrapper: rxjsWrapper
})
```

## RxJS State Testing

### Marble Testing
Use RxJS TestScheduler for testing observable streams:

```typescript
import { TestScheduler } from "rxjs/internal/testing/TestScheduler"

it("should emit values over time", () => {
  new TestScheduler((actual, expected) => {
    expect(actual).toEqual(expected)
  }).run(({ hot, expectObservable }) => {
    const source$ = hot("a-b-c", { a: 1, b: 2, c: 3 })
    expectObservable(source$).toBe("a-b-c", { a: 1, b: 2, c: 3 })
  })
})
```

### Testing BehaviorSubjects
Test state changes with BehaviorSubjects:

```typescript
import { BehaviorSubject } from "rxjs"

it("should update subject value", () => {
  const subject$ = new BehaviorSubject<string>("initial")
  
  subject$.next("updated")
  
  expect(subject$.value).toBe("updated")
})
```

## Mocking Patterns

### Module Mocking
Mock entire modules with `jest.mock()`:

```typescript
jest.mock("../../utils/sentry", () => ({
  logError: jest.fn()
}))

jest.mock("@react-navigation/native", () => ({
  ...jest.requireActual("@react-navigation/native"),
  useNavigation: () => ({
    navigate: jest.fn()
  })
}))
```

### Native Module Mocking
Mock React Native native modules in setup files:

```typescript
// .jest/spec/setupReanimated.ts
jest.mock("react-native-reanimated", () => {
  const Reanimated = require("react-native-reanimated/mock")
  return {
    ...Reanimated,
    useAnimatedStyle: (callback) => callback()
  }
})
```

### Network State Mocking
Mock network state for offline/online testing:

```typescript
import { useNetInfo } from "@react-native-community/netinfo"

const mockUseNetInfo = useNetInfo as jest.MockedFunction<typeof useNetInfo>

mockUseNetInfo.mockReturnValue({
  isConnected: true,
  isInternetReachable: true,
  type: "wifi"
})
```

## Test Setup Files

### Setup Structure
```
.jest/
├── spec/
│   ├── config.ts              # Main Jest config
│   ├── setupGlobals.ts        # Global test utilities
│   ├── setupAsyncStorage.ts   # AsyncStorage mock
│   ├── setupNetInfo.ts        # Network info mock
│   ├── setupReanimated.ts     # Reanimated mock
│   ├── setupSafeAreaContext.ts # Safe area mock
│   └── setupExpoAudioPlayer.ts # Audio player mock
```

### Common Setup Patterns
- Mock native modules that don't work in test environment
- Set up global test utilities and matchers
- Configure test timeouts and environment variables
- Mock platform-specific APIs

## Async Testing

### Promises and Async/Await
```typescript
it("should fetch data", async () => {
  const { getByText } = testRenderer(<MyComponent />)()
  
  await waitFor(() => {
    expect(getByText("Loaded")).toBeTruthy()
  })
})
```

### Testing Error States
```typescript
it("should handle errors", async () => {
  const mockFetch = jest.fn().mockRejectedValue(new Error("Failed"))
  
  const { getByText } = testRenderer(<MyComponent fetch={mockFetch} />)()
  
  await waitFor(() => {
    expect(getByText("Error occurred")).toBeTruthy()
  })
})
```

## Snapshot Testing

### Use Sparingly
- Avoid snapshot tests for complex components
- Use for simple, stable UI components only
- Update snapshots intentionally, not automatically
- Prefer explicit assertions over snapshots

```typescript
it("should match snapshot", () => {
  const { toJSON } = testRenderer(<SimpleComponent />)()
  expect(toJSON()).toMatchSnapshot()
})
```

## Test Coverage

### Coverage Goals
- Aim for meaningful coverage, not 100%
- Focus on critical paths and business logic
- Test error handling and edge cases
- Don't test implementation details

### Running Coverage
```bash
yarn test --coverage
```

## Best Practices

### Do's
- Test user behavior, not implementation details
- Use descriptive test names
- Keep tests isolated and independent
- Mock external dependencies
- Test error states and edge cases
- Use `waitFor` for async operations
- Clean up after tests with `afterEach` and `beforeEach`

### Don'ts
- Don't test library code
- Don't test implementation details (internal state, private methods)
- Don't create brittle tests that break on minor UI changes
- Don't use `setTimeout` in tests (use `waitFor` instead)
- Don't share state between tests
- Don't mock everything (test real behavior when possible)

---

# React Native Practices

## Component Architecture

### Functional Components Only
- Use functional components with hooks
- Avoid class components
- Use TypeScript for all components

```typescript
type MyComponentProps = {
  title: string
  onPress: () => void
}

export function MyComponent({ title, onPress }: MyComponentProps) {
  return (
    <TouchableOpacity onPress={onPress}>
      <Text>{title}</Text>
    </TouchableOpacity>
  )
}
```

### Component Organization
```typescript
// 1. Imports
import { View, Text } from "react-native"
import { useState } from "react"

// 2. Types
type Props = {
  // ...
}

// 3. Main component
export function MyComponent({ prop }: Props) {
  // 4. Hooks
  const [state, setState] = useState()
  
  // 5. Event handlers
  const handlePress = () => {
    // ...
  }
  
  // 6. Render
  return (
    <View>
      <Text>Content</Text>
    </View>
  )
}

// 7. Subcomponents (if needed)
function SubComponent() {
  return <View />
}
```

## State Management

### RxJS for Global State
Use RxJS BehaviorSubjects for global state management:

```typescript
import { BehaviorSubject } from "rxjs"
import { bind } from "@react-rxjs/core"

const myState$ = new BehaviorSubject<MyState>({ value: "initial" })

export const [useMyState] = bind(myState$)

export const updateMyState = (newValue: string) => {
  myState$.next({ value: newValue })
}
```

### React Hooks for Local State
Use React hooks for component-local state:

```typescript
const [isLoading, setIsLoading] = useState(false)
const [data, setData] = useState<Data | null>(null)
```

### Context for Feature State
Use React Context for feature-scoped state:

```typescript
type MyContextValue = {
  state: MyState
  dispatch: Dispatch<MyAction>
}

const MyContext = createContext<MyContextValue | undefined>(undefined)

export function useMyContext() {
  const context = useContext(MyContext)
  if (!context) {
    throw new Error("useMyContext must be used within MyProvider")
  }
  return context
}
```

### useReducer for Complex State
Use `useReducer` for complex state logic:

```typescript
type State = {
  status: "idle" | "loading" | "success" | "error"
  data: Data | null
}

type Action =
  | { type: "FETCH_START" }
  | { type: "FETCH_SUCCESS"; payload: Data }
  | { type: "FETCH_ERROR" }

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case "FETCH_START":
      return { ...state, status: "loading" }
    case "FETCH_SUCCESS":
      return { status: "success", data: action.payload }
    case "FETCH_ERROR":
      return { ...state, status: "error" }
    default:
      return state
  }
}

const [state, dispatch] = useReducer(reducer, initialState)
```

## Custom Hooks

### Hook Patterns
Create custom hooks for reusable logic:

```typescript
// State management hook
export function useSubjectState<T>(
  subject$: BehaviorSubject<T>,
  defaultValue: T
): [T, (newValue: T) => void] {
  const [value, setValue] = useState<T>(defaultValue)
  
  useEffect(() => {
    const subscription = subject$.subscribe(setValue)
    return () => subscription.unsubscribe()
  }, [subject$])
  
  const emitValue = useCallback(
    (newValue: T) => subject$.next(newValue),
    [subject$]
  )
  
  return [value, emitValue]
}
```

### Common Hook Patterns
- `useAccessibility()` - Screen reader detection and focus management
- `useRefresh()` - Pull-to-refresh with network checking
- `useSubjectState()` - Bridge between RxJS and React state
- `usePrevious()` - Track previous prop/state values

## Error Handling

### Error Boundaries
Use `react-error-boundary` for component error handling:

```typescript
import { ErrorBoundary } from "react-error-boundary"
import { ErrorFallbackComponent } from "./components/errorFallbackComponent"

<ErrorBoundary FallbackComponent={ErrorFallbackComponent}>
  <App />
</ErrorBoundary>
```

### Result Type Pattern
Use Result type for error handling in utilities:

```typescript
import { Ok, Err, type Result } from "./utils/result"

function parseData(input: string): Result<Data, Error> {
  try {
    const data = JSON.parse(input)
    return new Ok(data)
  } catch (error) {
    return new Err(new Error("Failed to parse data"))
  }
}

const result = parseData(input)
if (result.ok) {
  console.log(result.val) // Data
} else {
  console.error(result.val) // Error
}
```

### Error Logging
Use Sentry for error logging:

```typescript
import { logError } from "./utils/sentry"

try {
  await fetchData()
} catch (error) {
  logError(new Error("Failed to fetch data", { cause: error }))
}
```

## Platform-Specific Code

### Platform Detection
```typescript
import { Platform } from "react-native"

if (Platform.OS === "ios") {
  // iOS-specific code
} else if (Platform.OS === "android") {
  // Android-specific code
}

// For TV platforms
if (Platform.isTV) {
  // TV-specific code
}
```

### Platform-Specific Files
Use `.ios.tsx` and `.android.tsx` extensions:
```
MyComponent.tsx       # Shared code
MyComponent.ios.tsx   # iOS-specific
MyComponent.android.tsx # Android-specific
```

## Performance Optimization

### Memoization
Use `useMemo` and `useCallback` to prevent unnecessary re-renders:

```typescript
const memoizedValue = useMemo(() => {
  return expensiveComputation(data)
}, [data])

const memoizedCallback = useCallback(() => {
  doSomething(value)
}, [value])
```

### React.memo for Components
Memoize components that receive stable props:

```typescript
export const MyComponent = React.memo(function MyComponent({ data }: Props) {
  return <View>{/* render */}</View>
})
```

### FlatList Optimization
```typescript
<FlatList
  data={items}
  renderItem={renderItem}
  keyExtractor={(item) => item.id}
  removeClippedSubviews={true}
  maxToRenderPerBatch={10}
  windowSize={10}
  initialNumToRender={10}
  getItemLayout={(data, index) => ({
    length: ITEM_HEIGHT,
    offset: ITEM_HEIGHT * index,
    index
  })}
/>
```

## Accessibility

### Accessibility Props
Always include accessibility properties:

```typescript
<TouchableOpacity
  accessible={true}
  accessibilityRole="button"
  accessibilityLabel="Submit form"
  accessibilityHint="Submits the form and navigates to next screen"
  onPress={handlePress}
>
  <Text>Submit</Text>
</TouchableOpacity>
```

### Screen Reader Support
Use `useAccessibility` hook for screen reader detection:

```typescript
import { useAccessibility } from "./hooks/useAccessibility"

const { isScreenReaderEnabled, setElementAccessibilityFocus } = useAccessibility()

if (isScreenReaderEnabled) {
  // Adjust UI for screen reader users
}
```

### Focus Management
```typescript
const elementRef = useRef<View>(null)

useEffect(() => {
  if (shouldFocus) {
    setElementAccessibilityFocus(elementRef.current)
  }
}, [shouldFocus])

<View ref={elementRef}>
  {/* content */}
</View>
```

## Navigation

### React Navigation
Use `@react-navigation/native` for navigation:

```typescript
import { useNavigation } from "@react-navigation/native"
import type { StackNavigationProp } from "@react-navigation/stack"

type NavigationProp = StackNavigationProp<RootStackParamList, "Home">

function MyScreen() {
  const navigation = useNavigation<NavigationProp>()
  
  const handlePress = () => {
    navigation.navigate("Details", { id: "123" })
  }
  
  return <Button onPress={handlePress} />
}
```

### Type-Safe Navigation
Define navigation types:

```typescript
type RootStackParamList = {
  Home: undefined
  Details: { id: string }
  Profile: { userId: string }
}
```

## GraphQL Integration

### Apollo Client Hooks
```typescript
import { useQuery, useMutation } from "@apollo/client"
import { GET_STUDY_BY_ID } from "./queries"

function MyComponent({ studyId }: Props) {
  const { data, loading, error, refetch } = useQuery(GET_STUDY_BY_ID, {
    variables: { id: studyId }
  })
  
  if (loading) return <LoadingSpinner />
  if (error) return <ErrorMessage error={error} />
  
  return <StudyDetails study={data.studyByID} />
}
```

### Mutations
```typescript
const [createStudy, { loading, error }] = useMutation(CREATE_STUDY, {
  onCompleted: (data) => {
    console.log("Study created:", data.createStudy.study)
  },
  onError: (error) => {
    logError(error)
  }
})

const handleCreate = () => {
  createStudy({
    variables: {
      input: { title: "New Study" }
    }
  })
}
```

## Styling

### StyleSheet API
Use React Native's StyleSheet API:

```typescript
import { StyleSheet } from "react-native"
import { spacing, colors } from "./utils/styleguide"

const styles = StyleSheet.create({
  container: {
    padding: spacing.x4,
    backgroundColor: colors.background
  },
  text: {
    fontSize: 16,
    color: colors.text
  }
})
```

### Shared Style Constants
Import from `@lifeway/react-native-shared`:

```typescript
import {
  spacing,
  font,
  staticColors,
  borderRadius
} from "@lifeway/react-native-shared/utils/styleguide"
```

### Dynamic Styles
Create styles based on props or state:

```typescript
const dynamicStyles = StyleSheet.create({
  button: {
    backgroundColor: isPressed ? colors.primaryDark : colors.primary
  }
})
```

## Safe Area Management

### SafeAreaView
Always use SafeAreaView for screen-level components:

```typescript
import { SafeAreaView } from "react-native-safe-area-context"

function MyScreen() {
  return (
    <SafeAreaView style={{ flex: 1 }}>
      {/* content */}
    </SafeAreaView>
  )
}
```

### SafeAreaProvider
Wrap app root with SafeAreaProvider:

```typescript
import { SafeAreaProvider } from "react-native-safe-area-context"

<SafeAreaProvider>
  <App />
</SafeAreaProvider>
```

## Feature Organization

### Feature Structure
Organize code by feature, not by type:

```
features/
├── authentication/
│   ├── api/
│   ├── components/
│   ├── domain/
│   ├── state/
│   └── __tests__/
├── study/
│   ├── api/
│   ├── components/
│   ├── screens/
│   ├── state/
│   └── __tests__/
```

### Loose Coupling
- Minimize dependencies between features
- Use shared packages for truly shared code
- Prefer code duplication over tight coupling
- Communicate through defined interfaces

## Type Safety

### TypeScript Strict Mode
Enable strict mode in `tsconfig.json`:

```json
{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true
  }
}
```

### Avoid `any`
Use `unknown` and type guards instead:

```typescript
function processData(data: unknown) {
  if (typeof data === "string") {
    return data.toUpperCase()
  }
  throw new Error("Invalid data type")
}
```

### Type Inference
Let TypeScript infer types when possible:

```typescript
// Good - type inferred
const [count, setCount] = useState(0)

// Unnecessary - type is obvious
const [count, setCount] = useState<number>(0)
```

## Development Utilities

### Dev Assertions
Use `devAssert` for development-time checks:

```typescript
import { devAssert } from "./utils/devAssert"

devAssert(
  subject$ !== undefined,
  "Subject must be defined"
)
```

### Environment Variables
Use `ENV` for environment configuration:

```typescript
import { ENV } from "./ENV"

if (ENV.NODE_ENV === "development") {
  console.log("Development mode")
}
```

## UI and Styling

### Styling Approach
- Use React Native's StyleSheet API for component styling
- Import shared style constants from `@lifeway/react-native-shared/utils/styleguide`
- Use Expo's built-in components for common UI patterns and layouts
- Implement responsive design with Flexbox and Expo's useWindowDimensions for screen size adjustments
- Implement dark mode support using Expo's useColorScheme

### Shared Style Constants
```typescript
import {
  spacing,
  font,
  staticColors,
  borderRadius,
  nonScalingSpacing
} from "@lifeway/react-native-shared/utils/styleguide"
```

### Responsive Design
- Use Flexbox for layouts
- Use `useWindowDimensions` for screen size adjustments
- Test on multiple device sizes (phones, tablets, TV)

### Animations and Gestures
- Leverage react-native-reanimated for performant animations
- Use react-native-gesture-handler for gesture handling
- Keep animations at 60fps, especially on TV platforms

## React Native TV Development

### TV-Specific Considerations
- Use `@react-native-tvos/config-tv` for TV-specific configurations and setup
- Implement TV-specific navigation patterns using focus management with useTVEventHandler
- Design for 10-foot UI: larger touch targets (min 80x80dp), bigger fonts, and appropriate spacing for TV screens
- Use TVFocusGuideView to control focus navigation flow between components
- Handle remote control inputs using TVEventHandler for custom button mappings (menu, play/pause, etc.)
- Optimize for different TV screen sizes and aspect ratios (720p, 1080p, 4K)

### Focus Management
- Implement spatial navigation: ensure logical focus order with focusable prop and nextFocusDown/Up/Left/Right
- Use hasTVPreferredFocus prop to set initial focus on app launch or screen transitions
- Avoid hover states and tooltips; design for focus states with clear visual feedback
- Implement proper focus management in lists using FlatList with TV-optimized props

### TV Performance
- TV apps should maintain 60fps
- Avoid heavy animations during focus changes
- Handle back button behavior appropriately for TV navigation patterns
- Test on actual TV devices or Android TV/Apple TV simulators for accurate behavior
- Use `Platform.isTV` to conditionally render TV-specific UI components and layouts

## Security

### Best Practices
- Sanitize user inputs to prevent XSS attacks
- Ensure secure communication with APIs using HTTPS and proper authentication
- Use Expo's Security guidelines to protect your app: https://docs.expo.dev/guides/security/
- Store sensitive data using expo-secure-store
- Never commit API keys or secrets to version control

## Internationalization (i18n)

### Implementation
- Use expo-localization for internationalization and localization
- Use i18n-js for translation management
- Support multiple languages and RTL layouts
- Ensure text scaling and font adjustments for accessibility
- Test with different locales and languages

## Key Conventions

### Expo Workflow
1. Rely on Expo's managed workflow for streamlined development and deployment
2. Prioritize Mobile Web Vitals (Load Time, Jank, and Responsiveness)
3. Use expo-constants for managing environment variables and configuration
4. Use expo-permissions to handle device permissions gracefully
5. Implement expo-updates for over-the-air (OTA) updates
6. Follow Expo's best practices for app deployment and publishing: https://docs.expo.dev/distribution/introduction/
7. Ensure compatibility with iOS and Android, for TV apps will be tvOS and fireTV by testing extensively on the platforms

### Development Commands
Run all commands from the `frontend/` workspace root:

```bash
# Mobile app
yarn workspace mobile dev:simulator
yarn workspace mobile ios
yarn workspace mobile android
yarn workspace mobile test

# TV app
yarn workspace tv start
yarn workspace tv ios
yarn workspace tv android
yarn workspace tv test

# Type checking and linting
yarn workspace mobile lint
yarn workspace tv lint
```

## API Documentation

### Resources
- Expo documentation: https://docs.expo.dev/
- React Native documentation: https://reactnative.dev/
- React Navigation: https://reactnavigation.org/
- Apollo Client: https://www.apollographql.com/docs/react/

## Best Practices Summary

### Do's
- Use functional components with hooks
- Use TypeScript for type safety with strict mode enabled
- Use RxJS for global state, React hooks for local state
- Implement proper error boundaries
- Test user behavior, not implementation
- Use accessibility props on all interactive elements
- Organize code by feature with loose coupling
- Use SafeAreaView for proper inset handling
- Memoize expensive computations with useMemo and useCallback
- Log errors to Sentry
- Clean up subscriptions and listeners in useEffect cleanup
- Use descriptive test names that explain behavior
- Mock external dependencies in tests
- Test error states and edge cases

### Don'ts
- Don't use class components
- Don't use `any` type; use `unknown` with type guards
- Don't create tight coupling between features
- Don't ignore accessibility requirements
- Don't test implementation details
- Don't use inline styles for complex styling
- Don't forget to clean up subscriptions and listeners
- Don't block the main thread with heavy computations
- Don't use setTimeout in tests; use waitFor instead
- Don't share state between tests
- Don't mock everything; test real behavior when possible
- Don't ignore TypeScript errors or use @ts-ignore without good reason

---

Add Agent Hooks

Once you have steering and skills in place, you can wire Kiro directly into your PR workflow using agent hooks.

Here’s an example hook I use for deep PR reviews against our frontend repos:

{
  "enabled": true,
  "name": "PR Review - Best Practices",
  "description": "Comprehensive PR review checking for software engineering fundamentals including Kent Dodds principles, Pragmatic Programmer guidelines, SOLID principles, clean code practices, and testing best practices",
  "version": "1",
  "when": {
    "type": "userTriggered"
  },
  "then": {
    "type": "askAgent",
    "prompt": "You are a lead/senior developer conducting a thorough PR review. Review the current git diff with the experience and perspective of someone who has:\\n- Built and maintained large-scale production systems\\n- Mentored junior and mid-level developers\\n- Made architectural decisions and lived with their consequences\\n- Debugged production incidents at 3am\\n- Refactored legacy code and dealt with technical debt\\n\\nProvide a comprehensive code review based on software engineering best practices:\\n\\n1. **Kent Dodds Principles**: Check for testing best practices (test behavior not implementation, avoid test implementation details, use Testing Library queries properly)\\n\\n2. **Pragmatic Programmer Guidelines**: Look for DRY violations, orthogonality issues, reversibility, tracer bullets approach, and technical debt\\n\\n3. **SOLID Principles**: \\n   - Single Responsibility: Each module/function should have one reason to change\\n   - Open/Closed: Open for extension, closed for modification\\n   - Liskov Substitution: Subtypes must be substitutable for base types\\n   - Interface Segregation: Many specific interfaces over one general\\n   - Dependency Inversion: Depend on abstractions, not concretions\\n\\n4. **Clean Code Practices**:\\n   - Meaningful names (descriptive, searchable, pronounceable)\\n   - Functions should be small and do one thing\\n   - Minimize function arguments\\n   - No side effects\\n   - Error handling (don't return null, use exceptions properly)\\n   - Comments only when necessary (code should be self-documenting)\\n\\n5. **Code Smells**: Identify duplicated code, long methods, large classes, long parameter lists, divergent change, shotgun surgery, feature envy, data clumps\\n\\n6. **Testing Quality**:\\n   - Test coverage for critical paths\\n   - Tests are readable and maintainable\\n   - Proper use of mocks and stubs\\n   - Tests are isolated and independent\\n\\n7. **Architecture & Design**:\\n   - Proper separation of concerns\\n   - Appropriate abstraction levels\\n   - Loose coupling, high cohesion\\n   - Consistent patterns with existing codebase\\n\\n8. **Security & Performance**:\\n   - No obvious security vulnerabilities\\n   - Efficient algorithms and data structures\\n   - Proper resource management\\n\\n9. **Maintainability**:\\n   - Code is easy to understand\\n   - Follows project conventions\\n   - Proper error handling\\n   - Adequate documentation where needed\\n\\n10. **Production Readiness**:\\n   - Consider edge cases and error scenarios\\n   - Think about monitoring and observability\\n   - Assess impact on existing features\\n   - Evaluate rollback strategy if needed\\n\\nProvide specific feedback with:\\n- Line references where applicable\\n- Severity (Critical, Important, Suggestion)\\n- Concrete examples of how to improve\\n- Share wisdom from experience (\\\"I've seen this pattern cause issues when...\\\")\\n- Praise for well-written code and good decisions\\n- Constructive mentoring tone - explain the 'why' behind suggestions\\n\\nFormat your review as:\\n## Summary\\n[Overall assessment with senior developer perspective]\\n\\n## Critical Issues\\n[Must fix before merge - explain potential production impact]\\n\\n## Important Improvements\\n[Should address - share experience on why these matter]\\n\\n## Suggestions\\n[Nice to have - mentor on best practices]\\n\\n## Strengths\\n[What was done well - reinforce good patterns]\\n\\n## Learning Opportunities\\n[Share knowledge and context that would help the developer grow]"
  }
}

You can drop this into the Agent Hooks section of your workspace, or adapt the when clause to trigger automatically when a new PR is opened.

If you want to go further, you can also add a PR Template Generator hook that turns your local changes and specs into a pre-filled PR body:

{
  "enabled": true,
  "name": "PR Template Generator",
  "description": "Automatically generates PR content when you ask to create a PR",
  "version": "1",
  "when": {
    "type": "userTriggered"
  },
  "then": {
    "type": "askAgent",
    "prompt": "Generate PR content based on current changes:\\n\\n1. Run 'git diff master..HEAD' to see what changed\\n2. Check if there are any spec files in .kiro/specs/ related to these changes\\n3. If specs exist, read the requirements.md, design.md, and tasks.md to understand the feature\\n4. Generate a PR body with:\\n   - ## Purpose of PR: 1-2 sentence summary based on spec requirements or git changes\\n   - ## Changes Made: Bulleted list of actual changes from git diff and completed spec tasks\\n\\n5. If a PR already exists, update it with 'gh pr edit --body'. If not, create one with 'gh pr create --body'\\n\\nMake it concise and accurate."
  }
}