@buivietphi/skill-mobile-mt 2.0.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/AGENTS.md +482 -0
- package/README.md +528 -0
- package/SKILL.md +1399 -0
- package/android/android-native.md +480 -0
- package/bin/install.mjs +976 -0
- package/flutter/flutter.md +304 -0
- package/humanizer/humanizer-mobile.md +295 -0
- package/ios/ios-native.md +182 -0
- package/package.json +56 -0
- package/react-native/react-native.md +743 -0
- package/shared/agent-rules-template.md +343 -0
- package/shared/ai-dlc-workflow.md +237 -0
- package/shared/anti-patterns.md +407 -0
- package/shared/architecture-intelligence.md +416 -0
- package/shared/bug-detection.md +71 -0
- package/shared/ci-cd.md +423 -0
- package/shared/claude-md-template.md +125 -0
- package/shared/code-review.md +133 -0
- package/shared/common-pitfalls.md +117 -0
- package/shared/document-analysis.md +167 -0
- package/shared/error-recovery.md +467 -0
- package/shared/observability.md +688 -0
- package/shared/offline-first.md +377 -0
- package/shared/performance-prediction.md +210 -0
- package/shared/platform-excellence.md +244 -0
- package/shared/prompt-engineering.md +705 -0
- package/shared/release-checklist.md +82 -0
- package/shared/testing-strategy.md +332 -0
- package/shared/ui-ux-mobile.md +667 -0
- package/shared/version-management.md +526 -0
package/shared/ci-cd.md
ADDED
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
# CI/CD Pipelines — GitHub Actions for Mobile
|
|
2
|
+
|
|
3
|
+
> Automate: test → build → distribute. Never ship without CI.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## React Native — CI Pipeline
|
|
8
|
+
|
|
9
|
+
```yaml
|
|
10
|
+
# .github/workflows/rn-ci.yml
|
|
11
|
+
name: React Native CI
|
|
12
|
+
|
|
13
|
+
on:
|
|
14
|
+
push:
|
|
15
|
+
branches: [main, develop]
|
|
16
|
+
pull_request:
|
|
17
|
+
branches: [main]
|
|
18
|
+
|
|
19
|
+
jobs:
|
|
20
|
+
test:
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v4
|
|
24
|
+
|
|
25
|
+
- uses: actions/setup-node@v4
|
|
26
|
+
with:
|
|
27
|
+
node-version: '20'
|
|
28
|
+
cache: 'yarn' # or npm, pnpm
|
|
29
|
+
|
|
30
|
+
- name: Install dependencies
|
|
31
|
+
run: yarn install --frozen-lockfile
|
|
32
|
+
|
|
33
|
+
- name: TypeScript check
|
|
34
|
+
run: yarn tsc --noEmit
|
|
35
|
+
|
|
36
|
+
- name: Lint
|
|
37
|
+
run: yarn lint
|
|
38
|
+
|
|
39
|
+
- name: Unit tests
|
|
40
|
+
run: yarn test --ci --coverage --maxWorkers=2
|
|
41
|
+
|
|
42
|
+
- name: Upload coverage
|
|
43
|
+
uses: codecov/codecov-action@v4
|
|
44
|
+
with:
|
|
45
|
+
token: ${{ secrets.CODECOV_TOKEN }}
|
|
46
|
+
|
|
47
|
+
build-android:
|
|
48
|
+
runs-on: ubuntu-latest
|
|
49
|
+
needs: test
|
|
50
|
+
steps:
|
|
51
|
+
- uses: actions/checkout@v4
|
|
52
|
+
|
|
53
|
+
- uses: actions/setup-node@v4
|
|
54
|
+
with:
|
|
55
|
+
node-version: '20'
|
|
56
|
+
cache: 'yarn'
|
|
57
|
+
|
|
58
|
+
- uses: actions/setup-java@v4
|
|
59
|
+
with:
|
|
60
|
+
distribution: 'temurin'
|
|
61
|
+
java-version: '17'
|
|
62
|
+
|
|
63
|
+
- name: Cache Gradle
|
|
64
|
+
uses: actions/cache@v4
|
|
65
|
+
with:
|
|
66
|
+
path: |
|
|
67
|
+
~/.gradle/caches
|
|
68
|
+
~/.gradle/wrapper
|
|
69
|
+
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
|
|
70
|
+
|
|
71
|
+
- name: Install dependencies
|
|
72
|
+
run: yarn install --frozen-lockfile
|
|
73
|
+
|
|
74
|
+
- name: Build Android APK (debug)
|
|
75
|
+
run: cd android && ./gradlew assembleDebug
|
|
76
|
+
|
|
77
|
+
- name: Upload APK
|
|
78
|
+
uses: actions/upload-artifact@v4
|
|
79
|
+
with:
|
|
80
|
+
name: app-debug.apk
|
|
81
|
+
path: android/app/build/outputs/apk/debug/app-debug.apk
|
|
82
|
+
|
|
83
|
+
build-ios:
|
|
84
|
+
runs-on: macos-15
|
|
85
|
+
needs: test
|
|
86
|
+
steps:
|
|
87
|
+
- uses: actions/checkout@v4
|
|
88
|
+
|
|
89
|
+
- uses: actions/setup-node@v4
|
|
90
|
+
with:
|
|
91
|
+
node-version: '20'
|
|
92
|
+
cache: 'yarn'
|
|
93
|
+
|
|
94
|
+
- name: Cache CocoaPods
|
|
95
|
+
uses: actions/cache@v4
|
|
96
|
+
with:
|
|
97
|
+
path: ios/Pods
|
|
98
|
+
key: ${{ runner.os }}-pods-${{ hashFiles('ios/Podfile.lock') }}
|
|
99
|
+
|
|
100
|
+
- name: Install dependencies
|
|
101
|
+
run: yarn install --frozen-lockfile && cd ios && pod install
|
|
102
|
+
|
|
103
|
+
- name: Build iOS (simulator)
|
|
104
|
+
run: |
|
|
105
|
+
xcodebuild -workspace ios/MyApp.xcworkspace \
|
|
106
|
+
-scheme MyApp \
|
|
107
|
+
-sdk iphonesimulator \
|
|
108
|
+
-configuration Debug \
|
|
109
|
+
-derivedDataPath ios/build \
|
|
110
|
+
CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## React Native — E2E with Detox
|
|
116
|
+
|
|
117
|
+
```yaml
|
|
118
|
+
# .github/workflows/rn-e2e.yml
|
|
119
|
+
name: E2E Tests (Detox)
|
|
120
|
+
|
|
121
|
+
on:
|
|
122
|
+
push:
|
|
123
|
+
branches: [main]
|
|
124
|
+
|
|
125
|
+
jobs:
|
|
126
|
+
e2e-ios:
|
|
127
|
+
runs-on: macos-15
|
|
128
|
+
steps:
|
|
129
|
+
- uses: actions/checkout@v4
|
|
130
|
+
|
|
131
|
+
- uses: actions/setup-node@v4
|
|
132
|
+
with:
|
|
133
|
+
node-version: '20'
|
|
134
|
+
cache: 'yarn'
|
|
135
|
+
|
|
136
|
+
- name: Install dependencies
|
|
137
|
+
run: yarn install --frozen-lockfile && cd ios && pod install
|
|
138
|
+
|
|
139
|
+
- name: Install Detox CLI
|
|
140
|
+
run: npm install -g detox-cli
|
|
141
|
+
|
|
142
|
+
- name: Build for Detox
|
|
143
|
+
run: detox build --configuration ios.sim.debug
|
|
144
|
+
|
|
145
|
+
- name: Run E2E tests
|
|
146
|
+
run: detox test --configuration ios.sim.debug --headless
|
|
147
|
+
|
|
148
|
+
- name: Upload Detox artifacts on failure
|
|
149
|
+
if: failure()
|
|
150
|
+
uses: actions/upload-artifact@v4
|
|
151
|
+
with:
|
|
152
|
+
name: detox-artifacts
|
|
153
|
+
path: artifacts/
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Flutter — CI Pipeline
|
|
159
|
+
|
|
160
|
+
```yaml
|
|
161
|
+
# .github/workflows/flutter-ci.yml
|
|
162
|
+
name: Flutter CI
|
|
163
|
+
|
|
164
|
+
on:
|
|
165
|
+
push:
|
|
166
|
+
branches: [main, develop]
|
|
167
|
+
pull_request:
|
|
168
|
+
branches: [main]
|
|
169
|
+
|
|
170
|
+
jobs:
|
|
171
|
+
test:
|
|
172
|
+
runs-on: ubuntu-latest
|
|
173
|
+
steps:
|
|
174
|
+
- uses: actions/checkout@v4
|
|
175
|
+
|
|
176
|
+
- uses: subosito/flutter-action@v2
|
|
177
|
+
with:
|
|
178
|
+
flutter-version: '3.27.x'
|
|
179
|
+
cache: true
|
|
180
|
+
|
|
181
|
+
- name: Install dependencies
|
|
182
|
+
run: flutter pub get
|
|
183
|
+
|
|
184
|
+
- name: Analyze
|
|
185
|
+
run: flutter analyze
|
|
186
|
+
|
|
187
|
+
- name: Unit + Widget tests
|
|
188
|
+
run: flutter test --coverage
|
|
189
|
+
|
|
190
|
+
- name: Upload coverage
|
|
191
|
+
uses: codecov/codecov-action@v4
|
|
192
|
+
|
|
193
|
+
build-android:
|
|
194
|
+
runs-on: ubuntu-latest
|
|
195
|
+
needs: test
|
|
196
|
+
steps:
|
|
197
|
+
- uses: actions/checkout@v4
|
|
198
|
+
|
|
199
|
+
- uses: actions/setup-java@v4
|
|
200
|
+
with:
|
|
201
|
+
distribution: 'temurin'
|
|
202
|
+
java-version: '17'
|
|
203
|
+
|
|
204
|
+
- uses: subosito/flutter-action@v2
|
|
205
|
+
with:
|
|
206
|
+
flutter-version: '3.27.x'
|
|
207
|
+
cache: true
|
|
208
|
+
|
|
209
|
+
- name: Install dependencies
|
|
210
|
+
run: flutter pub get
|
|
211
|
+
|
|
212
|
+
- name: Build APK
|
|
213
|
+
run: flutter build apk --debug
|
|
214
|
+
|
|
215
|
+
- name: Upload APK
|
|
216
|
+
uses: actions/upload-artifact@v4
|
|
217
|
+
with:
|
|
218
|
+
name: flutter-debug.apk
|
|
219
|
+
path: build/app/outputs/flutter-apk/app-debug.apk
|
|
220
|
+
|
|
221
|
+
build-ios:
|
|
222
|
+
runs-on: macos-15
|
|
223
|
+
needs: test
|
|
224
|
+
steps:
|
|
225
|
+
- uses: actions/checkout@v4
|
|
226
|
+
|
|
227
|
+
- uses: subosito/flutter-action@v2
|
|
228
|
+
with:
|
|
229
|
+
flutter-version: '3.27.x'
|
|
230
|
+
cache: true
|
|
231
|
+
|
|
232
|
+
- name: Install dependencies
|
|
233
|
+
run: flutter pub get
|
|
234
|
+
|
|
235
|
+
- name: Build iOS (no codesign)
|
|
236
|
+
run: flutter build ios --debug --no-codesign
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## iOS — Release to TestFlight (Fastlane)
|
|
242
|
+
|
|
243
|
+
```yaml
|
|
244
|
+
# .github/workflows/ios-release.yml
|
|
245
|
+
name: iOS Release
|
|
246
|
+
|
|
247
|
+
on:
|
|
248
|
+
push:
|
|
249
|
+
tags: ['v*']
|
|
250
|
+
|
|
251
|
+
jobs:
|
|
252
|
+
release:
|
|
253
|
+
runs-on: macos-15
|
|
254
|
+
steps:
|
|
255
|
+
- uses: actions/checkout@v4
|
|
256
|
+
|
|
257
|
+
- uses: actions/setup-node@v4
|
|
258
|
+
with:
|
|
259
|
+
node-version: '20'
|
|
260
|
+
cache: 'yarn'
|
|
261
|
+
|
|
262
|
+
- name: Install dependencies
|
|
263
|
+
run: yarn install --frozen-lockfile && cd ios && pod install
|
|
264
|
+
|
|
265
|
+
- name: Setup certificates
|
|
266
|
+
uses: apple-actions/import-codesign-certs@v2
|
|
267
|
+
with:
|
|
268
|
+
p12-file-base64: ${{ secrets.CERTIFICATES_P12 }}
|
|
269
|
+
p12-password: ${{ secrets.CERTIFICATES_P12_PASSWORD }}
|
|
270
|
+
|
|
271
|
+
- name: Setup provisioning profile
|
|
272
|
+
uses: apple-actions/download-provisioning-profiles@v1
|
|
273
|
+
with:
|
|
274
|
+
bundle-id: com.myapp
|
|
275
|
+
issuer-id: ${{ secrets.APPSTORE_ISSUER_ID }}
|
|
276
|
+
api-key-id: ${{ secrets.APPSTORE_KEY_ID }}
|
|
277
|
+
api-private-key: ${{ secrets.APPSTORE_PRIVATE_KEY }}
|
|
278
|
+
|
|
279
|
+
- name: Deploy to TestFlight
|
|
280
|
+
run: bundle exec fastlane ios beta
|
|
281
|
+
env:
|
|
282
|
+
APP_STORE_CONNECT_API_KEY_KEY_ID: ${{ secrets.APPSTORE_KEY_ID }}
|
|
283
|
+
APP_STORE_CONNECT_API_KEY_ISSUER_ID: ${{ secrets.APPSTORE_ISSUER_ID }}
|
|
284
|
+
APP_STORE_CONNECT_API_KEY_KEY: ${{ secrets.APPSTORE_PRIVATE_KEY }}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
```ruby
|
|
288
|
+
# ios/Fastfile
|
|
289
|
+
lane :beta do
|
|
290
|
+
build_app(
|
|
291
|
+
workspace: "MyApp.xcworkspace",
|
|
292
|
+
scheme: "MyApp",
|
|
293
|
+
configuration: "Release",
|
|
294
|
+
export_method: "app-store"
|
|
295
|
+
)
|
|
296
|
+
upload_to_testflight(skip_waiting_for_build_processing: true)
|
|
297
|
+
end
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
## Android — Release to Play Store (Fastlane)
|
|
303
|
+
|
|
304
|
+
```yaml
|
|
305
|
+
# .github/workflows/android-release.yml
|
|
306
|
+
name: Android Release
|
|
307
|
+
|
|
308
|
+
on:
|
|
309
|
+
push:
|
|
310
|
+
tags: ['v*']
|
|
311
|
+
|
|
312
|
+
jobs:
|
|
313
|
+
release:
|
|
314
|
+
runs-on: ubuntu-latest
|
|
315
|
+
steps:
|
|
316
|
+
- uses: actions/checkout@v4
|
|
317
|
+
|
|
318
|
+
- uses: actions/setup-java@v4
|
|
319
|
+
with:
|
|
320
|
+
distribution: 'temurin'
|
|
321
|
+
java-version: '17'
|
|
322
|
+
|
|
323
|
+
- uses: actions/setup-node@v4
|
|
324
|
+
with:
|
|
325
|
+
node-version: '20'
|
|
326
|
+
cache: 'yarn'
|
|
327
|
+
|
|
328
|
+
- name: Install dependencies
|
|
329
|
+
run: yarn install --frozen-lockfile
|
|
330
|
+
|
|
331
|
+
- name: Decode keystore
|
|
332
|
+
run: echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > android/app/release.keystore
|
|
333
|
+
|
|
334
|
+
- name: Build release AAB
|
|
335
|
+
run: cd android && ./gradlew bundleRelease
|
|
336
|
+
env:
|
|
337
|
+
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
|
|
338
|
+
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
|
|
339
|
+
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
|
|
340
|
+
|
|
341
|
+
- name: Deploy to Play Store
|
|
342
|
+
uses: r0adkll/upload-google-play@v1
|
|
343
|
+
with:
|
|
344
|
+
serviceAccountJsonPlainText: ${{ secrets.PLAY_STORE_SERVICE_ACCOUNT_JSON }}
|
|
345
|
+
packageName: com.myapp
|
|
346
|
+
releaseFiles: android/app/build/outputs/bundle/release/*.aab
|
|
347
|
+
track: internal
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## Required GitHub Secrets
|
|
353
|
+
|
|
354
|
+
```
|
|
355
|
+
# iOS
|
|
356
|
+
CERTIFICATES_P12 ← base64-encoded .p12 file
|
|
357
|
+
CERTIFICATES_P12_PASSWORD ← password for .p12
|
|
358
|
+
APPSTORE_KEY_ID ← App Store Connect API key ID
|
|
359
|
+
APPSTORE_ISSUER_ID ← App Store Connect issuer ID
|
|
360
|
+
APPSTORE_PRIVATE_KEY ← App Store Connect private key (.p8 contents)
|
|
361
|
+
|
|
362
|
+
# Android
|
|
363
|
+
KEYSTORE_BASE64 ← base64-encoded release.keystore
|
|
364
|
+
KEYSTORE_PASSWORD ← keystore password
|
|
365
|
+
KEY_ALIAS ← key alias
|
|
366
|
+
KEY_PASSWORD ← key password
|
|
367
|
+
PLAY_STORE_SERVICE_ACCOUNT_JSON ← GCP service account JSON
|
|
368
|
+
|
|
369
|
+
# Shared
|
|
370
|
+
CODECOV_TOKEN ← coverage reporting
|
|
371
|
+
MAESTRO_API_KEY ← Maestro Cloud E2E (optional)
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
## Caching Strategy
|
|
377
|
+
|
|
378
|
+
```yaml
|
|
379
|
+
# Node modules — hash package-lock or yarn.lock
|
|
380
|
+
- uses: actions/cache@v4
|
|
381
|
+
with:
|
|
382
|
+
path: node_modules
|
|
383
|
+
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
|
|
384
|
+
restore-keys: ${{ runner.os }}-node-
|
|
385
|
+
|
|
386
|
+
# Gradle — hash .gradle files
|
|
387
|
+
- uses: actions/cache@v4
|
|
388
|
+
with:
|
|
389
|
+
path: |
|
|
390
|
+
~/.gradle/caches
|
|
391
|
+
~/.gradle/wrapper
|
|
392
|
+
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
|
|
393
|
+
|
|
394
|
+
# CocoaPods — hash Podfile.lock
|
|
395
|
+
- uses: actions/cache@v4
|
|
396
|
+
with:
|
|
397
|
+
path: ios/Pods
|
|
398
|
+
key: ${{ runner.os }}-pods-${{ hashFiles('ios/Podfile.lock') }}
|
|
399
|
+
|
|
400
|
+
# Flutter pub — hash pubspec.lock
|
|
401
|
+
- uses: actions/cache@v4
|
|
402
|
+
with:
|
|
403
|
+
path: ~/.pub-cache
|
|
404
|
+
key: ${{ runner.os }}-pub-${{ hashFiles('**/pubspec.lock') }}
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
---
|
|
408
|
+
|
|
409
|
+
## Anti-Patterns
|
|
410
|
+
|
|
411
|
+
```
|
|
412
|
+
❌ Committing signing credentials to repo
|
|
413
|
+
❌ Running E2E on every PR (too slow — run on main only)
|
|
414
|
+
❌ No caching (3x slower builds)
|
|
415
|
+
❌ Skipping unit tests before build jobs
|
|
416
|
+
❌ Building on push to every branch
|
|
417
|
+
|
|
418
|
+
✅ Store all secrets in GitHub Secrets
|
|
419
|
+
✅ Cache node_modules + Gradle + CocoaPods + pub-cache
|
|
420
|
+
✅ Unit tests on PR, E2E on merge to main
|
|
421
|
+
✅ needs: test before build jobs
|
|
422
|
+
✅ Upload build artifacts for download/QA
|
|
423
|
+
```
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# CLAUDE.md Template for Mobile Projects
|
|
2
|
+
|
|
3
|
+
> Copy this file to your project root as `CLAUDE.md`.
|
|
4
|
+
> Claude Code reads it automatically every session — no invocation needed.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## How to use
|
|
9
|
+
|
|
10
|
+
1. Copy this file to your project: `cp CLAUDE.md.template CLAUDE.md`
|
|
11
|
+
2. Edit the sections marked with `[FILL IN]`
|
|
12
|
+
3. Claude will follow these rules automatically in every conversation
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Template (copy below this line)
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
# Project: [FILL IN: Your App Name]
|
|
21
|
+
|
|
22
|
+
## Stack
|
|
23
|
+
|
|
24
|
+
- **Framework:** [React Native CLI / Expo SDK XX / Flutter X.X / iOS / Android]
|
|
25
|
+
- **Language:** [TypeScript / JavaScript / Dart / Swift / Kotlin]
|
|
26
|
+
- **State:** [Redux Toolkit / Zustand / Riverpod / BLoC / StateFlow]
|
|
27
|
+
- **Navigation:** [React Navigation v6 / Expo Router / GoRouter / UIKit / Jetpack]
|
|
28
|
+
- **API:** [axios / fetch / Dio / Firebase / GraphQL]
|
|
29
|
+
- **Package Manager:** [yarn / npm / pnpm / bun / flutter pub]
|
|
30
|
+
|
|
31
|
+
## Project Structure
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
[FILL IN: paste your src/ or lib/ tree here]
|
|
35
|
+
src/
|
|
36
|
+
├── features/
|
|
37
|
+
│ ├── auth/
|
|
38
|
+
│ └── home/
|
|
39
|
+
├── shared/
|
|
40
|
+
│ ├── components/
|
|
41
|
+
│ └── utils/
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Conventions
|
|
45
|
+
|
|
46
|
+
- **Naming:** [PascalCase screens, camelCase hooks/services]
|
|
47
|
+
- **Imports:** [absolute @/ aliases / relative paths]
|
|
48
|
+
- **Styling:** [StyleSheet / NativeWind / styled-components / Compose / SwiftUI]
|
|
49
|
+
|
|
50
|
+
## Auto-Check Rules (apply after EVERY code change)
|
|
51
|
+
|
|
52
|
+
Before saying "done" on any task, automatically verify:
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
□ No console.log / print / NSLog in production code
|
|
56
|
+
□ No hardcoded secrets, API keys, or tokens
|
|
57
|
+
□ No token storage in AsyncStorage / SharedPreferences / UserDefaults
|
|
58
|
+
□ All async operations wrapped in try/catch
|
|
59
|
+
□ All 4 states handled: loading / error / empty / success
|
|
60
|
+
□ useEffect / dispose / viewModelScope has cleanup
|
|
61
|
+
□ FlatList (not ScrollView) for lists > 20 items
|
|
62
|
+
□ No force unwrap (! / !! / as!) without null check
|
|
63
|
+
□ TypeScript: no implicit 'any'
|
|
64
|
+
□ New screens registered in navigator
|
|
65
|
+
□ Imports resolve (no broken paths)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
If ANY check fails → fix it before marking done.
|
|
69
|
+
|
|
70
|
+
## Performance Rules (apply when building lists, animations, heavy screens)
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
□ FlatList with keyExtractor + getItemLayout (if fixed height)
|
|
74
|
+
□ React.memo on list item components
|
|
75
|
+
□ useCallback on handlers passed to list items
|
|
76
|
+
□ Images: use FastImage / expo-image, specify width+height
|
|
77
|
+
□ Animations: use react-native-reanimated (not Animated API)
|
|
78
|
+
□ No heavy computation on main thread (use InteractionManager)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Security Rules (non-negotiable)
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
□ Tokens → SecureStore / Keychain / EncryptedSharedPreferences ONLY
|
|
85
|
+
□ Deep link params → validate before use
|
|
86
|
+
□ API calls → HTTPS only
|
|
87
|
+
□ Sensitive data → never in logs
|
|
88
|
+
□ User input → sanitize before display (XSS)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## What NOT to do
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
□ NEVER suggest migrating to a different framework/architecture
|
|
95
|
+
□ NEVER change state management library
|
|
96
|
+
□ NEVER add packages without checking SDK compatibility first
|
|
97
|
+
□ NEVER mix package managers (yarn + npm)
|
|
98
|
+
□ NEVER create new files for one-off operations
|
|
99
|
+
□ NEVER add comments to code you didn't change
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Preferred Commands
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
# Install
|
|
106
|
+
[yarn install / npm install / flutter pub get / pod install]
|
|
107
|
+
|
|
108
|
+
# Run
|
|
109
|
+
[yarn ios / yarn android / flutter run / xcodebuild / ./gradlew]
|
|
110
|
+
|
|
111
|
+
# Test
|
|
112
|
+
[yarn test / flutter test / xcodebuild test]
|
|
113
|
+
|
|
114
|
+
# Lint
|
|
115
|
+
[yarn lint / flutter analyze / swiftlint]
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Notes
|
|
119
|
+
|
|
120
|
+
[FILL IN: any project-specific quirks, known issues, or important context]
|
|
121
|
+
|
|
122
|
+
Example:
|
|
123
|
+
- Auth uses custom JWT refresh logic in src/services/auth/tokenManager.ts
|
|
124
|
+
- Push notifications require manual certificate setup (see docs/push-setup.md)
|
|
125
|
+
- Android flavor: 'staging' points to staging API, 'production' to prod
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Code Review — Senior Protocol
|
|
2
|
+
|
|
3
|
+
> 🔴 Always loaded. All platforms.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Checklist
|
|
8
|
+
|
|
9
|
+
### Architecture
|
|
10
|
+
- [ ] Single responsibility per file (max 300 lines)
|
|
11
|
+
- [ ] Dependencies flow inward (UI → Domain → Data)
|
|
12
|
+
- [ ] Follows existing project patterns
|
|
13
|
+
- [ ] New files in correct directory
|
|
14
|
+
|
|
15
|
+
### Correctness
|
|
16
|
+
- [ ] All states handled: loading, success, error, empty
|
|
17
|
+
- [ ] Edge cases: null, empty, timeout, concurrent
|
|
18
|
+
- [ ] Async errors caught with meaningful handling
|
|
19
|
+
- [ ] Cleanup on unmount/dispose (listeners, timers, subscriptions)
|
|
20
|
+
- [ ] No race conditions (double-tap, concurrent API calls)
|
|
21
|
+
|
|
22
|
+
### Performance
|
|
23
|
+
- [ ] Lists virtualized (FlatList / ListView.builder / LazyColumn)
|
|
24
|
+
- [ ] Memoized where needed (memo / const / remember)
|
|
25
|
+
- [ ] No inline functions in render/build
|
|
26
|
+
- [ ] Images cached and resized
|
|
27
|
+
- [ ] No main thread blocking
|
|
28
|
+
|
|
29
|
+
### Security
|
|
30
|
+
- [ ] No hardcoded secrets
|
|
31
|
+
- [ ] Secure storage for tokens (Keychain / EncryptedSharedPreferences)
|
|
32
|
+
- [ ] Input validated and sanitized
|
|
33
|
+
- [ ] Deep links validated before navigation
|
|
34
|
+
- [ ] No sensitive data in logs
|
|
35
|
+
|
|
36
|
+
### Platform
|
|
37
|
+
- [ ] Both iOS + Android tested (if cross-platform)
|
|
38
|
+
- [ ] Safe areas / notch handled
|
|
39
|
+
- [ ] Keyboard handled (dismiss, avoidance)
|
|
40
|
+
- [ ] Back button handled (Android)
|
|
41
|
+
- [ ] Accessibility labels on interactive elements
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Severity
|
|
46
|
+
|
|
47
|
+
| Level | Action | Example |
|
|
48
|
+
|-------|--------|---------|
|
|
49
|
+
| 🔴 CRITICAL | Must fix before merge | Crash, security hole, data loss |
|
|
50
|
+
| 🟠 HIGH | Should fix before merge | Memory leak, missing error state, race condition |
|
|
51
|
+
| 🟡 MEDIUM | Fix in follow-up | Naming inconsistency, missing memoization |
|
|
52
|
+
| 🔵 LOW | Nice to have | Minor style, comment improvement |
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Auto-Fail
|
|
57
|
+
|
|
58
|
+
**Any of these → block merge immediately:**
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
❌ console.log / print in production code
|
|
62
|
+
❌ Hardcoded secrets or API keys
|
|
63
|
+
❌ Force unwrap without null check (! / !! / as!)
|
|
64
|
+
❌ Empty catch blocks (error silently swallowed)
|
|
65
|
+
❌ 500+ line files
|
|
66
|
+
❌ Network call in render / build / Composable
|
|
67
|
+
❌ Index as list key
|
|
68
|
+
❌ Missing loading / error / empty state (blank screen)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Grounding Auto-Fail (AI-generated code)
|
|
72
|
+
|
|
73
|
+
**If the code was generated by AI, also check:**
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
❌ Import from a package NOT in package.json / pubspec.yaml
|
|
77
|
+
❌ Function/method call that doesn't exist in the codebase
|
|
78
|
+
❌ API endpoint or response shape not verified from actual service code
|
|
79
|
+
❌ Library version syntax that doesn't match installed version
|
|
80
|
+
❌ Platform API that doesn't exist in the project's min SDK
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Review Comment Templates
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
🔴 CRITICAL — [file:line]
|
|
89
|
+
Issue: [description]
|
|
90
|
+
Impact: [what breaks]
|
|
91
|
+
Fix: [specific code change]
|
|
92
|
+
|
|
93
|
+
🟠 HIGH — [file:line]
|
|
94
|
+
Issue: [description]
|
|
95
|
+
Fix: [suggestion]
|
|
96
|
+
|
|
97
|
+
🟡 MEDIUM — [file:line]
|
|
98
|
+
Suggestion: [improvement]
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Example Issues
|
|
104
|
+
|
|
105
|
+
### Before (Bad)
|
|
106
|
+
```typescript
|
|
107
|
+
// ❌ No error handling, no loading state, index as key
|
|
108
|
+
function ProductList() {
|
|
109
|
+
const [data, setData] = useState([]);
|
|
110
|
+
useEffect(() => { api.get('/products').then(r => setData(r.data)); }, []);
|
|
111
|
+
return data.map((item, i) => <ProductCard key={i} item={item} />);
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### After (Good)
|
|
116
|
+
```typescript
|
|
117
|
+
// ✅ All states, cleanup, stable key, error handling
|
|
118
|
+
function ProductList() {
|
|
119
|
+
const [state, setState] = useState({ data: [], loading: true, error: null });
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
let cancelled = false;
|
|
122
|
+
api.get('/products')
|
|
123
|
+
.then(r => { if (!cancelled) setState({ data: r.data, loading: false, error: null }); })
|
|
124
|
+
.catch(e => { if (!cancelled) setState({ data: [], loading: false, error: e.message }); });
|
|
125
|
+
return () => { cancelled = true; };
|
|
126
|
+
}, []);
|
|
127
|
+
|
|
128
|
+
if (state.loading) return <LoadingSkeleton />;
|
|
129
|
+
if (state.error) return <ErrorView message={state.error} onRetry={refresh} />;
|
|
130
|
+
if (!state.data.length) return <EmptyState />;
|
|
131
|
+
return <FlatList data={state.data} keyExtractor={item => item.id} renderItem={...} />;
|
|
132
|
+
}
|
|
133
|
+
```
|