@buivietphi/skill-mobile-mt 2.0.1 → 2.2.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 +96 -40
- package/README.md +77 -40
- package/SKILL.md +762 -54
- package/package.json +1 -1
- package/shared/bug-detection.md +411 -27
- package/shared/code-generation-templates.md +656 -0
- package/shared/code-review.md +899 -37
- package/shared/complex-ui-patterns.md +526 -0
- package/shared/data-flow-patterns.md +422 -0
- package/shared/debugging-intelligence.md +787 -0
- package/shared/error-handling.md +394 -0
- package/shared/i18n-localization.md +426 -0
- package/shared/intent-analysis.md +473 -0
- package/shared/navigation-patterns.md +375 -0
- package/shared/prompt-engineering.md +176 -20
- package/shared/spec-to-code.md +293 -0
- package/shared/storage-patterns.md +312 -0
- package/shared/testing-patterns.md +428 -0
package/shared/code-review.md
CHANGED
|
@@ -1,44 +1,278 @@
|
|
|
1
1
|
# Code Review — Senior Protocol
|
|
2
2
|
|
|
3
3
|
> 🔴 Always loaded. All platforms.
|
|
4
|
+
> Trained from: CodeRabbit, Qodo, obra/superpowers, Anthropic security review, WCAG 2.1.
|
|
4
5
|
|
|
5
6
|
---
|
|
6
7
|
|
|
7
|
-
##
|
|
8
|
+
## Platform Focus Rule
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
```
|
|
11
|
+
⛔ ONLY review patterns for the DETECTED platform. DO NOT cross-platform unless the code touches it.
|
|
12
|
+
|
|
13
|
+
REACT NATIVE / EXPO PROJECT:
|
|
14
|
+
→ Focus: RN patterns (stale closure, useEffect, FlatList, etc.)
|
|
15
|
+
→ Cross-reference Android/iOS ONLY IF:
|
|
16
|
+
- Bug involves native module (Java/Kotlin/Swift/ObjC bridge)
|
|
17
|
+
- Error comes from native layer (Logcat/Xcode console, not Metro)
|
|
18
|
+
- User explicitly asks to check native code
|
|
19
|
+
→ SKIP: Flutter patterns entirely
|
|
20
|
+
|
|
21
|
+
FLUTTER PROJECT:
|
|
22
|
+
→ Focus: Flutter/Dart patterns (mounted, dispose, BuildContext, etc.)
|
|
23
|
+
→ Cross-reference Android/iOS ONLY IF:
|
|
24
|
+
- Bug involves platform channel (MethodChannel/EventChannel)
|
|
25
|
+
- Error comes from native layer (not Dart stack trace)
|
|
26
|
+
- User explicitly asks to check native code
|
|
27
|
+
→ SKIP: React Native patterns entirely
|
|
28
|
+
|
|
29
|
+
ANDROID NATIVE PROJECT (Java/Kotlin):
|
|
30
|
+
→ Focus: Android patterns (lifecycle, ViewModel, coroutines, etc.)
|
|
31
|
+
→ SKIP: React Native AND Flutter patterns entirely
|
|
32
|
+
→ Cross-reference iOS ONLY IF user has KMM (Kotlin Multiplatform)
|
|
33
|
+
|
|
34
|
+
iOS NATIVE PROJECT (Swift/ObjC):
|
|
35
|
+
→ Focus: iOS patterns (optionals, @MainActor, Combine, etc.)
|
|
36
|
+
→ SKIP: React Native AND Flutter patterns entirely
|
|
37
|
+
→ Cross-reference Android ONLY IF user has KMM
|
|
38
|
+
|
|
39
|
+
CROSS-PLATFORM patterns (API contract, token expiry, navigation params):
|
|
40
|
+
→ Always applicable regardless of platform
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Review Modes (detect from user request)
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
⛔ DETECT which review mode the user wants BEFORE starting.
|
|
49
|
+
⛔ DO NOT default to full review — match the scope to what was asked.
|
|
50
|
+
|
|
51
|
+
USER SAYS → MODE → SCOPE
|
|
52
|
+
──────────────────────────────────────────────────────────────────────────────
|
|
53
|
+
"Review full code" → FULL → Read ALL src/ files → 12-category checklist
|
|
54
|
+
"Review changes / review diff" → CHANGES → git diff (unstaged + staged) → review only changed code
|
|
55
|
+
"Review file X / review this file" → FILE → Read specific file(s) → 12-category checklist on those files only
|
|
56
|
+
"Review function X / review this function" → FUNCTION → Find function → trace callers/callees → deep review that function
|
|
57
|
+
"Review PR / review pull request" → PR → git diff [base]..HEAD → Step 0 PR-Level + 12-category on diff
|
|
58
|
+
"Review modified files" → MODIFIED → git status (modified only) → read + review modified files
|
|
59
|
+
"Review commits" → COMMITS → git log → git show [commit] → review each commit's changes
|
|
60
|
+
"Check PR" → PR-CHECK → Step 0 ONLY (size, scope, tests, commits) → quick pass/fail
|
|
61
|
+
|
|
62
|
+
═══ MODE DETAILS ═══
|
|
63
|
+
|
|
64
|
+
MODE: FULL
|
|
65
|
+
→ Glob "src/**/*" → read ALL source files
|
|
66
|
+
→ Run 12-category checklist on entire codebase
|
|
67
|
+
→ Report organized by file → severity
|
|
68
|
+
→ ⚠️ EXPENSIVE: only when user explicitly asks "review full" or "audit"
|
|
69
|
+
→ Estimated: 15-30 files, comprehensive
|
|
70
|
+
|
|
71
|
+
MODE: CHANGES (most common)
|
|
72
|
+
→ git diff --name-only → list changed files
|
|
73
|
+
→ git diff → read actual changes
|
|
74
|
+
→ Review ONLY the changed lines + their immediate context
|
|
75
|
+
→ Check: did the change break anything? missing state? missing test?
|
|
76
|
+
→ ⛔ DO NOT review unchanged code in same files (unless directly affected)
|
|
77
|
+
|
|
78
|
+
MODE: FILE
|
|
79
|
+
→ User specifies file path or "this file" (IDE selection)
|
|
80
|
+
→ Read the full file
|
|
81
|
+
→ Run 12-category checklist on that file
|
|
82
|
+
→ Also check: imports used correctly? exports consumed properly?
|
|
83
|
+
|
|
84
|
+
MODE: FUNCTION
|
|
85
|
+
→ User specifies function name or "this function" (IDE selection)
|
|
86
|
+
→ Find function definition (Grep)
|
|
87
|
+
→ Read the function + its context (surrounding code)
|
|
88
|
+
→ Trace: who calls this function? (Grep callers)
|
|
89
|
+
→ Trace: what does this function call? (read callees)
|
|
90
|
+
→ Deep review: correctness, edge cases, performance, security
|
|
91
|
+
→ Check: return values handled by all callers?
|
|
92
|
+
|
|
93
|
+
MODE: PR
|
|
94
|
+
→ Step 0: PR-Level Review (size, scope, tests, commits)
|
|
95
|
+
→ git diff [base-branch]..HEAD → get all changes
|
|
96
|
+
→ Read all changed files
|
|
97
|
+
→ Run 12-category checklist on changed code ONLY
|
|
98
|
+
→ Check: breaking changes? migration needed? tests added?
|
|
99
|
+
→ Output: full review format with verdict
|
|
100
|
+
|
|
101
|
+
MODE: MODIFIED
|
|
102
|
+
→ git status → list modified files (not untracked)
|
|
103
|
+
→ git diff → read actual modifications
|
|
104
|
+
→ Review modified code with 12-category checklist
|
|
105
|
+
→ Lighter than PR mode — no PR-level checks, just code quality
|
|
106
|
+
|
|
107
|
+
MODE: COMMITS
|
|
108
|
+
→ git log --oneline -N → list recent commits
|
|
109
|
+
→ For each commit: git show [hash] → read diff
|
|
110
|
+
→ Review each commit independently
|
|
111
|
+
→ Check: does each commit make sense alone? atomic changes?
|
|
112
|
+
→ Flag: commits that should be squashed, split, or reworded
|
|
113
|
+
|
|
114
|
+
MODE: PR-CHECK (quick — no code review)
|
|
115
|
+
→ Step 0 ONLY — fast pass/fail:
|
|
116
|
+
□ PR size (LOC)
|
|
117
|
+
□ Single responsibility
|
|
118
|
+
□ Tests for logic changes
|
|
119
|
+
□ Commit hygiene
|
|
120
|
+
□ Description present
|
|
121
|
+
→ Output: ✅ PASS or 🔴 FAIL with specific issues
|
|
122
|
+
→ Does NOT review actual code — use MODE: PR for full review
|
|
123
|
+
|
|
124
|
+
⛔ DEFAULT: If user says just "review" without specifying:
|
|
125
|
+
→ Check git status first
|
|
126
|
+
→ If changes exist → MODE: CHANGES (review what changed)
|
|
127
|
+
→ If no changes → ASK: "Review full codebase or a specific file?"
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## ⛔ STEP 0: PR-Level Review (Before Code Review)
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
BEFORE reviewing any code, review the PR/change itself:
|
|
136
|
+
|
|
137
|
+
PR SIZE:
|
|
138
|
+
□ Under 400 LOC changed? (ideal)
|
|
139
|
+
□ Under 800 LOC? (acceptable)
|
|
140
|
+
□ 800+ LOC? → 🔴 SUGGEST SPLIT — "This PR does too many things. Split into:"
|
|
141
|
+
→ List each logical change that could be a separate PR
|
|
142
|
+
|
|
143
|
+
SINGLE RESPONSIBILITY:
|
|
144
|
+
□ Does this PR do ONE thing? (1 feature OR 1 bug fix OR 1 refactor)
|
|
145
|
+
□ ⛔ Mixed changes? (auth + payment + styling in one PR = bad)
|
|
146
|
+
→ Flag: "This PR mixes [X] and [Y]. Suggest splitting."
|
|
147
|
+
|
|
148
|
+
TEST ACCOMPANIMENT:
|
|
149
|
+
□ Did changed LOGIC get new/updated tests?
|
|
150
|
+
□ If no tests added AND logic changed → 🟠 FLAG:
|
|
151
|
+
"Logic changed in [file] but no test added/updated."
|
|
152
|
+
□ If test-only PR → good, no flag needed
|
|
153
|
+
□ If config/style-only change → tests optional
|
|
154
|
+
|
|
155
|
+
COMMIT HYGIENE:
|
|
156
|
+
□ Commit messages describe WHY, not just WHAT?
|
|
157
|
+
□ No "fix", "update", "wip" without context?
|
|
158
|
+
□ Ticket/issue reference if applicable?
|
|
159
|
+
|
|
160
|
+
⛔ If PR fails Step 0 → flag BEFORE diving into code
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Checklist (12 Categories)
|
|
166
|
+
|
|
167
|
+
### 1. Architecture
|
|
10
168
|
- [ ] Single responsibility per file (max 300 lines)
|
|
11
169
|
- [ ] Dependencies flow inward (UI → Domain → Data)
|
|
12
|
-
- [ ] Follows existing project patterns
|
|
170
|
+
- [ ] Follows existing project patterns (naming, imports, structure)
|
|
13
171
|
- [ ] New files in correct directory
|
|
172
|
+
- [ ] No circular dependencies (A imports B imports A)
|
|
173
|
+
- [ ] SOLID principles not violated:
|
|
174
|
+
- SRP: each class/function does ONE thing
|
|
175
|
+
- OCP: can extend without modifying existing code
|
|
176
|
+
- DIP: depends on abstractions, not concrete implementations
|
|
14
177
|
|
|
15
|
-
### Correctness
|
|
178
|
+
### 2. Correctness
|
|
16
179
|
- [ ] All states handled: loading, success, error, empty
|
|
17
180
|
- [ ] Edge cases: null, empty, timeout, concurrent
|
|
18
181
|
- [ ] Async errors caught with meaningful handling
|
|
19
182
|
- [ ] Cleanup on unmount/dispose (listeners, timers, subscriptions)
|
|
20
183
|
- [ ] No race conditions (double-tap, concurrent API calls)
|
|
184
|
+
- [ ] Return values checked (don't ignore function results)
|
|
185
|
+
|
|
186
|
+
### 3. Boundary Conditions (dedicated — AI code fails here most)
|
|
187
|
+
- [ ] **Null/undefined**: every nullable access has guard (optional chaining, null check)
|
|
188
|
+
- [ ] **Empty collections**: `.first`, `.last`, `[0]` on potentially empty array/list
|
|
189
|
+
- [ ] **Off-by-one**: loop bounds, substring ranges, pagination start/end
|
|
190
|
+
- [ ] **Numeric limits**: integer overflow, floating point precision, division by zero
|
|
191
|
+
- [ ] **String edge cases**: empty string, whitespace-only, unicode, emoji, RTL text
|
|
192
|
+
- [ ] **Date/time**: timezone handling, DST transitions, locale-specific formats
|
|
193
|
+
- [ ] **Concurrent access**: thread safety, shared mutable state guards
|
|
194
|
+
|
|
195
|
+
### 4. Test Quality
|
|
196
|
+
- [ ] **Test exists**: changed logic has corresponding test?
|
|
197
|
+
- [ ] **Happy path**: basic success case tested?
|
|
198
|
+
- [ ] **Error path**: error/failure cases tested?
|
|
199
|
+
- [ ] **Edge cases**: null, empty, boundary values tested?
|
|
200
|
+
- [ ] **Test behavior, not implementation**: tests assert WHAT, not HOW
|
|
201
|
+
- [ ] **No flaky patterns**: no `setTimeout`, no network calls in unit tests, no order-dependent tests
|
|
202
|
+
- [ ] **Mock correctly**: only mock boundaries (API, DB, native modules) — not internal logic
|
|
203
|
+
- [ ] **Meaningful assertions**: not just `expect(result).toBeDefined()` — check actual values
|
|
204
|
+
|
|
205
|
+
### 5. Readability & Maintainability
|
|
206
|
+
- [ ] **Naming**: variables/functions/classes named clearly — intent obvious from name
|
|
207
|
+
- [ ] **Cognitive complexity**: no function > 4 levels of nesting — flatten with guard clauses
|
|
208
|
+
- [ ] **Code duplication**: no copy-pasted blocks > 5 lines — extract shared function
|
|
209
|
+
- [ ] **Dead code**: no unused imports, unreachable branches, commented-out code
|
|
210
|
+
- [ ] **Function length**: functions under 40 lines — split if longer
|
|
211
|
+
- [ ] **Magic numbers/strings**: extracted to named constants
|
|
212
|
+
- [ ] **Early returns**: guard clauses at top, not deeply nested if/else
|
|
21
213
|
|
|
22
|
-
### Performance
|
|
214
|
+
### 6. Performance
|
|
23
215
|
- [ ] 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
|
-
|
|
30
|
-
|
|
31
|
-
- [ ]
|
|
32
|
-
- [ ]
|
|
33
|
-
- [ ]
|
|
34
|
-
- [ ]
|
|
35
|
-
|
|
36
|
-
|
|
216
|
+
- [ ] Memoized where needed (memo / const / remember / useMemo / useCallback)
|
|
217
|
+
- [ ] No inline functions in render/build (causes re-renders)
|
|
218
|
+
- [ ] Images cached and resized (no full-res from CDN)
|
|
219
|
+
- [ ] No main thread blocking (heavy work on background thread)
|
|
220
|
+
- [ ] No N+1 queries (loop with async call inside — batch instead)
|
|
221
|
+
|
|
222
|
+
### 7. Security (expanded — mobile-specific depth)
|
|
223
|
+
- [ ] No hardcoded secrets or API keys (check .env usage)
|
|
224
|
+
- [ ] Secure storage for tokens (Keychain / EncryptedSharedPreferences / SecureStore)
|
|
225
|
+
- [ ] Input validated and sanitized (SQL injection, XSS via WebView)
|
|
226
|
+
- [ ] Deep links validated before navigation (check params type + authorization)
|
|
227
|
+
- [ ] No sensitive data in logs (console.log user data)
|
|
228
|
+
- [ ] **Certificate pinning** for production API communication
|
|
229
|
+
- [ ] **JWT lifecycle**: access + refresh tokens, proper expiry check, secure rotation
|
|
230
|
+
- [ ] **Build config**: debug features disabled in release, ProGuard/obfuscation enabled
|
|
231
|
+
- [ ] **Native bridge security** (RN): bridge communications validated, no arbitrary eval
|
|
232
|
+
- [ ] **Biometric auth**: fallback to passcode, proper Keychain/Keystore integration
|
|
233
|
+
- [ ] **Dependency audit**: no known CVE in dependencies (npm audit / pub outdated)
|
|
234
|
+
|
|
235
|
+
### 8. Accessibility (a11y — WCAG 2.1 mobile)
|
|
236
|
+
- [ ] **Labels**: all interactive elements have accessibility label/hint
|
|
237
|
+
- [ ] **Touch targets**: minimum 44×44pt (iOS) / 48×48dp (Android) for tappable areas
|
|
238
|
+
- [ ] **Contrast**: text-to-background ratio ≥ 4.5:1 (normal text) / ≥ 3:1 (large text)
|
|
239
|
+
- [ ] **Screen reader**: content order makes sense for VoiceOver (iOS) / TalkBack (Android)
|
|
240
|
+
- [ ] **Font scaling**: UI doesn't break with Dynamic Type / font size settings (up to 200%)
|
|
241
|
+
- [ ] **Color-only info**: information NOT conveyed through color alone (add icons/text)
|
|
242
|
+
- [ ] **Focus management**: modal traps focus, focus restored after dismiss
|
|
243
|
+
- [ ] **Motion**: animations respect prefers-reduced-motion / Reduce Motion setting
|
|
244
|
+
- [ ] **Semantic roles**: buttons are buttons, headings are headings (not styled divs/texts)
|
|
245
|
+
|
|
246
|
+
### 9. Breaking Changes (critical for mobile — old versions persist)
|
|
247
|
+
- [ ] **Public API**: did exported function signature change? → backward compatible?
|
|
248
|
+
- [ ] **Deep links**: existing deep link URLs still work with new code?
|
|
249
|
+
- [ ] **Database schema**: migration added? Backward compatible with old app versions?
|
|
250
|
+
- [ ] **Push notification payload**: old app versions won't crash on new payload format?
|
|
251
|
+
- [ ] **Analytics events**: event names/properties changed? Dashboard will still work?
|
|
252
|
+
- [ ] **Feature flags**: new feature behind flag if risky? Gradual rollout?
|
|
253
|
+
- [ ] **Min SDK bump**: if SDK requirement increased, is it documented?
|
|
254
|
+
|
|
255
|
+
### 10. Platform
|
|
37
256
|
- [ ] Both iOS + Android tested (if cross-platform)
|
|
38
|
-
- [ ] Safe areas / notch handled
|
|
39
|
-
- [ ] Keyboard handled (dismiss, avoidance)
|
|
40
|
-
- [ ] Back button handled (Android)
|
|
41
|
-
- [ ]
|
|
257
|
+
- [ ] Safe areas / notch / Dynamic Island handled
|
|
258
|
+
- [ ] Keyboard handled (dismiss, avoidance, scroll-to-input)
|
|
259
|
+
- [ ] Back button handled (Android hardware + gesture back)
|
|
260
|
+
- [ ] Permissions requested at point of use (not on app launch)
|
|
261
|
+
- [ ] Status bar / navigation bar styled correctly (light/dark)
|
|
262
|
+
|
|
263
|
+
### 11. Documentation
|
|
264
|
+
- [ ] **Complex logic commented**: non-obvious algorithms have explanation
|
|
265
|
+
- [ ] **Public API documented**: exported functions/components have JSDoc/dartdoc
|
|
266
|
+
- [ ] **Design decisions**: trade-offs explained where non-obvious ("why" not "what")
|
|
267
|
+
- [ ] **README updated**: if behavior changed, docs reflect it
|
|
268
|
+
- [ ] **TODO tracking**: TODOs have ticket/issue number (not orphaned "TODO: fix later")
|
|
269
|
+
|
|
270
|
+
### 12. i18n / Localization (if app ships internationally)
|
|
271
|
+
- [ ] **No hardcoded user-facing strings** — all extracted to translation files
|
|
272
|
+
- [ ] **RTL layout**: UI mirrors correctly for Arabic/Hebrew
|
|
273
|
+
- [ ] **Text expansion**: UI handles longer translations (German/French = +30-40%)
|
|
274
|
+
- [ ] **Locale-aware formatting**: dates, numbers, currency use Intl / locale formatters
|
|
275
|
+
- [ ] **Pluralization**: plural forms handled correctly (not just "1 item" / "N items")
|
|
42
276
|
|
|
43
277
|
---
|
|
44
278
|
|
|
@@ -46,10 +280,10 @@
|
|
|
46
280
|
|
|
47
281
|
| Level | Action | Example |
|
|
48
282
|
|-------|--------|---------|
|
|
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 |
|
|
283
|
+
| 🔴 CRITICAL | Must fix before merge | Crash, security hole, data loss, PII leak |
|
|
284
|
+
| 🟠 HIGH | Should fix before merge | Memory leak, missing error state, race condition, no tests for logic |
|
|
285
|
+
| 🟡 MEDIUM | Fix in follow-up | Naming inconsistency, missing memoization, accessibility gap |
|
|
286
|
+
| 🔵 LOW | Nice to have | Minor style, comment improvement, dead code |
|
|
53
287
|
|
|
54
288
|
---
|
|
55
289
|
|
|
@@ -58,14 +292,23 @@
|
|
|
58
292
|
**Any of these → block merge immediately:**
|
|
59
293
|
|
|
60
294
|
```
|
|
295
|
+
CODE AUTO-FAILS:
|
|
61
296
|
❌ console.log / print in production code
|
|
62
297
|
❌ Hardcoded secrets or API keys
|
|
63
298
|
❌ Force unwrap without null check (! / !! / as!)
|
|
64
299
|
❌ Empty catch blocks (error silently swallowed)
|
|
65
300
|
❌ 500+ line files
|
|
66
301
|
❌ Network call in render / build / Composable
|
|
67
|
-
❌ Index as list key
|
|
302
|
+
❌ Index as list key (key={i})
|
|
68
303
|
❌ Missing loading / error / empty state (blank screen)
|
|
304
|
+
❌ PII in logs or analytics (email, phone, SSN)
|
|
305
|
+
❌ Token stored in AsyncStorage / SharedPreferences / UserDefaults (insecure)
|
|
306
|
+
|
|
307
|
+
PR AUTO-FAILS:
|
|
308
|
+
❌ PR > 1000 LOC with no description
|
|
309
|
+
❌ Logic changed but zero tests added/updated
|
|
310
|
+
❌ Breaking change without migration guide
|
|
311
|
+
❌ New dependency added without justification
|
|
69
312
|
```
|
|
70
313
|
|
|
71
314
|
### Grounding Auto-Fail (AI-generated code)
|
|
@@ -78,24 +321,418 @@
|
|
|
78
321
|
❌ API endpoint or response shape not verified from actual service code
|
|
79
322
|
❌ Library version syntax that doesn't match installed version
|
|
80
323
|
❌ Platform API that doesn't exist in the project's min SDK
|
|
324
|
+
❌ Test that tests implementation details instead of behavior
|
|
325
|
+
❌ Overly complex solution for a simple problem (over-engineering)
|
|
81
326
|
```
|
|
82
327
|
|
|
83
328
|
---
|
|
84
329
|
|
|
85
|
-
## Review
|
|
330
|
+
## Review Output Format
|
|
331
|
+
|
|
332
|
+
```
|
|
333
|
+
⛔ PR-LEVEL:
|
|
334
|
+
Size: [N] LOC changed — [OK / ⚠️ LARGE / 🔴 SPLIT]
|
|
335
|
+
Scope: [single-purpose / mixed — list each concern]
|
|
336
|
+
Tests: [present / 🟠 missing for logic changes]
|
|
337
|
+
Commits: [clean / needs cleanup]
|
|
338
|
+
|
|
339
|
+
FINDINGS:
|
|
340
|
+
|
|
341
|
+
🔴 CRITICAL — [file:line] [category]
|
|
342
|
+
Issue: [description]
|
|
343
|
+
Impact: [what breaks / who is affected]
|
|
344
|
+
Fix: [specific code change with before/after]
|
|
345
|
+
|
|
346
|
+
🟠 HIGH — [file:line] [category]
|
|
347
|
+
Issue: [description]
|
|
348
|
+
Fix: [suggestion]
|
|
349
|
+
|
|
350
|
+
🟡 MEDIUM — [file:line] [category]
|
|
351
|
+
Suggestion: [improvement]
|
|
352
|
+
|
|
353
|
+
🔵 LOW — [file:line]
|
|
354
|
+
Note: [observation]
|
|
355
|
+
|
|
356
|
+
SUMMARY:
|
|
357
|
+
Total: [N] findings ([N] critical, [N] high, [N] medium, [N] low)
|
|
358
|
+
Verdict: [✅ APPROVE / ⚠️ APPROVE WITH COMMENTS / 🔴 CHANGES REQUESTED]
|
|
359
|
+
Blocking: [list critical + high items that must be fixed]
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
---
|
|
363
|
+
|
|
364
|
+
## Review Workflow
|
|
365
|
+
|
|
366
|
+
```
|
|
367
|
+
STEP 1: PR-LEVEL REVIEW (Step 0 above)
|
|
368
|
+
→ Check size, scope, tests, commits BEFORE reading code
|
|
369
|
+
→ Flag PR-level issues first
|
|
370
|
+
|
|
371
|
+
STEP 2: SCAN ALL CHANGED FILES
|
|
372
|
+
→ Read every file in the diff
|
|
373
|
+
→ Don't skip test files or config changes
|
|
374
|
+
→ Note: files NOT changed but SHOULD have been (missing test, missing migration)
|
|
375
|
+
|
|
376
|
+
STEP 3: RUN CHECKLIST (12 categories above)
|
|
377
|
+
→ Check each category against the changed code
|
|
378
|
+
→ Only flag issues RELEVANT to the actual changes
|
|
379
|
+
→ Don't review code that wasn't changed (unless it's directly affected)
|
|
380
|
+
|
|
381
|
+
STEP 4: CHECK BREAKING CHANGES
|
|
382
|
+
→ Did any public API / exported function change?
|
|
383
|
+
→ Did any deep link / navigation route change?
|
|
384
|
+
→ Did any database schema change?
|
|
385
|
+
→ If yes to any → verify migration/backward compatibility
|
|
386
|
+
|
|
387
|
+
STEP 5: OUTPUT REVIEW (format above)
|
|
388
|
+
→ Organized by severity, not by file
|
|
389
|
+
→ Each finding cites exact file:line
|
|
390
|
+
→ Each finding has specific fix suggestion
|
|
391
|
+
→ Summary with verdict at the end
|
|
392
|
+
|
|
393
|
+
STEP 6: RE-REVIEW (after author fixes)
|
|
394
|
+
→ Only review the CHANGED items
|
|
395
|
+
→ Verify each flagged issue is actually fixed
|
|
396
|
+
→ Don't add NEW issues on re-review (unless critical)
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
## ⛔ Grounded Review Protocol (Anti-False-Positive)
|
|
402
|
+
|
|
403
|
+
**The #1 problem with AI code review: flagging things that are ACTUALLY CORRECT.**
|
|
404
|
+
**Every finding MUST be verified before reporting. NEVER flag based on memory alone.**
|
|
405
|
+
|
|
406
|
+
### Before Flagging ANY Issue — VERIFY:
|
|
407
|
+
|
|
408
|
+
```
|
|
409
|
+
⛔ RULE: If you haven't VERIFIED it, you can NOT flag it.
|
|
410
|
+
|
|
411
|
+
VERIFICATION CHECKLIST (run for EVERY finding):
|
|
412
|
+
|
|
413
|
+
1. FUNCTION/METHOD EXISTS?
|
|
414
|
+
→ Before saying "this function doesn't exist":
|
|
415
|
+
→ Grep for it in src/ AND node_modules/[package]/
|
|
416
|
+
→ Check package.json → is the library installed?
|
|
417
|
+
→ Check the library's type definitions (.d.ts) or source
|
|
418
|
+
→ If function exists in the installed version → DO NOT FLAG
|
|
419
|
+
⛔ NEVER say "this API doesn't exist" from memory
|
|
420
|
+
✅ ALWAYS verify: Grep "[function_name]" in project + node_modules
|
|
421
|
+
|
|
422
|
+
2. API SIGNATURE CORRECT?
|
|
423
|
+
→ Before saying "wrong parameters" or "wrong return type":
|
|
424
|
+
→ Read the actual type definition from node_modules or lib source
|
|
425
|
+
→ Check which VERSION of the library is installed (package.json)
|
|
426
|
+
→ APIs change between versions — verify against INSTALLED version
|
|
427
|
+
⛔ NEVER flag "wrong usage" based on a different version's docs
|
|
428
|
+
✅ ALWAYS check: installed version → actual API signature
|
|
429
|
+
|
|
430
|
+
3. DEPRECATED?
|
|
431
|
+
→ Before saying "this is deprecated":
|
|
432
|
+
→ WebSearch "[library] [function] deprecated [version]"
|
|
433
|
+
→ Check if deprecated in the INSTALLED version, not just latest
|
|
434
|
+
→ If not deprecated in installed version → DO NOT FLAG
|
|
435
|
+
⛔ NEVER flag "deprecated" from training data — verify live
|
|
436
|
+
|
|
437
|
+
4. IS THE PATTERN ACTUALLY WRONG?
|
|
438
|
+
→ Before saying "this is an anti-pattern":
|
|
439
|
+
→ Does the project have a REASON for this pattern? (read comments, README)
|
|
440
|
+
→ Is this a deliberate trade-off? (performance vs readability)
|
|
441
|
+
→ Does the team's own style guide allow it?
|
|
442
|
+
→ Is the "better" alternative actually compatible with this project?
|
|
443
|
+
⛔ NEVER impose theoretical best practice over working project conventions
|
|
444
|
+
✅ ASK: "Is there a reason for [pattern]?" before flagging
|
|
445
|
+
|
|
446
|
+
5. WILL THE FIX ACTUALLY WORK?
|
|
447
|
+
→ Before suggesting a fix:
|
|
448
|
+
→ Does the suggested replacement function/API exist?
|
|
449
|
+
→ Is it compatible with the project's SDK version?
|
|
450
|
+
→ Does it handle the same edge cases?
|
|
451
|
+
→ Will it break other code that depends on the current behavior?
|
|
452
|
+
⛔ NEVER suggest a fix you haven't mentally traced through
|
|
453
|
+
✅ ALWAYS show: "Replace [current] with [replacement] because [verified reason]"
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
### False Positive Prevention
|
|
457
|
+
|
|
458
|
+
```
|
|
459
|
+
COMMON AI FALSE POSITIVES — DO NOT FLAG THESE:
|
|
460
|
+
|
|
461
|
+
1. "Function X doesn't exist" → it DOES exist in the installed package
|
|
462
|
+
FIX: Grep in node_modules/[package]/ before flagging
|
|
463
|
+
|
|
464
|
+
2. "This API is deprecated" → deprecated in v5, but project uses v3
|
|
465
|
+
FIX: Check installed version first
|
|
466
|
+
|
|
467
|
+
3. "Missing error handling" → error is handled by parent/wrapper/interceptor
|
|
468
|
+
FIX: Trace the full call chain before flagging
|
|
469
|
+
|
|
470
|
+
4. "Unused variable" → used in JSX below, or by framework convention
|
|
471
|
+
FIX: Read the FULL file, not just the function
|
|
472
|
+
|
|
473
|
+
5. "Should use X instead of Y" → X doesn't exist in this framework version
|
|
474
|
+
FIX: Verify X exists in installed version
|
|
475
|
+
|
|
476
|
+
6. "This will crash on null" → data is guaranteed non-null by API contract/types
|
|
477
|
+
FIX: Check the TypeScript type or API response contract
|
|
478
|
+
|
|
479
|
+
7. "Security issue: no input validation" → validation done at API layer
|
|
480
|
+
FIX: Check if validation exists elsewhere in the pipeline
|
|
481
|
+
|
|
482
|
+
8. "Performance: should memoize" → component only renders once or has no expensive children
|
|
483
|
+
FIX: Check if memoization actually helps (it adds overhead too)
|
|
484
|
+
|
|
485
|
+
RULE: When in doubt → DO NOT FLAG. Ask instead:
|
|
486
|
+
"I noticed [pattern]. Is there a specific reason for this approach?"
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
### Confidence Levels for Findings
|
|
490
|
+
|
|
491
|
+
```
|
|
492
|
+
Every finding SHOULD have a confidence level:
|
|
493
|
+
|
|
494
|
+
🟢 HIGH CONFIDENCE — Flag with certainty
|
|
495
|
+
→ You read the code and traced the exact bug path
|
|
496
|
+
→ You verified the API doesn't exist in the installed version
|
|
497
|
+
→ You have concrete evidence (file:line, type definition, docs)
|
|
498
|
+
|
|
499
|
+
🟡 MEDIUM CONFIDENCE — Flag with caveat
|
|
500
|
+
→ Pattern looks problematic but you haven't verified full context
|
|
501
|
+
→ Prefix: "This may be intentional, but [concern]..."
|
|
502
|
+
→ Ask: "Can you confirm whether [X] is expected?"
|
|
503
|
+
|
|
504
|
+
🔴 LOW CONFIDENCE — Ask, don't flag
|
|
505
|
+
→ You're unsure if this is actually wrong
|
|
506
|
+
→ You haven't verified the library API
|
|
507
|
+
→ You're going by general knowledge, not project-specific evidence
|
|
508
|
+
→ Format: "Question: [describe what you see]. Is this intentional?"
|
|
509
|
+
|
|
510
|
+
⛔ NEVER flag LOW CONFIDENCE findings as 🔴 CRITICAL
|
|
511
|
+
⛔ NEVER flag MEDIUM CONFIDENCE findings without caveat
|
|
512
|
+
✅ ONLY flag HIGH CONFIDENCE findings as blocking issues
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
---
|
|
516
|
+
|
|
517
|
+
## Practical Usage Review (Find REAL Bugs)
|
|
518
|
+
|
|
519
|
+
**Beyond checklists — find bugs that actually crash in production.**
|
|
520
|
+
|
|
521
|
+
### Runtime Behavior Analysis
|
|
522
|
+
|
|
523
|
+
```
|
|
524
|
+
FOR EVERY changed function, trace the REAL execution:
|
|
525
|
+
|
|
526
|
+
1. DATA FLOW TRACING
|
|
527
|
+
→ Where does the input come from? (API / user input / navigation params / store)
|
|
528
|
+
→ What happens if the input is: null? empty? wrong type? huge? malformed?
|
|
529
|
+
→ Where does the output go? (UI / API call / store / navigation)
|
|
530
|
+
→ Who ELSE calls this function? (Grep for callers — side effects?)
|
|
531
|
+
|
|
532
|
+
2. TIMING & ORDER
|
|
533
|
+
→ Is this function called at the right time? (after init? before dispose?)
|
|
534
|
+
→ What if it's called TWICE rapidly? (double-tap, React strict mode)
|
|
535
|
+
→ What if network is slow? (3+ second delay between request and response)
|
|
536
|
+
→ What if user navigates away DURING the operation?
|
|
537
|
+
|
|
538
|
+
3. STATE CONSISTENCY
|
|
539
|
+
→ After this function runs, is state ALWAYS valid?
|
|
540
|
+
→ Can this function leave state in a "half-updated" condition?
|
|
541
|
+
→ If it fails midway, does it roll back or leave corrupted state?
|
|
542
|
+
→ Does it check CURRENT state before modifying? (stale closure problem)
|
|
543
|
+
|
|
544
|
+
4. INTERACTION WITH EXISTING CODE
|
|
545
|
+
→ Does this change affect OTHER features? (Grep for shared dependencies)
|
|
546
|
+
→ Does it change a shared util/service that other screens use?
|
|
547
|
+
→ If it modifies an API call, does the backend expect the new format?
|
|
548
|
+
→ If it adds a new event listener, does it conflict with existing ones?
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
### Production Bug Patterns (real-world, not theoretical)
|
|
552
|
+
|
|
553
|
+
```
|
|
554
|
+
THESE PATTERNS CAUSE PRODUCTION CRASHES — check for them specifically:
|
|
555
|
+
|
|
556
|
+
═══ CROSS-PLATFORM (all mobile) ═══
|
|
557
|
+
|
|
558
|
+
1. RACE CONDITION ON MOUNT/UNMOUNT
|
|
559
|
+
RN: async in useEffect → user navigates away → setState on unmounted → crash
|
|
560
|
+
Flutter: async callback → user pops screen → setState after dispose → crash
|
|
561
|
+
iOS: Task runs → view deallocated → access self → EXC_BAD_ACCESS
|
|
562
|
+
Android: coroutine runs → Activity destroyed → update UI → IllegalStateException
|
|
563
|
+
CHECK:
|
|
564
|
+
RN: useEffect has cleanup with cancelled flag or AbortController?
|
|
565
|
+
Flutter: check `mounted` before setState in every async callback?
|
|
566
|
+
iOS: [weak self] in every closure? Task cancelled in deinit?
|
|
567
|
+
Android: using viewModelScope (auto-cancels)? repeatOnLifecycle for flows?
|
|
568
|
+
|
|
569
|
+
2. NAVIGATION PARAM ASSUMPTION
|
|
570
|
+
RN: route.params.id → crashes from deep link without params
|
|
571
|
+
Flutter: ModalRoute.of(context)!.settings.arguments → null from deep link
|
|
572
|
+
iOS: force unwrap navigationController?.viewControllers → index out of range
|
|
573
|
+
Android: arguments!!.getString("id") → NPE from deep link
|
|
574
|
+
CHECK: every navigation param → has default value or null guard?
|
|
575
|
+
|
|
576
|
+
3. API CONTRACT MISMATCH
|
|
577
|
+
→ Frontend expects { data: [...] } but backend sends { items: [...] }
|
|
578
|
+
→ Frontend expects string but backend sends number/null
|
|
579
|
+
CHECK:
|
|
580
|
+
RN: TypeScript types match actual API response?
|
|
581
|
+
Flutter: fromJson factory handles null/missing fields?
|
|
582
|
+
iOS: Codable optional vs required fields match response?
|
|
583
|
+
Android: @SerializedName matches actual JSON keys? Nullable types correct?
|
|
584
|
+
|
|
585
|
+
4. TOKEN EXPIRY DURING LONG SESSION
|
|
586
|
+
→ User opens app → leaves for 2 hours → comes back → token expired
|
|
587
|
+
CHECK:
|
|
588
|
+
→ Auth interceptor handles token refresh?
|
|
589
|
+
→ Handles CONCURRENT 401s? (queue refresh, replay requests)
|
|
590
|
+
→ Refresh token also expired → redirect to login gracefully?
|
|
591
|
+
|
|
592
|
+
5. PLATFORM-SPECIFIC CRASH
|
|
593
|
+
→ Works on iOS, crashes on Android (or vice versa)
|
|
594
|
+
→ Common: keyboard behavior, back button, permission timing, font rendering
|
|
595
|
+
CHECK: any Platform.OS / Platform.isAndroid check → what happens on the OTHER platform?
|
|
596
|
+
|
|
597
|
+
═══ REACT NATIVE SPECIFIC ═══
|
|
598
|
+
|
|
599
|
+
6. STALE CLOSURE
|
|
600
|
+
→ useState value used inside useCallback/useEffect but not in dependency array
|
|
601
|
+
→ Symptom: function uses old state value, not current
|
|
602
|
+
→ CHECK: every state variable used inside callback → must be in deps[]
|
|
603
|
+
|
|
604
|
+
7. INFINITE RE-RENDER
|
|
605
|
+
→ Object/array created in render used as useEffect dependency
|
|
606
|
+
→ const filters = { status: 'active' }; // new ref every render
|
|
607
|
+
→ useEffect(() => { fetch(filters) }, [filters]); // infinite loop
|
|
608
|
+
→ CHECK: useEffect deps with objects/arrays → useMemo them
|
|
609
|
+
|
|
610
|
+
8. MEMORY LEAK — EVENT ACCUMULATION
|
|
611
|
+
→ addEventListener / on() without removeEventListener / off()
|
|
612
|
+
→ Each navigation adds ANOTHER listener → 50 visits → 50 listeners
|
|
613
|
+
→ CHECK: EVERY listener → has corresponding removal in useEffect cleanup
|
|
614
|
+
|
|
615
|
+
9. ASYNC STORAGE RACE
|
|
616
|
+
→ Two components read/write same AsyncStorage/MMKV key simultaneously
|
|
617
|
+
→ CHECK: shared storage key → is access serialized?
|
|
618
|
+
|
|
619
|
+
10. DEEP COPY vs SHALLOW COPY
|
|
620
|
+
→ const newState = { ...state }; // shallow — nested objects same reference
|
|
621
|
+
→ Mutating nested → mutates original → React skips re-render
|
|
622
|
+
→ CHECK: spread on nested objects → deep clone needed?
|
|
623
|
+
|
|
624
|
+
═══ FLUTTER SPECIFIC ═══
|
|
625
|
+
|
|
626
|
+
11. BUILDCONTEXT USED AFTER ASYNC GAP
|
|
627
|
+
→ await someAsync(); Navigator.of(context).pop(); → context may be invalid
|
|
628
|
+
→ CHECK: every context usage after await → guard with `if (!mounted) return;`
|
|
629
|
+
|
|
630
|
+
12. SETSTATE IN BUILD / INITSTATE SYNC
|
|
631
|
+
→ setState() called during build → "setState called during build" exception
|
|
632
|
+
→ setState() in initState without SchedulerBinding
|
|
633
|
+
→ CHECK: setState only in event handlers or SchedulerBinding.addPostFrameCallback
|
|
634
|
+
|
|
635
|
+
13. GLOBALKEY MISUSE
|
|
636
|
+
→ GlobalKey used for every list item → causes full subtree rebuild
|
|
637
|
+
→ CHECK: GlobalKey only for FormState, Scaffold, or explicit widget reference
|
|
638
|
+
→ Use ValueKey/ObjectKey for lists
|
|
639
|
+
|
|
640
|
+
14. STREAM / CONTROLLER NOT DISPOSED
|
|
641
|
+
→ StreamController / AnimationController / TextEditingController not disposed
|
|
642
|
+
→ Each screen visit creates new controller → memory leak
|
|
643
|
+
→ CHECK: every controller created → has dispose() in State.dispose()
|
|
644
|
+
|
|
645
|
+
15. PROVIDER/BLOC NOT FOUND
|
|
646
|
+
→ BlocProvider/ChangeNotifierProvider not above the widget that reads it
|
|
647
|
+
→ "Could not find the correct Provider" → crash on specific navigation path
|
|
648
|
+
→ CHECK: provider scope covers ALL screens that need it
|
|
649
|
+
|
|
650
|
+
═══ iOS SWIFT SPECIFIC ═══
|
|
651
|
+
|
|
652
|
+
16. FORCE UNWRAP CRASH (!)
|
|
653
|
+
→ let value = optional! → EXC_BAD_ACCESS if nil
|
|
654
|
+
→ Common with IBOutlet, UserDefaults, Codable
|
|
655
|
+
→ CHECK: every ! → should be guard let / if let / ?? default
|
|
86
656
|
|
|
657
|
+
17. RETAIN CYCLE (MEMORY LEAK)
|
|
658
|
+
→ closure captures self strongly → ViewController never deallocated
|
|
659
|
+
→ Each screen visit leaks entire VC + its views + its data
|
|
660
|
+
→ CHECK: every closure → [weak self] or [unowned self]
|
|
661
|
+
→ CHECK: delegate properties → declared as weak?
|
|
662
|
+
|
|
663
|
+
18. MAIN THREAD VIOLATION
|
|
664
|
+
→ UI update from background thread → crash or undefined behavior
|
|
665
|
+
→ URLSession callback → update label → random crash
|
|
666
|
+
→ CHECK: every completion handler → DispatchQueue.main.async or @MainActor
|
|
667
|
+
|
|
668
|
+
19. CODABLE CRASH ON API CHANGE
|
|
669
|
+
→ Backend adds/removes field → Codable decode fails → entire response lost
|
|
670
|
+
→ CHECK: optional properties for fields that might be absent
|
|
671
|
+
→ CHECK: custom init(from decoder:) with try? for graceful degradation
|
|
672
|
+
|
|
673
|
+
20. CORE DATA THREAD SAFETY
|
|
674
|
+
→ NSManagedObject accessed from wrong thread → crash
|
|
675
|
+
→ CHECK: perform() / performAndWait() for every Core Data operation
|
|
676
|
+
→ CHECK: NSManagedObjectID instead of passing objects between threads
|
|
677
|
+
|
|
678
|
+
═══ ANDROID KOTLIN SPECIFIC ═══
|
|
679
|
+
|
|
680
|
+
21. FORCE UNWRAP (!!) NPE
|
|
681
|
+
→ val value = nullable!! → NullPointerException
|
|
682
|
+
→ Common with Intent extras, Bundle arguments, findViewById
|
|
683
|
+
→ CHECK: every !! → should be ?. / ?: default / requireNotNull with message
|
|
684
|
+
|
|
685
|
+
22. ACTIVITY/FRAGMENT LIFECYCLE CRASH
|
|
686
|
+
→ Update UI after onDestroyView → IllegalStateException
|
|
687
|
+
→ Access binding after Fragment view destroyed → NPE
|
|
688
|
+
→ CHECK: viewLifecycleOwner for observers, _binding = null in onDestroyView
|
|
689
|
+
|
|
690
|
+
23. CONFIGURATION CHANGE CRASH
|
|
691
|
+
→ Activity recreated on rotation → lose state, duplicate Fragment
|
|
692
|
+
→ CHECK: ViewModel for state survival, savedInstanceState for transient state
|
|
693
|
+
→ CHECK: singleInstance/singleTask launch mode side effects
|
|
694
|
+
|
|
695
|
+
24. PARCELABLE SIZE LIMIT
|
|
696
|
+
→ Pass large object via Intent/Bundle → TransactionTooLargeException
|
|
697
|
+
→ CHECK: Bundle data < 500KB, pass ID instead of full object
|
|
698
|
+
|
|
699
|
+
25. COROUTINE EXCEPTION SWALLOWED
|
|
700
|
+
→ launch {} without CoroutineExceptionHandler → crash or silent failure
|
|
701
|
+
→ CHECK: supervisorScope for independent children
|
|
702
|
+
→ CHECK: try/catch inside launch, or CoroutineExceptionHandler
|
|
87
703
|
```
|
|
88
|
-
🔴 CRITICAL — [file:line]
|
|
89
|
-
Issue: [description]
|
|
90
|
-
Impact: [what breaks]
|
|
91
|
-
Fix: [specific code change]
|
|
92
704
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
705
|
+
### Library-Specific Usage Traps
|
|
706
|
+
|
|
707
|
+
```
|
|
708
|
+
VERIFY actual library behavior — NOT what you "think" it does:
|
|
709
|
+
|
|
710
|
+
REACT NATIVE:
|
|
711
|
+
→ FlatList: does onEndReached fire correctly with ListHeaderComponent?
|
|
712
|
+
→ Animated: is useNativeDriver used where possible? (can't animate layout props)
|
|
713
|
+
→ Navigation: are screen options in the right place? (Stack vs Tab vs Drawer)
|
|
714
|
+
→ StatusBar: does it reset when navigating back? (translucent gotcha)
|
|
715
|
+
|
|
716
|
+
FLUTTER:
|
|
717
|
+
→ BuildContext: not used after async gap? (mounted check required)
|
|
718
|
+
→ Key: is GlobalKey used unnecessarily? (causes rebuild of entire subtree)
|
|
719
|
+
→ Dispose: are ALL controllers disposed? (TextEditingController, AnimationController)
|
|
720
|
+
→ Platform channels: is the native side handling null correctly?
|
|
721
|
+
|
|
722
|
+
iOS SWIFT:
|
|
723
|
+
→ Combine: are cancellables stored? (without store, subscription dies immediately)
|
|
724
|
+
→ @MainActor: all UI updates on main actor? (crash if not)
|
|
725
|
+
→ Task: is cancellation checked in long operations? (isCancelled)
|
|
726
|
+
→ Codable: optional vs required fields match actual API response?
|
|
96
727
|
|
|
97
|
-
|
|
98
|
-
|
|
728
|
+
ANDROID KOTLIN:
|
|
729
|
+
→ LaunchedEffect: does it cancel correctly on recomposition?
|
|
730
|
+
→ remember: is the key correct? (wrong key = stale value)
|
|
731
|
+
→ Flow: is collect happening in lifecycle-aware scope? (repeatOnLifecycle)
|
|
732
|
+
→ Parcelable: are all custom classes Parcelable for SavedStateHandle?
|
|
733
|
+
|
|
734
|
+
⛔ NEVER flag library usage without reading the library's ACTUAL behavior
|
|
735
|
+
✅ If unsure → WebSearch "[library] [function] [version] gotchas"
|
|
99
736
|
```
|
|
100
737
|
|
|
101
738
|
---
|
|
@@ -131,3 +768,228 @@ function ProductList() {
|
|
|
131
768
|
return <FlatList data={state.data} keyExtractor={item => item.id} renderItem={...} />;
|
|
132
769
|
}
|
|
133
770
|
```
|
|
771
|
+
|
|
772
|
+
### Examples — React Native
|
|
773
|
+
|
|
774
|
+
```typescript
|
|
775
|
+
// ❌ No error handling, no loading state, index as key
|
|
776
|
+
function ProductList() {
|
|
777
|
+
const [data, setData] = useState([]);
|
|
778
|
+
useEffect(() => { api.get('/products').then(r => setData(r.data)); }, []);
|
|
779
|
+
return data.map((item, i) => <ProductCard key={i} item={item} />);
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// ✅ All states, cleanup, stable key
|
|
783
|
+
function ProductList() {
|
|
784
|
+
const [state, setState] = useState({ data: [], loading: true, error: null });
|
|
785
|
+
useEffect(() => {
|
|
786
|
+
let cancelled = false;
|
|
787
|
+
api.get('/products')
|
|
788
|
+
.then(r => { if (!cancelled) setState({ data: r.data, loading: false, error: null }); })
|
|
789
|
+
.catch(e => { if (!cancelled) setState({ data: [], loading: false, error: e.message }); });
|
|
790
|
+
return () => { cancelled = true; };
|
|
791
|
+
}, []);
|
|
792
|
+
if (state.loading) return <LoadingSkeleton />;
|
|
793
|
+
if (state.error) return <ErrorView message={state.error} onRetry={refresh} />;
|
|
794
|
+
if (!state.data.length) return <EmptyState />;
|
|
795
|
+
return <FlatList data={state.data} keyExtractor={item => item.id} renderItem={...} />;
|
|
796
|
+
}
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
### Examples — Flutter
|
|
800
|
+
|
|
801
|
+
```dart
|
|
802
|
+
// ❌ No mounted check, no error handling, setState in async
|
|
803
|
+
class _ProductListState extends State<ProductList> {
|
|
804
|
+
List<Product> data = [];
|
|
805
|
+
void initState() {
|
|
806
|
+
super.initState();
|
|
807
|
+
api.getProducts().then((r) => setState(() => data = r)); // crash if unmounted
|
|
808
|
+
}
|
|
809
|
+
Widget build(ctx) => ListView(children: data.map((e) => ProductCard(e)).toList()); // not lazy
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// ✅ mounted check, error handling, ListView.builder
|
|
813
|
+
class _ProductListState extends State<ProductList> {
|
|
814
|
+
List<Product> data = [];
|
|
815
|
+
bool loading = true;
|
|
816
|
+
String? error;
|
|
817
|
+
|
|
818
|
+
@override
|
|
819
|
+
void initState() {
|
|
820
|
+
super.initState();
|
|
821
|
+
_loadData();
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
Future<void> _loadData() async {
|
|
825
|
+
try {
|
|
826
|
+
final result = await api.getProducts();
|
|
827
|
+
if (!mounted) return; // guard
|
|
828
|
+
setState(() { data = result; loading = false; });
|
|
829
|
+
} catch (e) {
|
|
830
|
+
if (!mounted) return;
|
|
831
|
+
setState(() { error = e.toString(); loading = false; });
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
@override
|
|
836
|
+
Widget build(BuildContext context) {
|
|
837
|
+
if (loading) return const Center(child: CircularProgressIndicator());
|
|
838
|
+
if (error != null) return ErrorView(message: error!, onRetry: _loadData);
|
|
839
|
+
if (data.isEmpty) return const EmptyState();
|
|
840
|
+
return ListView.builder(
|
|
841
|
+
itemCount: data.length,
|
|
842
|
+
itemBuilder: (ctx, i) => ProductCard(key: ValueKey(data[i].id), product: data[i]),
|
|
843
|
+
);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
```
|
|
847
|
+
|
|
848
|
+
### Examples — iOS Swift
|
|
849
|
+
|
|
850
|
+
```swift
|
|
851
|
+
// ❌ Force unwrap, no error handling, main thread violation
|
|
852
|
+
class ProductListVC: UIViewController {
|
|
853
|
+
var data: [Product] = []
|
|
854
|
+
override func viewDidLoad() {
|
|
855
|
+
super.viewDidLoad()
|
|
856
|
+
api.getProducts { result in
|
|
857
|
+
self.data = result! // force unwrap + retain cycle + background thread UI update
|
|
858
|
+
self.tableView.reloadData()
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// ✅ Weak self, guard let, main thread, error handling
|
|
864
|
+
class ProductListVC: UIViewController {
|
|
865
|
+
private var data: [Product] = []
|
|
866
|
+
private var task: Task<Void, Never>?
|
|
867
|
+
|
|
868
|
+
override func viewDidLoad() {
|
|
869
|
+
super.viewDidLoad()
|
|
870
|
+
loadData()
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
private func loadData() {
|
|
874
|
+
task = Task { [weak self] in
|
|
875
|
+
do {
|
|
876
|
+
let result = try await api.getProducts()
|
|
877
|
+
guard let self, !Task.isCancelled else { return }
|
|
878
|
+
await MainActor.run {
|
|
879
|
+
self.data = result
|
|
880
|
+
self.tableView.reloadData()
|
|
881
|
+
}
|
|
882
|
+
} catch {
|
|
883
|
+
guard let self, !Task.isCancelled else { return }
|
|
884
|
+
await MainActor.run { self.showError(error.localizedDescription) }
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
deinit { task?.cancel() }
|
|
890
|
+
}
|
|
891
|
+
```
|
|
892
|
+
|
|
893
|
+
### Examples — Android Kotlin
|
|
894
|
+
|
|
895
|
+
```kotlin
|
|
896
|
+
// ❌ Force unwrap, no lifecycle awareness, leak
|
|
897
|
+
class ProductListFragment : Fragment() {
|
|
898
|
+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
899
|
+
lifecycleScope.launch {
|
|
900
|
+
val data = api.getProducts()!! // force unwrap
|
|
901
|
+
binding.recyclerView.adapter = ProductAdapter(data) // binding may be null
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
// ✅ ViewModel, StateFlow, lifecycle-aware, null-safe
|
|
907
|
+
// ViewModel
|
|
908
|
+
class ProductListViewModel : ViewModel() {
|
|
909
|
+
private val _uiState = MutableStateFlow<UiState<List<Product>>>(UiState.Loading)
|
|
910
|
+
val uiState: StateFlow<UiState<List<Product>>> = _uiState.asStateFlow()
|
|
911
|
+
|
|
912
|
+
init { loadData() }
|
|
913
|
+
|
|
914
|
+
private fun loadData() {
|
|
915
|
+
viewModelScope.launch {
|
|
916
|
+
try {
|
|
917
|
+
val data = api.getProducts()
|
|
918
|
+
_uiState.value = if (data.isEmpty()) UiState.Empty else UiState.Success(data)
|
|
919
|
+
} catch (e: Exception) {
|
|
920
|
+
_uiState.value = UiState.Error(e.message ?: "Unknown error")
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
// Fragment
|
|
927
|
+
class ProductListFragment : Fragment() {
|
|
928
|
+
private val viewModel: ProductListViewModel by viewModels()
|
|
929
|
+
private var _binding: FragmentProductListBinding? = null
|
|
930
|
+
private val binding get() = _binding!!
|
|
931
|
+
|
|
932
|
+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
933
|
+
viewLifecycleOwner.lifecycleScope.launch {
|
|
934
|
+
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
|
935
|
+
viewModel.uiState.collect { state ->
|
|
936
|
+
when (state) {
|
|
937
|
+
is UiState.Loading -> showLoading()
|
|
938
|
+
is UiState.Success -> showData(state.data)
|
|
939
|
+
is UiState.Empty -> showEmpty()
|
|
940
|
+
is UiState.Error -> showError(state.message)
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
override fun onDestroyView() { super.onDestroyView(); _binding = null }
|
|
948
|
+
}
|
|
949
|
+
```
|
|
950
|
+
|
|
951
|
+
### Accessibility Examples (all platforms)
|
|
952
|
+
|
|
953
|
+
```tsx
|
|
954
|
+
// ❌ RN — No accessibility
|
|
955
|
+
<TouchableOpacity onPress={onDelete}><Image source={trashIcon} /></TouchableOpacity>
|
|
956
|
+
|
|
957
|
+
// ✅ RN — Accessible
|
|
958
|
+
<TouchableOpacity onPress={onDelete} accessibilityLabel="Delete item"
|
|
959
|
+
accessibilityRole="button" style={{ minWidth: 44, minHeight: 44 }}>
|
|
960
|
+
<Image source={trashIcon} />
|
|
961
|
+
</TouchableOpacity>
|
|
962
|
+
```
|
|
963
|
+
|
|
964
|
+
```dart
|
|
965
|
+
// ❌ Flutter — No semantics
|
|
966
|
+
IconButton(icon: Icon(Icons.delete), onPressed: onDelete)
|
|
967
|
+
|
|
968
|
+
// ✅ Flutter — Accessible
|
|
969
|
+
Semantics(
|
|
970
|
+
label: 'Delete item', button: true,
|
|
971
|
+
child: IconButton(icon: Icon(Icons.delete), onPressed: onDelete,
|
|
972
|
+
constraints: BoxConstraints(minWidth: 48, minHeight: 48)),
|
|
973
|
+
)
|
|
974
|
+
```
|
|
975
|
+
|
|
976
|
+
```swift
|
|
977
|
+
// ❌ iOS — No accessibility
|
|
978
|
+
button.setImage(UIImage(named: "trash"), for: .normal)
|
|
979
|
+
|
|
980
|
+
// ✅ iOS — Accessible
|
|
981
|
+
button.setImage(UIImage(named: "trash"), for: .normal)
|
|
982
|
+
button.accessibilityLabel = "Delete item"
|
|
983
|
+
button.accessibilityTraits = .button
|
|
984
|
+
// Touch target >= 44x44pt (set via constraints)
|
|
985
|
+
```
|
|
986
|
+
|
|
987
|
+
```xml
|
|
988
|
+
<!-- ❌ Android — No content description -->
|
|
989
|
+
<ImageButton android:src="@drawable/ic_delete" />
|
|
990
|
+
|
|
991
|
+
<!-- ✅ Android — Accessible -->
|
|
992
|
+
<ImageButton android:src="@drawable/ic_delete"
|
|
993
|
+
android:contentDescription="@string/delete_item"
|
|
994
|
+
android:minWidth="48dp" android:minHeight="48dp" />
|
|
995
|
+
```
|