codecruise 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +111 -0
- package/bin/codecruise.js +68 -0
- package/config/CLAUDE.md +107 -0
- package/config/agents/analyst.md +48 -0
- package/config/agents/architect-reviewer.md +161 -0
- package/config/agents/architect.md +119 -0
- package/config/agents/critic.md +63 -0
- package/config/agents/developer.md +96 -0
- package/config/agents/devops.md +81 -0
- package/config/agents/orchestrator.md +91 -0
- package/config/agents/planner.md +139 -0
- package/config/agents/retro.md +52 -0
- package/config/agents/reviewer.md +101 -0
- package/config/agents/security-reviewer.md +57 -0
- package/config/agents/stack/expo/AGENT.md +473 -0
- package/config/agents/stack/expo/rules/critical.md +427 -0
- package/config/agents/stack/expo/rules/native.md +455 -0
- package/config/agents/stack/expo/rules/navigation.md +445 -0
- package/config/agents/stack/expo/rules/performance.md +415 -0
- package/config/agents/stack/fastify/AGENT.md +397 -0
- package/config/agents/stack/fastify/rules/api-design.md +283 -0
- package/config/agents/stack/fastify/rules/critical.md +232 -0
- package/config/agents/stack/fastify/rules/queues.md +303 -0
- package/config/agents/stack/fastify/rules/security.md +384 -0
- package/config/agents/stack/index.yaml +48 -0
- package/config/agents/stack/nextjs/AGENT.md +421 -0
- package/config/agents/stack/nextjs/rules/components.md +413 -0
- package/config/agents/stack/nextjs/rules/critical.md +391 -0
- package/config/agents/stack/nextjs/rules/performance.md +403 -0
- package/config/agents/stack/nextjs/rules/styling.md +334 -0
- package/config/agents/stack/shared-ts/AGENT.md +384 -0
- package/config/agents/stack/shared-ts/rules/critical.md +315 -0
- package/config/agents/stack/shared-ts/rules/patterns.md +384 -0
- package/config/agents/stack/shared-ts/rules/zod.md +427 -0
- package/config/agents/tester.md +79 -0
- package/config/commands/architect-discuss.md +366 -0
- package/config/commands/architect-list.md +160 -0
- package/config/commands/architect-review.md +111 -0
- package/config/commands/architect.md +118 -0
- package/config/commands/compact.md +118 -0
- package/config/commands/companion.md +279 -0
- package/config/commands/dashboard.md +152 -0
- package/config/commands/doctor.md +227 -0
- package/config/commands/dogfood-report.md +101 -0
- package/config/commands/flags/run-autonomous.md +110 -0
- package/config/commands/flags/run-pause.md +80 -0
- package/config/commands/ingest.md +173 -0
- package/config/commands/init.md +128 -0
- package/config/commands/metrics.md +87 -0
- package/config/commands/parallel.md +320 -0
- package/config/commands/pause.md +55 -0
- package/config/commands/plan-review.md +130 -0
- package/config/commands/plan.md +216 -0
- package/config/commands/production-check.md +308 -0
- package/config/commands/refine.md +323 -0
- package/config/commands/resume.md +72 -0
- package/config/commands/retro.md +121 -0
- package/config/commands/retry.md +75 -0
- package/config/commands/role.md +310 -0
- package/config/commands/run.md +417 -0
- package/config/commands/scope.md +85 -0
- package/config/commands/setup-permissions.md +104 -0
- package/config/commands/skip.md +75 -0
- package/config/commands/spec-forge.md +213 -0
- package/config/commands/spec-help.md +194 -0
- package/config/commands/spec-patch.md +342 -0
- package/config/commands/spec-resolve.md +110 -0
- package/config/commands/spec-review.md +153 -0
- package/config/commands/status.md +114 -0
- package/config/commands/sync.md +131 -0
- package/config/commands/task.md +138 -0
- package/config/commands/verify.md +124 -0
- package/config/hooks/README.md +632 -0
- package/config/hooks/activity-log.sh +187 -0
- package/config/hooks/anti-rationalize.sh +52 -0
- package/config/hooks/capture-verification.sh +112 -0
- package/config/hooks/collect-metrics.sh +135 -0
- package/config/hooks/enforce-file-scope.sh +75 -0
- package/config/hooks/enforce-state-machine.sh +161 -0
- package/config/hooks/enforce-tdd.sh +180 -0
- package/config/hooks/format.sh +40 -0
- package/config/hooks/lib/activity-helpers.sh +162 -0
- package/config/hooks/lib/read-settings.sh +71 -0
- package/config/hooks/load-context-skills.sh +95 -0
- package/config/hooks/notify.sh +81 -0
- package/config/hooks/pre-commit.sample +35 -0
- package/config/hooks/protect-files.sh +63 -0
- package/config/hooks/track-agents.sh +41 -0
- package/config/hooks/track-commands.sh +37 -0
- package/config/hooks/track-enforcement.sh +44 -0
- package/config/hooks/track-ooda.sh +77 -0
- package/config/hooks/validate-commit-msg.sh +35 -0
- package/config/hooks/validate-plan.sh +213 -0
- package/config/hooks/verify-criteria.sh +46 -0
- package/config/hooks/verify-todo-completion.sh +140 -0
- package/config/rules/comments.md +25 -0
- package/config/rules/decision-rules.md +308 -0
- package/config/rules/hygiene.md +247 -0
- package/config/rules/pattern-detection.md +372 -0
- package/config/rules/profiles.md +193 -0
- package/config/rules/recovery.md +83 -0
- package/config/rules/scope-detection.md +213 -0
- package/config/rules/standards.md +127 -0
- package/config/rules/workflow.md +121 -0
- package/config/schemas.md +767 -0
- package/config/settings.json +195 -0
- package/config/skills/backend/SKILL.md +734 -0
- package/config/skills/database/SKILL.md +426 -0
- package/config/skills/frontend/SKILL.md +434 -0
- package/config/skills/git/SKILL.md +396 -0
- package/config/skills/index.yaml +36 -0
- package/config/skills/observability/SKILL.md +430 -0
- package/config/skills/package-dev/SKILL.md +498 -0
- package/config/skills/performance/SKILL.md +378 -0
- package/config/skills/resilience/SKILL.md +573 -0
- package/config/skills/testing/SKILL.md +398 -0
- package/config/skills/testing-patterns/SKILL.md +276 -0
- package/config/skills/typescript/SKILL.md +152 -0
- package/config/templates/CLAUDE.md +70 -0
- package/config/templates/README.md +117 -0
- package/config/templates/steering/adr-template.md +102 -0
- package/config/templates/steering/product.md +60 -0
- package/config/templates/steering/rfc-template.md +159 -0
- package/config/templates/steering/structure.md +146 -0
- package/config/templates/steering/tech.md +85 -0
- package/package.json +40 -0
- package/src/install.js +163 -0
- package/src/report.js +310 -0
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: expo
|
|
3
|
+
description: React Native + Expo SDK, navigation, native modules. Use for mobile app development.
|
|
4
|
+
tools: Read, Write, Edit, Bash, Glob, Grep
|
|
5
|
+
model: sonnet
|
|
6
|
+
dependencies: [shared-ts]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Expo Stack Agent
|
|
10
|
+
|
|
11
|
+
You are a mobile specialist for React Native applications built with Expo.
|
|
12
|
+
|
|
13
|
+
## Stack Overview
|
|
14
|
+
|
|
15
|
+
| Component | Purpose | Version |
|
|
16
|
+
|-----------|---------|---------|
|
|
17
|
+
| Expo | Development platform | SDK 50+ |
|
|
18
|
+
| React Native | Mobile framework | 0.73+ |
|
|
19
|
+
| Expo Router | File-based routing | 3.x |
|
|
20
|
+
| NativeWind | Tailwind for RN | 4.x |
|
|
21
|
+
| React Query | Data fetching | 5.x |
|
|
22
|
+
| Zustand | State management | 4.x |
|
|
23
|
+
|
|
24
|
+
## Project Structure
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
apps/mobile/
|
|
28
|
+
├── app/ # Expo Router pages
|
|
29
|
+
│ ├── _layout.tsx # Root layout
|
|
30
|
+
│ ├── index.tsx # Home screen
|
|
31
|
+
│ ├── (tabs)/ # Tab navigator
|
|
32
|
+
│ │ ├── _layout.tsx
|
|
33
|
+
│ │ ├── home.tsx
|
|
34
|
+
│ │ └── profile.tsx
|
|
35
|
+
│ ├── (auth)/ # Auth stack
|
|
36
|
+
│ │ ├── _layout.tsx
|
|
37
|
+
│ │ ├── login.tsx
|
|
38
|
+
│ │ └── register.tsx
|
|
39
|
+
│ └── [id].tsx # Dynamic route
|
|
40
|
+
├── components/
|
|
41
|
+
│ ├── ui/ # Base components
|
|
42
|
+
│ └── features/ # Feature components
|
|
43
|
+
├── lib/
|
|
44
|
+
│ ├── api.ts
|
|
45
|
+
│ └── storage.ts
|
|
46
|
+
├── hooks/
|
|
47
|
+
├── stores/
|
|
48
|
+
├── app.json
|
|
49
|
+
├── tailwind.config.js
|
|
50
|
+
└── package.json
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Core Patterns
|
|
56
|
+
|
|
57
|
+
### Expo Router Setup
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
// app/_layout.tsx
|
|
61
|
+
import { Stack } from 'expo-router';
|
|
62
|
+
import { QueryClientProvider } from '@tanstack/react-query';
|
|
63
|
+
import { queryClient } from '@/lib/query';
|
|
64
|
+
|
|
65
|
+
export default function RootLayout() {
|
|
66
|
+
return (
|
|
67
|
+
<QueryClientProvider client={queryClient}>
|
|
68
|
+
<Stack>
|
|
69
|
+
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
|
70
|
+
<Stack.Screen name="(auth)" options={{ headerShown: false }} />
|
|
71
|
+
<Stack.Screen name="modal" options={{ presentation: 'modal' }} />
|
|
72
|
+
</Stack>
|
|
73
|
+
</QueryClientProvider>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Tab Navigation
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
// app/(tabs)/_layout.tsx
|
|
82
|
+
import { Tabs } from 'expo-router';
|
|
83
|
+
import { Home, User, Settings } from 'lucide-react-native';
|
|
84
|
+
|
|
85
|
+
export default function TabLayout() {
|
|
86
|
+
return (
|
|
87
|
+
<Tabs screenOptions={{
|
|
88
|
+
tabBarActiveTintColor: '#007AFF',
|
|
89
|
+
headerShown: true,
|
|
90
|
+
}}>
|
|
91
|
+
<Tabs.Screen
|
|
92
|
+
name="home"
|
|
93
|
+
options={{
|
|
94
|
+
title: 'Home',
|
|
95
|
+
tabBarIcon: ({ color, size }) => <Home size={size} color={color} />,
|
|
96
|
+
}}
|
|
97
|
+
/>
|
|
98
|
+
<Tabs.Screen
|
|
99
|
+
name="profile"
|
|
100
|
+
options={{
|
|
101
|
+
title: 'Profile',
|
|
102
|
+
tabBarIcon: ({ color, size }) => <User size={size} color={color} />,
|
|
103
|
+
}}
|
|
104
|
+
/>
|
|
105
|
+
</Tabs>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Screen Component
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
// app/(tabs)/home.tsx
|
|
114
|
+
import { View, Text, FlatList, RefreshControl } from 'react-native';
|
|
115
|
+
import { useQuery } from '@tanstack/react-query';
|
|
116
|
+
import { fetchPosts } from '@/lib/api';
|
|
117
|
+
|
|
118
|
+
export default function HomeScreen() {
|
|
119
|
+
const { data, isLoading, refetch, isRefetching } = useQuery({
|
|
120
|
+
queryKey: ['posts'],
|
|
121
|
+
queryFn: fetchPosts,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
if (isLoading) {
|
|
125
|
+
return <LoadingScreen />;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<FlatList
|
|
130
|
+
data={data}
|
|
131
|
+
keyExtractor={(item) => item.id}
|
|
132
|
+
renderItem={({ item }) => <PostCard post={item} />}
|
|
133
|
+
refreshControl={
|
|
134
|
+
<RefreshControl refreshing={isRefetching} onRefresh={refetch} />
|
|
135
|
+
}
|
|
136
|
+
contentContainerStyle={{ padding: 16, gap: 12 }}
|
|
137
|
+
/>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## NativeWind Styling
|
|
145
|
+
|
|
146
|
+
### Setup
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
// tailwind.config.js
|
|
150
|
+
module.exports = {
|
|
151
|
+
content: ['./app/**/*.{js,tsx}', './components/**/*.{js,tsx}'],
|
|
152
|
+
presets: [require('nativewind/preset')],
|
|
153
|
+
theme: {
|
|
154
|
+
extend: {},
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
// global.css
|
|
159
|
+
@tailwind base;
|
|
160
|
+
@tailwind components;
|
|
161
|
+
@tailwind utilities;
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Component Styling
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
import { View, Text, Pressable } from 'react-native';
|
|
168
|
+
|
|
169
|
+
function Card({ title, onPress }) {
|
|
170
|
+
return (
|
|
171
|
+
<Pressable
|
|
172
|
+
onPress={onPress}
|
|
173
|
+
className="bg-white dark:bg-slate-800 rounded-xl p-4 shadow-sm active:opacity-80"
|
|
174
|
+
>
|
|
175
|
+
<Text className="text-lg font-semibold text-slate-900 dark:text-white">
|
|
176
|
+
{title}
|
|
177
|
+
</Text>
|
|
178
|
+
</Pressable>
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Platform-Specific Styles
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
import { Platform } from 'react-native';
|
|
187
|
+
|
|
188
|
+
<View className={`
|
|
189
|
+
p-4 rounded-lg
|
|
190
|
+
${Platform.OS === 'ios' ? 'shadow-sm' : 'elevation-2'}
|
|
191
|
+
`}>
|
|
192
|
+
|
|
193
|
+
// Or use Platform.select
|
|
194
|
+
const shadowStyle = Platform.select({
|
|
195
|
+
ios: 'shadow-md',
|
|
196
|
+
android: 'elevation-4',
|
|
197
|
+
});
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Navigation Patterns
|
|
203
|
+
|
|
204
|
+
### Type-Safe Navigation
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
// types/navigation.ts
|
|
208
|
+
import { Href } from 'expo-router';
|
|
209
|
+
|
|
210
|
+
export const routes = {
|
|
211
|
+
home: '/(tabs)/home' as Href,
|
|
212
|
+
profile: '/(tabs)/profile' as Href,
|
|
213
|
+
userDetail: (id: string) => `/users/${id}` as Href,
|
|
214
|
+
settings: '/settings' as Href,
|
|
215
|
+
} as const;
|
|
216
|
+
|
|
217
|
+
// Usage
|
|
218
|
+
import { router } from 'expo-router';
|
|
219
|
+
import { routes } from '@/types/navigation';
|
|
220
|
+
|
|
221
|
+
router.push(routes.userDetail(userId));
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Navigation with Params
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
// app/users/[id].tsx
|
|
228
|
+
import { useLocalSearchParams } from 'expo-router';
|
|
229
|
+
|
|
230
|
+
export default function UserDetailScreen() {
|
|
231
|
+
const { id } = useLocalSearchParams<{ id: string }>();
|
|
232
|
+
|
|
233
|
+
const { data: user } = useQuery({
|
|
234
|
+
queryKey: ['user', id],
|
|
235
|
+
queryFn: () => fetchUser(id),
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// ...
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Navigate with params
|
|
242
|
+
router.push({ pathname: '/users/[id]', params: { id: '123' } });
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Protected Routes
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
// app/(auth)/_layout.tsx
|
|
249
|
+
import { Redirect, Stack } from 'expo-router';
|
|
250
|
+
import { useAuth } from '@/hooks/use-auth';
|
|
251
|
+
|
|
252
|
+
export default function AuthLayout() {
|
|
253
|
+
const { isAuthenticated, isLoading } = useAuth();
|
|
254
|
+
|
|
255
|
+
if (isLoading) {
|
|
256
|
+
return <SplashScreen />;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (isAuthenticated) {
|
|
260
|
+
return <Redirect href="/(tabs)/home" />;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return <Stack screenOptions={{ headerShown: false }} />;
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## State Management
|
|
270
|
+
|
|
271
|
+
### Zustand Store
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
// stores/auth.ts
|
|
275
|
+
import { create } from 'zustand';
|
|
276
|
+
import { persist, createJSONStorage } from 'zustand/middleware';
|
|
277
|
+
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
278
|
+
|
|
279
|
+
interface AuthState {
|
|
280
|
+
user: User | null;
|
|
281
|
+
token: string | null;
|
|
282
|
+
setAuth: (user: User, token: string) => void;
|
|
283
|
+
logout: () => void;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export const useAuthStore = create<AuthState>()(
|
|
287
|
+
persist(
|
|
288
|
+
(set) => ({
|
|
289
|
+
user: null,
|
|
290
|
+
token: null,
|
|
291
|
+
setAuth: (user, token) => set({ user, token }),
|
|
292
|
+
logout: () => set({ user: null, token: null }),
|
|
293
|
+
}),
|
|
294
|
+
{
|
|
295
|
+
name: 'auth-storage',
|
|
296
|
+
storage: createJSONStorage(() => AsyncStorage),
|
|
297
|
+
}
|
|
298
|
+
)
|
|
299
|
+
);
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### React Query Setup
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
// lib/query.ts
|
|
306
|
+
import { QueryClient } from '@tanstack/react-query';
|
|
307
|
+
|
|
308
|
+
export const queryClient = new QueryClient({
|
|
309
|
+
defaultOptions: {
|
|
310
|
+
queries: {
|
|
311
|
+
staleTime: 1000 * 60 * 5, // 5 minutes
|
|
312
|
+
gcTime: 1000 * 60 * 30, // 30 minutes
|
|
313
|
+
retry: 2,
|
|
314
|
+
refetchOnWindowFocus: false, // Not applicable in RN
|
|
315
|
+
},
|
|
316
|
+
},
|
|
317
|
+
});
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
## Native Features
|
|
323
|
+
|
|
324
|
+
### Secure Storage
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
import * as SecureStore from 'expo-secure-store';
|
|
328
|
+
|
|
329
|
+
export const secureStorage = {
|
|
330
|
+
async getItem(key: string) {
|
|
331
|
+
return SecureStore.getItemAsync(key);
|
|
332
|
+
},
|
|
333
|
+
async setItem(key: string, value: string) {
|
|
334
|
+
return SecureStore.setItemAsync(key, value);
|
|
335
|
+
},
|
|
336
|
+
async removeItem(key: string) {
|
|
337
|
+
return SecureStore.deleteItemAsync(key);
|
|
338
|
+
},
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
// For tokens
|
|
342
|
+
await secureStorage.setItem('auth_token', token);
|
|
343
|
+
const token = await secureStorage.getItem('auth_token');
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### Haptics
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
import * as Haptics from 'expo-haptics';
|
|
350
|
+
|
|
351
|
+
// Light feedback for selections
|
|
352
|
+
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
|
|
353
|
+
|
|
354
|
+
// Success feedback
|
|
355
|
+
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
|
|
356
|
+
|
|
357
|
+
// Error feedback
|
|
358
|
+
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error);
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### Push Notifications
|
|
362
|
+
|
|
363
|
+
```typescript
|
|
364
|
+
import * as Notifications from 'expo-notifications';
|
|
365
|
+
import { Platform } from 'react-native';
|
|
366
|
+
|
|
367
|
+
export async function registerForPushNotifications() {
|
|
368
|
+
const { status: existingStatus } = await Notifications.getPermissionsAsync();
|
|
369
|
+
let finalStatus = existingStatus;
|
|
370
|
+
|
|
371
|
+
if (existingStatus !== 'granted') {
|
|
372
|
+
const { status } = await Notifications.requestPermissionsAsync();
|
|
373
|
+
finalStatus = status;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (finalStatus !== 'granted') {
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const token = await Notifications.getExpoPushTokenAsync({
|
|
381
|
+
projectId: 'your-project-id',
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
if (Platform.OS === 'android') {
|
|
385
|
+
Notifications.setNotificationChannelAsync('default', {
|
|
386
|
+
name: 'default',
|
|
387
|
+
importance: Notifications.AndroidImportance.MAX,
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return token.data;
|
|
392
|
+
}
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
## Performance Patterns
|
|
398
|
+
|
|
399
|
+
### Optimized Lists
|
|
400
|
+
|
|
401
|
+
```typescript
|
|
402
|
+
import { FlashList } from '@shopify/flash-list';
|
|
403
|
+
|
|
404
|
+
function UserList({ users }) {
|
|
405
|
+
return (
|
|
406
|
+
<FlashList
|
|
407
|
+
data={users}
|
|
408
|
+
keyExtractor={(item) => item.id}
|
|
409
|
+
renderItem={({ item }) => <UserCard user={item} />}
|
|
410
|
+
estimatedItemSize={80}
|
|
411
|
+
contentContainerStyle={{ padding: 16 }}
|
|
412
|
+
/>
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### Memoization
|
|
418
|
+
|
|
419
|
+
```typescript
|
|
420
|
+
import { memo, useCallback } from 'react';
|
|
421
|
+
|
|
422
|
+
const UserCard = memo(function UserCard({ user, onPress }) {
|
|
423
|
+
return (
|
|
424
|
+
<Pressable onPress={() => onPress(user.id)}>
|
|
425
|
+
<Text>{user.name}</Text>
|
|
426
|
+
</Pressable>
|
|
427
|
+
);
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
function UserList({ users }) {
|
|
431
|
+
const handlePress = useCallback((id: string) => {
|
|
432
|
+
router.push(`/users/${id}`);
|
|
433
|
+
}, []);
|
|
434
|
+
|
|
435
|
+
return (
|
|
436
|
+
<FlashList
|
|
437
|
+
data={users}
|
|
438
|
+
renderItem={({ item }) => (
|
|
439
|
+
<UserCard user={item} onPress={handlePress} />
|
|
440
|
+
)}
|
|
441
|
+
/>
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
---
|
|
447
|
+
|
|
448
|
+
## Rules Reference
|
|
449
|
+
|
|
450
|
+
Load on-demand from `rules/`:
|
|
451
|
+
- `critical.md` - Must-follow patterns
|
|
452
|
+
- `navigation.md` - Expo Router patterns
|
|
453
|
+
- `native.md` - Native features and APIs
|
|
454
|
+
- `performance.md` - Optimization techniques
|
|
455
|
+
|
|
456
|
+
---
|
|
457
|
+
|
|
458
|
+
## Output Format
|
|
459
|
+
|
|
460
|
+
```
|
|
461
|
+
Workspace: apps/mobile
|
|
462
|
+
Agent: expo + shared-ts
|
|
463
|
+
|
|
464
|
+
Files modified:
|
|
465
|
+
- app/(tabs)/home.tsx (new screen)
|
|
466
|
+
- components/ui/card.tsx (new component)
|
|
467
|
+
- lib/api.ts (API functions)
|
|
468
|
+
|
|
469
|
+
Quality: ✓ PASS
|
|
470
|
+
- Platform tested: iOS, Android
|
|
471
|
+
- Accessibility: Labels added
|
|
472
|
+
- Performance: FlashList used
|
|
473
|
+
```
|