Wire project infrastructure

This commit is contained in:
2026-04-24 15:27:17 +02:00
parent 68e4a5637a
commit 015d8d51d0
66 changed files with 7276 additions and 211 deletions

View File

@@ -0,0 +1,138 @@
---
phase: 01-project-infrastructure-module-wiring
plan: 04
subsystem: client-bootstrap
tags: [koin, kermit, di, logging, ios-bridge, android-application, wasm-bootstrap]
requires:
- 01-02 (build-logic conventions providing Koin + Kermit dependencies via recipe.kotlin.multiplatform)
- 01-03 (composeApp/build.gradle.kts wired to convention plugin + libs.koin.android in androidMain)
provides:
- "initKoin(config: KoinAppDeclaration?): KoinApplication — single bootstrap helper"
- "appModule: Koin Module — empty placeholder; Phase 2+ extends with authModule, syncModule, catalogModule"
- "configureLogging() — sets Kermit Logger.setTag(\"recipe\")"
- "KoinIosKt.doInitKoin() — Swift-callable iOS bridge"
- "MainApplication: Android Application subclass invoking configureLogging + initKoin on process boot"
affects:
- "All future phases (2-11) plug Koin modules into appModule and call Logger.x { } via Kermit"
- "Phase 2 (Auth) will register authModule; Phase 4 (SyncEngine) will register syncModule singleton"
tech-stack:
added: []
patterns:
- "Single initKoin() call site per platform entry point (PITFALL #4 — no double-init on iOS)"
- "configureLogging() ALWAYS precedes initKoin() so Koin module loading can use Kermit"
- "App.kt (@Composable) NEVER calls startKoin (Pattern 4 anti-pattern guard)"
- "iOS Kotlin bridge: top-level fun doInitKoin in KoinIos.kt → Swift symbol KoinIosKt.doInitKoin"
- "Wasm init order: configureLogging → initKoin → ComposeViewport (PITFALL #8)"
key-files:
created:
- composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/di/Koin.kt
- composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/di/AppModule.kt
- composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/logging/Logging.kt
- composeApp/src/iosMain/kotlin/dev/ulfrx/recipe/di/KoinIos.kt
- composeApp/src/androidMain/kotlin/dev/ulfrx/recipe/MainApplication.kt
modified:
- composeApp/src/androidMain/AndroidManifest.xml
- composeApp/src/jvmMain/kotlin/dev/ulfrx/recipe/main.kt
- composeApp/src/webMain/kotlin/dev/ulfrx/recipe/main.kt
- iosApp/iosApp/iOSApp.swift
unchanged_by_design:
- composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/App.kt # anti-pattern guard: no startKoin in @Composable
- composeApp/src/iosMain/kotlin/dev/ulfrx/recipe/MainViewController.kt # PITFALL #4: Koin started exclusively in iOSApp.init()
- iosApp/iosApp/ContentView.swift # already wraps MainViewControllerKt.MainViewController()
decisions:
- "Kermit tag = \"recipe\" (D-15) — exact string"
- "appModule is empty in Phase 1 (D-14); Phase 2+ adds modules"
- "Single iOS Koin call site is iOSApp.init() (PITFALL #4 mitigation)"
- "androidContext(this@MainApplication) — qualified `this` because initKoin lambda receiver is KoinApplication"
metrics:
tasks_completed: 3
tasks_total: 3
files_created: 5
files_modified: 4
duration: ~10m
completed: 2026-04-24
---
# Phase 1 Plan 4: Koin + Kermit Bootstrap Wiring — Summary
Wired the Koin DI container and Kermit structured logger across all four composeApp platform entry points (Android Application subclass, iOS SwiftUI App.init, JVM desktop main, Wasm browser main) with a single `initKoin()` helper in commonMain and an empty `appModule` placeholder that Phase 2+ extends.
## What was built
### Task 1 — commonMain DI + logging + iOS bridge (commit `cc5002d`)
Created four files:
- **`composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/di/Koin.kt`** — exports `fun initKoin(config: KoinAppDeclaration? = null): KoinApplication = startKoin { config?.invoke(this); modules(appModule) }`. The optional `config` lambda is how Android passes `androidContext(...)` and how Phase 2+ tests can inject overrides without touching the helper itself.
- **`composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/di/AppModule.kt`** — declares `val appModule = module { }` (empty per D-14). Phase 2 adds `authModule`, Phase 4 adds `syncModule`, etc.
- **`composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/logging/Logging.kt`** — `fun configureLogging() { Logger.setTag("recipe") }`. Kermit's per-platform writers (OSLog/LogCat/println) install themselves by default; setting the tag is the only required call.
- **`composeApp/src/iosMain/kotlin/dev/ulfrx/recipe/di/KoinIos.kt`** — `fun doInitKoin() { configureLogging(); initKoin() }`. The top-level `fun` in file `KoinIos.kt` becomes the Swift-accessible symbol `KoinIosKt.doInitKoin()` automatically (Kotlin/Native generates `<FileName>Kt` for top-level decls).
### Task 2 — Android MainApplication + manifest (commit `8cd608a`)
- **`composeApp/src/androidMain/kotlin/dev/ulfrx/recipe/MainApplication.kt`** — `class MainApplication : Application()` whose `onCreate()` calls `super.onCreate()`, then `configureLogging()`, then `initKoin { androidContext(this@MainApplication) }`. Qualified `this@MainApplication` is required because the `initKoin { }` lambda receiver is `KoinApplication`, not the `Application`.
- **`composeApp/src/androidMain/AndroidManifest.xml`** — added `android:name=".MainApplication"` as the first attribute on `<application>`. All other attributes and the `<activity>`/`<intent-filter>` subtree preserved verbatim.
### Task 3 — JVM + Wasm + Swift entry points (commit `fd3e7e1`)
- **`composeApp/src/jvmMain/kotlin/dev/ulfrx/recipe/main.kt`** — converted `fun main() = application { ... }` (single-expression) into a body block: `configureLogging()``initKoin()``application { Window(title = "recipe") { App() } }`. Window title and exit handler preserved.
- **`composeApp/src/webMain/kotlin/dev/ulfrx/recipe/main.kt`** — same init order before `ComposeViewport { App() }`. `@OptIn(ExperimentalComposeUiApi::class)` retained. Defensive against PITFALL #8 (Wasm composition running before DI is ready) — Phase 1 has no ViewModels so the symptom would not surface yet, but the shape is correct from day 1.
- **`iosApp/iosApp/iOSApp.swift`** — added `import ComposeApp` (matches framework basename set by `recipe.kotlin.multiplatform`) and `init() { KoinIosKt.doInitKoin() }`. The `WindowGroup { ContentView() }` body is unchanged. `MainViewController.kt` and `ContentView.swift` were intentionally NOT modified — Koin is bootstrapped exclusively from `iOSApp.init()` (PITFALL #4 mitigation).
## Init order invariant (every platform)
```
configureLogging() → installs Kermit tag "recipe"
initKoin() → starts Koin with empty appModule
[platform composition entry — application { } / ComposeViewport { } / ComposeUIViewController { } / setContent { }]
```
## Deviations from Plan
None — plan executed exactly as written. All 3 tasks completed; all artifacts produced; all `<acceptance_criteria>` satisfied.
## Confirmations (per `<output>` section of PLAN)
- Kermit tag = `"recipe"` (D-15) — set in `configureLogging()`.
- `appModule` content: empty (D-14) — `val appModule = module { }`.
- `App.kt` NOT modified (anti-pattern guard).
- `MainViewController.kt` NOT modified (PITFALL #4 guard — Koin started outside).
- `ContentView.swift` NOT modified (already wraps `MainViewControllerKt.MainViewController()`).
## Threat Mitigations Verified
| Threat ID | Mitigation in delivered code |
|-----------|------------------------------|
| T-01-04-01 (Koin double-init iOS) | `KoinIosKt.doInitKoin()` is the only init call site on iOS; `MainViewController.kt` does not call `startKoin`. |
| T-01-04-02 (Wasm init order) | webMain `main()` orders `configureLogging() → initKoin() → ComposeViewport { }`. |
| T-01-04-03 (App.kt calling startKoin) | `App.kt` unchanged; verified no `startKoin` reference outside `Koin.kt`. |
## Verification gates
- All three task `<automated>` grep blocks passed.
- No build files modified → `tools/verify-no-version-literals.sh` and `tools/verify-shared-pure.sh` remain at exit 0.
- Compile gates (`./gradlew build`, `:composeApp:jvmTest`) deferred to Plan 07 per the verification block in 01-04-PLAN.md.
## Commits
- `cc5002d` — feat(01-04): add Koin + Kermit bootstrap commonMain + iOS bridge
- `8cd608a` — feat(01-04): add Android MainApplication + manifest registration
- `fd3e7e1` — feat(01-04): wire JVM + Wasm main + Swift iOSApp to bootstrap Koin + Kermit
## Self-Check: PASSED
Files verified to exist on disk:
- FOUND: composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/di/Koin.kt
- FOUND: composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/di/AppModule.kt
- FOUND: composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/logging/Logging.kt
- FOUND: composeApp/src/iosMain/kotlin/dev/ulfrx/recipe/di/KoinIos.kt
- FOUND: composeApp/src/androidMain/kotlin/dev/ulfrx/recipe/MainApplication.kt
- FOUND: composeApp/src/androidMain/AndroidManifest.xml (modified, contains `android:name=".MainApplication"`)
- FOUND: composeApp/src/jvmMain/kotlin/dev/ulfrx/recipe/main.kt (modified)
- FOUND: composeApp/src/webMain/kotlin/dev/ulfrx/recipe/main.kt (modified)
- FOUND: iosApp/iosApp/iOSApp.swift (modified)
Commits verified in `git log`:
- FOUND: cc5002d
- FOUND: 8cd608a
- FOUND: fd3e7e1