Files
2026-04-29 21:07:49 +02:00

8.9 KiB

phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, decisions, metrics
phase plan subsystem tags requires provides affects tech-stack key-files decisions metrics
01-project-infrastructure-module-wiring 04 client-bootstrap
koin
kermit
di
logging
ios-bridge
android-application
wasm-bootstrap
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)
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
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
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)
created modified unchanged_by_design
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
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
composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/App.kt
composeApp/src/iosMain/kotlin/dev/ulfrx/recipe/MainViewController.kt
iosApp/iosApp/ContentView.swift
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
tasks_completed tasks_total files_created files_modified duration completed
3 3 5 4 ~10m 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.ktfun 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.ktfun 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.ktclass 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: