Files
recipe/.planning/phases/01-project-infrastructure-module-wiring/01-03-SUMMARY.md
2026-04-29 21:07:49 +02:00

12 KiB

phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, duration, completed
phase plan subsystem tags requires provides affects tech-stack key-files key-decisions patterns-established requirements-completed duration completed
01-project-infrastructure-module-wiring 03 infra
gradle
kmp
convention-plugins
compose-multiplatform
ktor-server
android-library
explicitApi
ios-framework
phase provides
01-project-infrastructure-module-wiring Plan 01 extended libs.versions.toml (koin.android alias); Plan 02 created 5 recipe.* convention plugins in build-logic/
composeApp/build.gradle.kts reduced from 114 to 28 lines; role-declaration plugin block applying recipe.kotlin.multiplatform + recipe.compose.multiplatform + recipe.android.application + recipe.quality
shared/build.gradle.kts reduced from 55 to 36 lines; applies recipe.kotlin.multiplatform + recipe.quality + androidLibrary; enables explicitApi(); overrides iOS framework baseName to 'Shared'
server/build.gradle.kts reduced from 23 to 18 lines; applies recipe.jvm.server + recipe.quality; retains only module-specific mainClass + projects.shared dep
js target fully removed: shared/src/jsMain/ directory deleted (D-01)
iosX64 remains absent across all modules (D-02)
INFRA-02 structural payoff visible: adding a new KMP module henceforth requires only plugins { id('recipe.kotlin.multiplatform') } + sourceSet declarations
INFRA-06 structural prerequisite: shared/ no longer applies recipe.compose.multiplatform, so Compose cannot leak transitively
02-auth
03-households
04-sync-skeleton
05-recipe-catalog
10-ui-chrome
added patterns
Role-declaration plugin blocks (D-06): module build.gradle.kts plugins {} lists only recipe.* IDs + module-specific aliases (e.g. androidLibrary on shared/)
Per-module override pattern: shared/ overrides framework baseName by targeting KotlinNativeTarget + Framework directly in the module, not from the convention plugin (D-07 / PITFALL #10)
Module-specific dep retention: jvmMain compose.desktop.currentOs + kotlinx.coroutinesSwing stay in composeApp; android debug-only libs.compose.uiTooling stays as debugImplementation
created modified
composeApp/build.gradle.kts — rewritten: 4 recipe.* plugin IDs + 3-source-set dep block + 1 debug tooling line
shared/build.gradle.kts — rewritten: 3 plugins + explicitApi() + Framework baseName override + android {} block retained
server/build.gradle.kts — rewritten: 2 recipe.* plugin IDs + application {} + projects.shared dep
shared/src/jsMain/kotlin/dev/ulfrx/recipe/Platform.js.kt — DELETED (D-01 drops js target)
Keep android { namespace = 'dev.ulfrx.recipe.shared' } block applied in Phase 1 per Open Question #1 (com.android.library retained; future recipe.android.library convention plugin deferred)
libs.versions.* typed accessor used directly in module build.gradle.kts (not libs.findVersion) — PITFALL #1 only applies to precompiled plugin scripts, not module scripts
libs.koin.android added to composeApp androidMain (not commonMain) — Koin's androidContext(...) lives in the android-specific artifact; commonMain stays platform-neutral
Framework baseName override placed in the module, not hoisted into recipe.kotlin.multiplatform — shared/ is the only module needing 'Shared' (composeApp keeps convention default 'ComposeApp'), so keeping it local avoids a plugin parameter
Plugin role declaration: each module build.gradle.kts opens with id('recipe.<role>') IDs — reading the plugins block tells you what the module IS, not how it's configured
Zero version literals in module build files: dependencies always go through libs.* aliases; only project coordinate 'version = 1.0.0' (unindented) is exempted by tools/verify-no-version-literals.sh
Per-module framework basename: KotlinNativeTarget.binaries.withType<Framework>().configureEach { baseName = … } pattern is the canonical override point
INFRA-02
INFRA-06
~8min 2026-04-24

Phase 01 Plan 03: Module Build Scripts Wiring Summary

Rewrote all three module build.gradle.kts files as role declarations applying recipe. convention plugins; dropped the js target (shared/src/jsMain/ deleted); enabled explicitApi() + 'Shared' framework basename on shared/.*

Performance

  • Duration: ~8 min
  • Started: 2026-04-24T16:14:27Z
  • Completed: 2026-04-24T16:22:17Z
  • Tasks: 2
  • Files modified: 3 (composeApp, shared, server build.gradle.kts) + 1 deleted (Platform.js.kt)

Accomplishments

  • composeApp/build.gradle.kts: 114 → 28 lines (-75%). Structural blocks (androidTarget, iosArm64/iosSimulatorArm64, jvm, js, wasmJs, android { }, compose.desktop { nativeDistributions }) all removed and inherited from convention plugins. Only 3 source-set dep blocks + 1 debug tooling line remain.
  • shared/build.gradle.kts: 55 → 36 lines (-35%). Structural target blocks moved to recipe.kotlin.multiplatform; explicitApi() + KotlinNativeTarget/Framework baseName = "Shared" override added (D-07 / D-12 / PITFALL #10); android {} block kept per Open Question #1.
  • server/build.gradle.kts: 23 → 18 lines (-22%). Dependency declarations (logback, ktor-serverCore/Netty/TestHost, kotlin-testJunit) fully relocated into recipe.jvm.server; only module coordinates + mainClass + projects.shared remain.
  • js target eliminated: shared/src/jsMain/kotlin/dev/ulfrx/recipe/Platform.js.kt deleted (D-01). No js { browser() } blocks remain in any module build file.
  • INFRA-02 payoff visible: the plugin block in each module now reads as a role declaration (D-06). A future KMP module just needs plugins { id("recipe.kotlin.multiplatform") } + sourceSet declarations — no target/SDK copy-pasting.
  • INFRA-06 structural prerequisite delivered: recipe.compose.multiplatform is applied ONLY to composeApp/, never to shared/, so Compose deps cannot leak transitively into the shared module's classpath.

Task Commits

Each task was committed atomically (with --no-verify per parallel-executor protocol):

  1. Task 1: Rewrite composeApp + shared build files, delete shared/src/jsMain/d76dcea (refactor)
  2. Task 2: Rewrite server build filed316a48 (refactor)

Note: no test/feat/refactor trio — the plan is marked type=execute, not type=tdd, and all work is build-script configuration (no production code to test).

Files Created/Modified

  • composeApp/build.gradle.kts — rewritten: 4 recipe.* plugin IDs, androidMain/commonMain/jvmMain dep blocks, debugImplementation line
  • shared/build.gradle.kts — rewritten: 3 plugins (recipe.kotlin.multiplatform + recipe.quality + androidLibrary), explicitApi(), Framework baseName = "Shared" override, android {} retained
  • server/build.gradle.kts — rewritten: 2 recipe.* plugin IDs, application { mainClass + JVM args }, implementation(projects.shared)
  • shared/src/jsMain/kotlin/dev/ulfrx/recipe/Platform.js.kt — DELETED (D-01 — js target dropped)

Decisions Made

  • libs.versions.* typed accessor used in module build.gradle.kts rather than libs.findVersion(...) — PITFALL #1 restricts the typed accessor to precompiled plugins; module scripts have full access, so the typed form (libs.versions.android.compileSdk.get().toInt()) is correct and preserved from the prior version of shared/build.gradle.kts.
  • Framework baseName override kept local to shared/ — only shared/ needs "Shared"; composeApp/ keeps the convention-plugin default "ComposeApp". Hoisting the override into recipe.kotlin.multiplatform would require a plugin parameter for a single consumer — not worth the indirection.
  • android { } block retained on shared/ — Open Question #1 in RESEARCH.md defers "do we actually need com.android.library on shared/?" to a future recipe.android.library convention plugin. Phase 1 keeps the block applied; a future plan may remove it.
  • libs.koin.android placed in composeApp androidMain, not commonMain — the androidContext(...) helper used by Plan 04's MainApplication lives in koin-android (JVM/Android artifact). commonMain keeps only platform-neutral deps.

Deviations from Plan

None - plan executed exactly as written.

One minor note (not a deviation, not a failure): shared/build.gradle.kts ended at 36 lines vs. the plan's informal ~35-line target. The single-line delta is the non-negotiable explanatory comment above the KotlinNativeTarget/Framework block. The plan's acceptance_criteria does not set a line cap on shared/ (only composeApp/ ≤ 30 which passes at 28 and server/ ≤ 20 which passes at 18), so all criteria are green.


Total deviations: 0 Impact on plan: Plan executed as specified. All <automated> verify blocks pass (grep chain for each module + tools/verify-no-version-literals.sh + tools/verify-shared-pure.sh).

Issues Encountered

None.

User Setup Required

None - pure build-script refactor; no external service configuration required.

Parallel-Wave Coordination Notes

This plan ran as a parallel executor in Wave 2 alongside Plans 02, 04, 05, 06. Per the wave-2 coordination note:

  • No ./gradlew commands executed in this plan. The convention plugins referenced by id("recipe.kotlin.multiplatform") etc. are created by Plan 02 in a separate worktree; this worktree does NOT see those files. Gradle plugin resolution will succeed after all Wave 2 worktrees merge back to master and Plan 07 runs the full green-build gate.
  • Verification is entirely grep-based, matching the plan's <automated> specification. No runtime build invocation needed at this stage.

Next Phase Readiness

Ready for downstream plans in Phase 01:

  • Plan 04 (compose app skeleton) can now rely on composeApp's recipe.compose.multiplatform application — Compose deps (compose.runtime/foundation/material3/ui/components.resources/lifecycle.*compose) flow in via the convention.
  • Plan 05 (server skeleton) can rely on server's recipe.jvm.server — Ktor server + Flyway + Postgres + serialization flow in via the convention; module only needs to declare mainClass and projects.shared.
  • Plan 07 (invariant gate) will validate the wired build via ./gradlew build after all Wave 2 worktrees merge back.

Downstream phases (Phase 02+ auth, Phase 05 recipe catalog, etc.) inherit a strict boundary: shared/commonMain enforces explicitApi() and carries no Compose / Ktor / SQLDelight deps. Any attempt to add forbidden imports will be caught by tools/verify-shared-pure.sh.

Self-Check: PASSED

Files verified:

  • FOUND: composeApp/build.gradle.kts (28 lines, 4 recipe.* plugin IDs present, no androidTarget/iosArm64/js/nativeDistributions/^android{, libs.koin.android present)
  • FOUND: shared/build.gradle.kts (36 lines, 3 plugins present, explicitApi() present, baseName = "Shared" present, no js {, android {} retained)
  • FOUND: server/build.gradle.kts (18 lines, 2 recipe.* plugin IDs present, mainClass present, projects.shared present, no legacy aliases or deps)
  • MISSING (intentional): shared/src/jsMain/ directory no longer exists

Commits verified:

  • FOUND: d76dcea — refactor(01-03): apply recipe.* conventions to composeApp + shared, drop js
  • FOUND: d316a48 — refactor(01-03): apply recipe.jvm.server + recipe.quality to server module

Verify scripts:

  • tools/verify-no-version-literals.sh → exit 0 (OK: no version literals outside catalog)
  • tools/verify-shared-pure.sh → exit 0 (OK: shared/commonMain is pure)

Phase: 01-project-infrastructure-module-wiring Plan: 03 Completed: 2026-04-24