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.
Files changed (194) hide show
  1. package/AGENTS.md +228 -0
  2. package/CLAUDE.md +236 -0
  3. package/LICENSE +21 -0
  4. package/README.md +652 -0
  5. package/agents/android/android-crash-analyzer/README.md +134 -0
  6. package/agents/android/android-crash-analyzer/agent.md +206 -0
  7. package/agents/android/android-crash-analyzer/example-input.md +138 -0
  8. package/agents/android/android-crash-analyzer/example-output.md +225 -0
  9. package/agents/android/anr-investigation/README.md +78 -0
  10. package/agents/android/anr-investigation/agent.md +117 -0
  11. package/agents/android/anr-investigation/example-input.md +62 -0
  12. package/agents/android/anr-investigation/example-output.md +103 -0
  13. package/agents/android/axiom/agent.md +197 -0
  14. package/agents/android/code-reviewer/README.md +108 -0
  15. package/agents/android/code-reviewer/agent.md +210 -0
  16. package/agents/android/code-reviewer/example-input.md +116 -0
  17. package/agents/android/code-reviewer/example-output.md +115 -0
  18. package/agents/android/compose-navigation/README.md +76 -0
  19. package/agents/android/compose-navigation/agent.md +121 -0
  20. package/agents/android/compose-navigation/example-input.md +49 -0
  21. package/agents/android/compose-navigation/example-output.md +325 -0
  22. package/agents/android/compose-screen-builder/README.md +95 -0
  23. package/agents/android/compose-screen-builder/agent.md +170 -0
  24. package/agents/android/compose-screen-builder/example-input.md +40 -0
  25. package/agents/android/compose-screen-builder/example-output.md +568 -0
  26. package/agents/android/compose-ui-reviewer/README.md +73 -0
  27. package/agents/android/compose-ui-reviewer/agent.md +253 -0
  28. package/agents/android/compose-ui-reviewer/example-input.md +84 -0
  29. package/agents/android/compose-ui-reviewer/example-output.md +102 -0
  30. package/agents/android/crash-analyzer/README.md +53 -0
  31. package/agents/android/crash-analyzer/agent.md +295 -0
  32. package/agents/android/memory-leak-analyzer/README.md +79 -0
  33. package/agents/android/memory-leak-analyzer/agent.md +116 -0
  34. package/agents/android/memory-leak-analyzer/example-input.md +78 -0
  35. package/agents/android/memory-leak-analyzer/example-output.md +128 -0
  36. package/agents/cross-platform/accessibility-auditor/README.md +47 -0
  37. package/agents/cross-platform/accessibility-auditor/agent.md +268 -0
  38. package/agents/cross-platform/accessibility-auditor/example-input.md +84 -0
  39. package/agents/cross-platform/accessibility-auditor/example-output.md +132 -0
  40. package/agents/cross-platform/appforge/agent.md +303 -0
  41. package/agents/cross-platform/ci-cd-generator/README.md +65 -0
  42. package/agents/cross-platform/ci-cd-generator/agent.md +422 -0
  43. package/agents/cross-platform/ci-cd-generator/example-input.md +69 -0
  44. package/agents/cross-platform/ci-cd-generator/example-output.md +261 -0
  45. package/agents/cross-platform/crasher/agent.md +221 -0
  46. package/agents/cross-platform/figma/agent.md +247 -0
  47. package/agents/cross-platform/launchpad/agent.md +330 -0
  48. package/agents/cross-platform/mobile-harness/agent.md +422 -0
  49. package/agents/cross-platform/mobile-memory/agent.md +429 -0
  50. package/agents/cross-platform/perf/agent.md +261 -0
  51. package/agents/cross-platform/pipeline/agent.md +288 -0
  52. package/agents/cross-platform/release-notes-generator/README.md +63 -0
  53. package/agents/cross-platform/release-notes-generator/agent.md +218 -0
  54. package/agents/cross-platform/release-notes-generator/example-input.md +71 -0
  55. package/agents/cross-platform/release-notes-generator/example-output.md +96 -0
  56. package/agents/cross-platform/scribe/agent.md +180 -0
  57. package/agents/cross-platform/security-scanner/README.md +61 -0
  58. package/agents/cross-platform/security-scanner/agent.md +287 -0
  59. package/agents/cross-platform/security-scanner/example-input.md +77 -0
  60. package/agents/cross-platform/security-scanner/example-output.md +141 -0
  61. package/agents/cross-platform/sentinel/agent.md +217 -0
  62. package/agents/cross-platform/store-listing-writer/README.md +58 -0
  63. package/agents/cross-platform/store-listing-writer/agent.md +380 -0
  64. package/agents/cross-platform/store-listing-writer/example-input.md +67 -0
  65. package/agents/cross-platform/store-listing-writer/example-output.md +131 -0
  66. package/agents/flutter/bloc-feature-builder/README.md +101 -0
  67. package/agents/flutter/bloc-feature-builder/agent.md +225 -0
  68. package/agents/flutter/bloc-feature-builder/example-input.md +47 -0
  69. package/agents/flutter/bloc-feature-builder/example-output.md +838 -0
  70. package/agents/flutter/dart/agent.md +223 -0
  71. package/agents/flutter/widget-generator/README.md +66 -0
  72. package/agents/flutter/widget-generator/agent.md +264 -0
  73. package/agents/flutter/widget-generator/example-input.md +71 -0
  74. package/agents/flutter/widget-generator/example-output.md +163 -0
  75. package/agents/gaming/forge/agent.md +197 -0
  76. package/agents/gaming/unreal/agent.md +200 -0
  77. package/agents/ios/crash-analyzer/README.md +69 -0
  78. package/agents/ios/crash-analyzer/agent.md +300 -0
  79. package/agents/ios/crash-analyzer/example-input.md +83 -0
  80. package/agents/ios/crash-analyzer/example-output.md +126 -0
  81. package/agents/ios/swift/agent.md +206 -0
  82. package/agents/ios/swift-reviewer/README.md +68 -0
  83. package/agents/ios/swift-reviewer/agent.md +231 -0
  84. package/agents/ios/swift-reviewer/example-input.md +100 -0
  85. package/agents/ios/swift-reviewer/example-output.md +127 -0
  86. package/agents/react-native/bridge/agent.md +212 -0
  87. package/agents/react-native/performance-optimizer/README.md +81 -0
  88. package/agents/react-native/performance-optimizer/agent.md +284 -0
  89. package/agents/react-native/performance-optimizer/example-input.md +104 -0
  90. package/agents/react-native/performance-optimizer/example-output.md +132 -0
  91. package/agents/unity/shader-generator/README.md +77 -0
  92. package/agents/unity/shader-generator/agent.md +270 -0
  93. package/agents/unity/shader-generator/example-input.md +70 -0
  94. package/agents/unity/shader-generator/example-output.md +160 -0
  95. package/agents/unreal/blueprint-advisor/README.md +71 -0
  96. package/agents/unreal/blueprint-advisor/agent.md +311 -0
  97. package/agents/unreal/blueprint-advisor/example-input.md +73 -0
  98. package/agents/unreal/blueprint-advisor/example-output.md +217 -0
  99. package/cli/index.js +1091 -0
  100. package/docs/agent-guide.md +138 -0
  101. package/docs/chatgpt-integration.md +219 -0
  102. package/docs/core-loops.md +143 -0
  103. package/docs/cursor-integration.md +143 -0
  104. package/docs/getting-started.md +132 -0
  105. package/docs/index.html +711 -0
  106. package/docs/installation.md +271 -0
  107. package/docs/mobile-harness.md +160 -0
  108. package/docs/mobile-mcp.md +172 -0
  109. package/docs/mobile-memory.md +349 -0
  110. package/docs/release-process.md +85 -0
  111. package/docs/releases/release-v1.0.0.md +165 -0
  112. package/docs/roadmap/github-issues.md +239 -0
  113. package/docs/skill-categories.md +125 -0
  114. package/docs/vscode-copilot-integration.md +104 -0
  115. package/docs/wiki.md +615 -0
  116. package/install.sh +507 -0
  117. package/mobile-karpathy.md +79 -0
  118. package/package.json +57 -0
  119. package/skills/android/README.md +31 -0
  120. package/skills/android/android-tdd.md +89 -0
  121. package/skills/android/anr-investigation.md +251 -0
  122. package/skills/android/code-review.md +98 -0
  123. package/skills/android/compose-migration.md +48 -0
  124. package/skills/android/compose-review.md +78 -0
  125. package/skills/android/kotlin-modernize.md +51 -0
  126. package/skills/android/memory-leak-investigation.md +256 -0
  127. package/skills/android/proguard-rules.md +48 -0
  128. package/skills/cross-platform/accessibility-audit.md +63 -0
  129. package/skills/cross-platform/api-versioning.md +59 -0
  130. package/skills/cross-platform/clean-code-audit.md +239 -0
  131. package/skills/cross-platform/crash-triage.md +53 -0
  132. package/skills/cross-platform/deeplink-debug.md +57 -0
  133. package/skills/cross-platform/feature-slice.md +48 -0
  134. package/skills/cross-platform/grill-mobile.md +64 -0
  135. package/skills/cross-platform/mobile-mcp-qa.md +193 -0
  136. package/skills/cross-platform/mobile-memory-graph.md +179 -0
  137. package/skills/cross-platform/mobile-memory-save.md +210 -0
  138. package/skills/cross-platform/mobile-memory-search.md +154 -0
  139. package/skills/cross-platform/perf-audit.md +61 -0
  140. package/skills/cross-platform/prd-verification.md +324 -0
  141. package/skills/cross-platform/release-prep.md +64 -0
  142. package/skills/cross-platform/security-audit.md +281 -0
  143. package/skills/cross-platform/store-listing.md +75 -0
  144. package/skills/flutter/README.md +32 -0
  145. package/skills/flutter/dart-modernize.md +47 -0
  146. package/skills/flutter/flutter-review.md +59 -0
  147. package/skills/flutter/flutter-tdd.md +73 -0
  148. package/skills/flutter/widget-extract.md +41 -0
  149. package/skills/flutter/widget-gen.md +131 -0
  150. package/skills/gaming/blueprint-to-cpp.md +51 -0
  151. package/skills/gaming/game-perf.md +54 -0
  152. package/skills/gaming/shader-gen.md +44 -0
  153. package/skills/gaming/unity-tdd.md +85 -0
  154. package/skills/ios/README.md +45 -0
  155. package/skills/ios/data-persistence.md +151 -0
  156. package/skills/ios/ios-tdd.md +90 -0
  157. package/skills/ios/networking.md +139 -0
  158. package/skills/ios/performance.md +122 -0
  159. package/skills/ios/swift-concurrency.md +57 -0
  160. package/skills/ios/swift-review.md +124 -0
  161. package/skills/ios/swiftui-review.md +49 -0
  162. package/skills/ios/swiftui-state.md +122 -0
  163. package/skills/ios/unit-testing.md +144 -0
  164. package/skills/ios/xcode-warnings.md +37 -0
  165. package/skills/react-native/README.md +42 -0
  166. package/skills/react-native/bridge-audit.md +104 -0
  167. package/skills/react-native/expo-optimize.md +55 -0
  168. package/skills/react-native/new-arch-migrate.md +53 -0
  169. package/skills/react-native/performance.md +124 -0
  170. package/skills/react-native/rn-review.md +53 -0
  171. package/skills/react-native/rn-tdd.md +76 -0
  172. package/skills/shared/README.md +46 -0
  173. package/skills/shared/accessibility-audit.md +132 -0
  174. package/skills/shared/crash-analysis.md +110 -0
  175. package/skills/shared/security-scan.md +149 -0
  176. package/skills/unity/README.md +31 -0
  177. package/skills/unity/shader-review.md +110 -0
  178. package/templates/agent-template.md +88 -0
  179. package/templates/prompt-template.md +50 -0
  180. package/templates/skill-template.md +64 -0
  181. package/templates/workflow-template.md +48 -0
  182. package/workflows/app-launch.md +66 -0
  183. package/workflows/appforge-workflow.md +232 -0
  184. package/workflows/ci-setup.md +77 -0
  185. package/workflows/crash-to-fix.md +71 -0
  186. package/workflows/feature-ship.md +61 -0
  187. package/workflows/game-level.md +72 -0
  188. package/workflows/mobile-harness.md +319 -0
  189. package/workflows/mobile-mcp-qa.md +228 -0
  190. package/workflows/mobile-memory-workflow.md +243 -0
  191. package/workflows/new-project-setup.md +93 -0
  192. package/workflows/new-screen.md +68 -0
  193. package/workflows/perf-sprint.md +78 -0
  194. package/workflows/release-cycle.md +71 -0
