REDI-101: remove AI/tooling attribution from docs & source comments

Make the repo read as a hand-authored reference project by dropping
references to the authoring tooling — the internal convention "skills", the
Linear backlog, and the REDI issue ids — from the README and source comments.

- README: remove the "Convention skills index" section and its TOC entry,
  strip every `See **android-...**` / koin-constructor-dsl citation, drop the
  Linear backlog link, and reword the REDI-99 Room reference to "an optional
  stretch".
- Source comments: neutralize the four skill citations in HttpClientExt,
  CharactersPresentationModule, CharacterListRobot, ErrorDemoViewModel.
- Also correct the testing section that the MockK migration left stale: it
  described "Fakes, not mocks" / FakeCharacterRepository (now deleted) — now
  describes MockK, and adds MockK to the stack lists.

Verified: git grep finds no AI/Claude/Anthropic/Linear/skill references in any
tracked file; assembleDebug + assembleDebugAndroidTest green.
This commit is contained in:
2026-06-10 16:19:36 +02:00
parent e44dd9896f
commit 2ae94e473d
5 changed files with 19 additions and 54 deletions

View File

@@ -8,11 +8,10 @@ Data comes from the no-key [Rick & Morty API](https://rickandmortyapi.com/). The
characters, opens a detail screen, renders that same list **twice** (Compose and classic Views), has characters, opens a detail screen, renders that same list **twice** (Compose and classic Views), has
a small MVVM *About* screen for contrast, and a dedicated **error-handling demo**. a small MVVM *About* screen for contrast, and a dedicated **error-handling demo**.
> **Status:** built milestone-by-milestone from the > **Status:** built milestone-by-milestone. Foundation, Core Infrastructure, the flagship MVI
> [Linear backlog](https://linear.app/adrian-kuta/project/android-architecture-showcase-b5ecdeddda6c). > feature, Breadth & Contrast, and Quality & Docs are complete; the project assembles green and ships
> Foundation, Core Infrastructure, the flagship MVI feature, Breadth & Contrast, and Quality & Docs > unit + UI tests. The only optional item left is the Room offline-cache stretch (see
> are complete; the project assembles green and ships unit + UI tests. The only optional item left is > [Optional: Room stretch](#optional-room-stretch)).
> the Room offline-cache stretch (see [Optional: Room stretch](#optional-room-stretch)).
--- ---
@@ -29,7 +28,6 @@ a small MVVM *About* screen for contrast, and a dedicated **error-handling demo*
- [Testing](#testing) - [Testing](#testing)
- [Build & run (`android` CLI)](#build--run-android-cli) - [Build & run (`android` CLI)](#build--run-android-cli)
- [Optional: Room stretch](#optional-room-stretch) - [Optional: Room stretch](#optional-room-stretch)
- [Convention skills index](#convention-skills-index)
--- ---
@@ -46,7 +44,7 @@ a small MVVM *About* screen for contrast, and a dedicated **error-handling demo*
| Navigation | type-safe Compose Navigation (`@Serializable` routes) | | Navigation | type-safe Compose Navigation (`@Serializable` routes) |
| Logging | Timber | | Logging | Timber |
| Async | Coroutines + Flow | | Async | Coroutines + Flow |
| Testing | JUnit 5, Turbine, AssertK, `kotlinx-coroutines-test`, Ktor `MockEngine`, Compose UI test | | Testing | JUnit 5, MockK, Turbine, AssertK, `kotlinx-coroutines-test`, Ktor `MockEngine`, Compose UI test |
> **AGP 9 gotcha:** AGP 9.0 has **built-in Kotlin**. Applying `com.android.application`/`library` > **AGP 9 gotcha:** AGP 9.0 has **built-in Kotlin**. Applying `com.android.application`/`library`
> auto-applies the Kotlin Android plugin, so the convention plugins must **not** apply > auto-applies the Kotlin Android plugin, so the convention plugins must **not** apply
@@ -90,8 +88,6 @@ A key consequence: `:core:presentation`'s `UiText` is **Compose-free**, and the
uses `implementation` (not `api`), so the UI-agnostic `:feature:characters:presentation` never gets uses `implementation` (not `api`), so the UI-agnostic `:feature:characters:presentation` never gets
Compose on its classpath — which is what lets two different renderers share one ViewModel. Compose on its classpath — which is what lets two different renderers share one ViewModel.
See **android-module-structure**.
--- ---
## The data → UI flow ## The data → UI flow
@@ -118,10 +114,9 @@ CharacterListScreen / CharacterListFragment (:presentation-compose / -views)
``` ```
- **DTOs** (`*Dto`) live in `data`; **domain models** are separate and never become DTOs/entities. - **DTOs** (`*Dto`) live in `data`; **domain models** are separate and never become DTOs/entities.
Mappers are pure extension functions in a `mappers/` package (`toDomain()`). See Mappers are pure extension functions in a `mappers/` package (`toDomain()`).
**android-data-layer**, **android-data-layer-mappers**.
- **UI models** (`*Ui`) live in `presentation` and carry display-ready data (e.g. blank detail fields - **UI models** (`*Ui`) live in `presentation` and carry display-ready data (e.g. blank detail fields
pre-formatted to an em dash). See **android-presentation-mvi**. pre-formatted to an em dash).
### Note — when to add a UseCase ### Note — when to add a UseCase
@@ -133,7 +128,7 @@ rule it illustrates:
> would merely forward a single repository call, injecting the repository directly is fine. > would merely forward a single repository call, injecting the repository directly is fine.
Here the list VM uses the UseCase; the detail VM calls `CharacterRepository` directly — both are Here the list VM uses the UseCase; the detail VM calls `CharacterRepository` directly — both are
correct, and the contrast is the point. See **android-module-structure**, **android-di-koin**. correct, and the contrast is the point.
--- ---
@@ -167,8 +162,6 @@ Views side with `repeatOnLifecycle`.
Reach for MVI when state is complex **and** side effects matter. Reach for plain MVVM when the screen Reach for MVI when state is complex **and** side effects matter. Reach for plain MVVM when the screen
is small, mostly static, and has no real side effects — the *About* screen is the canonical case. is small, mostly static, and has no real side effects — the *About* screen is the canonical case.
See **android-presentation-mvi**, **android-compose-ui**.
--- ---
## One ViewModel, two renderers (Compose vs Views) ## One ViewModel, two renderers (Compose vs Views)
@@ -191,8 +184,6 @@ from navigation.
> the classic Views (e.g. `MaterialToolbar`, `?attr/colorOnSurfaceVariant`) require. A plain > the classic Views (e.g. `MaterialToolbar`, `?attr/colorOnSurfaceVariant`) require. A plain
> `ComponentActivity` or a non-Material theme breaks the Fragment renderer. > `ComponentActivity` or a non-Material theme breaks the Fragment renderer.
See **android-compose-ui**, **android-module-structure**.
--- ---
## Errors: `Result`, `DataError`, `UiText` ## Errors: `Result`, `DataError`, `UiText`
@@ -230,8 +221,6 @@ Three distinct cases (`NO_INTERNET`, `NOT_FOUND`, `SERVER_ERROR`) each render th
**Retry** re-issues the last request as an Action; a successful load **clears** the error. The same **Retry** re-issues the last request as an Action; a successful load **clears** the error. The same
`ErrorState` + retry Action is what the real list and detail screens use. `ErrorState` + retry Action is what the real list and detail screens use.
See **android-error-handling**, **android-presentation-mvi**.
--- ---
## Navigation ## Navigation
@@ -255,8 +244,6 @@ in `:app`.
UI-agnostic `presentation` module needs **no** navigation dependency. The same `SavedStateHandle` UI-agnostic `presentation` module needs **no** navigation dependency. The same `SavedStateHandle`
also persists the list's loaded page across process death. also persists the list's loaded page across process death.
See **android-navigation**.
--- ---
## Dependency injection (Koin) ## Dependency injection (Koin)
@@ -285,26 +272,24 @@ The lambda form (`single { … }`) appears only where a constructor reference ca
not a constructor). Compose roots inject with `koinViewModel()`; the Fragment uses `by viewModel()` not a constructor). Compose roots inject with `koinViewModel()`; the Fragment uses `by viewModel()`
both resolve the **same** `CharacterListViewModel` class and supply its `SavedStateHandle`. both resolve the **same** `CharacterListViewModel` class and supply its `SavedStateHandle`.
See **android-di-koin**, **koin-constructor-dsl**.
--- ---
## Testing ## Testing
Tests prove the architecture, not just the code. Stack: **JUnit 5**, **Turbine** (Flow), **AssertK**, Tests prove the architecture, not just the code. Stack: **JUnit 5**, **MockK**, **Turbine** (Flow),
`kotlinx-coroutines-test`, Ktor **`MockEngine`**, and Compose UI test. **AssertK**, `kotlinx-coroutines-test`, Ktor **`MockEngine`**, and Compose UI test.
| What | Where | Kind | | What | Where | Kind |
|---|---|---| |---|---|---|
| `GetCharactersPageUseCase` | `:feature:characters:domain` `src/test` | pure JVM, JUnit 5 | | `GetCharactersPageUseCase` | `:feature:characters:domain` `src/test` | pure JVM, JUnit 5 |
| `CharacterListViewModel`, `CharacterDetailViewModel` | `:feature:characters:presentation` `src/test` | JVM unit, fakes + Turbine + `SavedStateHandle` | | `CharacterListViewModel`, `CharacterDetailViewModel` | `:feature:characters:presentation` `src/test` | JVM unit, MockK + Turbine + `SavedStateHandle` |
| `NetworkCharacterRepository` | `:feature:characters:data` `src/test` | JVM unit, Ktor `MockEngine` | | `NetworkCharacterRepository` | `:feature:characters:data` `src/test` | JVM unit, Ktor `MockEngine` |
| `CharacterListScreen` (robot) | `:feature:characters:presentation-compose` `src/androidTest` | instrumented Compose UI | | `CharacterListScreen` (robot) | `:feature:characters:presentation-compose` `src/androidTest` | instrumented Compose UI |
Conventions demonstrated: Conventions demonstrated:
- **Fakes, not mocks.** `FakeCharacterRepository` is a real in-memory implementation with a - **MockK for collaborators.** The ViewModel/UseCase tests stub the `CharacterRepository` interface
`failWith` toggle and call counts — tests assert against working behaviour, not recorded calls. with MockK — `coEvery` scripts the suspend calls, `coVerify` asserts the paging/retry interactions.
- **VM tested through its public MVI surface** (State/Action/Event) with a directly-constructed - **VM tested through its public MVI surface** (State/Action/Event) with a directly-constructed
`SavedStateHandle`, so the same tests hold for either renderer. Coverage includes happy path, `SavedStateHandle`, so the same tests hold for either renderer. Coverage includes happy path,
error → `UiText` + snackbar `Event`, pagination end-reached, **process-death restore**, and the error → `UiText` + snackbar `Event`, pagination end-reached, **process-death restore**, and the
@@ -330,8 +315,6 @@ runs on a device/emulator via `./gradlew :feature:characters:presentation-compos
(CI compiles it via `assembleDebugAndroidTest`). An Espresso test for the Fragment renderer is (CI compiles it via `assembleDebugAndroidTest`). An Espresso test for the Fragment renderer is
possible but intentionally omitted (the VM logic is already covered by the shared unit tests). possible but intentionally omitted (the VM logic is already covered by the shared unit tests).
See **android-testing**.
--- ---
## Build & run (`android` CLI) ## Build & run (`android` CLI)
@@ -362,27 +345,9 @@ Requires JDK 17+ (the Gradle build pins a Java 17 toolchain) and the Android SDK
## Optional: Room stretch ## Optional: Room stretch
Out of core scope and **not implemented** (tracked as the optional REDI-99). It would add a `room` Out of core scope and **not implemented** (an optional stretch). It would add a `room`
convention plugin and a `:core:database` (or feature Room set) with `CharacterEntity` + DAO + convention plugin and a `:core:database` (or feature Room set) with `CharacterEntity` + DAO +
`@Database` (prefer `autoMigrations`), then convert the repository to **offline-first** `@Database` (prefer `autoMigrations`), then convert the repository to **offline-first**
(`OfflineFirstCharacterRepository`: network → persist → expose a DB `Flow`; the ViewModel observes the (`OfflineFirstCharacterRepository`: network → persist → expose a DB `Flow`; the ViewModel observes the
DB, never the network response). The current `CharacterRepository` returning the `DataError` DB, never the network response). The current `CharacterRepository` returning the `DataError`
supertype already anticipates a multi-source implementation. See **android-data-layer**. supertype already anticipates a multi-source implementation.
---
## Convention skills index
This repo is a narrative index of these conventions:
| Skill | Where it shows up |
|---|---|
| android-module-structure | module graph, dependency rules, convention plugins |
| android-presentation-mvi | characters list/detail/error-demo (State/Action/Event/VM) |
| android-compose-ui | Compose renderers, design-system, previews, stability |
| android-navigation | type-safe routes, per-feature graphs, callback decoupling |
| android-di-koin / koin-constructor-dsl | feature Koin modules, `*Of` constructor DSL |
| android-data-layer / android-data-layer-mappers | data sources, repository, DTOs, mappers |
| android-error-handling | `Result`/`DataError`/`UiText`, `safeCall`, the error demo |
| android-testing | unit tests, fakes, `MockEngine`, the robot UI test |
| android-cli | build/run/emulator steps above |

View File

@@ -98,7 +98,7 @@ internal fun logNetworkError(throwable: Throwable, message: String) {
Timber.tag("HttpClient").e(throwable, message) Timber.tag("HttpClient").e(throwable, message)
} }
/** Maps HTTP status codes to typed [DataError.Network] (extends the skill table with 400/403/404). */ /** Maps HTTP status codes to typed [DataError.Network] (covering 400/403/404 as well). */
suspend inline fun <reified T> responseToResult( suspend inline fun <reified T> responseToResult(
response: HttpResponse, response: HttpResponse,
): Result<T, DataError.Network> { ): Result<T, DataError.Network> {

View File

@@ -14,7 +14,7 @@ import org.junit.Assert.assertTrue
* Robot for [CharacterListScreen] UI tests. Each method returns `this` so calls read as a fluent * Robot for [CharacterListScreen] UI tests. Each method returns `this` so calls read as a fluent
* scenario (`robot.setContent(state).assertCharacterShown(...).clickCharacter(...)`). The robot owns * scenario (`robot.setContent(state).assertCharacterShown(...).clickCharacter(...)`). The robot owns
* the interaction vocabulary; the test owns the assertions' intent — keeping tests readable and * the interaction vocabulary; the test owns the assertions' intent — keeping tests readable and
* resilient to UI structure changes. See android-testing. * resilient to UI structure changes.
*/ */
class CharacterListRobot( class CharacterListRobot(
private val composeRule: ComposeContentTestRule, private val composeRule: ComposeContentTestRule,

View File

@@ -27,7 +27,7 @@ import kotlinx.coroutines.launch
* The outcome is *simulated* (no real request) only so every case — including NO_INTERNET, which you * The outcome is *simulated* (no real request) only so every case — including NO_INTERNET, which you
* can't reliably trigger on demand — is reachable deterministically. [OnRetry] re-issues the last * can't reliably trigger on demand — is reachable deterministically. [OnRetry] re-issues the last
* attempt (proving retry is an Action); [OnLoadSuccess] clears the error (proving it clears on * attempt (proving retry is an Action); [OnLoadSuccess] clears the error (proving it clears on
* success). See android-error-handling. * success).
*/ */
class ErrorDemoViewModel : ViewModel() { class ErrorDemoViewModel : ViewModel() {

View File

@@ -11,7 +11,7 @@ import org.koin.dsl.module
/** Presentation DI for the characters feature. Lives with the (UI-agnostic) ViewModels it provides. */ /** Presentation DI for the characters feature. Lives with the (UI-agnostic) ViewModels it provides. */
val charactersPresentationModule = module { val charactersPresentationModule = module {
// Stateless domain UseCase — `factoryOf` (a fresh, cheap instance per resolution). Koin supplies // Stateless domain UseCase — `factoryOf` (a fresh, cheap instance per resolution). Koin supplies
// its CharacterRepository from charactersDataModule. See koin-constructor-dsl. // its CharacterRepository from charactersDataModule.
factoryOf(::GetCharactersPageUseCase) factoryOf(::GetCharactersPageUseCase)
viewModelOf(::CharacterListViewModel) viewModelOf(::CharacterListViewModel)
viewModelOf(::CharacterDetailViewModel) viewModelOf(::CharacterDetailViewModel)