--- phase: 02.1 plan: 01 type: execute wave: 0 depends_on: [] files_modified: - gradle/libs.versions.toml - composeApp/build.gradle.kts - composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/navigation/NavigationTest.kt - composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/ui/components/glass/GlassBackendTest.kt - composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/ui/components/glass/GlassBackendOverrideTest.kt - composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/ui/screens/shell/AppShellGateTest.kt - composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/ui/screens/recipes/RecipesSearchViewModelTest.kt - composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/ui/screens/pantry/PantrySearchViewModelTest.kt autonomous: true requirements: [UI-03, UI-04, UI-09, UI-10] tags: [kotlin, kmp, compose-multiplatform, gradle, navigation, liquid, haze, compose-unstyled, wave-0] must_haves: truths: - "navigation-compose 2.9.2, compose-unstyled 1.49.9, liquid 1.1.1, haze 1.6.10 resolve cleanly for iosArm64 and iosSimulatorArm64" - "Material Icons Outlined (CalendarMonth, MenuBook, Inventory2, ShoppingCart, Search) compile from accessible package" - "Wave 0 test stub files exist with @Test functions in @Ignore state for V-01..V-07 anchors" artifacts: - path: "gradle/libs.versions.toml" provides: "version catalog entries: navigation-compose, compose-unstyled, liquid, haze, compose-material-icons-extended (if needed)" contains: "navigation-compose = \"2.9.2\"" - path: "composeApp/build.gradle.kts" provides: "commonMain dependencies wired" contains: "libs.navigation.compose" - path: "composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/navigation/NavigationTest.kt" provides: "V-01 test stub" - path: "composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/ui/components/glass/GlassBackendTest.kt" provides: "V-02 test stub" - path: "composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/ui/components/glass/GlassBackendOverrideTest.kt" provides: "V-03 test stub" - path: "composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/ui/screens/shell/AppShellGateTest.kt" provides: "V-04 test stub" - path: "composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/ui/screens/recipes/RecipesSearchViewModelTest.kt" provides: "V-05/V-06 test stubs" - path: "composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/ui/screens/pantry/PantrySearchViewModelTest.kt" provides: "V-07 test stub" key_links: - from: "composeApp/build.gradle.kts" to: "gradle/libs.versions.toml" via: "libs.navigation.compose / libs.compose.unstyled / libs.liquid / libs.haze references" pattern: "libs\\.(navigation\\.compose|compose\\.unstyled|liquid|haze)" --- Wave 0 — verify the three load-bearing assumptions (A1: Liquid iOS klibs resolve; A2: Material Icons Outlined available; A3: nav-compose 2.9.2 K/N back-stack save/restore), add the four new dependencies to the version catalog and `composeApp` build, and land the six commonTest stub files referenced by VALIDATION.md so V-01..V-07 anchors have target locations from day 1. Purpose: De-risk the rest of the phase. If A1 fails, the GlassSurface backend default flips to Haze before any UI code is written; if A2 fails, `material-icons-extended` is added before screens reference icons. Output: Updated `libs.versions.toml`, updated `composeApp/build.gradle.kts`, six failing-but-compiling test stubs. @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/02.1-app-shell-navigation-search-foundation/02.1-CONTEXT.md @.planning/phases/02.1-app-shell-navigation-search-foundation/02.1-RESEARCH.md @.planning/phases/02.1-app-shell-navigation-search-foundation/02.1-VALIDATION.md @.planning/phases/02.1-app-shell-navigation-search-foundation/02.1-PATTERNS.md @gradle/libs.versions.toml @composeApp/build.gradle.kts Existing test analog (LoginViewModelTest.kt — pattern shape only): ```kotlin import kotlin.test.Test import kotlin.test.assertEquals import kotlinx.coroutines.test.runTest class XxxTest { @Test fun behaviorName() = runTest { // arrange // act // assert } } ``` Existing libs.versions.toml relevant entries (already present): - composeMultiplatform = "1.10.3" - material3 = "1.10.0-alpha05" - multiplatformSettings = "1.3.0" - compose-components-resources, compose-foundation, compose-runtime, compose-ui already wired. Task 1: Add nav-compose / compose-unstyled / liquid / haze (and material-icons-extended if needed) to version catalog and composeApp build gradle/libs.versions.toml, composeApp/build.gradle.kts - gradle/libs.versions.toml (current state — append-only edits; preserve all existing entries verbatim) - composeApp/build.gradle.kts (current state — locate the `commonMain.dependencies { ... }` block) - .planning/phases/02.1-app-shell-navigation-search-foundation/02.1-RESEARCH.md § Standard Stack (lines 117-178; locked coordinates and versions) - .planning/phases/02.1-app-shell-navigation-search-foundation/02.1-RESEARCH.md § Pitfall D (Material Icons availability — lines 461-465) - .planning/phases/02.1-app-shell-navigation-search-foundation/02.1-PATTERNS.md § `gradle/libs.versions.toml` (modified) Edit `gradle/libs.versions.toml`: 1. Append to `[versions]` block (after the existing `multiplatformSettings = "1.3.0"` line, preserving alphabetical/grouping conventions seen in the file): ```toml navigation-compose = "2.9.2" compose-unstyled = "1.49.9" liquid = "1.1.1" haze = "1.6.10" ``` 2. Append to `[libraries]` block (after the existing Phase 2 client block, separated by a comment header `# Phase 2.1 — App shell foundation (UI-03, UI-04, UI-09, UI-10)`): ```toml navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "navigation-compose" } compose-unstyled = { module = "com.composables:composeunstyled", version.ref = "compose-unstyled" } liquid = { module = "io.github.fletchmckee.liquid:liquid", version.ref = "liquid" } haze = { module = "dev.chrisbanes.haze:haze", version.ref = "haze" } ``` Edit `composeApp/build.gradle.kts`: 3. Inside the `commonMain.dependencies { ... }` block (locate by grep), append (after existing `implementation(libs.multiplatform.settings)` line, or after the last existing `implementation(...)` in commonMain): ```kotlin implementation(libs.navigation.compose) implementation(libs.compose.unstyled) implementation(libs.liquid) implementation(libs.haze) ``` 4. Verify A2 — Material Icons Outlined availability. Run a quick Gradle resolution probe: ```bash ./gradlew :composeApp:dependencies --configuration commonMainImplementation 2>&1 | grep -E "(material-icons-extended|material3)" | head -20 ``` The four icons referenced in UI-SPEC (`Icons.Outlined.CalendarMonth`, `MenuBook`, `Inventory2`, `ShoppingCart`) are NOT in the baseline icon set. They live in `material-icons-extended`. Add to catalog (per RESEARCH § Pitfall D): ```toml # in [versions]: compose-material-icons-extended = "1.7.3" # in [libraries]: compose-material-icons-extended = { module = "org.jetbrains.compose.material:material-icons-extended", version.ref = "compose-material-icons-extended" } ``` And in `composeApp/build.gradle.kts` `commonMain.dependencies`: ```kotlin implementation(libs.compose.material.icons.extended) ``` Use the kebab-style alias-to-Kotlin-camel-case convention already in use (e.g. `multiplatform-settings` → `libs.multiplatform.settings`). Do NOT modify any existing entries. Preserve all comments. Append only. count="$(./gradlew :composeApp:dependencies --configuration iosSimulatorArm64MainResolvableDependenciesMetadata 2>&1 | grep -E "(navigation-compose:2\\.9\\.2|composeunstyled:1\\.49\\.9|liquid:1\\.1\\.1|haze:1\\.6\\.10|material-icons-extended)" | wc -l | tr -d ' ')"; test "$count" -ge 5 - `grep -c '^navigation-compose = ' gradle/libs.versions.toml` returns 1 (the version entry) - `grep -c 'navigation-compose:navigation-compose' gradle/libs.versions.toml` returns 1 - `grep -c 'composables:composeunstyled' gradle/libs.versions.toml` returns 1 - `grep -c 'fletchmckee.liquid:liquid' gradle/libs.versions.toml` returns 1 - `grep -c 'chrisbanes.haze:haze' gradle/libs.versions.toml` returns 1 - `grep -c 'material-icons-extended' gradle/libs.versions.toml` returns at least 1 (version + library = 2) - `grep -c 'libs.navigation.compose' composeApp/build.gradle.kts` returns at least 1 - `grep -c 'libs.compose.unstyled' composeApp/build.gradle.kts` returns at least 1 - `grep -c 'libs.liquid' composeApp/build.gradle.kts` returns at least 1 - `grep -c 'libs.haze' composeApp/build.gradle.kts` returns at least 1 - `./gradlew :composeApp:help -q` exits 0 (catalog parses without error) - `./gradlew :composeApp:linkDebugFrameworkIosSimulatorArm64 -q` exits 0 (A1 + A3 verified by successful K/N link with new dependencies on classpath) - All pre-existing `[versions]` and `[libraries]` keys are still present (`grep -c '^kotlin = ' gradle/libs.versions.toml` returns 1; `grep -c '^lokksmith-compose' gradle/libs.versions.toml` returns 1) Version catalog declares the four new libraries (plus material-icons-extended) at the exact pinned versions; composeApp/build.gradle.kts wires them into commonMain; the iOS simulator framework links cleanly, proving A1 (Liquid iOS klibs resolve) and A3 (nav-compose 2.9.2 K/N classpath OK). Task 2: Land six failing test stubs for V-01..V-07 anchors composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/navigation/NavigationTest.kt, composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/ui/components/glass/GlassBackendTest.kt, composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/ui/components/glass/GlassBackendOverrideTest.kt, composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/ui/screens/shell/AppShellGateTest.kt, composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/ui/screens/recipes/RecipesSearchViewModelTest.kt, composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/ui/screens/pantry/PantrySearchViewModelTest.kt - composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/auth/LoginViewModelTest.kt (analog — `runTest`/`@Test`/`assertEquals` skeleton) - composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/auth/AuthSessionTest.kt (analog — state-flow gate test shape) - .planning/phases/02.1-app-shell-navigation-search-foundation/02.1-VALIDATION.md § Wave 0 Requirements (locked file paths and anchor coverage) - .planning/phases/02.1-app-shell-navigation-search-foundation/02.1-RESEARCH.md § Validation Architecture lines 715-755 - .planning/phases/02.1-app-shell-navigation-search-foundation/02.1-PATTERNS.md § Test files (new) lines 386-415 Create six commonTest files. Each contains compiling test scaffolds that reference yet-to-be-created production types via `@Ignore`d test bodies (so the test compiles but does not yet pass — Wave 0 produces the targets, later waves implement and un-ignore). Use `kotlin.test` (`org.junit.*` is forbidden; `kotlin.test` only — matches Phase 2 convention). File 1 — `composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/navigation/NavigationTest.kt`: ```kotlin package dev.ulfrx.recipe.navigation import kotlin.test.Ignore import kotlin.test.Test /** * V-01 — UI-03 — `navigateToTab()` extension applies * popUpTo(graph.findStartDestination().id) { saveState = true }; launchSingleTop = true; restoreState = true. * Implemented in plan 02.1-04 (RootNavHost / Routes). */ class NavigationTest { @Test @Ignore fun navigateToTab_appliesPopUpToWithSaveState() { // TODO(02.1-04): assert NavOptionsBuilder lambda flips popUpToId+saveState=true, // launchSingleTop=true, restoreState=true. Use TestNavHostController if available // in CMP commonTest; else capture a fake NavOptionsBuilder. } } ``` File 2 — `composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/ui/components/glass/GlassBackendTest.kt`: ```kotlin package dev.ulfrx.recipe.ui.components.glass import kotlin.test.Ignore import kotlin.test.Test /** * V-02 — UI-04 — `resolveGlassBackend(...)` returns Liquid for iOS source-set defaults * with no debug override. Implemented in plan 02.1-03 (GlassSurface). */ class GlassBackendTest { @Test @Ignore fun resolveGlassBackend_iosDefault_returnsLiquid() { // TODO(02.1-03): assert resolveGlassBackend(settings = MapSettings(), isDebug = false, // default = GlassBackend.Liquid) == GlassBackend.Liquid } } ``` File 3 — `composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/ui/components/glass/GlassBackendOverrideTest.kt`: ```kotlin package dev.ulfrx.recipe.ui.components.glass import kotlin.test.Ignore import kotlin.test.Test /** * V-03 — UI-04 — debug-build runtime override via multiplatform-settings honors * "debug.glass_backend" key with values "liquid" / "haze" / "flat". * Implemented in plan 02.1-03 (GlassSurface). */ class GlassBackendOverrideTest { @Test @Ignore fun resolveGlassBackend_debugBuildHonorsSettingsOverride() { // TODO(02.1-03): use com.russhwolf.settings.MapSettings, set // "debug.glass_backend" = "haze", isDebug = true, assert returns Haze. } @Test @Ignore fun resolveGlassBackend_productionBuildIgnoresSettingsOverride() { // TODO(02.1-03): same map but isDebug = false → returns the compile-time default. } } ``` File 4 — `composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/ui/screens/shell/AppShellGateTest.kt`: ```kotlin package dev.ulfrx.recipe.ui.screens.shell import kotlin.test.Ignore import kotlin.test.Test /** * V-04 — UI-09 — App.kt's Authenticated + currentUser != null branch resolves to AppShell, * not PostLoginPlaceholderScreen. Implemented in plan 02.1-08 (App.kt wire-up). * * Style: mirror AuthSessionTest.kt — runTest + state-flow assertion + Koin test container. */ class AppShellGateTest { @Test @Ignore fun authenticatedWithUser_routesToAppShell_notPlaceholder() { // TODO(02.1-08): drive AuthSession through Authenticated state with a non-null currentUser // and assert the App() composable selects the AppShell branch (via a probe-flag injected // into the composition or via a refactored RootRouter pure function). } } ``` File 5 — `composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/ui/screens/recipes/RecipesSearchViewModelTest.kt`: ```kotlin package dev.ulfrx.recipe.ui.screens.recipes import kotlin.test.Ignore import kotlin.test.Test /** * V-05/V-06 — UI-10 — RecipesSearchViewModel state machine semantics. * Implemented in plan 02.1-07 (Search foundation). */ class RecipesSearchViewModelTest { @Test @Ignore fun openThenQueryChangeThenClose_clearsQueryAndResetsIsOpen() { // V-05: TODO(02.1-07) — open() → onQueryChange("foo") → close() leaves // state = SearchState(isOpen = false, query = "") } @Test @Ignore fun clear_resetsQueryButKeepsIsOpenTrue() { // V-06: TODO(02.1-07) — open() → onQueryChange("foo") → clear() leaves // state = SearchState(isOpen = true, query = "") } } ``` File 6 — `composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/ui/screens/pantry/PantrySearchViewModelTest.kt`: ```kotlin package dev.ulfrx.recipe.ui.screens.pantry import kotlin.test.Ignore import kotlin.test.Test /** * V-07 — UI-10 — PantrySearchViewModel parity with RecipesSearchViewModel * (open/close/clear semantics). Implemented in plan 02.1-07 (Search foundation). */ class PantrySearchViewModelTest { @Test @Ignore fun openThenQueryChangeThenClose_clearsQueryAndResetsIsOpen() { // V-07: TODO(02.1-07) — same semantics as RecipesSearchViewModelTest. } @Test @Ignore fun clear_resetsQueryButKeepsIsOpenTrue() { // V-07: TODO(02.1-07). } } ``` All six files use `kotlin.test.Test` + `kotlin.test.Ignore` only — no library types referenced (so they compile without depending on yet-to-be-created production code). ./gradlew :composeApp:compileTestKotlinIosSimulatorArm64 -q - `find composeApp/src/commonTest -name '*.kt' -path '*/navigation/NavigationTest.kt' -o -path '*/glass/GlassBackendTest.kt' -o -path '*/glass/GlassBackendOverrideTest.kt' -o -path '*/shell/AppShellGateTest.kt' -o -path '*/recipes/RecipesSearchViewModelTest.kt' -o -path '*/pantry/PantrySearchViewModelTest.kt' | wc -l` returns 6 - `grep -l 'V-01' composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/navigation/NavigationTest.kt` matches - `grep -l 'V-02' composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/ui/components/glass/GlassBackendTest.kt` matches - `grep -l 'V-03' composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/ui/components/glass/GlassBackendOverrideTest.kt` matches - `grep -l 'V-04' composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/ui/screens/shell/AppShellGateTest.kt` matches - `grep -l 'V-05' composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/ui/screens/recipes/RecipesSearchViewModelTest.kt` matches - `grep -l 'V-07' composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/ui/screens/pantry/PantrySearchViewModelTest.kt` matches - `grep -c '@Ignore' composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/ui/screens/recipes/RecipesSearchViewModelTest.kt` returns 2 - `./gradlew :composeApp:commonTest -q` exits 0 (no failures because all tests are `@Ignore`d) - No file imports `androidx.compose.material3` (Material 3 boundary): `grep -c 'material3' composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/ui/screens/shell/AppShellGateTest.kt` returns 0 Six test files exist under `commonTest/`, each compiles, each contains @Ignore'd @Test functions referencing the V-anchor it covers, commonTest run is green (no real assertions yet — production code lands in subsequent waves). - Catalog parses: `./gradlew :composeApp:help -q` exits 0 - iOS framework links: `./gradlew :composeApp:linkDebugFrameworkIosSimulatorArm64 -q` exits 0 (proves A1 + A3) - commonTest compiles + green: `./gradlew :composeApp:commonTest -q` exits 0 - All 6 test files exist at the exact paths listed in VALIDATION.md § Wave 0 Requirements 1. nav-compose 2.9.2 + compose-unstyled 1.49.9 + liquid 1.1.1 + haze 1.6.10 + material-icons-extended 1.7.3 declared in `gradle/libs.versions.toml` and wired into `composeApp/build.gradle.kts` commonMain. 2. iOS simulator K/N framework links successfully (assumptions A1 and A3 confirmed). 3. Material Icons Outlined for the five icons used by this phase (CalendarMonth, MenuBook, Inventory2, ShoppingCart, Search) are reachable through the new `compose-material-icons-extended` artifact (assumption A2 resolved via preemptive add per RESEARCH § Open Question 2 recommendation). 4. Six commonTest stub files exist at the exact paths specified in VALIDATION.md § Wave 0 Requirements; all contain @Ignore'd @Test functions referencing their V-anchor IDs. 5. `./gradlew :composeApp:commonTest` exits green. After completion, create `.planning/phases/02.1-app-shell-navigation-search-foundation/02.1-01-SUMMARY.md` per `$HOME/.claude/get-shit-done/templates/summary.md`. Record the exact resolved versions of nav-compose, compose-unstyled, liquid, haze, and material-icons-extended (from `./gradlew dependencies` output) so subsequent plans can reference verified coordinates.