mobile-ai-agents 1.0.23
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 +228 -0
- package/CLAUDE.md +236 -0
- package/LICENSE +21 -0
- package/README.md +652 -0
- package/agents/android/android-crash-analyzer/README.md +134 -0
- package/agents/android/android-crash-analyzer/agent.md +206 -0
- package/agents/android/android-crash-analyzer/example-input.md +138 -0
- package/agents/android/android-crash-analyzer/example-output.md +225 -0
- package/agents/android/anr-investigation/README.md +78 -0
- package/agents/android/anr-investigation/agent.md +117 -0
- package/agents/android/anr-investigation/example-input.md +62 -0
- package/agents/android/anr-investigation/example-output.md +103 -0
- package/agents/android/axiom/agent.md +197 -0
- package/agents/android/code-reviewer/README.md +108 -0
- package/agents/android/code-reviewer/agent.md +210 -0
- package/agents/android/code-reviewer/example-input.md +116 -0
- package/agents/android/code-reviewer/example-output.md +115 -0
- package/agents/android/compose-navigation/README.md +76 -0
- package/agents/android/compose-navigation/agent.md +121 -0
- package/agents/android/compose-navigation/example-input.md +49 -0
- package/agents/android/compose-navigation/example-output.md +325 -0
- package/agents/android/compose-screen-builder/README.md +95 -0
- package/agents/android/compose-screen-builder/agent.md +170 -0
- package/agents/android/compose-screen-builder/example-input.md +40 -0
- package/agents/android/compose-screen-builder/example-output.md +568 -0
- package/agents/android/compose-ui-reviewer/README.md +73 -0
- package/agents/android/compose-ui-reviewer/agent.md +253 -0
- package/agents/android/compose-ui-reviewer/example-input.md +84 -0
- package/agents/android/compose-ui-reviewer/example-output.md +102 -0
- package/agents/android/crash-analyzer/README.md +53 -0
- package/agents/android/crash-analyzer/agent.md +295 -0
- package/agents/android/memory-leak-analyzer/README.md +79 -0
- package/agents/android/memory-leak-analyzer/agent.md +116 -0
- package/agents/android/memory-leak-analyzer/example-input.md +78 -0
- package/agents/android/memory-leak-analyzer/example-output.md +128 -0
- package/agents/cross-platform/accessibility-auditor/README.md +47 -0
- package/agents/cross-platform/accessibility-auditor/agent.md +268 -0
- package/agents/cross-platform/accessibility-auditor/example-input.md +84 -0
- package/agents/cross-platform/accessibility-auditor/example-output.md +132 -0
- package/agents/cross-platform/appforge/agent.md +303 -0
- package/agents/cross-platform/ci-cd-generator/README.md +65 -0
- package/agents/cross-platform/ci-cd-generator/agent.md +422 -0
- package/agents/cross-platform/ci-cd-generator/example-input.md +69 -0
- package/agents/cross-platform/ci-cd-generator/example-output.md +261 -0
- package/agents/cross-platform/crasher/agent.md +221 -0
- package/agents/cross-platform/figma/agent.md +247 -0
- package/agents/cross-platform/launchpad/agent.md +330 -0
- package/agents/cross-platform/mobile-harness/agent.md +422 -0
- package/agents/cross-platform/mobile-memory/agent.md +429 -0
- package/agents/cross-platform/perf/agent.md +261 -0
- package/agents/cross-platform/pipeline/agent.md +288 -0
- package/agents/cross-platform/release-notes-generator/README.md +63 -0
- package/agents/cross-platform/release-notes-generator/agent.md +218 -0
- package/agents/cross-platform/release-notes-generator/example-input.md +71 -0
- package/agents/cross-platform/release-notes-generator/example-output.md +96 -0
- package/agents/cross-platform/scribe/agent.md +180 -0
- package/agents/cross-platform/security-scanner/README.md +61 -0
- package/agents/cross-platform/security-scanner/agent.md +287 -0
- package/agents/cross-platform/security-scanner/example-input.md +77 -0
- package/agents/cross-platform/security-scanner/example-output.md +141 -0
- package/agents/cross-platform/sentinel/agent.md +217 -0
- package/agents/cross-platform/store-listing-writer/README.md +58 -0
- package/agents/cross-platform/store-listing-writer/agent.md +380 -0
- package/agents/cross-platform/store-listing-writer/example-input.md +67 -0
- package/agents/cross-platform/store-listing-writer/example-output.md +131 -0
- package/agents/flutter/bloc-feature-builder/README.md +101 -0
- package/agents/flutter/bloc-feature-builder/agent.md +225 -0
- package/agents/flutter/bloc-feature-builder/example-input.md +47 -0
- package/agents/flutter/bloc-feature-builder/example-output.md +838 -0
- package/agents/flutter/dart/agent.md +223 -0
- package/agents/flutter/widget-generator/README.md +66 -0
- package/agents/flutter/widget-generator/agent.md +264 -0
- package/agents/flutter/widget-generator/example-input.md +71 -0
- package/agents/flutter/widget-generator/example-output.md +163 -0
- package/agents/gaming/forge/agent.md +197 -0
- package/agents/gaming/unreal/agent.md +200 -0
- package/agents/ios/crash-analyzer/README.md +69 -0
- package/agents/ios/crash-analyzer/agent.md +300 -0
- package/agents/ios/crash-analyzer/example-input.md +83 -0
- package/agents/ios/crash-analyzer/example-output.md +126 -0
- package/agents/ios/swift/agent.md +206 -0
- package/agents/ios/swift-reviewer/README.md +68 -0
- package/agents/ios/swift-reviewer/agent.md +231 -0
- package/agents/ios/swift-reviewer/example-input.md +100 -0
- package/agents/ios/swift-reviewer/example-output.md +127 -0
- package/agents/react-native/bridge/agent.md +212 -0
- package/agents/react-native/performance-optimizer/README.md +81 -0
- package/agents/react-native/performance-optimizer/agent.md +284 -0
- package/agents/react-native/performance-optimizer/example-input.md +104 -0
- package/agents/react-native/performance-optimizer/example-output.md +132 -0
- package/agents/unity/shader-generator/README.md +77 -0
- package/agents/unity/shader-generator/agent.md +270 -0
- package/agents/unity/shader-generator/example-input.md +70 -0
- package/agents/unity/shader-generator/example-output.md +160 -0
- package/agents/unreal/blueprint-advisor/README.md +71 -0
- package/agents/unreal/blueprint-advisor/agent.md +311 -0
- package/agents/unreal/blueprint-advisor/example-input.md +73 -0
- package/agents/unreal/blueprint-advisor/example-output.md +217 -0
- package/cli/index.js +1091 -0
- package/docs/agent-guide.md +138 -0
- package/docs/chatgpt-integration.md +219 -0
- package/docs/core-loops.md +143 -0
- package/docs/cursor-integration.md +143 -0
- package/docs/getting-started.md +132 -0
- package/docs/index.html +711 -0
- package/docs/installation.md +271 -0
- package/docs/mobile-harness.md +160 -0
- package/docs/mobile-mcp.md +172 -0
- package/docs/mobile-memory.md +349 -0
- package/docs/release-process.md +85 -0
- package/docs/releases/release-v1.0.0.md +165 -0
- package/docs/roadmap/github-issues.md +239 -0
- package/docs/skill-categories.md +125 -0
- package/docs/vscode-copilot-integration.md +104 -0
- package/docs/wiki.md +615 -0
- package/install.sh +507 -0
- package/mobile-karpathy.md +79 -0
- package/package.json +57 -0
- package/skills/android/README.md +31 -0
- package/skills/android/android-tdd.md +89 -0
- package/skills/android/anr-investigation.md +251 -0
- package/skills/android/code-review.md +98 -0
- package/skills/android/compose-migration.md +48 -0
- package/skills/android/compose-review.md +78 -0
- package/skills/android/kotlin-modernize.md +51 -0
- package/skills/android/memory-leak-investigation.md +256 -0
- package/skills/android/proguard-rules.md +48 -0
- package/skills/cross-platform/accessibility-audit.md +63 -0
- package/skills/cross-platform/api-versioning.md +59 -0
- package/skills/cross-platform/clean-code-audit.md +239 -0
- package/skills/cross-platform/crash-triage.md +53 -0
- package/skills/cross-platform/deeplink-debug.md +57 -0
- package/skills/cross-platform/feature-slice.md +48 -0
- package/skills/cross-platform/grill-mobile.md +64 -0
- package/skills/cross-platform/mobile-mcp-qa.md +193 -0
- package/skills/cross-platform/mobile-memory-graph.md +179 -0
- package/skills/cross-platform/mobile-memory-save.md +210 -0
- package/skills/cross-platform/mobile-memory-search.md +154 -0
- package/skills/cross-platform/perf-audit.md +61 -0
- package/skills/cross-platform/prd-verification.md +324 -0
- package/skills/cross-platform/release-prep.md +64 -0
- package/skills/cross-platform/security-audit.md +281 -0
- package/skills/cross-platform/store-listing.md +75 -0
- package/skills/flutter/README.md +32 -0
- package/skills/flutter/dart-modernize.md +47 -0
- package/skills/flutter/flutter-review.md +59 -0
- package/skills/flutter/flutter-tdd.md +73 -0
- package/skills/flutter/widget-extract.md +41 -0
- package/skills/flutter/widget-gen.md +131 -0
- package/skills/gaming/blueprint-to-cpp.md +51 -0
- package/skills/gaming/game-perf.md +54 -0
- package/skills/gaming/shader-gen.md +44 -0
- package/skills/gaming/unity-tdd.md +85 -0
- package/skills/ios/README.md +45 -0
- package/skills/ios/data-persistence.md +151 -0
- package/skills/ios/ios-tdd.md +90 -0
- package/skills/ios/networking.md +139 -0
- package/skills/ios/performance.md +122 -0
- package/skills/ios/swift-concurrency.md +57 -0
- package/skills/ios/swift-review.md +124 -0
- package/skills/ios/swiftui-review.md +49 -0
- package/skills/ios/swiftui-state.md +122 -0
- package/skills/ios/unit-testing.md +144 -0
- package/skills/ios/xcode-warnings.md +37 -0
- package/skills/react-native/README.md +42 -0
- package/skills/react-native/bridge-audit.md +104 -0
- package/skills/react-native/expo-optimize.md +55 -0
- package/skills/react-native/new-arch-migrate.md +53 -0
- package/skills/react-native/performance.md +124 -0
- package/skills/react-native/rn-review.md +53 -0
- package/skills/react-native/rn-tdd.md +76 -0
- package/skills/shared/README.md +46 -0
- package/skills/shared/accessibility-audit.md +132 -0
- package/skills/shared/crash-analysis.md +110 -0
- package/skills/shared/security-scan.md +149 -0
- package/skills/unity/README.md +31 -0
- package/skills/unity/shader-review.md +110 -0
- package/templates/agent-template.md +88 -0
- package/templates/prompt-template.md +50 -0
- package/templates/skill-template.md +64 -0
- package/templates/workflow-template.md +48 -0
- package/workflows/app-launch.md +66 -0
- package/workflows/appforge-workflow.md +232 -0
- package/workflows/ci-setup.md +77 -0
- package/workflows/crash-to-fix.md +71 -0
- package/workflows/feature-ship.md +61 -0
- package/workflows/game-level.md +72 -0
- package/workflows/mobile-harness.md +319 -0
- package/workflows/mobile-mcp-qa.md +228 -0
- package/workflows/mobile-memory-workflow.md +243 -0
- package/workflows/new-project-setup.md +93 -0
- package/workflows/new-screen.md +68 -0
- package/workflows/perf-sprint.md +78 -0
- package/workflows/release-cycle.md +71 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Skill — Swift Code Review
|
|
2
|
+
|
|
3
|
+
**Platform:** iOS / macOS (Swift)
|
|
4
|
+
**Category:** Code Quality
|
|
5
|
+
**Composable With:** agents/ios/swift-reviewer/agent.md
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Purpose
|
|
10
|
+
|
|
11
|
+
Primes an LLM with the most common Swift safety rules for a focused code review session. Use standalone for quick reviews or compose into the full Swift Reviewer agent.
|
|
12
|
+
|
|
13
|
+
## When to Use
|
|
14
|
+
|
|
15
|
+
- Quick inline review of a Swift class during a pair-programming session
|
|
16
|
+
- When you want only memory safety and concurrency checks, not the full structured report
|
|
17
|
+
- As a pre-commit hook prompt for Swift files
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Skill Prompt
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
When reviewing Swift code, enforce these rules:
|
|
25
|
+
|
|
26
|
+
MEMORY SAFETY (ARC)
|
|
27
|
+
- Every escaping closure that captures self must use [weak self] unless there is an explicit
|
|
28
|
+
documented reason to use [unowned self]. Unowned is only safe when self is guaranteed to
|
|
29
|
+
outlive the closure (e.g., non-escaping closures, closures stored inside self).
|
|
30
|
+
- After [weak self], always guard let self = self else { return } before accessing self.
|
|
31
|
+
- Flag every delegate property that is not declared weak — strong delegates create retain cycles.
|
|
32
|
+
- Flag NotificationCenter observers not removed in deinit (use addObserver with a token
|
|
33
|
+
and store it as a property for automatic cleanup).
|
|
34
|
+
|
|
35
|
+
FORCE UNWRAP
|
|
36
|
+
- Every ! operator is a candidate for removal.
|
|
37
|
+
- Preferred alternatives:
|
|
38
|
+
guard let x = optional else { return/throw/break }
|
|
39
|
+
optional ?? defaultValue
|
|
40
|
+
if let x = optional { ... }
|
|
41
|
+
- The only acceptable ! is in @IBOutlet and in unit test assertions.
|
|
42
|
+
|
|
43
|
+
MAIN THREAD SAFETY
|
|
44
|
+
- All UIKit/SwiftUI mutations must occur on the main thread.
|
|
45
|
+
- Flag any DispatchQueue.global().async block that writes to a @Published property,
|
|
46
|
+
UILabel, UIImageView, or any UI element without DispatchQueue.main.async wrapping.
|
|
47
|
+
- Prefer @MainActor annotation on ObservableObject classes over scattered DispatchQueue.main.async.
|
|
48
|
+
|
|
49
|
+
CONCURRENCY (Swift 5.9+)
|
|
50
|
+
- Prefer async/await over completion handlers for new code.
|
|
51
|
+
- Flag Task.detached — it loses the actor context and should be used only with explicit justification.
|
|
52
|
+
- Flag unstructured Task { } in view bodies — use .task { } modifier instead (auto-cancelled on disappear).
|
|
53
|
+
- Sendable conformance: types shared across actors must be Sendable or @unchecked Sendable with a comment.
|
|
54
|
+
|
|
55
|
+
TESTABILITY
|
|
56
|
+
- No singletons accessed directly: NetworkManager.shared, DatabaseManager.shared accessed
|
|
57
|
+
from within a class under test make it untestable — inject via protocol.
|
|
58
|
+
- All testable classes should accept dependencies via init parameters with protocol types.
|
|
59
|
+
|
|
60
|
+
For each issue: state the line or property name, what is wrong, and the corrected Swift snippet.
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Example
|
|
66
|
+
|
|
67
|
+
**Input (after applying skill):**
|
|
68
|
+
```
|
|
69
|
+
Review this Swift class:
|
|
70
|
+
|
|
71
|
+
class ImageLoader: ObservableObject {
|
|
72
|
+
@Published var image: UIImage?
|
|
73
|
+
|
|
74
|
+
func load(url: URL) {
|
|
75
|
+
URLSession.shared.dataTask(with: url) { data, _, _ in
|
|
76
|
+
if let data = data {
|
|
77
|
+
self.image = UIImage(data: data) // Main thread violation
|
|
78
|
+
}
|
|
79
|
+
}.resume()
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**Output:**
|
|
85
|
+
```
|
|
86
|
+
Line 6 — Strong self capture in completion handler
|
|
87
|
+
Problem: self.image is a @Published property that drives SwiftUI/UIKit updates.
|
|
88
|
+
Assigning it from a background URLSession callback is a main thread violation.
|
|
89
|
+
Fix:
|
|
90
|
+
URLSession.shared.dataTask(with: url) { [weak self] data, _, _ in
|
|
91
|
+
guard let self, let data = data else { return }
|
|
92
|
+
DispatchQueue.main.async {
|
|
93
|
+
self.image = UIImage(data: data)
|
|
94
|
+
}
|
|
95
|
+
}.resume()
|
|
96
|
+
|
|
97
|
+
Better — use async/await instead:
|
|
98
|
+
@MainActor
|
|
99
|
+
func load(url: URL) async throws {
|
|
100
|
+
let (data, _) = try await URLSession.shared.data(from: url)
|
|
101
|
+
image = UIImage(data: data)
|
|
102
|
+
}
|
|
103
|
+
// @MainActor guarantees all property mutations are on the main thread.
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Composition Example
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
Prepend this skill prompt before the Swift Code Reviewer system prompt to add
|
|
112
|
+
ARC, force-unwrap, and concurrency checks alongside the full structured output.
|
|
113
|
+
|
|
114
|
+
Or use standalone: paste this skill, then paste your Swift class and ask
|
|
115
|
+
"Review this for the issues listed above."
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Notes
|
|
121
|
+
|
|
122
|
+
- Swift 6 strict concurrency makes many of these runtime errors into compile-time errors.
|
|
123
|
+
Set `SWIFT_STRICT_CONCURRENCY = complete` in your Xcode build settings to catch them early.
|
|
124
|
+
- Objective-C interop files may require @objc annotations — the skill does not cover ObjC patterns.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Skill — /swiftui-review
|
|
2
|
+
|
|
3
|
+
**Platform:** iOS (SwiftUI)
|
|
4
|
+
**Slash Command:** `/swiftui-review`
|
|
5
|
+
**Composable With:** agents/ios/swift/agent.md
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Purpose
|
|
10
|
+
|
|
11
|
+
SwiftUI view lifecycle audit — unnecessary redraws, incorrect state ownership, @StateObject vs @ObservedObject misuse, and performance issues.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Skill Prompt
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
Audit the provided SwiftUI code for view lifecycle and performance issues:
|
|
19
|
+
|
|
20
|
+
1. STATE OWNERSHIP
|
|
21
|
+
- @StateObject: owns the object lifecycle (use in the view that CREATES the object).
|
|
22
|
+
- @ObservedObject: receives an externally owned object (use in child views).
|
|
23
|
+
- Confusing these causes: premature deallocation (@ObservedObject in root view) or
|
|
24
|
+
memory leaks (new @StateObject created on every parent redraw).
|
|
25
|
+
- @State: local, ephemeral state only. Never use for shared or persisted data.
|
|
26
|
+
|
|
27
|
+
2. UNNECESSARY REDRAWS
|
|
28
|
+
- Large ObservableObject with many @Published properties: every published change
|
|
29
|
+
triggers ALL views observing it. Split into focused models.
|
|
30
|
+
- body computed from non-@State, non-@Published values that change outside SwiftUI.
|
|
31
|
+
- Expensive operations in body (sorting, filtering large arrays): move to
|
|
32
|
+
computed properties on the ViewModel or use .task { } for async work.
|
|
33
|
+
|
|
34
|
+
3. LIFECYCLE HOOKS
|
|
35
|
+
- .onAppear: correct for view-visible triggers but fires again on navigation return.
|
|
36
|
+
Use .task { } for async work that should be cancellable and lifecycle-aware.
|
|
37
|
+
- .onDisappear: ensure async tasks are cancelled; use structured concurrency.
|
|
38
|
+
|
|
39
|
+
4. ENVIRONMENT
|
|
40
|
+
- @EnvironmentObject without ensuring injection — causes runtime crash if missing.
|
|
41
|
+
- Overuse of @EnvironmentObject for data not needed by every child.
|
|
42
|
+
|
|
43
|
+
5. ANIMATIONS
|
|
44
|
+
- Animations triggered by @Published changes: wrap in withAnimation { } at the
|
|
45
|
+
call site, not inside the view.
|
|
46
|
+
- matchedGeometryEffect: requires @Namespace in the parent view, not the child.
|
|
47
|
+
|
|
48
|
+
For each issue: view name, property/modifier, what causes the redraw, and the fix.
|
|
49
|
+
```
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Skill — SwiftUI State Management Review
|
|
2
|
+
|
|
3
|
+
**Platform:** iOS / macOS (SwiftUI)
|
|
4
|
+
**Category:** Code Quality
|
|
5
|
+
**Composable With:** agents/ios/swift-reviewer/agent.md, skills/ios/swift-review.md
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Purpose
|
|
10
|
+
|
|
11
|
+
Focuses review on SwiftUI-specific state management mistakes — the most common source of bugs in SwiftUI apps. Covers property wrapper selection, view re-render costs, and lifecycle pitfalls.
|
|
12
|
+
|
|
13
|
+
## When to Use
|
|
14
|
+
|
|
15
|
+
- Reviewing a SwiftUI View file for correctness
|
|
16
|
+
- When components exhibit unexpected re-renders or stale state
|
|
17
|
+
- When onboarding a developer new to SwiftUI state management
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Skill Prompt
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
When reviewing SwiftUI code, enforce these state management rules:
|
|
25
|
+
|
|
26
|
+
PROPERTY WRAPPER SELECTION
|
|
27
|
+
- @State: local, ephemeral, value-type UI state owned by this view. Never use for reference
|
|
28
|
+
types — use @StateObject instead.
|
|
29
|
+
- @StateObject: owns a reference-type ObservableObject. Created once for the view's lifetime.
|
|
30
|
+
ONLY use in the view that creates the object. Never pass an @StateObject down as @StateObject.
|
|
31
|
+
- @ObservedObject: observes an ObservableObject it does NOT own. Use when the parent creates
|
|
32
|
+
the object and passes it down. If the parent is destroyed and recreated, the @ObservedObject
|
|
33
|
+
may be reset — flag this as a bug if the state must survive parent recreation.
|
|
34
|
+
- @EnvironmentObject: injection through the view hierarchy. Flag if injected but never consumed,
|
|
35
|
+
or if used in deeply nested views without a clear injection point.
|
|
36
|
+
- @Binding: two-way connection to a parent's @State. Flag if used to mutate parent state in
|
|
37
|
+
a way that creates a callback loop (child mutates binding → parent re-renders → child re-renders).
|
|
38
|
+
- @AppStorage / @SceneStorage: persistent storage. Flag if used for large objects (>1KB) —
|
|
39
|
+
use it only for primitives and small strings.
|
|
40
|
+
|
|
41
|
+
EXPENSIVE VIEW BODY
|
|
42
|
+
- body must be a pure function of state. Flag any: network calls, file I/O, heavy computation,
|
|
43
|
+
or object instantiation directly in body (not in a .task, .onAppear, or @StateObject).
|
|
44
|
+
- Flag ForEach or List over large collections where items are not Identifiable with stable IDs.
|
|
45
|
+
- Flag animation code that runs unconditionally in body — use withAnimation { } or .animation() modifier.
|
|
46
|
+
|
|
47
|
+
LIFECYCLE PITFALLS
|
|
48
|
+
- .onAppear fires every time the view appears (tab switch, modal dismiss, navigation push/pop).
|
|
49
|
+
If the intent is "run once on first appear," use a @State Bool flag or .task (which respects
|
|
50
|
+
the view's lifetime and cancels on disappear).
|
|
51
|
+
- .task is preferred over .onAppear for async work — it is automatically cancelled when the
|
|
52
|
+
view disappears.
|
|
53
|
+
- Do not use DispatchQueue.main.async inside .task — the task already runs on the MainActor.
|
|
54
|
+
|
|
55
|
+
EQUATABLE AND RE-RENDER OPTIMIZATION
|
|
56
|
+
- Views with complex, stable subviews should conform to Equatable and use .equatable()
|
|
57
|
+
modifier to prevent unnecessary re-renders when parent state changes but this view's inputs don't.
|
|
58
|
+
- Flag @ObservedObject properties where only one property is actually used — consider splitting
|
|
59
|
+
the ObservableObject or using Combine to observe a single publisher.
|
|
60
|
+
|
|
61
|
+
For each issue: state the property/modifier/location, what is wrong, and the corrected SwiftUI code.
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Example
|
|
67
|
+
|
|
68
|
+
**Input:**
|
|
69
|
+
```
|
|
70
|
+
struct ProfileView: View {
|
|
71
|
+
@ObservedObject var viewModel = ProfileViewModel()
|
|
72
|
+
|
|
73
|
+
var body: some View {
|
|
74
|
+
VStack {
|
|
75
|
+
Text(viewModel.name)
|
|
76
|
+
}
|
|
77
|
+
.onAppear {
|
|
78
|
+
viewModel.loadProfile()
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**Output:**
|
|
85
|
+
```
|
|
86
|
+
Line 2 — @ObservedObject used where @StateObject is required
|
|
87
|
+
Problem: @ObservedObject var viewModel = ProfileViewModel() creates a new ProfileViewModel
|
|
88
|
+
on every re-render of the parent that contains ProfileView. The ViewModel loses its
|
|
89
|
+
state whenever the parent re-renders, causing loadProfile() to re-fire and losing
|
|
90
|
+
any in-progress state.
|
|
91
|
+
Fix: @StateObject private var viewModel = ProfileViewModel()
|
|
92
|
+
@StateObject guarantees the ViewModel is created once for the view's lifetime.
|
|
93
|
+
|
|
94
|
+
Line 7 — .onAppear fires on every appear, not just once
|
|
95
|
+
Problem: If ProfileView appears multiple times (e.g., tab switch), loadProfile() fires
|
|
96
|
+
each time, causing redundant network calls and potential state resets.
|
|
97
|
+
Fix: Replace .onAppear with .task for async work (auto-cancelled on disappear),
|
|
98
|
+
or add a guard inside loadProfile() to skip if already loaded.
|
|
99
|
+
.task {
|
|
100
|
+
await viewModel.loadProfile()
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Composition Example
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
Combine with skills/ios/swift-review.md for a full Swift + SwiftUI review:
|
|
110
|
+
|
|
111
|
+
[paste swift-review.md skill prompt]
|
|
112
|
+
[paste swiftui-state.md skill prompt]
|
|
113
|
+
|
|
114
|
+
Now review this SwiftUI file: [paste file]
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Notes
|
|
120
|
+
|
|
121
|
+
- @Observable (Swift 5.9 / iOS 17+) replaces ObservableObject in new code. If the project targets iOS 17+, suggest migration.
|
|
122
|
+
- The Equatable conformance optimization is most impactful in complex list cell views — measure with Instruments before applying widely.
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# Skill — iOS Unit Testing
|
|
2
|
+
|
|
3
|
+
**Platform:** iOS / macOS (Swift)
|
|
4
|
+
**Category:** Code Quality
|
|
5
|
+
**Composable With:** agents/ios/swift-reviewer/agent.md, skills/ios/swift-review.md
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Purpose
|
|
10
|
+
|
|
11
|
+
Reviews iOS unit tests (XCTest and Swift Testing) and guides generation of new tests. Covers async test patterns, mocking, isolation, and test naming conventions.
|
|
12
|
+
|
|
13
|
+
## When to Use
|
|
14
|
+
|
|
15
|
+
- When reviewing or writing tests for ViewModels, services, or use cases
|
|
16
|
+
- When onboarding a developer new to Swift Testing (iOS 17+)
|
|
17
|
+
- As a checklist before merging code with new untested logic
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Skill Prompt
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
When reviewing or writing iOS unit tests, enforce these rules:
|
|
25
|
+
|
|
26
|
+
FRAMEWORK SELECTION
|
|
27
|
+
- iOS 17+ / Swift 5.9+: prefer Swift Testing (@Test, @Suite, #expect, #require).
|
|
28
|
+
It is more expressive, supports parameterized tests, and integrates with Xcode 16.
|
|
29
|
+
- iOS 16 and below: use XCTest (XCTestCase, XCTAssertEqual, async throws methods).
|
|
30
|
+
- Do not mix frameworks in the same test target — pick one and be consistent.
|
|
31
|
+
|
|
32
|
+
SWIFT TESTING PATTERNS (@Test)
|
|
33
|
+
- Use #expect() for soft assertions (test continues on failure).
|
|
34
|
+
Use #require() for hard assertions (test stops on failure, like XCTUnwrap).
|
|
35
|
+
- Name tests as plain sentences: @Test("returns nil when userId is empty")
|
|
36
|
+
- Parameterized tests: @Test("validates email", arguments: ["", "bad", "a@b.com"])
|
|
37
|
+
func validateEmail(_ email: String) { ... }
|
|
38
|
+
- Group related tests: @Suite("ProfileViewModel") struct ProfileViewModelTests { }
|
|
39
|
+
- Async tests: mark the @Test func as async — no extra setup needed.
|
|
40
|
+
@Test func fetchProfileSucceeds() async throws { ... }
|
|
41
|
+
|
|
42
|
+
XCTEST PATTERNS
|
|
43
|
+
- Async tests: func testFetchProfile() async throws { ... } — no XCTestExpectation needed for async throws.
|
|
44
|
+
- For Combine publishers: use XCTestExpectation + sink, or use the async sequence publisher.
|
|
45
|
+
- @MainActor on the test class if testing @MainActor-isolated code:
|
|
46
|
+
@MainActor final class ProfileViewModelTests: XCTestCase { }
|
|
47
|
+
|
|
48
|
+
MOCKING
|
|
49
|
+
- Use protocol-based dependencies for all testable classes. Never mock concrete types.
|
|
50
|
+
protocol ProfileServiceProtocol { func fetchProfile(id: String) async throws -> Profile }
|
|
51
|
+
struct MockProfileService: ProfileServiceProtocol { var result: Result<Profile, Error> }
|
|
52
|
+
- Mocks should be minimal structs defined in the test file, not external libraries.
|
|
53
|
+
- Avoid Sourcery, SwiftMock, or OHHTTPStubs for unit tests — they add fragility.
|
|
54
|
+
Use URLProtocol subclasses for integration tests that hit URLSession.
|
|
55
|
+
|
|
56
|
+
TEST ISOLATION
|
|
57
|
+
- Each test must set up its own dependencies in setUp / @Test init — never share mutable state across tests.
|
|
58
|
+
- Avoid singletons in tests — inject all dependencies. If testing a singleton, reset it in tearDown.
|
|
59
|
+
- Use @TestStorage (Swift Testing) or static properties reset in setUp/tearDown to isolate global state.
|
|
60
|
+
|
|
61
|
+
ASSERTIONS
|
|
62
|
+
- Assert behavior, not implementation. Test what the system does, not how it does it.
|
|
63
|
+
Good: #expect(viewModel.state == .loaded(user))
|
|
64
|
+
Bad: #expect(mockService.fetchWasCalled == true) — tests the mock, not the ViewModel.
|
|
65
|
+
- Test the unhappy path equally: empty input, network error, decoding failure, cancellation.
|
|
66
|
+
- For async state (StateFlow, @Published): collect the state sequence with a timeout,
|
|
67
|
+
don't sleep(0.1) hoping the state has settled.
|
|
68
|
+
|
|
69
|
+
NAMING
|
|
70
|
+
- XCTest: func testGivenEmptyId_whenFetchCalled_thenReturnsError()
|
|
71
|
+
- Swift Testing: @Test("returns error when id is empty")
|
|
72
|
+
- One behavior per test. If a test method has two XCTAssert calls for unrelated things, split it.
|
|
73
|
+
|
|
74
|
+
For each issue found: state the test name, what is wrong, and the corrected test code.
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Example
|
|
80
|
+
|
|
81
|
+
**Input:**
|
|
82
|
+
```swift
|
|
83
|
+
class ProfileViewModelTests: XCTestCase {
|
|
84
|
+
func testLoad() async {
|
|
85
|
+
let vm = ProfileViewModel(service: ProfileService()) // real service
|
|
86
|
+
await vm.loadProfile(id: "1")
|
|
87
|
+
XCTAssertNotNil(vm.profile)
|
|
88
|
+
XCTAssertNil(vm.errorMessage)
|
|
89
|
+
XCTAssertEqual(vm.state, .loaded) // 3 assertions for same scenario
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Output:**
|
|
95
|
+
```
|
|
96
|
+
testLoad — uses real ProfileService (not a mock)
|
|
97
|
+
Problem: The test hits the real network. It will fail in CI, be slow, and be non-deterministic.
|
|
98
|
+
Fix: Inject a MockProfileService that returns a canned result.
|
|
99
|
+
|
|
100
|
+
testLoad — three assertions in one test
|
|
101
|
+
Problem: If the first assert fails, the others don't run, hiding additional failures.
|
|
102
|
+
Fix: One assertion per test, or use one assertion that covers the whole expected state.
|
|
103
|
+
|
|
104
|
+
Corrected:
|
|
105
|
+
struct MockProfileService: ProfileServiceProtocol {
|
|
106
|
+
var result: Result<Profile, Error> = .success(Profile(id: "1", name: "Alice"))
|
|
107
|
+
func fetchProfile(id: String) async throws -> Profile {
|
|
108
|
+
try result.get()
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
@MainActor
|
|
113
|
+
final class ProfileViewModelTests: XCTestCase {
|
|
114
|
+
func testLoadSucceeds() async throws {
|
|
115
|
+
let vm = ProfileViewModel(service: MockProfileService())
|
|
116
|
+
await vm.loadProfile(id: "1")
|
|
117
|
+
XCTAssertEqual(vm.profile?.name, "Alice")
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
func testLoadSetsErrorOnFailure() async throws {
|
|
121
|
+
let service = MockProfileService(result: .failure(URLError(.notConnectedToInternet)))
|
|
122
|
+
let vm = ProfileViewModel(service: service)
|
|
123
|
+
await vm.loadProfile(id: "1")
|
|
124
|
+
XCTAssertNotNil(vm.errorMessage)
|
|
125
|
+
XCTAssertNil(vm.profile)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Composition Example
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
Use before generating tests with prompts/ios/generate-unit-test.md:
|
|
136
|
+
paste this skill → paste the class to test → "generate a unit test following these rules."
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Notes
|
|
142
|
+
|
|
143
|
+
- Swift Testing is available from Xcode 16 / iOS 17+ minimum deployment target. For older targets, stick with XCTest.
|
|
144
|
+
- `@MainActor` on the test class is required when the ViewModel under test is `@MainActor` — otherwise actor isolation warnings become errors in Swift 6.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Skill — /xcode-warnings
|
|
2
|
+
|
|
3
|
+
**Platform:** iOS (Swift / Xcode)
|
|
4
|
+
**Slash Command:** `/xcode-warnings`
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Purpose
|
|
9
|
+
|
|
10
|
+
Explains Xcode warnings and Swift compiler warnings in plain English, then provides the correct fix. No more Stack Overflow spelunking.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Skill Prompt
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
Explain the provided Xcode or Swift compiler warning and give the correct fix:
|
|
18
|
+
|
|
19
|
+
For each warning:
|
|
20
|
+
1. State what the warning means in plain English (not compiler jargon).
|
|
21
|
+
2. Explain WHY the compiler/Xcode is flagging it — the underlying problem.
|
|
22
|
+
3. Provide the correct fix with a code snippet.
|
|
23
|
+
4. Note if there is a Quick Fix available in Xcode (lightning bolt) or if manual change is needed.
|
|
24
|
+
|
|
25
|
+
COMMON WARNINGS AND THEIR REAL MEANINGS:
|
|
26
|
+
- "Initialization of variable 'x' was never used" → you created a variable you don't read.
|
|
27
|
+
- "Result of call to 'async-named-function' is unused" → you're calling async without await.
|
|
28
|
+
- "Expression always evaluates to true/false" → redundant condition.
|
|
29
|
+
- "'weak' variable 'x' is never used" → [weak self] captured but self never called.
|
|
30
|
+
- "Immutable value 'x' was never used" → let binding where a constant wasn't needed.
|
|
31
|
+
- "Closure captures 'x' before it is declared" → capture list ordering issue.
|
|
32
|
+
- "Publishing changes from background threads is not allowed" → @Published write off main thread.
|
|
33
|
+
- "Non-sendable type 'X' in implicitly asynchronous access to..." → Swift 6 concurrency warning.
|
|
34
|
+
- Deprecation warnings: what replaces the deprecated API and why it changed.
|
|
35
|
+
|
|
36
|
+
If multiple warnings are provided, handle each one.
|
|
37
|
+
```
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# React Native Skills
|
|
2
|
+
|
|
3
|
+
Reusable skill prompt modules for React Native (TypeScript) performance reviews and bridge audits.
|
|
4
|
+
|
|
5
|
+
## Index
|
|
6
|
+
|
|
7
|
+
| Skill | File | When to Use |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| RN Performance | [performance.md](performance.md) | Re-render prevention, `useCallback`/`useMemo`, FlatList config, animation thread |
|
|
10
|
+
| Bridge Audit | [bridge-audit.md](bridge-audit.md) | Old Architecture bridge call overhead, synchronous native calls, JSI migration candidates |
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
# Quick performance triage
|
|
16
|
+
[paste skill prompt from performance.md]
|
|
17
|
+
|
|
18
|
+
Review this component for performance issues: [paste component]
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Composable Pattern
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
# Full performance + bridge audit
|
|
25
|
+
[paste performance.md skill]
|
|
26
|
+
[paste bridge-audit.md skill]
|
|
27
|
+
|
|
28
|
+
Audit this screen: [paste code]
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Composable With
|
|
32
|
+
|
|
33
|
+
- [`agents/react-native/performance-optimizer`](../../agents/react-native/performance-optimizer/) — full structured performance audit
|
|
34
|
+
|
|
35
|
+
## Contributing
|
|
36
|
+
|
|
37
|
+
Ideas for new skills:
|
|
38
|
+
- `typescript-patterns.md` — TypeScript anti-patterns in RN codebases
|
|
39
|
+
- `navigation-patterns.md` — React Navigation v7 type-safety and deep linking
|
|
40
|
+
- `testing-patterns.md` — Jest, React Native Testing Library, Detox patterns
|
|
41
|
+
|
|
42
|
+
Copy `templates/skill-template.md`.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Skill — React Native Bridge Audit
|
|
2
|
+
|
|
3
|
+
**Platform:** React Native (TypeScript)
|
|
4
|
+
**Category:** Performance & Optimization
|
|
5
|
+
**Composable With:** agents/react-native/performance-optimizer/agent.md, skills/react-native/performance.md
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Purpose
|
|
10
|
+
|
|
11
|
+
Audits React Native code specifically for Old Architecture bridge overhead — synchronous calls, excessive serialization, and patterns that block the JS thread. Also flags migration candidates for the New Architecture (JSI/Fabric).
|
|
12
|
+
|
|
13
|
+
## When to Use
|
|
14
|
+
|
|
15
|
+
- When profiling shows dropped frames but re-render analysis doesn't explain it
|
|
16
|
+
- When auditing an Old Architecture app before migrating to New Architecture
|
|
17
|
+
- When a native module feels slow even though JS logic is fast
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Skill Prompt
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
When auditing React Native code for bridge overhead, check for:
|
|
25
|
+
|
|
26
|
+
OLD ARCHITECTURE — BRIDGE CALL PATTERNS
|
|
27
|
+
- NativeModules calls in the render path or in useEffect without debouncing:
|
|
28
|
+
Each bridge call serializes arguments to JSON, crosses the bridge asynchronously,
|
|
29
|
+
deserializes on the native side, and returns the same way. In a hot path (scroll,
|
|
30
|
+
animation, frequent re-render), this compounds into dropped frames.
|
|
31
|
+
Fix: cache native module results, debounce calls, or move to useRef to avoid re-triggering.
|
|
32
|
+
|
|
33
|
+
- Synchronous native calls (NativeModules.SomeModule.callSyncMethod()):
|
|
34
|
+
Synchronous bridge calls BLOCK the JS thread until the native side returns.
|
|
35
|
+
If the native operation takes >1ms, it will cause visible jank at 60 FPS.
|
|
36
|
+
Fix: replace with async calls + caching, or migrate to JSI TurboModules (async by default,
|
|
37
|
+
faster than the bridge even for async calls).
|
|
38
|
+
|
|
39
|
+
- High-frequency bridge calls in event handlers:
|
|
40
|
+
onScroll, onMomentumScrollEnd, and gesture handlers that call native modules on every event
|
|
41
|
+
are problematic. Use the Animated API with useNativeDriver: true to keep animation logic
|
|
42
|
+
on the native side without any bridge crossing.
|
|
43
|
+
|
|
44
|
+
- Large payload serialization:
|
|
45
|
+
Passing large objects (e.g., full image buffers, large arrays) across the bridge serializes
|
|
46
|
+
them to JSON on the JS side and deserializes on native. Use native JSI references (Blob, ArrayBuffer)
|
|
47
|
+
or keep large data on the native side and pass handles (IDs) across the bridge instead.
|
|
48
|
+
|
|
49
|
+
NEW ARCHITECTURE — JSI / FABRIC MIGRATION CANDIDATES
|
|
50
|
+
Flag these patterns as HIGH priority migration candidates to JSI TurboModules:
|
|
51
|
+
- Any NativeModule called more than once per second
|
|
52
|
+
- Any synchronous NativeModule call
|
|
53
|
+
- Any module that transfers binary data (images, audio, video buffers)
|
|
54
|
+
- Custom gesture handling that uses PanResponder — migrate to react-native-gesture-handler (runs on UI thread via JSI)
|
|
55
|
+
|
|
56
|
+
FABRIC (New Renderer) COMPATIBILITY
|
|
57
|
+
- Ensure all custom native view components use the Fabric component API if targeting New Architecture.
|
|
58
|
+
- Flag any legacy requireNativeComponent() calls — they do not work with Fabric.
|
|
59
|
+
|
|
60
|
+
For each issue: identify the call site, explain the bridge cost, and suggest the JSI/async alternative.
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Example
|
|
66
|
+
|
|
67
|
+
**Input:**
|
|
68
|
+
```
|
|
69
|
+
const ProfileScreen = () => {
|
|
70
|
+
const [deviceId, setDeviceId] = useState('');
|
|
71
|
+
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
const id = NativeModules.DeviceInfo.getDeviceIdSync();
|
|
74
|
+
setDeviceId(id);
|
|
75
|
+
}, []);
|
|
76
|
+
|
|
77
|
+
return <Text>{deviceId}</Text>;
|
|
78
|
+
};
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Output:**
|
|
82
|
+
```
|
|
83
|
+
Synchronous bridge call in useEffect — HIGH impact
|
|
84
|
+
Problem: getDeviceIdSync() blocks the JS thread until the native side returns.
|
|
85
|
+
If DeviceInfo takes >1ms (disk read, system call), JS is frozen during that time.
|
|
86
|
+
This is noticeable during screen mount, especially on low-end devices.
|
|
87
|
+
Fix:
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
NativeModules.DeviceInfo.getDeviceId().then(setDeviceId);
|
|
90
|
+
// Or if using a library: DeviceInfo.getDeviceId().then(setDeviceId)
|
|
91
|
+
}, []);
|
|
92
|
+
|
|
93
|
+
Migration candidate: HIGH — device ID is fetched once; migrate to a TurboModule that
|
|
94
|
+
caches the value natively and returns via JSI without serialization cost.
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Notes
|
|
100
|
+
|
|
101
|
+
- This skill is most relevant for apps still on Old Architecture (RN < 0.74 or new arch not enabled).
|
|
102
|
+
- New Architecture apps are largely immune to bridge-crossing overhead but still benefit from
|
|
103
|
+
avoiding large payload transfers across JSI.
|
|
104
|
+
- Pair with Flipper's Bridge Spy plugin to measure actual bridge traffic before and after fixes.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Skill — /expo-optimize
|
|
2
|
+
|
|
3
|
+
**Platform:** React Native (Expo)
|
|
4
|
+
**Slash Command:** `/expo-optimize`
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Purpose
|
|
9
|
+
|
|
10
|
+
Expo config audit, OTA update strategy, bundle size reduction, and EAS Build optimization. The complete Expo production checklist.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Skill Prompt
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
Audit the provided Expo project configuration for production optimization:
|
|
18
|
+
|
|
19
|
+
APP.JSON / APP.CONFIG.JS AUDIT:
|
|
20
|
+
- Missing splash screen configuration (white flash on startup).
|
|
21
|
+
- Missing android.adaptiveIcon (Android 8+ adaptive icons).
|
|
22
|
+
- Missing ios.infoPlist privacy usage descriptions for permissions.
|
|
23
|
+
- jsEngine not set: set to "hermes" for better startup performance.
|
|
24
|
+
- Missing updates configuration for OTA updates.
|
|
25
|
+
- android.versionCode and ios.buildNumber automation strategy.
|
|
26
|
+
|
|
27
|
+
OTA UPDATE STRATEGY (expo-updates):
|
|
28
|
+
- Set updates.fallbackToCacheTimeout appropriately (default 0 is risky).
|
|
29
|
+
- Configure update channels (production, staging, preview) via eas.json.
|
|
30
|
+
- Conditionally check for updates: use Updates.checkForUpdateAsync() with user notification.
|
|
31
|
+
- Critical updates: force reload with Updates.reloadAsync().
|
|
32
|
+
- Never OTA-update native code changes (config plugins, new native modules).
|
|
33
|
+
|
|
34
|
+
BUNDLE SIZE:
|
|
35
|
+
- Run: npx expo export --analyze to identify large modules.
|
|
36
|
+
- Remove unused expo-* SDK packages from app.json plugins array.
|
|
37
|
+
- Use Metro bundle splitting for large feature modules.
|
|
38
|
+
- Replace heavy libraries with lighter alternatives:
|
|
39
|
+
moment.js → date-fns (20× smaller)
|
|
40
|
+
lodash (full) → lodash/pick (tree-shaken)
|
|
41
|
+
react-native-vector-icons → @expo/vector-icons (bundled, no native setup)
|
|
42
|
+
|
|
43
|
+
EAS BUILD OPTIMIZATION:
|
|
44
|
+
- eas.json build profiles: development (simulator), preview (APK for testing), production (AAB/IPA).
|
|
45
|
+
- Enable build cache to avoid redundant Gradle/Xcode builds.
|
|
46
|
+
- Use managed workflow unless you genuinely need custom native code.
|
|
47
|
+
- Add .easignore to exclude dev files from upload.
|
|
48
|
+
|
|
49
|
+
HERMES OPTIMIZATION:
|
|
50
|
+
- Confirm hermes is enabled (app.json jsEngine: "hermes").
|
|
51
|
+
- Hermes benefits: bytecode pre-compilation, lower memory on Android.
|
|
52
|
+
- Incompatible libraries with Hermes (flag if found in package.json).
|
|
53
|
+
|
|
54
|
+
Output a prioritized list of changes with the config/code snippet for each.
|
|
55
|
+
```
|