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
a small MVVM *About* screen for contrast, and a dedicated **error-handling demo**.
> **Status:** built milestone-by-milestone from the
> [Linear backlog](https://linear.app/adrian-kuta/project/android-architecture-showcase-b5ecdeddda6c).
> Foundation, Core Infrastructure, the flagship MVI feature, Breadth & Contrast, and Quality & Docs
> are complete; the project assembles green and ships unit + UI tests. The only optional item left is
> the Room offline-cache stretch (see [Optional: Room stretch](#optional-room-stretch)).
> **Status:** built milestone-by-milestone. Foundation, Core Infrastructure, the flagship MVI
> feature, Breadth & Contrast, and Quality & Docs are complete; the project assembles green and ships
> unit + UI tests. The only optional item left is 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)
- [Build & run (`android` CLI)](#build--run-android-cli)
- [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) |
| Logging | Timber |
| 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`
> 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
Compose on its classpath — which is what lets two different renderers share one ViewModel.
See **android-module-structure**.
---
## 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.
Mappers are pure extension functions in a `mappers/` package (`toDomain()`). See
**android-data-layer**, **android-data-layer-mappers**.
Mappers are pure extension functions in a `mappers/` package (`toDomain()`).
- **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
@@ -133,7 +128,7 @@ rule it illustrates:
> 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
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
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)
@@ -191,8 +184,6 @@ from navigation.
> the classic Views (e.g. `MaterialToolbar`, `?attr/colorOnSurfaceVariant`) require. A plain
> `ComponentActivity` or a non-Material theme breaks the Fragment renderer.
See **android-compose-ui**, **android-module-structure**.
---
## 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
`ErrorState` + retry Action is what the real list and detail screens use.
See **android-error-handling**, **android-presentation-mvi**.
---
## Navigation
@@ -255,8 +244,6 @@ in `:app`.
UI-agnostic `presentation` module needs **no** navigation dependency. The same `SavedStateHandle`
also persists the list's loaded page across process death.
See **android-navigation**.
---
## 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()`
both resolve the **same** `CharacterListViewModel` class and supply its `SavedStateHandle`.
See **android-di-koin**, **koin-constructor-dsl**.
---
## Testing
Tests prove the architecture, not just the code. Stack: **JUnit 5**, **Turbine** (Flow), **AssertK**,
`kotlinx-coroutines-test`, Ktor **`MockEngine`**, and Compose UI test.
Tests prove the architecture, not just the code. Stack: **JUnit 5**, **MockK**, **Turbine** (Flow),
**AssertK**, `kotlinx-coroutines-test`, Ktor **`MockEngine`**, and Compose UI test.
| What | Where | Kind |
|---|---|---|
| `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` |
| `CharacterListScreen` (robot) | `:feature:characters:presentation-compose` `src/androidTest` | instrumented Compose UI |
Conventions demonstrated:
- **Fakes, not mocks.** `FakeCharacterRepository` is a real in-memory implementation with a
`failWith` toggle and call counts — tests assert against working behaviour, not recorded calls.
- **MockK for collaborators.** The ViewModel/UseCase tests stub the `CharacterRepository` interface
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
`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
@@ -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
possible but intentionally omitted (the VM logic is already covered by the shared unit tests).
See **android-testing**.
---
## 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
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 +
`@Database` (prefer `autoMigrations`), then convert the repository to **offline-first**
(`OfflineFirstCharacterRepository`: network → persist → expose a DB `Flow`; the ViewModel observes the
DB, never the network response). The current `CharacterRepository` returning the `DataError`
supertype already anticipates a multi-source implementation. See **android-data-layer**.
---
## 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 |
supertype already anticipates a multi-source implementation.

View File

@@ -98,7 +98,7 @@ internal fun logNetworkError(throwable: Throwable, message: String) {
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(
response: HttpResponse,
): 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
* scenario (`robot.setContent(state).assertCharacterShown(...).clickCharacter(...)`). The robot owns
* 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(
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
* 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
* success). See android-error-handling.
* success).
*/
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. */
val charactersPresentationModule = module {
// 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)
viewModelOf(::CharacterListViewModel)
viewModelOf(::CharacterDetailViewModel)