@@ -0,0 +1,568 @@
1
+ # Example Output — Compose Screen Builder
2
+
3
+ Generated from [`example-input.md`](example-input.md).
4
+
5
+ ---
6
+
7
+ ## File: OrderHistoryUiState.kt
8
+
9
+ ```kotlin
10
+ package com.example.app.ui.orders.history
11
+
12
+ // One-shot events that should not be replayed on recomposition.
13
+ sealed class OrderHistoryUiEvent {
14
+ data class NavigateToDetail(val orderId: String) : OrderHistoryUiEvent()
15
+ object NavigateToHome : OrderHistoryUiEvent()
16
+ object NavigateUp : OrderHistoryUiEvent()
17
+ }
18
+
19
+ // Single source of truth for the screen's visual state.
20
+ sealed class OrderHistoryUiState {
21
+ object Loading : OrderHistoryUiState()
22
+ object Empty : OrderHistoryUiState()
23
+ data class Success(
24
+ val orders: List<OrderUiModel>,
25
+ val isRefreshing: Boolean = false
26
+ ) : OrderHistoryUiState()
27
+ data class Error(val message: String) : OrderHistoryUiState()
28
+ }
29
+
30
+ data class OrderUiModel(
31
+ val orderId: String,
32
+ val displayId: String, // e.g. "#ORD-4821"
33
+ val status: OrderStatus,
34
+ val itemCount: Int,
35
+ val totalPrice: String, // pre-formatted, e.g. "$48.50"
36
+ val orderDate: String // pre-formatted, e.g. "May 12, 2025"
37
+ )
38
+
39
+ enum class OrderStatus { DELIVERED, PROCESSING, CANCELLED }
40
+ ```
41
+
42
+ ---
43
+
44
+ ## File: OrderHistoryViewModel.kt
45
+
46
+ ```kotlin
47
+ package com.example.app.ui.orders.history
48
+
49
+ import androidx.lifecycle.SavedStateHandle
50
+ import androidx.lifecycle.ViewModel
51
+ import androidx.lifecycle.viewModelScope
52
+ import dagger.hilt.android.lifecycle.HiltViewModel
53
+ import kotlinx.coroutines.CancellationException
54
+ import kotlinx.coroutines.channels.Channel
55
+ import kotlinx.coroutines.flow.MutableStateFlow
56
+ import kotlinx.coroutines.flow.StateFlow
57
+ import kotlinx.coroutines.flow.asStateFlow
58
+ import kotlinx.coroutines.flow.receiveAsFlow
59
+ import kotlinx.coroutines.launch
60
+ import javax.inject.Inject
61
+
62
+ @HiltViewModel
63
+ class OrderHistoryViewModel @Inject constructor(
64
+ savedStateHandle: SavedStateHandle,
65
+ private val getOrderHistory: GetOrderHistoryUseCase
66
+ ) : ViewModel() {
67
+
68
+ // Pulled from navigation args — NavController puts them in SavedStateHandle automatically.
69
+ private val userId: String = checkNotNull(savedStateHandle["userId"])
70
+
71
+ private val _uiState = MutableStateFlow<OrderHistoryUiState>(OrderHistoryUiState.Loading)
72
+ val uiState: StateFlow<OrderHistoryUiState> = _uiState.asStateFlow()
73
+
74
+ // Channel for one-shot events — SharedFlow would re-emit on new collectors (config change).
75
+ // Channel with BUFFERED capacity holds the event until the screen collects it.
76
+ private val _uiEvents = Channel<OrderHistoryUiEvent>(Channel.BUFFERED)
77
+ val uiEvents = _uiEvents.receiveAsFlow()
78
+
79
+ init {
80
+ loadOrders()
81
+ }
82
+
83
+ fun loadOrders() {
84
+ viewModelScope.launch {
85
+ _uiState.value = OrderHistoryUiState.Loading
86
+ try {
87
+ val orders = getOrderHistory(userId)
88
+ _uiState.value = if (orders.isEmpty()) {
89
+ OrderHistoryUiState.Empty
90
+ } else {
91
+ OrderHistoryUiState.Success(orders = orders.map { it.toUiModel() })
92
+ }
93
+ } catch (e: CancellationException) {
94
+ throw e // Never swallow CancellationException
95
+ } catch (e: Exception) {
96
+ _uiState.value = OrderHistoryUiState.Error(
97
+ message = e.message ?: "Failed to load orders"
98
+ )
99
+ }
100
+ }
101
+ }
102
+
103
+ fun refresh() {
104
+ val current = _uiState.value
105
+ if (current is OrderHistoryUiState.Success) {
106
+ // Mark as refreshing without losing the existing list.
107
+ _uiState.value = current.copy(isRefreshing = true)
108
+ }
109
+ viewModelScope.launch {
110
+ try {
111
+ val orders = getOrderHistory(userId)
112
+ _uiState.value = if (orders.isEmpty()) {
113
+ OrderHistoryUiState.Empty
114
+ } else {
115
+ OrderHistoryUiState.Success(orders = orders.map { it.toUiModel() })
116
+ }
117
+ } catch (e: CancellationException) {
118
+ throw e
119
+ } catch (e: Exception) {
120
+ // On refresh failure, keep the existing list but stop the spinner.
121
+ if (_uiState.value is OrderHistoryUiState.Success) {
122
+ _uiState.value = (current as OrderHistoryUiState.Success).copy(isRefreshing = false)
123
+ } else {
124
+ _uiState.value = OrderHistoryUiState.Error(e.message ?: "Refresh failed")
125
+ }
126
+ }
127
+ }
128
+ }
129
+
130
+ fun onOrderTapped(orderId: String) {
131
+ viewModelScope.launch {
132
+ _uiEvents.send(OrderHistoryUiEvent.NavigateToDetail(orderId))
133
+ }
134
+ }
135
+
136
+ fun onStartShoppingTapped() {
137
+ viewModelScope.launch {
138
+ _uiEvents.send(OrderHistoryUiEvent.NavigateToHome)
139
+ }
140
+ }
141
+
142
+ fun onNavigateUp() {
143
+ viewModelScope.launch {
144
+ _uiEvents.send(OrderHistoryUiEvent.NavigateUp)
145
+ }
146
+ }
147
+ }
148
+
149
+ // --- Domain stub (place in domain module, zero Android imports) ---
150
+
151
+ class GetOrderHistoryUseCase @Inject constructor(
152
+ private val repository: OrderRepository
153
+ ) {
154
+ suspend operator fun invoke(userId: String): List<Order> =
155
+ repository.getOrderHistory(userId)
156
+ }
157
+
158
+ // --- Mapper (place in UI or data-ui mapping layer) ---
159
+
160
+ fun Order.toUiModel() = OrderUiModel(
161
+ orderId = id,
162
+ displayId = "#ORD-${id.takeLast(4).uppercase()}",
163
+ status = when (status) {
164
+ "DELIVERED" -> OrderStatus.DELIVERED
165
+ "PROCESSING" -> OrderStatus.PROCESSING
166
+ "CANCELLED" -> OrderStatus.CANCELLED
167
+ else -> OrderStatus.PROCESSING
168
+ },
169
+ itemCount = items.size,
170
+ totalPrice = "$${String.format("%.2f", totalPrice)}",
171
+ orderDate = date.toDisplayString() // your date formatting extension
172
+ )
173
+ ```
174
+
175
+ ---
176
+
177
+ ## File: OrderHistoryScreen.kt
178
+
179
+ ```kotlin
180
+ package com.example.app.ui.orders.history
181
+
182
+ import android.content.res.Configuration.UI_MODE_NIGHT_YES
183
+ import androidx.compose.foundation.clickable
184
+ import androidx.compose.foundation.layout.*
185
+ import androidx.compose.foundation.lazy.LazyColumn
186
+ import androidx.compose.foundation.lazy.items
187
+ import androidx.compose.material.icons.Icons
188
+ import androidx.compose.material.icons.rounded.ArrowBack
189
+ import androidx.compose.material.icons.rounded.ShoppingBag
190
+ import androidx.compose.material3.*
191
+ import androidx.compose.material3.pulltorefresh.PullToRefreshBox
192
+ import androidx.compose.runtime.*
193
+ import androidx.compose.ui.Alignment
194
+ import androidx.compose.ui.Modifier
195
+ import androidx.compose.ui.graphics.Color
196
+ import androidx.compose.ui.semantics.contentDescription
197
+ import androidx.compose.ui.semantics.semantics
198
+ import androidx.compose.ui.tooling.preview.Preview
199
+ import androidx.compose.ui.unit.dp
200
+ import androidx.hilt.navigation.compose.hiltViewModel
201
+ import androidx.lifecycle.compose.collectAsStateWithLifecycle
202
+ import androidx.navigation.NavController
203
+ import androidx.navigation.compose.rememberNavController
204
+
205
+ // ─── Route definition ────────────────────────────────────────────────────────
206
+
207
+ object OrderHistoryRoute {
208
+ const val ROUTE = "order_history/{userId}"
209
+ fun build(userId: String) = "order_history/$userId"
210
+ }
211
+
212
+ // ─── Screen entry point ──────────────────────────────────────────────────────
213
+
214
+ @Composable
215
+ fun OrderHistoryScreen(
216
+ navController: NavController,
217
+ viewModel: OrderHistoryViewModel = hiltViewModel()
218
+ ) {
219
+ val uiState by viewModel.uiState.collectAsStateWithLifecycle()
220
+
221
+ // One-shot event handling — collected once per composition lifetime.
222
+ LaunchedEffect(Unit) {
223
+ viewModel.uiEvents.collect { event ->
224
+ when (event) {
225
+ is OrderHistoryUiEvent.NavigateToDetail ->
226
+ navController.navigate("order_detail/${event.orderId}")
227
+ OrderHistoryUiEvent.NavigateToHome ->
228
+ navController.navigate("home") { popUpTo("home") { inclusive = false } }
229
+ OrderHistoryUiEvent.NavigateUp ->
230
+ navController.navigateUp()
231
+ }
232
+ }
233
+ }
234
+
235
+ OrderHistoryContent(
236
+ uiState = uiState,
237
+ onRefresh = viewModel::refresh,
238
+ onOrderTapped = viewModel::onOrderTapped,
239
+ onStartShopping = viewModel::onStartShoppingTapped,
240
+ onRetry = viewModel::loadOrders,
241
+ onNavigateUp = viewModel::onNavigateUp
242
+ )
243
+ }
244
+
245
+ // ─── Stateless content (testable in isolation) ───────────────────────────────
246
+
247
+ @OptIn(ExperimentalMaterial3Api::class)
248
+ @Composable
249
+ fun OrderHistoryContent(
250
+ uiState: OrderHistoryUiState,
251
+ onRefresh: () -> Unit,
252
+ onOrderTapped: (String) -> Unit,
253
+ onStartShopping: () -> Unit,
254
+ onRetry: () -> Unit,
255
+ onNavigateUp: () -> Unit
256
+ ) {
257
+ Scaffold(
258
+ topBar = {
259
+ CenterAlignedTopAppBar(
260
+ title = { Text("My Orders") },
261
+ navigationIcon = {
262
+ IconButton(
263
+ onClick = onNavigateUp,
264
+ modifier = Modifier.minimumInteractiveComponentSize()
265
+ ) {
266
+ Icon(
267
+ imageVector = Icons.Rounded.ArrowBack,
268
+ contentDescription = "Go back"
269
+ )
270
+ }
271
+ }
272
+ )
273
+ }
274
+ ) { innerPadding ->
275
+ when (uiState) {
276
+ OrderHistoryUiState.Loading -> OrderHistoryLoading(innerPadding)
277
+
278
+ OrderHistoryUiState.Empty -> OrderHistoryEmpty(
279
+ modifier = Modifier.padding(innerPadding),
280
+ onStartShopping = onStartShopping
281
+ )
282
+
283
+ is OrderHistoryUiState.Error -> OrderHistoryError(
284
+ message = uiState.message,
285
+ onRetry = onRetry,
286
+ modifier = Modifier.padding(innerPadding)
287
+ )
288
+
289
+ is OrderHistoryUiState.Success -> OrderHistorySuccess(
290
+ orders = uiState.orders,
291
+ isRefreshing = uiState.isRefreshing,
292
+ onRefresh = onRefresh,
293
+ onOrderTapped = onOrderTapped,
294
+ modifier = Modifier.padding(innerPadding)
295
+ )
296
+ }
297
+ }
298
+ }
299
+
300
+ // ─── State renderers ─────────────────────────────────────────────────────────
301
+
302
+ @Composable
303
+ private fun OrderHistoryLoading(paddingValues: PaddingValues) {
304
+ Box(
305
+ modifier = Modifier
306
+ .fillMaxSize()
307
+ .padding(paddingValues),
308
+ contentAlignment = Alignment.Center
309
+ ) {
310
+ CircularProgressIndicator(
311
+ modifier = Modifier.semantics { contentDescription = "Loading orders" }
312
+ )
313
+ }
314
+ }
315
+
316
+ @Composable
317
+ private fun OrderHistoryEmpty(
318
+ onStartShopping: () -> Unit,
319
+ modifier: Modifier = Modifier
320
+ ) {
321
+ Column(
322
+ modifier = modifier.fillMaxSize(),
323
+ horizontalAlignment = Alignment.CenterHorizontally,
324
+ verticalArrangement = Arrangement.Center
325
+ ) {
326
+ Icon(
327
+ imageVector = Icons.Rounded.ShoppingBag,
328
+ contentDescription = null, // decorative — message text provides context
329
+ modifier = Modifier.size(72.dp),
330
+ tint = MaterialTheme.colorScheme.onSurfaceVariant
331
+ )
332
+ Spacer(Modifier.height(16.dp))
333
+ Text(
334
+ text = "No orders yet",
335
+ style = MaterialTheme.typography.titleMedium,
336
+ color = MaterialTheme.colorScheme.onSurface
337
+ )
338
+ Spacer(Modifier.height(8.dp))
339
+ Text(
340
+ text = "Your completed orders will appear here.",
341
+ style = MaterialTheme.typography.bodyMedium,
342
+ color = MaterialTheme.colorScheme.onSurfaceVariant
343
+ )
344
+ Spacer(Modifier.height(24.dp))
345
+ FilledTonalButton(onClick = onStartShopping) {
346
+ Text("Start Shopping")
347
+ }
348
+ }
349
+ }
350
+
351
+ @Composable
352
+ private fun OrderHistoryError(
353
+ message: String,
354
+ onRetry: () -> Unit,
355
+ modifier: Modifier = Modifier
356
+ ) {
357
+ Column(
358
+ modifier = modifier.fillMaxSize(),
359
+ horizontalAlignment = Alignment.CenterHorizontally,
360
+ verticalArrangement = Arrangement.Center
361
+ ) {
362
+ Text(
363
+ text = "Something went wrong",
364
+ style = MaterialTheme.typography.titleMedium
365
+ )
366
+ Spacer(Modifier.height(8.dp))
367
+ Text(
368
+ text = message,
369
+ style = MaterialTheme.typography.bodySmall,
370
+ color = MaterialTheme.colorScheme.onSurfaceVariant
371
+ )
372
+ Spacer(Modifier.height(24.dp))
373
+ Button(onClick = onRetry) { Text("Retry") }
374
+ }
375
+ }
376
+
377
+ @OptIn(ExperimentalMaterial3Api::class)
378
+ @Composable
379
+ private fun OrderHistorySuccess(
380
+ orders: List<OrderUiModel>,
381
+ isRefreshing: Boolean,
382
+ onRefresh: () -> Unit,
383
+ onOrderTapped: (String) -> Unit,
384
+ modifier: Modifier = Modifier
385
+ ) {
386
+ PullToRefreshBox(
387
+ isRefreshing = isRefreshing,
388
+ onRefresh = onRefresh,
389
+ modifier = modifier.fillMaxSize()
390
+ ) {
391
+ LazyColumn(
392
+ contentPadding = PaddingValues(horizontal = 16.dp, vertical = 12.dp),
393
+ verticalArrangement = Arrangement.spacedBy(12.dp)
394
+ ) {
395
+ items(
396
+ items = orders,
397
+ key = { it.orderId }, // stable key prevents unnecessary recomposition
398
+ contentType = { "order_card" } // allows Compose to reuse composition nodes
399
+ ) { order ->
400
+ OrderCard(order = order, onClick = { onOrderTapped(order.orderId) })
401
+ }
402
+ }
403
+ }
404
+ }
405
+
406
+ // ─── Order card component ────────────────────────────────────────────────────
407
+
408
+ @Composable
409
+ private fun OrderCard(
410
+ order: OrderUiModel,
411
+ onClick: () -> Unit,
412
+ modifier: Modifier = Modifier
413
+ ) {
414
+ val statusColor = when (order.status) {
415
+ OrderStatus.DELIVERED -> Color(0xFF2E7D32) // exception: semantic color not in M3 scheme
416
+ OrderStatus.PROCESSING -> Color(0xFFE65100)
417
+ OrderStatus.CANCELLED -> MaterialTheme.colorScheme.error
418
+ }
419
+ val statusLabel = when (order.status) {
420
+ OrderStatus.DELIVERED -> "Delivered"
421
+ OrderStatus.PROCESSING -> "Processing"
422
+ OrderStatus.CANCELLED -> "Cancelled"
423
+ }
424
+
425
+ Card(
426
+ modifier = modifier
427
+ .fillMaxWidth()
428
+ .clickable(
429
+ onClickLabel = "View order ${order.displayId}",
430
+ onClick = onClick
431
+ ),
432
+ colors = CardDefaults.cardColors(
433
+ containerColor = MaterialTheme.colorScheme.surfaceContainerLow
434
+ )
435
+ ) {
436
+ Column(modifier = Modifier.padding(16.dp)) {
437
+ Row(
438
+ modifier = Modifier.fillMaxWidth(),
439
+ horizontalArrangement = Arrangement.SpaceBetween,
440
+ verticalAlignment = Alignment.CenterVertically
441
+ ) {
442
+ Text(
443
+ text = order.displayId,
444
+ style = MaterialTheme.typography.titleSmall
445
+ )
446
+ Surface(
447
+ color = statusColor.copy(alpha = 0.12f),
448
+ shape = MaterialTheme.shapes.small
449
+ ) {
450
+ Text(
451
+ text = statusLabel,
452
+ modifier = Modifier.padding(horizontal = 8.dp, vertical = 2.dp),
453
+ style = MaterialTheme.typography.labelSmall,
454
+ color = statusColor
455
+ )
456
+ }
457
+ }
458
+ Spacer(Modifier.height(8.dp))
459
+ Row(
460
+ modifier = Modifier.fillMaxWidth(),
461
+ horizontalArrangement = Arrangement.SpaceBetween
462
+ ) {
463
+ Text(
464
+ text = "${order.itemCount} ${if (order.itemCount == 1) "item" else "items"}",
465
+ style = MaterialTheme.typography.bodySmall,
466
+ color = MaterialTheme.colorScheme.onSurfaceVariant
467
+ )
468
+ Text(
469
+ text = order.totalPrice,
470
+ style = MaterialTheme.typography.bodyMedium
471
+ )
472
+ }
473
+ Spacer(Modifier.height(4.dp))
474
+ Text(
475
+ text = order.orderDate,
476
+ style = MaterialTheme.typography.bodySmall,
477
+ color = MaterialTheme.colorScheme.onSurfaceVariant
478
+ )
479
+ }
480
+ }
481
+ }
482
+
483
+ // ─── Previews ────────────────────────────────────────────────────────────────
484
+
485
+ private val previewOrders = listOf(
486
+ OrderUiModel("1", "#ORD-4821", OrderStatus.DELIVERED, 3, "$48.50", "May 12, 2025"),
487
+ OrderUiModel("2", "#ORD-3917", OrderStatus.PROCESSING, 1, "$12.99", "May 10, 2025"),
488
+ OrderUiModel("3", "#ORD-3105", OrderStatus.CANCELLED, 2, "$29.00", "Apr 28, 2025"),
489
+ )
490
+
491
+ @Preview(showBackground = true, name = "Light — Success")
492
+ @Composable
493
+ private fun OrderHistorySuccessPreview() {
494
+ MaterialTheme {
495
+ OrderHistoryContent(
496
+ uiState = OrderHistoryUiState.Success(orders = previewOrders),
497
+ onRefresh = {}, onOrderTapped = {}, onStartShopping = {}, onRetry = {}, onNavigateUp = {}
498
+ )
499
+ }
500
+ }
501
+
502
+ @Preview(showBackground = true, uiMode = UI_MODE_NIGHT_YES, name = "Dark — Success")
503
+ @Composable
504
+ private fun OrderHistorySuccessDarkPreview() {
505
+ MaterialTheme {
506
+ OrderHistoryContent(
507
+ uiState = OrderHistoryUiState.Success(orders = previewOrders),
508
+ onRefresh = {}, onOrderTapped = {}, onStartShopping = {}, onRetry = {}, onNavigateUp = {}
509
+ )
510
+ }
511
+ }
512
+
513
+ @Preview(showBackground = true, name = "Empty")
514
+ @Composable
515
+ private fun OrderHistoryEmptyPreview() {
516
+ MaterialTheme {
517
+ OrderHistoryContent(
518
+ uiState = OrderHistoryUiState.Empty,
519
+ onRefresh = {}, onOrderTapped = {}, onStartShopping = {}, onRetry = {}, onNavigateUp = {}
520
+ )
521
+ }
522
+ }
523
+
524
+ @Preview(showBackground = true, name = "Error")
525
+ @Composable
526
+ private fun OrderHistoryErrorPreview() {
527
+ MaterialTheme {
528
+ OrderHistoryContent(
529
+ uiState = OrderHistoryUiState.Error("Unable to connect to server"),
530
+ onRefresh = {}, onOrderTapped = {}, onStartShopping = {}, onRetry = {}, onNavigateUp = {}
531
+ )
532
+ }
533
+ }
534
+ ```
535
+
536
+ ---
537
+
538
+ ## Navigation Registration
539
+
540
+ Add this block to your `NavHost` in your app's main `NavGraph.kt`:
541
+
542
+ ```kotlin
543
+ composable(
544
+ route = OrderHistoryRoute.ROUTE,
545
+ arguments = listOf(navArgument("userId") { type = NavType.StringType })
546
+ ) {
547
+ OrderHistoryScreen(navController = navController)
548
+ }
549
+ ```
550
+
551
+ Navigate to this screen from anywhere:
552
+
553
+ ```kotlin
554
+ navController.navigate(OrderHistoryRoute.build(userId = currentUserId))
555
+ ```
556
+
557
+ ---
558
+
559
+ ## Gradle Dependencies
560
+
561
+ ```kotlin
562
+ // All required for this screen — add to app/build.gradle.kts if not already present:
563
+ implementation("androidx.compose.material3:material3:1.3.1")
564
+ implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.7")
565
+ implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.7") // collectAsStateWithLifecycle
566
+ implementation("androidx.hilt:hilt-navigation-compose:1.2.0")
567
+ implementation("androidx.navigation:navigation-compose:2.8.5")
568
+ ```
@@ -0,0 +1,73 @@
1
+ # Android Compose UI Reviewer Agent
2
+
3
+ > A focused Compose-only reviewer. Paste a `@Composable` function and get a recomposition scope analysis, state hoisting assessment, `remember`/`derivedStateOf` correctness check, and `LazyColumn` performance audit.
4
+
5
+ ---
6
+
7
+ ## What This Agent Does
8
+
9
+ Specialized for Compose-specific issues that the general Android Code Reviewer misses. Analyzes:
10
+
11
+ - **Recomposition scope** — is state read inside the smallest possible lambda to minimize rebuild cost?
12
+ - **`remember` vs `rememberSaveable` vs `derivedStateOf`** — is the right tool being used? Are `remember` keys correct?
13
+ - **State hoisting** — is mutable state owned at the right level? Is there split-brain state?
14
+ - **`LazyColumn` / `LazyRow`** — `key`, `contentType`, expensive operations inside `items { }`, `Column { items.forEach }` anti-pattern
15
+ - **Side effects** — `LaunchedEffect`, `SideEffect`, `DisposableEffect` placement and key correctness
16
+ - **Slot API design** — is complex child content passed as a `@Composable () -> Unit` lambda?
17
+ - **Stability** — are unstable types (`List`, `Map`, regular classes) causing unnecessary recompositions?
18
+
19
+ ---
20
+
21
+ ## When to Use This Instead of the General Code Reviewer
22
+
23
+ | Situation | Agent to Use |
24
+ |---|---|
25
+ | Full file review (architecture, coroutines, Compose) | `agents/android/code-reviewer` |
26
+ | Compose-specific performance issue (jank, recompositions) | **This agent** |
27
+ | LazyColumn scrolling is slow | **This agent** |
28
+ | Unexplained UI flicker or state loss | **This agent** |
29
+ | Compose compiler metrics showing unstable types | **This agent** |
30
+
31
+ ---
32
+
33
+ ## Files
34
+
35
+ | File | Purpose |
36
+ |---|---|
37
+ | [`agent.md`](agent.md) | Input format, output format, full system prompt |
38
+ | [`example-input.md`](example-input.md) | FeedScreen with LazyColumn and recomposition issues |
39
+ | [`example-output.md`](example-output.md) | Full recomposition + state hoisting report |
40
+
41
+ ---
42
+
43
+ ## Quick Start
44
+
45
+ ```
46
+ COMPOSE_VERSION: 1.7
47
+ KOTLIN_VERSION: 2.0
48
+ FILE_PATH: ui/feed/FeedScreen.kt
49
+ CODE:
50
+ [paste your @Composable function]
51
+ ```
52
+
53
+ ---
54
+
55
+ ## Output Preview
56
+
57
+ ```
58
+ [CRITICAL] FeedScreen — SimpleDateFormat instantiated inside LazyColumn items lambda
59
+ Problem : Created fresh for every post on every recomposition — expensive object allocation.
60
+ Fix : remember(post.createdAt) { SimpleDateFormat(...).format(post.createdAt) }
61
+ Or: format in the ViewModel and expose pre-formatted strings.
62
+
63
+ [WARNING] FeedScreen — LazyColumn items missing key parameter
64
+ Problem : Without key, any list change causes PostItem to lose its isExpanded state.
65
+ Fix : items(posts, key = { it.id }) { post -> ... }
66
+ ```
67
+
68
+ ---
69
+
70
+ ## Related Agents
71
+
72
+ - [`agents/android/code-reviewer`](../code-reviewer/) — full-file review including architecture and coroutines
73
+ - [`agents/android/compose-screen-builder`](../compose-screen-builder/) — generate Compose screens that follow these patterns from the start