--- phase: 02.1 plan: 04 subsystem: navigation tags: [kotlin, compose-multiplatform, navigation, navigation-compose, type-safe-routes, multi-back-stack] requires: ["02.1-01", "02.1-02"] provides: - "navigation/Routes.kt — 8 @Serializable route types (4 graphs + 4 home destinations)" - "navigation/BottomBarDestination.kt — enum binding routes ↔ string resources ↔ icons ↔ search visibility" - "navigation/RootNavHost.kt — single root NavHost with 4 nested navigation() sub-graphs" - "navigation/NavExtensions.kt — NavHostController.navigateToTab() with four-flag multi-back-stack incantation" - "9 new shared shell/search keys in strings.xml" affects: - "composeApp/src/commonMain/composeResources/values/strings.xml (append-only — 9 new keys)" - "composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/navigation/NavigationTest.kt (un-Ignored, real assertions)" tech-stack: added: - "androidx.navigation.compose.NavHost / navigation / composable (typed routes via @Serializable)" - "androidx.navigation.navOptions DSL (used in tests)" patterns: - "Multi-back-stack tab navigation: popUpTo(graph.findStartDestination().id){saveState=true} + launchSingleTop + restoreState" - "Per-tab parent NavBackStackEntry retrieval for future Koin VM scoping (RESEARCH § Pattern 2)" key-files: created: - "composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/navigation/Routes.kt" - "composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/navigation/BottomBarDestination.kt" - "composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/navigation/RootNavHost.kt" - "composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/navigation/NavExtensions.kt" modified: - "composeApp/src/commonMain/composeResources/values/strings.xml" - "composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/navigation/NavigationTest.kt" decisions: - "Used Icons.AutoMirrored.Outlined.MenuBook (deprecation warning was fatal under -Werror)" - "TabHomePlaceholder uses BasicText from compose-foundation — avoids Material 1/3" - "NavigationTest uses navOptions{} DSL with public shouldXxx() accessors; fake-builder fallback was not needed" metrics: duration: ~6m completed: 2026-05-08 --- # Phase 02.1 Plan 04: Navigation Foundation Summary Type-safe navigation skeleton with 4 nested tab graphs (Planner/Recipes/Pantry/Shopping), a `BottomBarDestination` enum exposing routes/labels/icons/search visibility, and a `navigateToTab` extension that enforces the multi-back-stack four-flag contract — verified by 3 unit tests. ## What Was Built - **Routes.kt** — 8 `@Serializable data object` types: PlannerGraph/PlannerHome, RecipesGraph/RecipesHome, PantryGraph/PantryHome, ShoppingGraph/ShoppingHome. - **BottomBarDestination.kt** — enum in D-03 order (Planner first as `Default`); only Recipes + Pantry have `hasSearch=true`/non-null `searchPlaceholder`. Bound to `Icons.Outlined.{CalendarMonth,Inventory2,ShoppingCart}` and `Icons.AutoMirrored.Outlined.MenuBook`. - **RootNavHost.kt** — single root `NavHost(startDestination = PlannerGraph)` containing four `navigation<*Graph>(startDestination = *Home)` blocks. Each `composable<*Home>` retrieves the parent graph's `NavBackStackEntry` via `navController.getBackStackEntry(*Graph)` (Pattern 2 wired and ready for plan 02.1-08 to consume with `koinViewModel(viewModelStoreOwner = parent)`). Renders private `TabHomePlaceholder` using `BasicText` — no Material dependency. - **NavExtensions.kt** — `fun NavHostController.navigateToTab(graphRoute: Any)` applies `popUpTo(graph.findStartDestination().id){saveState=true}`, `launchSingleTop = true`, `restoreState = true`. - **strings.xml** — 9 new keys appended (4 tab labels + 2 search placeholders + 3 search a11y), Polish copy verbatim from UI-SPEC. All 7 existing `auth_*` keys preserved. - **NavigationTest.kt** — `@Ignore` removed; 3 tests assert the four-flag contract via the public `navOptions { ... }` DSL and `shouldLaunchSingleTop()` / `shouldRestoreState()` / `shouldPopUpToSaveState()` accessors. ## Verification - `./gradlew :composeApp:compileKotlinIosSimulatorArm64 -q` → exit 0 - `./gradlew :composeApp:iosSimulatorArm64Test --tests "dev.ulfrx.recipe.navigation.NavigationTest"` → BUILD SUCCESSFUL (3 tests pass) - `./gradlew :composeApp:linkDebugFrameworkIosSimulatorArm64 -q` → exit 0 - All acceptance grep counts match per task. ## Deviations from Plan ### Auto-fixed Issues **1. [Rule 1 - Bug] Deprecated `Icons.Outlined.MenuBook` failed -Werror compile** - **Found during:** Task 1 verify - **Issue:** `'val Icons.Outlined.MenuBook: ImageVector' is deprecated. Use the AutoMirrored version` — Werror promoted the warning to a build failure. - **Fix:** Switched to `Icons.AutoMirrored.Outlined.MenuBook` and updated import. - **Files modified:** `composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/navigation/BottomBarDestination.kt` - **Commit:** 9b9029a (folded into Task 1 commit) No other deviations. Plan executed as written. ## Open Questions Resolved - **navOptions DSL availability under nav-compose 2.9.2 K/N:** Public `navOptions { ... }` builder and `shouldLaunchSingleTop()` / `shouldRestoreState()` / `shouldPopUpToSaveState()` accessors are all publicly exposed. The fake-builder fallback path described in the plan was not needed. - **TabHomePlaceholder text strategy:** Settled on `androidx.compose.foundation.text.BasicText` — keeps the placeholder Material-free per UI-SPEC line 31. Plan 02.1-08 will replace with real `Tab*Screen` composables. ## Commits - 9b9029a `feat(02.1-04): add type-safe routes and bottom bar destinations` - 5634171 `feat(02.1-04): add RootNavHost and navigateToTab extension` - 41d9bf4 `test(02.1-04): assert navigateToTab applies four-flag back-stack contract` ## Self-Check: PASSED - FOUND: composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/navigation/Routes.kt - FOUND: composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/navigation/BottomBarDestination.kt - FOUND: composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/navigation/RootNavHost.kt - FOUND: composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/navigation/NavExtensions.kt - FOUND commit 9b9029a, 5634171, 41d9bf4