Files

20 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, tags, must_haves
phase plan type wave depends_on files_modified autonomous requirements tags must_haves
02.1 01 execute 0
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
true
UI-03
UI-04
UI-09
UI-10
kotlin
kmp
compose-multiplatform
gradle
navigation
liquid
haze
compose-unstyled
wave-0
truths artifacts key_links
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
path provides contains
gradle/libs.versions.toml version catalog entries: navigation-compose, compose-unstyled, liquid, haze, compose-material-icons-extended (if needed) navigation-compose = "2.9.2"
path provides contains
composeApp/build.gradle.kts commonMain dependencies wired libs.navigation.compose
path provides
composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/navigation/NavigationTest.kt V-01 test stub
path provides
composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/ui/components/glass/GlassBackendTest.kt V-02 test stub
path provides
composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/ui/components/glass/GlassBackendOverrideTest.kt V-03 test stub
path provides
composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/ui/screens/shell/AppShellGateTest.kt V-04 test stub
path provides
composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/ui/screens/recipes/RecipesSearchViewModelTest.kt V-05/V-06 test stubs
path provides
composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/ui/screens/pantry/PantrySearchViewModelTest.kt V-07 test stub
from to via pattern
composeApp/build.gradle.kts gradle/libs.versions.toml libs.navigation.compose / libs.compose.unstyled / libs.liquid / libs.haze references 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.

<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>

@.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):
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

<success_criteria>

  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. </success_criteria>
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.