# Phase 1: Project Infrastructure & Module Wiring - Discussion Log > **Audit trail only.** Do not use as input to planning, research, or execution agents. > Decisions are captured in CONTEXT.md — this log preserves the alternatives considered. **Date:** 2026-04-24 **Phase:** 01-project-infrastructure-module-wiring **Areas discussed:** Target matrix, Convention plugin split, Code-quality toolchain, "Running-but-empty" scope --- ## Target matrix ### Q1: JS target in composeApp + shared — drop it? | Option | Description | Selected | |--------|-------------|----------| | Drop js, keep wasmJs | PROJECT.md mentions Wasm but not js; js is legacy Kotlin/JS path | ✓ | | Keep both js and wasmJs | Preserve template exactly; zero risk of Kotlin/JS regression on future merges | | | Drop both | Strictest minimum; re-add wasmJs only if web becomes real | | **User's choice:** Drop js, keep wasmJs. ### Q2: iosX64 target — include? | Option | Description | Selected | |--------|-------------|----------| | Skip iosX64 | User is on Apple Silicon; iosArm64 + iosSimulatorArm64 sufficient | ✓ | | Add iosX64 for safety | Intel-Mac safety net; costs a second iOS compile per build | | **User's choice:** Skip iosX64. ### Q3: Desktop JVM target — role? | Option | Description | Selected | |--------|-------------|----------| | Dev-tool only, no shipped artifact | Hot-reload loop retained; no packaging; PROJECT.md alignment | ✓ | | Full desktop app, packaged + shipped | dmg/msi/exe as release surface; out of PROJECT.md scope | | | Drop desktop target entirely | Simplest; loses hot reload | | **User's choice:** Dev-tool only, no shipped artifact. ### Q4: shared/ target set — mirror composeApp? | Option | Description | Selected | |--------|-------------|----------| | Mirror composeApp exactly | Same target set; consistent dep graph | ✓ | | Superset: ship everything KMP supports | Every target just in case; build cost | | | Minimum: only what's used today | Strictest diet; same as Option 1 if we drop js | | **User's choice:** Mirror composeApp exactly. --- ## Convention plugin split ### Q1: How granular should the convention plugins be? | Option | Description | Selected | |--------|-------------|----------| | Fine-grained (4–5 plugins) | recipe.kotlin.multiplatform + .compose.multiplatform + .android.application + .jvm.server + .quality | ✓ | | Coarse (2 plugins) | recipe.kmp + recipe.server; leaks Compose config into shared | | | Monolith (1 plugin) | Single recipe.conventions with conditional logic | | **User's choice:** Fine-grained (4–5 plugins). Preview showed the role-declaration pattern in each module's plugins block. ### Q2: What does the KMP convention plugin lock in? | Option | Description | Selected | |--------|-------------|----------| | Targets + toolchain + common test deps | New KMP module = apply plugin, done | ✓ | | Targets + toolchain only | Thinner plugin, more repetition downstream | | | Everything incl. Koin + Kermit wiring | Upfront convenience, invasive over time | | **User's choice:** Targets + toolchain + common test deps. ### Q3: JVM toolchain version? | Option | Description | Selected | |--------|-------------|----------| | JVM 21 everywhere, androidTarget stays JVM 11 | Split kept; modern JDK on server, Android constrained | ✓ | | JVM 17 everywhere | Unified; loses JVM-21 features (virtual threads) | | | Keep template defaults | Zero refactor risk; loses explicit control | | **User's choice:** JVM 21 everywhere, androidTarget stays JVM 11. ### Q4: Where do library version strings live? | Option | Description | Selected | |--------|-------------|----------| | All versions in libs.versions.toml, nowhere else | Strict INFRA-01 SC#2 | ✓ | | Catalog for libs, plugin versions inline | Technically violates SC#2 | | **User's choice:** All versions in libs.versions.toml, nowhere else. --- ## Code-quality toolchain User clarified: "What are Detekt alternatives? Is ktlint OK?" Discussion explained Detekt = static analysis, ktlint = formatting — not alternatives, usually paired. Presented tiers (minimal / standard / architecture-aware) and user chose minimal. ### Q1: Static analysis (Detekt)? | Option | Description | Selected | |--------|-------------|----------| | Wire Detekt now | Default ruleset + baseline; catches Kotlin footguns | | | Skip Detekt; lean on IDE + compiler | No CI gate for static analysis | ✓ | | Placeholder task, no rules | Wire-in-place for future enablement | | **User's choice:** Skip Detekt. Minimal baseline. **Notes:** Konsist (architecture fitness) deferred to ~Phase 4 when SyncEngine rules exist. ### Q2: Formatting / linting? | Option | Description | Selected | |--------|-------------|----------| | ktlint via Spotless plugin | One tool: Kotlin + Gradle + markdown | ✓ | | ktlint plugin directly | Thinner; loses multi-format coverage | | | Skip, rely on IDE + .editorconfig | No CI-level gate | | **User's choice:** ktlint via Spotless plugin. ### Q3: Compiler warnings as errors? | Option | Description | Selected | |--------|-------------|----------| | allWarningsAsErrors = true everywhere | Max discipline; deprecations force conscious suppression | ✓ | | Warn only | Noise accumulates | | | As-errors for module code, relaxed for generated | Small config carve-out | | **User's choice:** allWarningsAsErrors = true everywhere. ### Q4: Explicit API mode for shared/? User clarified: "I don't understand it. What is this explicit api?" and later "Is this some kind of a standard because I am writing kotlin server applications and didn't meet with that". Discussion explained explicitApi as a library-authoring convention (stdlib, coroutines, Ktor etc.) requiring `public` keyword + explicit return types. User weighed the tradeoff and picked strict-on-shared/ on library-contract grounds. | Option | Description | Selected | |--------|-------------|----------| | Skip entirely | Kotlin defaults; no `public` ceremony | | | Strict on shared/ only | Library discipline on the cross-runtime contract | ✓ | **User's choice:** Strict on shared/ only. ### Q5: Git hooks? | Option | Description | Selected | |--------|-------------|----------| | No git hooks | `./gradlew check` is the gate; CI later | ✓ | | Pre-commit hook running spotlessCheck | Blocks commits with formatting drift | | **User's choice:** No git hooks. --- ## "Running-but-empty" scope ### Q1: Koin DI bootstrap — wire it in Phase 1? | Option | Description | Selected | |--------|-------------|----------| | Wire minimal bootstrap now | Empty appModule + startKoin in App() and MainViewController | ✓ | | Defer to Phase 2 | Phase 2 does DI + auth together | | **User's choice:** Wire minimal bootstrap now. ### Q2: Kermit logger bootstrap? | Option | Description | Selected | |--------|-------------|----------| | Set up Kermit now | Logger available from day 1 | ✓ | | Defer | Add when first feature needs logging | | **User's choice:** Set up Kermit now. ### Q3: Server "running-but-empty" — `/health` + Flyway scaffold + Postgres config? | Option | Description | Selected | |--------|-------------|----------| | Health endpoint + Flyway scaffold + Postgres conn config | Phase 3 migrations drop into an already-wired migrator | ✓ | | Health endpoint only, no DB | Phase 3 wires Flyway + Postgres together | | | Strictly the template skeleton | Most minimal; Phase 2 and 3 do more | | **User's choice:** Health endpoint + Flyway scaffold + Postgres conn config. ### Q4: docker-compose.yml in Phase 1? | Option | Description | Selected | |--------|-------------|----------| | Add docker-compose.yml now | Phase 3 doesn't have to litigate local-Postgres setup | ✓ | | Defer to Phase 3 | Compose arrives with first migration | | | No compose; use homelab Postgres directly | Fastest setup; dev pollutes shared instance | | **User's choice:** Add docker-compose.yml now. --- ## Claude's Discretion - Exact ordering of plugin application inside each `build.gradle.kts` - Specific Spotless ktlint ruleset version (pick latest stable from catalog) - Whether `application.conf` or a Kotlin config class owns env-var parsing - Flyway `cleanDisabled` / `baselineOnMigrate` flag choices - iOS Koin bootstrap idiom (`KoinApplication` vs `startKoin` in MainViewController) - `docker-compose.yml` shape: `.env` file vs inline localhost defaults - Exact sentinel JSON body for `/health` ## Deferred Ideas - Detekt static analysis — revisit only if review misses start compounding - Konsist architecture fitness tests — revisit ~Phase 4 (SyncEngine rules) - CI pipeline — Phase 11 (deployment) - Git hooks — considered; revisit only on recurring format drift - explicitApi for composeApp / server — rejected (app code, not libraries) - iosX64 target — rejected (no Intel-Mac contributors) - `js` target — rejected (wasmJs covers future-web intent) - Compose Desktop packaging (dmg/msi/exe) — Desktop is dev-only