diff --git a/.planning/phases/01-project-infrastructure-module-wiring/01-04-SUMMARY.md b/.planning/phases/01-project-infrastructure-module-wiring/01-04-SUMMARY.md new file mode 100644 index 0000000..0e74f69 --- /dev/null +++ b/.planning/phases/01-project-infrastructure-module-wiring/01-04-SUMMARY.md @@ -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 `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 ``. All other attributes and the ``/`` 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 `` satisfied. + +## Confirmations (per `` 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 `` 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