--- name: create-feature-module description: Use when creating a new feature module, screen, or feature area. Triggered by phrases like "create feature", "add feature module", "new feature", "create a screen for", "add new screen". --- # Create Feature Module Given a feature name `` (e.g., `about`): ## Step 1 — Create API module **`feature//api/build.gradle.kts`** ```kotlin plugins { alias(libs.plugins.apkanalyzer.feature.api) } android { namespace = "sk.styk.martin.apkanalyzer.feature..api" androidResources.enable = false } ``` Drop the `androidResources.enable` line once this module gets its own `res/` — most commonly in Step 8, when a top-level destination needs its tab-label string in `feature//api/src/main/res/values/strings.xml`. **`feature//api/src/main/kotlin/sk/styk/martin/apkanalyzer/feature//api/NavKey.kt`** ```kotlin package sk.styk.martin.apkanalyzer.feature..api import androidx.navigation3.runtime.NavKey import kotlinx.serialization.Serializable @Serializable object NavKey : NavKey ``` If the NavKey needs parameters, use `data class` instead: ```kotlin @Serializable data class NavKey(val id: String) : NavKey ``` ## Step 2 — Create impl module **`feature//impl/build.gradle.kts`** ```kotlin plugins { alias(libs.plugins.apkanalyzer.feature.impl) } android { namespace = "sk.styk.martin.apkanalyzer.feature..impl" androidResources.enable = false } dependencies { api(projects.feature..api) // Add core module dependencies as needed: // implementation(projects.core.apps) // implementation(libs.coil.compose) } ``` Drop the `androidResources.enable` line as soon as the screen has real UI copy and gets its own `res/values/strings.xml` — every user-facing string in this module's Composables must come from `stringResource`, so this almost always happens by the time Step 4's placeholder is replaced. ## Step 3 — Create entry provider **`feature//impl/src/main/kotlin/sk/styk/martin/apkanalyzer/feature//impl/navigation/EntryProvider.kt`** Without navigation (self-contained screen): ```kotlin package sk.styk.martin.apkanalyzer.feature..impl.navigation import androidx.navigation3.runtime.EntryProviderScope import androidx.navigation3.runtime.NavKey import sk.styk.martin.apkanalyzer.feature..api.NavKey import sk.styk.martin.apkanalyzer.feature..impl.Screen fun EntryProviderScope.Entries() { entry<NavKey> { Screen() } } ``` With navigation (back or to other screens): ```kotlin package sk.styk.martin.apkanalyzer.feature..impl.navigation import androidx.navigation3.runtime.EntryProviderScope import androidx.navigation3.runtime.NavKey import sk.styk.martin.apkanalyzer.core.navigation.Navigator import sk.styk.martin.apkanalyzer.feature..api.NavKey import sk.styk.martin.apkanalyzer.feature..impl.Screen fun EntryProviderScope.Entries(navigator: Navigator) { entry<NavKey> { Screen( onBack = { navigator.goBack() }, ) } } ``` ## Step 4 — Create screen composable (placeholder) **`feature//impl/src/main/kotlin/sk/styk/martin/apkanalyzer/feature//impl/Screen.kt`** ```kotlin package sk.styk.martin.apkanalyzer.feature..impl import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import sk.styk.martin.apkanalyzer.core.uilibrary.components.Text import sk.styk.martin.apkanalyzer.core.uilibrary.theme.ApkAnalyzerTheme @Composable internal fun Screen() { Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center, ) { Text(text = "") } } @Preview @Composable private fun ScreenPreview() { ApkAnalyzerTheme { Screen() } } ``` ## Step 5 — Register in `settings.gradle.kts` Add inside `include(...)`: ```kotlin ":feature::api", ":feature::impl", ``` ## Step 6 — Add dependency in `app/build.gradle.kts` ```kotlin implementation(projects.feature..impl) ``` ## Step 7 — Wire in `app/src/main/kotlin/sk/styk/martin/apkanalyzer/ui/ApkAnalyzerApp.kt` Add import: ```kotlin import sk.styk.martin.apkanalyzer.feature..impl.navigation.Entries ``` Add inside `entryProvider { }` block: ```kotlin Entries() // or Entries(navigator) if Navigator is needed ``` ## Step 8 — If top-level destination (bottom nav tab) Add to `app/src/main/kotlin/sk/styk/martin/apkanalyzer/ui/navigation/TopLevelDestinations.kt`: - Import the NavKey and add a `NavigationBarItem` to `TOP_LEVEL_DESTINATIONS` - Choose icons from `ApkAnalyzerIcons` - Add string resource in the api module: `feature//api/src/main/res/values/strings.xml` ## Step 9 — Create module docs Every feature carries one `AGENTS.md` at `feature//AGENTS.md` covering **both** its `api` and `impl` submodules (not two separate files — see `feature/settings/AGENTS.md` or `feature/app-detail/AGENTS.md` for the exact format: Purpose, Sub-modules, API module key types, Impl Structure, Key Patterns, Dependencies), plus a one-line `CLAUDE.md` pointer at the same level: **`feature//CLAUDE.md`** ``` @AGENTS.md ``` If the feature starts as a placeholder screen (Step 4's stub), say so explicitly in the `AGENTS.md` purpose line — e.g. "Status: stub/placeholder — not yet implemented" (see `feature/browse/AGENTS.md`) — so a future agent doesn't assume real behavior exists. Update it once the feature is actually built out. The root `AGENTS.md` does not list individual modules, so there is nothing to update there. Run `./gradlew validateAgentContext` to confirm the new module has the AGENTS/CLAUDE pair it requires. ## Naming Reference | Thing | Convention | Example | |-------|-----------|---------| | Module directory | kebab-case | `app-detail` | | Gradle accessor | camelCase | `projects.feature.appDetail.impl` | | Package segment | concatenated lowercase | `feature.appdetail.impl` | | NavKey class | PascalCase + `NavKey` | `AppDetailNavKey` | | Entry provider function | camelCase + `Entries` | `appDetailEntries` |