@buivietphi/skill-mobile-mt 2.0.0 β†’ 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
- ## Checklist
8
+ ## Platform Focus Rule
8
9
 
9
- ### Architecture
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 / review toΓ n bα»™" β†’ FULL β†’ Read ALL src/ files β†’ 12-category checklist
54
+ "Review thay Δ‘α»•i / review changes" β†’ CHANGES β†’ git diff (unstaged + staged) β†’ review only changed code
55
+ "Review file X / review file nΓ y" β†’ FILE β†’ Read specific file(s) β†’ 12-category checklist on those files only
56
+ "Review function X / review hΓ m nΓ y" β†’ 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 cΓ‘c file Δ‘Γ£ sα»­a / modified files" β†’ MODIFIED β†’ git status (modified only) β†’ read + review modified files
59
+ "Review commit / review cΓ‘c commit" β†’ COMMITS β†’ git log β†’ git show [commit] β†’ review each commit's changes
60
+ "Check PR / check review 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 hoαΊ·c file cα»₯ thể nΓ o?"
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
- ### 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
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
- - [ ] Accessibility labels on interactive elements
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 Comment Templates
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
- 🟠 HIGH β€” [file:line]
94
- Issue: [description]
95
- Fix: [suggestion]
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
- 🟑 MEDIUM β€” [file:line]
98
- Suggestion: [improvement]
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
+ ```