agentic-team-templates 0.3.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/README.md +280 -0
- package/bin/cli.js +5 -0
- package/package.json +47 -0
- package/src/index.js +521 -0
- package/templates/_shared/code-quality.md +162 -0
- package/templates/_shared/communication.md +114 -0
- package/templates/_shared/core-principles.md +62 -0
- package/templates/_shared/git-workflow.md +165 -0
- package/templates/_shared/security-fundamentals.md +173 -0
- package/templates/blockchain/.cursorrules/defi-patterns.md +520 -0
- package/templates/blockchain/.cursorrules/gas-optimization.md +339 -0
- package/templates/blockchain/.cursorrules/overview.md +130 -0
- package/templates/blockchain/.cursorrules/security.md +318 -0
- package/templates/blockchain/.cursorrules/smart-contracts.md +364 -0
- package/templates/blockchain/.cursorrules/testing.md +415 -0
- package/templates/blockchain/.cursorrules/web3-integration.md +538 -0
- package/templates/blockchain/CLAUDE.md +389 -0
- package/templates/cli-tools/.cursorrules/architecture.md +412 -0
- package/templates/cli-tools/.cursorrules/arguments.md +406 -0
- package/templates/cli-tools/.cursorrules/distribution.md +546 -0
- package/templates/cli-tools/.cursorrules/error-handling.md +455 -0
- package/templates/cli-tools/.cursorrules/overview.md +136 -0
- package/templates/cli-tools/.cursorrules/testing.md +537 -0
- package/templates/cli-tools/.cursorrules/user-experience.md +545 -0
- package/templates/cli-tools/CLAUDE.md +356 -0
- package/templates/data-engineering/.cursorrules/data-modeling.md +367 -0
- package/templates/data-engineering/.cursorrules/data-quality.md +455 -0
- package/templates/data-engineering/.cursorrules/overview.md +85 -0
- package/templates/data-engineering/.cursorrules/performance.md +339 -0
- package/templates/data-engineering/.cursorrules/pipeline-design.md +280 -0
- package/templates/data-engineering/.cursorrules/security.md +460 -0
- package/templates/data-engineering/.cursorrules/testing.md +452 -0
- package/templates/data-engineering/CLAUDE.md +974 -0
- package/templates/devops-sre/.cursorrules/capacity-planning.md +653 -0
- package/templates/devops-sre/.cursorrules/change-management.md +584 -0
- package/templates/devops-sre/.cursorrules/chaos-engineering.md +651 -0
- package/templates/devops-sre/.cursorrules/disaster-recovery.md +641 -0
- package/templates/devops-sre/.cursorrules/incident-management.md +565 -0
- package/templates/devops-sre/.cursorrules/observability.md +714 -0
- package/templates/devops-sre/.cursorrules/overview.md +230 -0
- package/templates/devops-sre/.cursorrules/postmortems.md +588 -0
- package/templates/devops-sre/.cursorrules/runbooks.md +760 -0
- package/templates/devops-sre/.cursorrules/slo-sli.md +617 -0
- package/templates/devops-sre/.cursorrules/toil-reduction.md +567 -0
- package/templates/devops-sre/CLAUDE.md +1007 -0
- package/templates/documentation/.cursorrules/adr.md +277 -0
- package/templates/documentation/.cursorrules/api-documentation.md +411 -0
- package/templates/documentation/.cursorrules/code-comments.md +253 -0
- package/templates/documentation/.cursorrules/maintenance.md +260 -0
- package/templates/documentation/.cursorrules/overview.md +82 -0
- package/templates/documentation/.cursorrules/readme-standards.md +306 -0
- package/templates/documentation/CLAUDE.md +120 -0
- package/templates/fullstack/.cursorrules/api-contracts.md +331 -0
- package/templates/fullstack/.cursorrules/architecture.md +298 -0
- package/templates/fullstack/.cursorrules/overview.md +109 -0
- package/templates/fullstack/.cursorrules/shared-types.md +348 -0
- package/templates/fullstack/.cursorrules/testing.md +386 -0
- package/templates/fullstack/CLAUDE.md +349 -0
- package/templates/ml-ai/.cursorrules/data-engineering.md +483 -0
- package/templates/ml-ai/.cursorrules/deployment.md +601 -0
- package/templates/ml-ai/.cursorrules/model-development.md +538 -0
- package/templates/ml-ai/.cursorrules/monitoring.md +658 -0
- package/templates/ml-ai/.cursorrules/overview.md +131 -0
- package/templates/ml-ai/.cursorrules/security.md +637 -0
- package/templates/ml-ai/.cursorrules/testing.md +678 -0
- package/templates/ml-ai/CLAUDE.md +1136 -0
- package/templates/mobile/.cursorrules/navigation.md +246 -0
- package/templates/mobile/.cursorrules/offline-first.md +302 -0
- package/templates/mobile/.cursorrules/overview.md +71 -0
- package/templates/mobile/.cursorrules/performance.md +345 -0
- package/templates/mobile/.cursorrules/testing.md +339 -0
- package/templates/mobile/CLAUDE.md +233 -0
- package/templates/platform-engineering/.cursorrules/ci-cd.md +778 -0
- package/templates/platform-engineering/.cursorrules/developer-experience.md +632 -0
- package/templates/platform-engineering/.cursorrules/infrastructure-as-code.md +600 -0
- package/templates/platform-engineering/.cursorrules/kubernetes.md +710 -0
- package/templates/platform-engineering/.cursorrules/observability.md +747 -0
- package/templates/platform-engineering/.cursorrules/overview.md +215 -0
- package/templates/platform-engineering/.cursorrules/security.md +855 -0
- package/templates/platform-engineering/.cursorrules/testing.md +878 -0
- package/templates/platform-engineering/CLAUDE.md +850 -0
- package/templates/utility-agent/.cursorrules/action-control.md +284 -0
- package/templates/utility-agent/.cursorrules/context-management.md +186 -0
- package/templates/utility-agent/.cursorrules/hallucination-prevention.md +253 -0
- package/templates/utility-agent/.cursorrules/overview.md +78 -0
- package/templates/utility-agent/.cursorrules/token-optimization.md +369 -0
- package/templates/utility-agent/CLAUDE.md +513 -0
- package/templates/web-backend/.cursorrules/api-design.md +255 -0
- package/templates/web-backend/.cursorrules/authentication.md +309 -0
- package/templates/web-backend/.cursorrules/database-patterns.md +298 -0
- package/templates/web-backend/.cursorrules/error-handling.md +366 -0
- package/templates/web-backend/.cursorrules/overview.md +69 -0
- package/templates/web-backend/.cursorrules/security.md +358 -0
- package/templates/web-backend/.cursorrules/testing.md +395 -0
- package/templates/web-backend/CLAUDE.md +366 -0
- package/templates/web-frontend/.cursorrules/accessibility.md +296 -0
- package/templates/web-frontend/.cursorrules/component-patterns.md +204 -0
- package/templates/web-frontend/.cursorrules/overview.md +72 -0
- package/templates/web-frontend/.cursorrules/performance.md +325 -0
- package/templates/web-frontend/.cursorrules/state-management.md +227 -0
- package/templates/web-frontend/.cursorrules/styling.md +271 -0
- package/templates/web-frontend/.cursorrules/testing.md +311 -0
- package/templates/web-frontend/CLAUDE.md +399 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# Mobile Navigation
|
|
2
|
+
|
|
3
|
+
Best practices for navigation in mobile applications.
|
|
4
|
+
|
|
5
|
+
## Navigation Patterns
|
|
6
|
+
|
|
7
|
+
### Stack Navigation
|
|
8
|
+
For hierarchical content flow.
|
|
9
|
+
|
|
10
|
+
```tsx
|
|
11
|
+
// React Native example
|
|
12
|
+
const Stack = createNativeStackNavigator();
|
|
13
|
+
|
|
14
|
+
function AppNavigator() {
|
|
15
|
+
return (
|
|
16
|
+
<Stack.Navigator>
|
|
17
|
+
<Stack.Screen name="Home" component={HomeScreen} />
|
|
18
|
+
<Stack.Screen name="Details" component={DetailsScreen} />
|
|
19
|
+
<Stack.Screen
|
|
20
|
+
name="Settings"
|
|
21
|
+
component={SettingsScreen}
|
|
22
|
+
options={{ presentation: 'modal' }}
|
|
23
|
+
/>
|
|
24
|
+
</Stack.Navigator>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Tab Navigation
|
|
30
|
+
For top-level app sections.
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
const Tab = createBottomTabNavigator();
|
|
34
|
+
|
|
35
|
+
function MainTabs() {
|
|
36
|
+
return (
|
|
37
|
+
<Tab.Navigator>
|
|
38
|
+
<Tab.Screen
|
|
39
|
+
name="Home"
|
|
40
|
+
component={HomeStack}
|
|
41
|
+
options={{
|
|
42
|
+
tabBarIcon: ({ color }) => <HomeIcon color={color} />,
|
|
43
|
+
}}
|
|
44
|
+
/>
|
|
45
|
+
<Tab.Screen name="Search" component={SearchStack} />
|
|
46
|
+
<Tab.Screen name="Profile" component={ProfileStack} />
|
|
47
|
+
</Tab.Navigator>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Drawer Navigation
|
|
53
|
+
For app-wide menu access.
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
const Drawer = createDrawerNavigator();
|
|
57
|
+
|
|
58
|
+
function AppDrawer() {
|
|
59
|
+
return (
|
|
60
|
+
<Drawer.Navigator>
|
|
61
|
+
<Drawer.Screen name="Main" component={MainTabs} />
|
|
62
|
+
<Drawer.Screen name="Settings" component={SettingsScreen} />
|
|
63
|
+
<Drawer.Screen name="Help" component={HelpScreen} />
|
|
64
|
+
</Drawer.Navigator>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Deep Linking
|
|
70
|
+
|
|
71
|
+
### Configuration
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
const linking = {
|
|
75
|
+
prefixes: ['myapp://', 'https://myapp.com'],
|
|
76
|
+
config: {
|
|
77
|
+
screens: {
|
|
78
|
+
Home: '',
|
|
79
|
+
Details: 'details/:id',
|
|
80
|
+
Profile: 'user/:userId',
|
|
81
|
+
Settings: 'settings',
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
function App() {
|
|
87
|
+
return (
|
|
88
|
+
<NavigationContainer linking={linking}>
|
|
89
|
+
<AppNavigator />
|
|
90
|
+
</NavigationContainer>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Handling Deep Links
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
// Handle incoming links
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
const handleDeepLink = (event: { url: string }) => {
|
|
101
|
+
const route = parseDeepLink(event.url);
|
|
102
|
+
if (route) {
|
|
103
|
+
navigation.navigate(route.screen, route.params);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const subscription = Linking.addEventListener('url', handleDeepLink);
|
|
108
|
+
return () => subscription.remove();
|
|
109
|
+
}, []);
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Type-Safe Navigation
|
|
113
|
+
|
|
114
|
+
```tsx
|
|
115
|
+
// Define route params
|
|
116
|
+
type RootStackParamList = {
|
|
117
|
+
Home: undefined;
|
|
118
|
+
Details: { id: string };
|
|
119
|
+
Profile: { userId: string; initialTab?: string };
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// Type-safe navigation
|
|
123
|
+
const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>();
|
|
124
|
+
|
|
125
|
+
// Type-safe route params
|
|
126
|
+
const route = useRoute<RouteProp<RootStackParamList, 'Details'>>();
|
|
127
|
+
const { id } = route.params;
|
|
128
|
+
|
|
129
|
+
// Navigate with type checking
|
|
130
|
+
navigation.navigate('Details', { id: '123' }); // ✓
|
|
131
|
+
navigation.navigate('Details'); // ✗ Error: id required
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Navigation State
|
|
135
|
+
|
|
136
|
+
### Persisting State
|
|
137
|
+
|
|
138
|
+
```tsx
|
|
139
|
+
function App() {
|
|
140
|
+
const [isReady, setIsReady] = useState(false);
|
|
141
|
+
const [initialState, setInitialState] = useState();
|
|
142
|
+
|
|
143
|
+
useEffect(() => {
|
|
144
|
+
const restoreState = async () => {
|
|
145
|
+
const savedState = await AsyncStorage.getItem('NAV_STATE');
|
|
146
|
+
if (savedState) {
|
|
147
|
+
setInitialState(JSON.parse(savedState));
|
|
148
|
+
}
|
|
149
|
+
setIsReady(true);
|
|
150
|
+
};
|
|
151
|
+
restoreState();
|
|
152
|
+
}, []);
|
|
153
|
+
|
|
154
|
+
if (!isReady) return <SplashScreen />;
|
|
155
|
+
|
|
156
|
+
return (
|
|
157
|
+
<NavigationContainer
|
|
158
|
+
initialState={initialState}
|
|
159
|
+
onStateChange={(state) => {
|
|
160
|
+
AsyncStorage.setItem('NAV_STATE', JSON.stringify(state));
|
|
161
|
+
}}
|
|
162
|
+
>
|
|
163
|
+
<AppNavigator />
|
|
164
|
+
</NavigationContainer>
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Authentication Flow
|
|
170
|
+
|
|
171
|
+
```tsx
|
|
172
|
+
function RootNavigator() {
|
|
173
|
+
const { user, isLoading } = useAuth();
|
|
174
|
+
|
|
175
|
+
if (isLoading) {
|
|
176
|
+
return <SplashScreen />;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return (
|
|
180
|
+
<Stack.Navigator screenOptions={{ headerShown: false }}>
|
|
181
|
+
{user ? (
|
|
182
|
+
<Stack.Screen name="Main" component={MainNavigator} />
|
|
183
|
+
) : (
|
|
184
|
+
<Stack.Screen name="Auth" component={AuthNavigator} />
|
|
185
|
+
)}
|
|
186
|
+
</Stack.Navigator>
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Best Practices
|
|
192
|
+
|
|
193
|
+
### Avoid Prop Drilling Navigation
|
|
194
|
+
|
|
195
|
+
```tsx
|
|
196
|
+
// Bad: Passing navigation through props
|
|
197
|
+
<ChildComponent navigation={navigation} />
|
|
198
|
+
|
|
199
|
+
// Good: Use hooks
|
|
200
|
+
function ChildComponent() {
|
|
201
|
+
const navigation = useNavigation();
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Handle Android Back Button
|
|
206
|
+
|
|
207
|
+
```tsx
|
|
208
|
+
useEffect(() => {
|
|
209
|
+
const onBackPress = () => {
|
|
210
|
+
if (shouldPreventBack) {
|
|
211
|
+
Alert.alert('Discard changes?', '...', [
|
|
212
|
+
{ text: 'Cancel', style: 'cancel' },
|
|
213
|
+
{ text: 'Discard', onPress: () => navigation.goBack() },
|
|
214
|
+
]);
|
|
215
|
+
return true; // Prevent default
|
|
216
|
+
}
|
|
217
|
+
return false; // Allow default
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
BackHandler.addEventListener('hardwareBackPress', onBackPress);
|
|
221
|
+
return () => BackHandler.removeEventListener('hardwareBackPress', onBackPress);
|
|
222
|
+
}, []);
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Use beforeRemove for Unsaved Changes
|
|
226
|
+
|
|
227
|
+
```tsx
|
|
228
|
+
useEffect(() => {
|
|
229
|
+
const unsubscribe = navigation.addListener('beforeRemove', (e) => {
|
|
230
|
+
if (!hasUnsavedChanges) return;
|
|
231
|
+
|
|
232
|
+
e.preventDefault();
|
|
233
|
+
|
|
234
|
+
Alert.alert('Discard changes?', 'You have unsaved changes.', [
|
|
235
|
+
{ text: 'Cancel', style: 'cancel' },
|
|
236
|
+
{
|
|
237
|
+
text: 'Discard',
|
|
238
|
+
style: 'destructive',
|
|
239
|
+
onPress: () => navigation.dispatch(e.data.action),
|
|
240
|
+
},
|
|
241
|
+
]);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
return unsubscribe;
|
|
245
|
+
}, [hasUnsavedChanges]);
|
|
246
|
+
```
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
# Offline-First Development
|
|
2
|
+
|
|
3
|
+
Building mobile apps that work reliably without network connectivity.
|
|
4
|
+
|
|
5
|
+
## Principles
|
|
6
|
+
|
|
7
|
+
### 1. Assume Unreliable Network
|
|
8
|
+
Mobile networks are inconsistent. Design for it.
|
|
9
|
+
|
|
10
|
+
### 2. Local-First
|
|
11
|
+
Read from local storage, sync in background.
|
|
12
|
+
|
|
13
|
+
### 3. Optimistic Updates
|
|
14
|
+
Update UI immediately, reconcile later.
|
|
15
|
+
|
|
16
|
+
### 4. Graceful Degradation
|
|
17
|
+
Show what's available, queue what isn't.
|
|
18
|
+
|
|
19
|
+
## Data Storage
|
|
20
|
+
|
|
21
|
+
### Choosing Storage
|
|
22
|
+
|
|
23
|
+
| Storage Type | Use Case | Persistence |
|
|
24
|
+
|--------------|----------|-------------|
|
|
25
|
+
| AsyncStorage | Simple key-value, settings | Persistent |
|
|
26
|
+
| SQLite/Realm | Structured data, queries | Persistent |
|
|
27
|
+
| MMKV | Fast key-value | Persistent |
|
|
28
|
+
| In-memory | Temporary, session data | Session only |
|
|
29
|
+
|
|
30
|
+
### Local Database
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
// Example with SQLite/TypeORM
|
|
34
|
+
@Entity()
|
|
35
|
+
class Task {
|
|
36
|
+
@PrimaryColumn()
|
|
37
|
+
id: string;
|
|
38
|
+
|
|
39
|
+
@Column()
|
|
40
|
+
title: string;
|
|
41
|
+
|
|
42
|
+
@Column({ default: false })
|
|
43
|
+
completed: boolean;
|
|
44
|
+
|
|
45
|
+
@Column({ default: false })
|
|
46
|
+
syncedToServer: boolean;
|
|
47
|
+
|
|
48
|
+
@Column({ type: 'datetime', nullable: true })
|
|
49
|
+
lastModified: Date;
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Sync Strategies
|
|
54
|
+
|
|
55
|
+
### Pull Strategy (Server → Device)
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
async function pullChanges() {
|
|
59
|
+
const lastSync = await getLastSyncTimestamp();
|
|
60
|
+
|
|
61
|
+
const changes = await api.getChanges({ since: lastSync });
|
|
62
|
+
|
|
63
|
+
await db.transaction(async (tx) => {
|
|
64
|
+
for (const item of changes.items) {
|
|
65
|
+
await tx.upsert('tasks', item);
|
|
66
|
+
}
|
|
67
|
+
await tx.update('metadata', { lastSync: changes.timestamp });
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Push Strategy (Device → Server)
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
async function pushChanges() {
|
|
76
|
+
const pendingChanges = await db.query(
|
|
77
|
+
'SELECT * FROM tasks WHERE syncedToServer = false'
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
for (const change of pendingChanges) {
|
|
81
|
+
try {
|
|
82
|
+
await api.syncTask(change);
|
|
83
|
+
await db.update('tasks', change.id, { syncedToServer: true });
|
|
84
|
+
} catch (error) {
|
|
85
|
+
if (!isNetworkError(error)) {
|
|
86
|
+
throw error; // Server rejected, need conflict resolution
|
|
87
|
+
}
|
|
88
|
+
// Network error, will retry later
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Conflict Resolution
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
type ConflictResolution = 'server-wins' | 'client-wins' | 'manual';
|
|
98
|
+
|
|
99
|
+
async function resolveConflict(
|
|
100
|
+
local: Task,
|
|
101
|
+
server: Task,
|
|
102
|
+
strategy: ConflictResolution
|
|
103
|
+
): Promise<Task> {
|
|
104
|
+
switch (strategy) {
|
|
105
|
+
case 'server-wins':
|
|
106
|
+
return server;
|
|
107
|
+
|
|
108
|
+
case 'client-wins':
|
|
109
|
+
await api.updateTask(local);
|
|
110
|
+
return local;
|
|
111
|
+
|
|
112
|
+
case 'manual':
|
|
113
|
+
// Store conflict for user resolution
|
|
114
|
+
await storeConflict({ local, server });
|
|
115
|
+
throw new ConflictError('Manual resolution required');
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Network State Management
|
|
121
|
+
|
|
122
|
+
### Detecting Connectivity
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
import NetInfo from '@react-native-community/netinfo';
|
|
126
|
+
|
|
127
|
+
function useNetworkStatus() {
|
|
128
|
+
const [isOnline, setIsOnline] = useState(true);
|
|
129
|
+
const [connectionType, setConnectionType] = useState<string | null>(null);
|
|
130
|
+
|
|
131
|
+
useEffect(() => {
|
|
132
|
+
const unsubscribe = NetInfo.addEventListener((state) => {
|
|
133
|
+
setIsOnline(state.isConnected ?? false);
|
|
134
|
+
setConnectionType(state.type);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
return unsubscribe;
|
|
138
|
+
}, []);
|
|
139
|
+
|
|
140
|
+
return { isOnline, connectionType };
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Queueing Operations
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
class OperationQueue {
|
|
148
|
+
private queue: Operation[] = [];
|
|
149
|
+
|
|
150
|
+
async enqueue(operation: Operation) {
|
|
151
|
+
await this.persistQueue([...this.queue, operation]);
|
|
152
|
+
this.queue.push(operation);
|
|
153
|
+
this.processIfOnline();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private async processIfOnline() {
|
|
157
|
+
if (!await isOnline()) return;
|
|
158
|
+
|
|
159
|
+
while (this.queue.length > 0) {
|
|
160
|
+
const op = this.queue[0];
|
|
161
|
+
try {
|
|
162
|
+
await this.execute(op);
|
|
163
|
+
this.queue.shift();
|
|
164
|
+
await this.persistQueue(this.queue);
|
|
165
|
+
} catch (error) {
|
|
166
|
+
if (isNetworkError(error)) {
|
|
167
|
+
break; // Stop, will retry when online
|
|
168
|
+
}
|
|
169
|
+
throw error;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Optimistic Updates
|
|
177
|
+
|
|
178
|
+
```tsx
|
|
179
|
+
function useOptimisticTask() {
|
|
180
|
+
const queryClient = useQueryClient();
|
|
181
|
+
|
|
182
|
+
const updateTask = useMutation({
|
|
183
|
+
mutationFn: api.updateTask,
|
|
184
|
+
|
|
185
|
+
onMutate: async (newTask) => {
|
|
186
|
+
// Cancel outgoing refetches
|
|
187
|
+
await queryClient.cancelQueries({ queryKey: ['tasks'] });
|
|
188
|
+
|
|
189
|
+
// Snapshot current value
|
|
190
|
+
const previousTasks = queryClient.getQueryData(['tasks']);
|
|
191
|
+
|
|
192
|
+
// Optimistically update
|
|
193
|
+
queryClient.setQueryData(['tasks'], (old: Task[]) =>
|
|
194
|
+
old.map((t) => (t.id === newTask.id ? newTask : t))
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
return { previousTasks };
|
|
198
|
+
},
|
|
199
|
+
|
|
200
|
+
onError: (err, newTask, context) => {
|
|
201
|
+
// Rollback on error
|
|
202
|
+
queryClient.setQueryData(['tasks'], context?.previousTasks);
|
|
203
|
+
},
|
|
204
|
+
|
|
205
|
+
onSettled: () => {
|
|
206
|
+
// Refetch after error or success
|
|
207
|
+
queryClient.invalidateQueries({ queryKey: ['tasks'] });
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
return updateTask;
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Caching
|
|
216
|
+
|
|
217
|
+
### Cache Headers
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
const cacheConfig = {
|
|
221
|
+
images: { maxAge: 7 * 24 * 60 * 60 }, // 1 week
|
|
222
|
+
apiResponses: { maxAge: 5 * 60 }, // 5 minutes
|
|
223
|
+
staticAssets: { maxAge: 30 * 24 * 60 * 60 }, // 30 days
|
|
224
|
+
};
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Stale-While-Revalidate
|
|
228
|
+
|
|
229
|
+
```ts
|
|
230
|
+
async function fetchWithSWR<T>(key: string, fetcher: () => Promise<T>): Promise<T> {
|
|
231
|
+
// Return cached data immediately
|
|
232
|
+
const cached = await cache.get(key);
|
|
233
|
+
if (cached) {
|
|
234
|
+
// Revalidate in background
|
|
235
|
+
fetcher()
|
|
236
|
+
.then((fresh) => cache.set(key, fresh))
|
|
237
|
+
.catch(console.error);
|
|
238
|
+
return cached;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// No cache, wait for network
|
|
242
|
+
const data = await fetcher();
|
|
243
|
+
await cache.set(key, data);
|
|
244
|
+
return data;
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## UI Patterns
|
|
249
|
+
|
|
250
|
+
### Show Sync Status
|
|
251
|
+
|
|
252
|
+
```tsx
|
|
253
|
+
function TaskItem({ task }: { task: Task }) {
|
|
254
|
+
return (
|
|
255
|
+
<View style={styles.item}>
|
|
256
|
+
<Text>{task.title}</Text>
|
|
257
|
+
{!task.syncedToServer && (
|
|
258
|
+
<View style={styles.syncIndicator}>
|
|
259
|
+
<CloudOffIcon />
|
|
260
|
+
<Text>Pending sync</Text>
|
|
261
|
+
</View>
|
|
262
|
+
)}
|
|
263
|
+
</View>
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Offline Banner
|
|
269
|
+
|
|
270
|
+
```tsx
|
|
271
|
+
function OfflineBanner() {
|
|
272
|
+
const { isOnline } = useNetworkStatus();
|
|
273
|
+
|
|
274
|
+
if (isOnline) return null;
|
|
275
|
+
|
|
276
|
+
return (
|
|
277
|
+
<View style={styles.banner}>
|
|
278
|
+
<Text>You're offline. Changes will sync when connected.</Text>
|
|
279
|
+
</View>
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Queue Indicator
|
|
285
|
+
|
|
286
|
+
```tsx
|
|
287
|
+
function SyncStatus() {
|
|
288
|
+
const { pendingCount, isSyncing } = useSyncStatus();
|
|
289
|
+
|
|
290
|
+
if (pendingCount === 0) return null;
|
|
291
|
+
|
|
292
|
+
return (
|
|
293
|
+
<View style={styles.syncStatus}>
|
|
294
|
+
{isSyncing ? (
|
|
295
|
+
<ActivityIndicator />
|
|
296
|
+
) : (
|
|
297
|
+
<Text>{pendingCount} changes pending</Text>
|
|
298
|
+
)}
|
|
299
|
+
</View>
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
```
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Mobile Development
|
|
2
|
+
|
|
3
|
+
Guidelines for building mobile applications.
|
|
4
|
+
|
|
5
|
+
## Scope
|
|
6
|
+
|
|
7
|
+
This ruleset applies to:
|
|
8
|
+
- React Native applications
|
|
9
|
+
- Flutter applications
|
|
10
|
+
- Native iOS/Android development
|
|
11
|
+
- Cross-platform mobile frameworks
|
|
12
|
+
|
|
13
|
+
## Key Principles
|
|
14
|
+
|
|
15
|
+
### 1. Mobile-First UX
|
|
16
|
+
Design for touch, small screens, and variable connectivity.
|
|
17
|
+
|
|
18
|
+
### 2. Performance Matters
|
|
19
|
+
60fps animations, fast startup, efficient battery usage.
|
|
20
|
+
|
|
21
|
+
### 3. Offline-First
|
|
22
|
+
Apps should work without network when possible.
|
|
23
|
+
|
|
24
|
+
### 4. Platform Conventions
|
|
25
|
+
Respect each platform's design language and conventions.
|
|
26
|
+
|
|
27
|
+
## Project Structure
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
src/
|
|
31
|
+
├── components/ # Reusable UI components
|
|
32
|
+
├── screens/ # Screen/page components
|
|
33
|
+
├── navigation/ # Navigation configuration
|
|
34
|
+
├── hooks/ # Custom hooks
|
|
35
|
+
├── services/ # API and native service wrappers
|
|
36
|
+
├── store/ # State management
|
|
37
|
+
├── utils/ # Utility functions
|
|
38
|
+
├── types/ # TypeScript definitions
|
|
39
|
+
└── assets/ # Images, fonts, etc.
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Core Concerns
|
|
43
|
+
|
|
44
|
+
### Navigation
|
|
45
|
+
- Use standard navigation patterns (stack, tab, drawer)
|
|
46
|
+
- Handle deep linking
|
|
47
|
+
- Manage navigation state
|
|
48
|
+
- Support gestures
|
|
49
|
+
|
|
50
|
+
### Native Features
|
|
51
|
+
- Camera, location, notifications
|
|
52
|
+
- File system, storage
|
|
53
|
+
- Biometric authentication
|
|
54
|
+
- Background tasks
|
|
55
|
+
|
|
56
|
+
### Performance
|
|
57
|
+
- Optimize list rendering (virtualization)
|
|
58
|
+
- Minimize re-renders
|
|
59
|
+
- Lazy load images
|
|
60
|
+
- Profile and optimize startup time
|
|
61
|
+
|
|
62
|
+
## Definition of Done
|
|
63
|
+
|
|
64
|
+
A mobile feature is complete when:
|
|
65
|
+
- [ ] Works on both platforms (if cross-platform)
|
|
66
|
+
- [ ] Handles offline gracefully
|
|
67
|
+
- [ ] Animations run at 60fps
|
|
68
|
+
- [ ] Accessible (screen readers, dynamic type)
|
|
69
|
+
- [ ] Tests pass on device/simulator
|
|
70
|
+
- [ ] No memory leaks
|
|
71
|
+
- [ ] Handles permissions correctly
|