20 KiB
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 |
|
true |
|
|
|
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.
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>
- 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.tomland wired intocomposeApp/build.gradle.ktscommonMain. - iOS simulator K/N framework links successfully (assumptions A1 and A3 confirmed).
- Material Icons Outlined for the five icons used by this phase (CalendarMonth, MenuBook, Inventory2, ShoppingCart, Search) are reachable through the new
compose-material-icons-extendedartifact (assumption A2 resolved via preemptive add per RESEARCH § Open Question 2 recommendation). - 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.
./gradlew :composeApp:commonTestexits green. </success_criteria>