android-create-feature
Crear una feature Android de UI en Rally Copilot (screen Compose, ViewModel con UiState/StateFlow, ruta type-safe en AppNavHost, tests JVM); no capa de datos (android-create-data-layer) ni sistema.
SKILL.md.claude/skills/android-create-feature/SKILL.md
GitHubandroid-create-feature
Crea la feature $ARGUMENTS en :app (paquete app.trombito.rallycopilot.ui.<feature>, con lo que necesite en domain/ y data/).
Cuándo usarla
- Pantalla o flujo nuevo con estado propio y navegación.
Cuándo NO usarla
- Entity, DAO o repository →
android-create-data-layer. - Módulo Hilt, permiso o componente de sistema →
android-create-hilt-module,android-add-permission,android-create-system-component. - Cambios menores en una pantalla existente: editar directamente.
- Lógica pura (geometría, notas, cronometraje, sectores, deltas, GPX/KML) → va en
:motor(Kotlin JVM, sin Android), no aquí.
Precondiciones
$ARGUMENTSpresente; derivar<Feature>(PascalCase) y<feature>(minúsculas). Si falta, pedirlo y parar.- Leer
docs/ESTADO.mdy el hito activo deROADMAP.md. Glob de una feature existente para copiar su estructura; no inventar otra. - Textos, flujo y diseño de pantalla son producto: si no están definidos en el hito, apuntar la duda en
QUESTIONS.mdy seguir con lo que no dependa. - Si lee o escribe datos, el repository (interfaz en
domain/) debe existir; si no,android-create-data-layerantes. La UI observa Room. - Sin dependencias nuevas sin la skill
dependency-add.
Pasos
<Feature>UiState.kt:data classinmutable (soloval) con estados de carga/vacío/error explícitos.<Feature>ViewModel.ktcon@HiltViewModel: depende de casos de uso o interfaces de dominio (clases simples endomain/). Exponeval uiState: StateFlow<<Feature>UiState>víacombine(...).stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), inicial)yfun onXxx(...)por acción. Eventos de una sola vez (navegar, snackbar) porChannelexpuesto comoreceiveAsFlow(). Dispatchers inyectados.<Feature>Screen.kten dos niveles:<Feature>Route(hiltViewModel(),collectAsStateWithLifecycle(), recoge el Channel) y<Feature>Screen(state, ...)sin lógica de negocio ni acceso a repositories.@Previewcon estados de ejemplo (cargando, contenido, error), Material 3 y el tema del proyecto (oscuro por defecto, estética WRC). Textos enstrings_<feature>.xml(solo español).- Registrar la ruta type-safe en
ui/AppNavHost.kt:@Serializable data object|data class <Feature>Rutaycomposable<<Feature>Ruta> { … }(Navigation Compose 2.9); argumentos consavedStateHandle.toRoute<<Feature>Ruta>()en el ViewModel. Nada de rutas en string. - Test del ViewModel en
src/test(JUnit4, coroutines-test, fakes a mano, dispatcher de test): secuencia de estados (inicial, carga, éxito, error), cada acción y los eventos del Channel. - Si hay UI crítica, test Compose con Robolectric y el harness
BancoCompose/LimpiezaComposeensrc/test(noandroidTest): estados fabricados y callbacks invocados. - Si toca permisos, ubicación, audio o ciclo de vida, pedir además
android-review.
Validaciones / tests
./gradlew testDebugUnitTest../gradlew ktlintCheck detekt lintDebug../gradlew assembleDebugcompila con la ruta registrada.
Definition of Done
-
UiStateinmutable yStateFlowexpuesto; sinMutableStateFlowpúblico. - Ningún composable contiene lógica de negocio ni depende de repositories.
- Ruta type-safe registrada en
AppNavHosty navegable. - Tests de ViewModel y, si aplica, Compose/Robolectric en verde.
- Sin dependencias nuevas sin
dependency-add; sin secretos. -
./gradlew ktlintCheck detekt lintDebug testDebugUnitTest assembleDebugen verde.
Docs a actualizar
docs/arquitectura.mdsi cambia el mapa de navegación o módulos · docs/ESTADO.md (skillsession-handoff)