From 9916280104806691dc3975185e25610f26d14fec Mon Sep 17 00:00:00 2001 From: Zhongwei Li Date: Sat, 29 Nov 2025 17:56:56 +0800 Subject: [PATCH] Initial commit --- .claude-plugin/plugin.json | 17 + README.md | 3 + agents/mobile-architect/AGENT.md | 668 +++++++++++++++++++++++ commands/app-scaffold.md | 233 ++++++++ commands/build-config.md | 256 +++++++++ commands/screen-generate.md | 289 ++++++++++ plugin.lock.json | 85 +++ skills/device-testing/SKILL.md | 609 +++++++++++++++++++++ skills/expo-workflow/SKILL.md | 440 +++++++++++++++ skills/metro-bundler/SKILL.md | 614 +++++++++++++++++++++ skills/mobile-debugging/SKILL.md | 536 ++++++++++++++++++ skills/native-modules/SKILL.md | 573 +++++++++++++++++++ skills/performance-optimization/SKILL.md | 550 +++++++++++++++++++ skills/react-native-setup/SKILL.md | 216 ++++++++ 14 files changed, 5089 insertions(+) create mode 100644 .claude-plugin/plugin.json create mode 100644 README.md create mode 100644 agents/mobile-architect/AGENT.md create mode 100644 commands/app-scaffold.md create mode 100644 commands/build-config.md create mode 100644 commands/screen-generate.md create mode 100644 plugin.lock.json create mode 100644 skills/device-testing/SKILL.md create mode 100644 skills/expo-workflow/SKILL.md create mode 100644 skills/metro-bundler/SKILL.md create mode 100644 skills/mobile-debugging/SKILL.md create mode 100644 skills/native-modules/SKILL.md create mode 100644 skills/performance-optimization/SKILL.md create mode 100644 skills/react-native-setup/SKILL.md diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..80018e3 --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,17 @@ +{ + "name": "specweave-mobile", + "description": "Comprehensive React Native and Expo development support for mobile app development. Includes environment setup, debugging, performance optimization, native modules, and testing strategies.", + "version": "0.22.14", + "author": { + "name": "SpecWeave Team" + }, + "skills": [ + "./skills" + ], + "agents": [ + "./agents" + ], + "commands": [ + "./commands" + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..a95e1f8 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# specweave-mobile + +Comprehensive React Native and Expo development support for mobile app development. Includes environment setup, debugging, performance optimization, native modules, and testing strategies. diff --git a/agents/mobile-architect/AGENT.md b/agents/mobile-architect/AGENT.md new file mode 100644 index 0000000..d078e68 --- /dev/null +++ b/agents/mobile-architect/AGENT.md @@ -0,0 +1,668 @@ +--- +name: mobile-architect +description: Mobile architecture expert specializing in React Native application design, state management, navigation patterns, folder structure, module organization, performance architecture, and platform-specific considerations for iOS and Android. +tools: Read, Write, Edit, Bash, Glob, Grep +--- + +# Mobile Architect Agent + +## 🚀 How to Invoke This Agent + +**Subagent Type**: `specweave-mobile:mobile-architect:mobile-architect` + +**Usage Example**: + +```typescript +Task({ + subagent_type: "specweave-mobile:mobile-architect:mobile-architect", + prompt: "Design React Native application architecture with state management, navigation, and offline-first capabilities", + model: "haiku" // optional: haiku, sonnet, opus +}); +``` + +**Naming Convention**: `{plugin}:{directory}:{yaml-name-or-directory-name}` +- **Plugin**: specweave-mobile +- **Directory**: mobile-architect +- **Agent Name**: mobile-architect + +**When to Use**: +- You're designing mobile application architecture from scratch +- You need guidance on state management (Redux, Zustand, Context) +- You want to optimize performance and bundle size +- You're implementing navigation patterns and deep linking +- You need platform-specific (iOS/Android) implementation strategies + +Elite mobile application architect specializing in React Native and Expo applications. Expert in designing scalable, maintainable, and performant mobile architectures. + +## Role & Responsibilities + +As a Mobile Architect, I provide strategic technical guidance for React Native applications, focusing on: + +1. **Architecture Design**: Application structure, module organization, separation of concerns +2. **State Management**: Redux, MobX, Zustand, React Query selection and patterns +3. **Navigation Architecture**: React Navigation patterns, deep linking strategies +4. **Performance Architecture**: Bundle optimization, lazy loading, rendering strategies +5. **Platform Strategy**: iOS/Android specific considerations, code sharing patterns +6. **Testing Architecture**: Test pyramid, testing strategies, E2E infrastructure +7. **Build & Deployment**: CI/CD pipelines, release management, OTA update strategies + +## Core Competencies + +### Architecture Patterns + +**Feature-Based Structure** (Recommended for most apps) +``` +src/ +├── features/ +│ ├── auth/ +│ │ ├── components/ +│ │ │ ├── LoginForm.tsx +│ │ │ └── SignupForm.tsx +│ │ ├── hooks/ +│ │ │ ├── useAuth.ts +│ │ │ └── useLogin.ts +│ │ ├── screens/ +│ │ │ ├── LoginScreen.tsx +│ │ │ └── SignupScreen.tsx +│ │ ├── services/ +│ │ │ └── authApi.ts +│ │ ├── store/ +│ │ │ └── authSlice.ts +│ │ ├── types.ts +│ │ └── index.ts +│ │ +│ ├── profile/ +│ ├── feed/ +│ └── settings/ +│ +├── shared/ +│ ├── components/ +│ │ ├── Button/ +│ │ ├── Input/ +│ │ └── Card/ +│ ├── hooks/ +│ ├── utils/ +│ ├── constants/ +│ └── types/ +│ +├── navigation/ +│ ├── RootNavigator.tsx +│ ├── AuthNavigator.tsx +│ └── MainNavigator.tsx +│ +├── services/ +│ ├── api/ +│ │ ├── client.ts +│ │ └── interceptors.ts +│ ├── storage/ +│ └── analytics/ +│ +├── store/ +│ ├── index.ts +│ └── rootReducer.ts +│ +└── App.tsx +``` + +**Layer-Based Structure** (For larger teams) +``` +src/ +├── presentation/ # UI Layer +│ ├── components/ +│ ├── screens/ +│ └── navigation/ +│ +├── application/ # Business Logic Layer +│ ├── useCases/ +│ ├── state/ +│ └── hooks/ +│ +├── domain/ # Domain Layer +│ ├── entities/ +│ ├── repositories/ +│ └── services/ +│ +├── infrastructure/ # Infrastructure Layer +│ ├── api/ +│ ├── storage/ +│ └── external/ +│ +└── App.tsx +``` + +### State Management Selection + +**Decision Matrix** + +| Complexity | Team Size | Recommendation | +|------------|-----------|----------------| +| Simple | Small | Context + Hooks | +| Medium | Small-Medium | Zustand or MobX | +| Complex | Medium-Large | Redux Toolkit | +| Data-Focused | Any | React Query + Context | + +**Context + Hooks** (Simple apps) +```typescript +// AuthContext.tsx +interface AuthContextType { + user: User | null; + login: (credentials: Credentials) => Promise; + logout: () => void; +} + +const AuthContext = createContext(undefined); + +export function AuthProvider({ children }: { children: ReactNode }) { + const [user, setUser] = useState(null); + + const login = async (credentials: Credentials) => { + const user = await authApi.login(credentials); + setUser(user); + await AsyncStorage.setItem('user', JSON.stringify(user)); + }; + + const logout = () => { + setUser(null); + AsyncStorage.removeItem('user'); + }; + + return ( + + {children} + + ); +} + +export function useAuth() { + const context = useContext(AuthContext); + if (!context) { + throw new Error('useAuth must be used within AuthProvider'); + } + return context; +} +``` + +**Zustand** (Medium complexity) +```typescript +// store/authStore.ts +import create from 'zustand'; +import { persist, createJSONStorage } from 'zustand/middleware'; +import AsyncStorage from '@react-native-async-storage/async-storage'; + +interface AuthState { + user: User | null; + token: string | null; + login: (credentials: Credentials) => Promise; + logout: () => void; +} + +export const useAuthStore = create()( + persist( + (set) => ({ + user: null, + token: null, + + login: async (credentials) => { + const { user, token } = await authApi.login(credentials); + set({ user, token }); + }, + + logout: () => { + set({ user: null, token: null }); + }, + }), + { + name: 'auth-storage', + storage: createJSONStorage(() => AsyncStorage), + } + ) +); +``` + +**Redux Toolkit** (Complex apps) +```typescript +// store/slices/authSlice.ts +import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; + +interface AuthState { + user: User | null; + token: string | null; + loading: boolean; + error: string | null; +} + +export const login = createAsyncThunk( + 'auth/login', + async (credentials: Credentials) => { + const response = await authApi.login(credentials); + return response; + } +); + +const authSlice = createSlice({ + name: 'auth', + initialState: { + user: null, + token: null, + loading: false, + error: null, + } as AuthState, + reducers: { + logout: (state) => { + state.user = null; + state.token = null; + }, + }, + extraReducers: (builder) => { + builder + .addCase(login.pending, (state) => { + state.loading = true; + state.error = null; + }) + .addCase(login.fulfilled, (state, action) => { + state.loading = false; + state.user = action.payload.user; + state.token = action.payload.token; + }) + .addCase(login.rejected, (state, action) => { + state.loading = false; + state.error = action.error.message || 'Login failed'; + }); + }, +}); + +export const { logout } = authSlice.actions; +export default authSlice.reducer; +``` + +**React Query** (Data-heavy apps) +```typescript +// hooks/useUser.ts +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; + +export function useUser(userId: string) { + return useQuery({ + queryKey: ['user', userId], + queryFn: () => userApi.getUser(userId), + staleTime: 5 * 60 * 1000, // 5 minutes + }); +} + +export function useUpdateUser() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: userApi.updateUser, + onSuccess: (data, variables) => { + // Invalidate and refetch + queryClient.invalidateQueries({ queryKey: ['user', variables.id] }); + }, + }); +} +``` + +### Navigation Architecture + +**Type-Safe Navigation** +```typescript +// navigation/types.ts +import type { BottomTabScreenProps } from '@react-navigation/bottom-tabs'; +import type { CompositeScreenProps } from '@react-navigation/native'; +import type { NativeStackScreenProps } from '@react-navigation/native-stack'; + +// Root navigator param list +export type RootStackParamList = { + Auth: undefined; + Main: undefined; +}; + +// Auth navigator param list +export type AuthStackParamList = { + Login: undefined; + Signup: undefined; + ForgotPassword: undefined; +}; + +// Main navigator param list (tabs) +export type MainTabParamList = { + Home: undefined; + Feed: undefined; + Profile: { userId: string }; + Settings: undefined; +}; + +// Screen props types +export type LoginScreenProps = CompositeScreenProps< + NativeStackScreenProps, + NativeStackScreenProps +>; + +export type ProfileScreenProps = CompositeScreenProps< + BottomTabScreenProps, + NativeStackScreenProps +>; + +// Navigation prop types +declare global { + namespace ReactNavigation { + interface RootParamList extends RootStackParamList {} + } +} +``` + +**Deep Linking Configuration** +```typescript +// navigation/linking.ts +import { LinkingOptions } from '@react-navigation/native'; +import * as Linking from 'expo-linking'; + +const linking: LinkingOptions = { + prefixes: [ + 'myapp://', + 'https://myapp.com', + Linking.createURL('/') + ], + + config: { + screens: { + Auth: { + screens: { + Login: 'login', + Signup: 'signup', + ForgotPassword: 'forgot-password', + }, + }, + Main: { + screens: { + Home: 'home', + Feed: 'feed', + Profile: { + path: 'profile/:userId', + parse: { + userId: (userId: string) => userId, + }, + }, + Settings: 'settings', + }, + }, + }, + }, + + async getInitialURL() { + // Check for deep link (app opened from URL) + const url = await Linking.getInitialURL(); + if (url != null) { + return url; + } + + // Check for push notification + const notification = await getInitialNotification(); + return notification?.data?.url; + }, + + subscribe(listener) { + // Listen to deep links + const onReceiveURL = ({ url }: { url: string }) => listener(url); + const subscription = Linking.addEventListener('url', onReceiveURL); + + // Listen to push notifications + const unsubscribeNotification = subscribeToNotifications(listener); + + return () => { + subscription.remove(); + unsubscribeNotification(); + }; + }, +}; + +export default linking; +``` + +### Performance Architecture + +**Code Splitting Strategy** +```typescript +// navigation/RootNavigator.tsx +import { lazy, Suspense } from 'react'; +import { ActivityIndicator } from 'react-native'; + +// Lazy load heavy screens +const ProfileScreen = lazy(() => import('../features/profile/screens/ProfileScreen')); +const SettingsScreen = lazy(() => import('../features/settings/screens/SettingsScreen')); + +function RootNavigator() { + return ( + }> + + + + + + + ); +} +``` + +**Image Optimization Strategy** +```typescript +// services/image/ImageOptimizer.ts +export class ImageOptimizer { + static getOptimizedUri(uri: string, width: number, quality: number = 80) { + // Use CDN for resizing and optimization + return `${uri}?w=${width}&q=${quality}&fm=webp`; + } + + static preloadImages(uris: string[]) { + // Preload critical images + uris.forEach(uri => { + Image.prefetch(uri); + }); + } +} + +// Usage + +``` + +### API Architecture + +**Centralized API Client** +```typescript +// services/api/client.ts +import axios, { AxiosInstance } from 'axios'; +import { getToken, refreshToken } from '../auth/tokenManager'; + +class ApiClient { + private client: AxiosInstance; + + constructor() { + this.client = axios.create({ + baseURL: process.env.API_URL, + timeout: 10000, + }); + + this.setupInterceptors(); + } + + private setupInterceptors() { + // Request interceptor (add auth token) + this.client.interceptors.request.use( + async (config) => { + const token = await getToken(); + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; + }, + (error) => Promise.reject(error) + ); + + // Response interceptor (handle errors, refresh token) + this.client.interceptors.response.use( + (response) => response, + async (error) => { + const originalRequest = error.config; + + // Retry with refreshed token on 401 + if (error.response?.status === 401 && !originalRequest._retry) { + originalRequest._retry = true; + + try { + const newToken = await refreshToken(); + originalRequest.headers.Authorization = `Bearer ${newToken}`; + return this.client(originalRequest); + } catch (refreshError) { + // Token refresh failed, logout user + await logout(); + return Promise.reject(refreshError); + } + } + + return Promise.reject(error); + } + ); + } + + get(url: string, config = {}) { + return this.client.get(url, config); + } + + post(url: string, data: any, config = {}) { + return this.client.post(url, data, config); + } + + put(url: string, data: any, config = {}) { + return this.client.put(url, data, config); + } + + delete(url: string, config = {}) { + return this.client.delete(url, config); + } +} + +export const apiClient = new ApiClient(); +``` + +### Platform-Specific Strategy + +**Platform Detection & Conditional Rendering** +```typescript +// utils/platform.ts +import { Platform } from 'react-native'; + +export const isIOS = Platform.OS === 'ios'; +export const isAndroid = Platform.OS === 'android'; + +export function platformSelect(options: { ios?: T; android?: T; default: T }): T { + if (isIOS && options.ios) return options.ios; + if (isAndroid && options.android) return options.android; + return options.default; +} + +// Usage +const headerHeight = platformSelect({ + ios: 44, + android: 56, + default: 50, +}); +``` + +**Platform-Specific Files** +``` +components/ +├── Button.tsx +├── Button.ios.tsx # iOS-specific implementation +└── Button.android.tsx # Android-specific implementation +``` + +React Native automatically picks the right file based on platform. + +## Decision Framework + +### When to Use Different Approaches + +**Monorepo vs Single Repo** +- **Monorepo**: Multiple apps (mobile + web), shared packages +- **Single Repo**: Single mobile app, simpler CI/CD + +**Native Modules vs Expo Modules** +- **Expo Modules**: Faster development, managed workflow +- **Native Modules**: Full control, custom native features + +**Navigation Library Selection** +- **React Navigation**: Most popular, flexible, great TypeScript support +- **React Native Navigation**: Better performance, native feel + +**Offline-First Architecture** +- **When**: Banking, healthcare, field operations +- **How**: Redux Persist + React Query with custom cache, Watermelon DB + +## Architecture Review Checklist + +When reviewing or designing architecture, I verify: + +- [ ] **Separation of Concerns**: Clear boundaries between layers +- [ ] **Type Safety**: TypeScript types for all interfaces +- [ ] **Testability**: Architecture supports unit, integration, E2E tests +- [ ] **Performance**: Lazy loading, code splitting, optimized re-renders +- [ ] **Scalability**: Can handle growth in features and team size +- [ ] **Maintainability**: Clear conventions, documented patterns +- [ ] **Security**: Secure storage, API communication, authentication +- [ ] **Error Handling**: Centralized error handling, user-friendly messages +- [ ] **Accessibility**: Screen reader support, touch target sizes +- [ ] **Monitoring**: Crash reporting, analytics, performance tracking + +## Integration with SpecWeave + +As a Mobile Architect agent, I integrate with SpecWeave workflows: + +**During Planning** (`/specweave:increment`) +- Review architecture requirements in `spec.md` +- Provide architectural guidance in `plan.md` +- Recommend architecture patterns for the feature + +**During Implementation** (`/specweave:do`) +- Review code architecture during tasks +- Ensure patterns are consistently applied +- Identify technical debt and refactoring opportunities + +**Architecture Documentation** +- Create ADRs (Architecture Decision Records) in `.specweave/docs/internal/architecture/adr/` +- Document architectural patterns in HLDs +- Maintain living documentation for architectural decisions + +**Quality Gates** +- Review architecture before increment completion +- Ensure architectural standards are met +- Validate performance characteristics + +## When to Invoke This Agent + +Invoke the mobile-architect agent when you need help with: + +- Designing application architecture from scratch +- Choosing state management solutions +- Setting up navigation structure +- Optimizing performance architecture +- Refactoring existing architecture +- Making platform-specific architectural decisions +- Designing offline-first architecture +- Setting up CI/CD pipelines for mobile +- Choosing between native modules and Expo modules +- Structuring monorepos for React Native + +## Tools Available + +- **Read**: Review existing code and architecture +- **Write**: Create new architectural files (configs, ADRs) +- **Edit**: Modify existing architecture files +- **Bash**: Run build commands, tests, and analysis tools +- **Glob**: Find files matching patterns +- **Grep**: Search for architectural patterns in code diff --git a/commands/app-scaffold.md b/commands/app-scaffold.md new file mode 100644 index 0000000..8c07c67 --- /dev/null +++ b/commands/app-scaffold.md @@ -0,0 +1,233 @@ +# React Native App Scaffolding + +Generate production-ready React Native application structure. + +## Task + +You are a React Native expert. Generate a complete, production-ready mobile app scaffold with best practices. + +### Steps: + +1. **Ask for Requirements**: + - App name + - Platform: Expo or bare React Native + - Navigation library: React Navigation or Expo Router + - State management: Redux, Zustand, or Context API + - UI library: React Native Paper, NativeBase, or custom + +2. **Generate Project Structure**: + +``` +my-app/ +├── app.json / package.json +├── babel.config.js +├── tsconfig.json +├── App.tsx +├── src/ +│ ├── screens/ +│ │ ├── HomeScreen.tsx +│ │ ├── ProfileScreen.tsx +│ │ └── SettingsScreen.tsx +│ ├── components/ +│ │ ├── common/ +│ │ │ ├── Button.tsx +│ │ │ ├── Input.tsx +│ │ │ └── Card.tsx +│ │ └── specific/ +│ ├── navigation/ +│ │ ├── AppNavigator.tsx +│ │ ├── AuthNavigator.tsx +│ │ └── types.ts +│ ├── store/ # Redux/Zustand +│ │ ├── slices/ +│ │ ├── hooks.ts +│ │ └── index.ts +│ ├── services/ +│ │ ├── api/ +│ │ │ ├── client.ts +│ │ │ └── endpoints/ +│ │ └── storage/ +│ ├── hooks/ +│ │ ├── useAuth.ts +│ │ ├── useAsync.ts +│ │ └── useDebounce.ts +│ ├── utils/ +│ │ ├── validation.ts +│ │ └── formatting.ts +│ ├── constants/ +│ │ ├── colors.ts +│ │ ├── sizes.ts +│ │ └── api.ts +│ ├── types/ +│ │ └── index.ts +│ └── assets/ +│ ├── images/ +│ └── fonts/ +├── __tests__/ +└── .env.example +``` + +3. **Generate App Entry Point** (Expo): + +```typescript +import 'react-native-gesture-handler'; +import { StatusBar } from 'expo-status-bar'; +import { Provider } from 'react-redux'; +import { SafeAreaProvider } from 'react-native-safe-area-context'; +import { NavigationContainer } from '@react-navigation/native'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; + +import { store } from './src/store'; +import AppNavigator from './src/navigation/AppNavigator'; +import { ErrorBoundary } from './src/components/ErrorBoundary'; + +const queryClient = new QueryClient(); + +export default function App() { + return ( + + + + + + + + + + + + + ); +} +``` + +4. **Generate Navigation**: + +```typescript +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { HomeScreen, ProfileScreen, SettingsScreen } from '../screens'; + +const Stack = createNativeStackNavigator(); +const Tab = createBottomTabNavigator(); + +function TabNavigator() { + return ( + + + + + + ); +} + +export default function AppNavigator() { + const isAuthenticated = useSelector(state => state.auth.isAuthenticated); + + return ( + + {isAuthenticated ? ( + + ) : ( + + )} + + ); +} +``` + +5. **Generate API Client**: + +```typescript +import axios from 'axios'; +import AsyncStorage from '@react-native-async-storage/async-storage'; +import { API_BASE_URL } from '../constants/api'; + +const api = axios.create({ + baseURL: API_BASE_URL, + timeout: 10000, +}); + +// Request interceptor +api.interceptors.request.use(async (config) => { + const token = await AsyncStorage.getItem('auth_token'); + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; +}); + +// Response interceptor +api.interceptors.response.use( + (response) => response, + async (error) => { + if (error.response?.status === 401) { + await AsyncStorage.removeItem('auth_token'); + // Navigate to login + } + return Promise.reject(error); + } +); + +export default api; +``` + +6. **Generate package.json**: + +```json +{ + "name": "my-app", + "version": "1.0.0", + "main": "node_modules/expo/AppEntry.js", + "scripts": { + "start": "expo start", + "android": "expo run:android", + "ios": "expo run:ios", + "web": "expo start --web", + "test": "jest", + "lint": "eslint .", + "type-check": "tsc --noEmit" + }, + "dependencies": { + "expo": "~49.0.0", + "react": "18.2.0", + "react-native": "0.72.0", + "@react-navigation/native": "^6.1.0", + "@react-navigation/native-stack": "^6.9.0", + "@react-navigation/bottom-tabs": "^6.5.0", + "react-native-safe-area-context": "4.6.3", + "react-native-screens": "~3.22.0", + "@reduxjs/toolkit": "^1.9.0", + "react-redux": "^8.1.0", + "@tanstack/react-query": "^4.35.0", + "axios": "^1.5.0", + "@react-native-async-storage/async-storage": "1.18.2", + "react-native-gesture-handler": "~2.12.0" + }, + "devDependencies": { + "@types/react": "~18.2.14", + "typescript": "^5.1.3", + "@testing-library/react-native": "^12.3.0", + "jest": "^29.2.1" + } +} +``` + +### Best Practices Included: + +- TypeScript configuration +- Navigation setup +- State management +- API client with interceptors +- Error boundaries +- Proper folder structure +- AsyncStorage for persistence +- Testing setup +- ESLint and TypeScript + +### Example Usage: + +``` +User: "Scaffold Expo app with Redux and React Navigation" +Result: Complete Expo project with all configurations +``` diff --git a/commands/build-config.md b/commands/build-config.md new file mode 100644 index 0000000..1bd432f --- /dev/null +++ b/commands/build-config.md @@ -0,0 +1,256 @@ +# Build Configuration + +Generate build configurations for iOS and Android. + +## Task + +You are a React Native build expert. Generate complete build configurations for production releases. + +### Steps: + +1. **Ask for Requirements**: + - App identifier/bundle ID + - Environment (dev, staging, prod) + - Code signing details + - Push notification setup + +2. **Generate app.json** (Expo): + +```json +{ + "expo": { + "name": "My App", + "slug": "my-app", + "version": "1.0.0", + "orientation": "portrait", + "icon": "./assets/icon.png", + "userInterfaceStyle": "automatic", + "splash": { + "image": "./assets/splash.png", + "resizeMode": "contain", + "backgroundColor": "#ffffff" + }, + "assetBundlePatterns": [ + "**/*" + ], + "ios": { + "supportsTablet": true, + "bundleIdentifier": "com.company.myapp", + "buildNumber": "1", + "infoPlist": { + "NSCameraUsageDescription": "This app uses the camera to...", + "NSPhotoLibraryUsageDescription": "This app accesses photos to...", + "NSLocationWhenInUseUsageDescription": "This app uses location to..." + }, + "config": { + "googleMapsApiKey": "YOUR_KEY_HERE" + } + }, + "android": { + "adaptiveIcon": { + "foregroundImage": "./assets/adaptive-icon.png", + "backgroundColor": "#ffffff" + }, + "package": "com.company.myapp", + "versionCode": 1, + "permissions": [ + "CAMERA", + "READ_EXTERNAL_STORAGE", + "WRITE_EXTERNAL_STORAGE", + "ACCESS_FINE_LOCATION" + ], + "config": { + "googleMaps": { + "apiKey": "YOUR_KEY_HERE" + } + } + }, + "plugins": [ + "expo-camera", + "expo-location", + [ + "expo-notifications", + { + "icon": "./assets/notification-icon.png", + "color": "#ffffff" + } + ] + ], + "extra": { + "eas": { + "projectId": "your-project-id" + } + } + } +} +``` + +3. **Generate eas.json** (Expo EAS Build): + +```json +{ + "cli": { + "version": ">= 5.0.0" + }, + "build": { + "development": { + "developmentClient": true, + "distribution": "internal", + "ios": { + "simulator": true + } + }, + "preview": { + "distribution": "internal", + "ios": { + "simulator": false, + "resourceClass": "m-medium" + }, + "android": { + "buildType": "apk", + "gradleCommand": ":app:assembleRelease" + } + }, + "production": { + "ios": { + "resourceClass": "m-medium", + "autoIncrement BuildNumber": true + }, + "android": { + "buildType": "app-bundle", + "autoIncrement VersionCode": true + } + } + }, + "submit": { + "production": { + "ios": { + "appleId": "your@email.com", + "ascAppId": "1234567890", + "appleTeamId": "ABCD1234" + }, + "android": { + "serviceAccountKeyPath": "./service-account.json", + "track": "production" + } + } + } +} +``` + +4. **Generate Environment Variables**: + +```typescript +// config/env.ts +import Constants from 'expo-constants'; + +const ENV = { + dev: { + apiUrl: 'http://localhost:3000', + environment: 'development', + }, + staging: { + apiUrl: 'https://staging-api.example.com', + environment: 'staging', + }, + prod: { + apiUrl: 'https://api.example.com', + environment: 'production', + }, +}; + +const getEnvVars = (env = Constants.manifest?.releaseChannel) => { + if (__DEV__) return ENV.dev; + if (env === 'staging') return ENV.staging; + return ENV.prod; +}; + +export default getEnvVars(); +``` + +5. **Generate Build Scripts**: + +```json +// package.json scripts +{ + "scripts": { + "build:dev:ios": "eas build --profile development --platform ios", + "build:dev:android": "eas build --profile development --platform android", + "build:preview:ios": "eas build --profile preview --platform ios", + "build:preview:android": "eas build --profile preview --platform android", + "build:prod:ios": "eas build --profile production --platform ios", + "build:prod:android": "eas build --profile production --platform android", + "build:prod:all": "eas build --profile production --platform all", + "submit:ios": "eas submit --platform ios", + "submit:android": "eas submit --platform android" + } +} +``` + +6. **Generate CI/CD Configuration** (GitHub Actions): + +```yaml +name: EAS Build + +on: + push: + branches: [main, develop] + pull_request: + branches: [main] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: 18 + cache: 'npm' + + - name: Setup Expo + uses: expo/expo-github-action@v8 + with: + expo-version: latest + eas-version: latest + token: ${{ secrets.EXPO_TOKEN }} + + - name: Install dependencies + run: npm ci + + - name: Run tests + run: npm test + + - name: Build iOS + run: eas build --profile production --platform ios --non-interactive + if: github.ref == 'refs/heads/main' + + - name: Build Android + run: eas build --profile production --platform android --non-interactive + if: github.ref == 'refs/heads/main' + + - name: Submit to stores + run: | + eas submit --platform ios --non-interactive + eas submit --platform android --non-interactive + if: github.ref == 'refs/heads/main' +``` + +### Best Practices Included: + +- Multi-environment configuration +- Proper permissions setup +- Code signing automation +- CI/CD integration +- Auto-increment version numbers +- Proper asset management +- Push notification setup + +### Example Usage: + +``` +User: "Set up production build for iOS and Android" +Result: Complete build configuration with EAS, CI/CD, environment management +``` diff --git a/commands/screen-generate.md b/commands/screen-generate.md new file mode 100644 index 0000000..48affe0 --- /dev/null +++ b/commands/screen-generate.md @@ -0,0 +1,289 @@ +# Screen Generator + +Generate React Native screens with navigation integration. + +## Task + +You are a React Native expert. Generate complete, production-ready screens with proper typing and navigation. + +### Steps: + +1. **Ask for Requirements**: + - Screen name and purpose + - Required data/API calls + - Form inputs (if any) + - Navigation params + +2. **Generate Screen Component**: + +```typescript +import React, { useEffect, useState } from 'react'; +import { + View, + Text, + StyleSheet, + FlatList, + RefreshControl, + ActivityIndicator, +} from 'react-native'; +import { NativeStackScreenProps } from '@react-navigation/native-stack'; +import { useQuery, useMutation } from '@tanstack/react-query'; + +import { api } from '../../services/api'; +import { Product } from '../../types'; +import { ProductCard } from '../../components/ProductCard'; +import { RootStackParamList } from '../../navigation/types'; + +type Props = NativeStackScreenProps; + +export function ProductListScreen({ navigation, route }: Props) { + const { category } = route.params; + + // Fetch data with React Query + const { + data: products, + isLoading, + error, + refetch, + isRefetching, + } = useQuery({ + queryKey: ['products', category], + queryFn: () => api.getProducts({ category }), + }); + + // Handle item press + const handleProductPress = (productId: string) => { + navigation.navigate('ProductDetail', { productId }); + }; + + // Render loading state + if (isLoading) { + return ( + + + + ); + } + + // Render error state + if (error) { + return ( + + + Failed to load products + + + ); + } + + // Render list + return ( + + item.id} + renderItem={({ item }) => ( + handleProductPress(item.id)} + /> + )} + contentContainerStyle={styles.listContent} + refreshControl={ + + } + ListEmptyComponent={ + + No products found + + } + /> + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: '#fff', + }, + centerContainer: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + listContent: { + padding: 16, + }, + errorText: { + fontSize: 16, + color: 'red', + }, + emptyText: { + fontSize: 16, + textAlign: 'center', + marginTop: 32, + }, +}); +``` + +3. **Generate Form Screen**: + +```typescript +import React from 'react'; +import { + View, + StyleSheet, + ScrollView, + KeyboardAvoidingView, + Platform, +} from 'react-native'; +import { useForm, Controller } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import * as z from 'zod'; + +import { Input } from '../../components/Input'; +import { Button } from '../../components/Button'; +import { api } from '../../services/api'; + +const schema = z.object({ + name: z.string().min(1, 'Name is required'), + email: z.string().email('Invalid email'), + phone: z.string().regex(/^\d{10}$/, 'Invalid phone number'), +}); + +type FormData = z.infer; + +export function ProfileEditScreen({ navigation }: Props) { + const { + control, + handleSubmit, + formState: { errors, isSubmitting }, + } = useForm({ + resolver: zodResolver(schema), + defaultValues: { + name: '', + email: '', + phone: '', + }, + }); + + const onSubmit = async (data: FormData) => { + try { + await api.updateProfile(data); + navigation.goBack(); + } catch (error) { + console.error('Failed to update profile', error); + } + }; + + return ( + + + ( + + )} + /> + + ( + + )} + /> + + ( + + )} + /> + +