Implement main app navigation

This commit is contained in:
2026-05-08 14:03:26 +02:00
parent f7e866a08d
commit 794e27c554
90 changed files with 11725 additions and 187 deletions

View File

@@ -0,0 +1,219 @@
---
phase: 02.1
plan: 08
subsystem: app-shell-final-integration
tags: [koin, di, navigation, glass, app-entry, integration]
requires:
- 02.1-02-SUMMARY (RecipeTheme + LocalRecipe* providers)
- 02.1-03-SUMMARY (GlassBackend / LocalGlassBackend / resolveGlassBackend)
- 02.1-04-SUMMARY (RootNavHost skeleton + per-tab graphs)
- 02.1-05-SUMMARY (AppShell composable)
- 02.1-06-SUMMARY (Recipes/Pantry SearchViewModels)
- 02.1-07-SUMMARY (Tab screens + tab ViewModels)
provides:
- shellModule (Koin) — registers 4 tab VMs + 2 search VMs + ShellViewModel + GlassBackend single
- resolveRootRoute(AuthState, hasCurrentUser) — pure routing helper for V-04 unit testing
- RootRoute enum (Splash / Login / Shell)
- LocalGlassBackend wired through RecipeTheme
- Authenticated users now land in AppShell (UI-09 closure)
affects:
- composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/App.kt
- composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/di/AppModule.kt
- composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/navigation/RootNavHost.kt
- composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/ui/theme/RecipeTheme.kt
- composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/ui/screens/shell/AppShellGateTest.kt
tech-stack:
added: []
patterns:
- Pure routing helper extracted for unit testing (RootRoute enum + resolveRootRoute)
- Per-tab koinViewModel(viewModelStoreOwner = parent) scoping (RESEARCH § Pattern 2)
- GlassBackend resolved at composition root and provided via CompositionLocal
key-files:
created:
- composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/di/ShellModule.kt
modified:
- composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/App.kt
- composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/di/AppModule.kt
- composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/navigation/RootNavHost.kt
- composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/ui/theme/RecipeTheme.kt
- composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/ui/screens/shell/AppShellGateTest.kt
decisions:
- Routing logic extracted to a pure resolveRootRoute helper so V-04 can unit-test the auth gate without instrumenting Compose composition.
- PostLoginPlaceholderScreen and PostLoginViewModel source files preserved (logout-bridge per CONTEXT line 101 / Open Questions Q3 RESOLVED) — only the imports + call site removed from App.kt.
- GlassBackend default = Liquid (iOS+Android primary path, CONTEXT D-16).
metrics:
duration: ~25 min
completed: 2026-05-08
requirements: [UI-09]
---
# Phase 02.1 Plan 08: Final Integration Summary
Wire the seven shell ViewModels and the GlassBackend resolver into a Koin
shellModule, extend appModule.includes, provide LocalGlassBackend through
RecipeTheme, replace the four TabHomePlaceholder stubs in RootNavHost with the
real Tab*Screen composables, and swap App.kt's Authenticated branch from
PostLoginPlaceholderScreen to AppShell — closing UI-09.
## What landed
### Task 1 — ShellModule + AppModule + RecipeTheme glass provider — `9714765`
- New `di/ShellModule.kt` registers:
- `single<GlassBackend> { resolveGlassBackend(get<Settings>(), isDebugBuild, default = GlassBackend.Liquid) }`
- `viewModel<ShellViewModel>()`
- 4 tab VMs (`Planner` / `Recipes` / `Pantry` / `Shopping`)
- 2 search VMs (`RecipesSearchViewModel` / `PantrySearchViewModel`)
- `di/AppModule.kt` extended: `includes(authModule, userModule, shellModule)`
- `ui/theme/RecipeTheme.kt` adds one new `provides` entry —
`LocalGlassBackend provides koinInject<GlassBackend>()` — at the same level as
the other Recipe locals. The `MaterialTheme(...)` wrapper is preserved unchanged
so legacy auth screens (Login / PostLoginPlaceholder / Splash) keep resolving
`MaterialTheme.colorScheme.*` (Open Question Q3 RESOLVED).
- Settings binding: registered in `auth/IosAuthModule.kt` and
`auth/AndroidAuthModule.kt` (Phase 2 wiring for SecureAuthStateStore) — reused
by shellModule, no commonMain Settings binding was needed.
### Task 2 — RootNavHost wires real tab screens — `20e840e`
- All four `TabHomePlaceholder(...)` calls replaced with
`koinViewModel<*ViewModel>(viewModelStoreOwner = parent)` lookups followed by
the real `*Screen(viewModel = vm)` calls.
- `private fun TabHomePlaceholder(...)` deleted; placeholder imports
(`BasicText`, `Box`) removed.
- All four `TODO(02.1-08)` markers cleared.
- Each tab's ViewModelStoreOwner remains the parent graph's
`NavBackStackEntry`, so tab VMs survive across home-detail navigations
within the graph (RESEARCH § Pattern 2).
### Task 3 — App.kt routes Authenticated to AppShell — `2639244`
- New top-level `enum class RootRoute { Splash, Login, Shell }` and
`internal fun resolveRootRoute(authState, hasCurrentUser): RootRoute`.
- `App()` body now switches on `resolveRootRoute(authState, currentUser != null)`
with three branches: Splash / Login / Shell. Authenticated + user goes to
`AppShell()`; Authenticated + null user still holds on `SplashScreen()`.
- `LaunchedEffect(authSession) { initialize() }` and the `RecipeTheme { ... }`
wrapper preserved verbatim.
- `PostLoginPlaceholderScreen.kt` and `PostLoginViewModel.kt` source files
remain on disk (CONTEXT line 101 / Open Question Q3 RESOLVED — logout-bridge
possibility). Only their imports and the call site in App.kt are removed.
### Task 4 — AppShellGateTest backed by real assertions (V-04) — `26392df`
- `@Ignore` removed; five assertions cover all `AuthState × hasCurrentUser`
combinations:
1. `Authenticated + user → Shell` (V-04 anchor)
2. `Authenticated + null user → Splash` (two-layer gate)
3. `Unauthenticated → Login`
4. `Loading → Splash`
5. `Loading + stale user → Splash` (defensive)
- Tests run through the pure `resolveRootRoute` helper, sidestepping the
immature CMP iOS Compose UI testing surface (VALIDATION.md line 27).
- All Wave-0 `@Ignore` stubs across the phase are now backed by real
assertions: `grep -r '@Ignore' composeApp/src/commonTest/` returns 0.
### Task 5 — spotless formatting — `a6f0d46`
- Spotless reformatted plan-08 files (App.kt, RootNavHost.kt, RecipeTheme.kt) —
multi-line function signature for `resolveRootRoute`, multi-line `remember`
blocks. Only changes to plan-08 files committed; pre-existing spotless
violations in unrelated files (LokksmithOidcSupport, OidcClient, AuthSession,
etc.) left out of scope per Rule SCOPE BOUNDARY — those failures predate
this plan and require their own cleanup pass.
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 3 — Blocking import]** Initial ShellModule.kt used
`org.koin.core.module.dsl.viewModel` which expects a `definition` lambda; the
no-arg `viewModel<T>()` form lives in `org.koin.plugin.module.dsl.viewModel`
(matching `auth/AuthModule.kt`).
- Files modified: `di/ShellModule.kt`
- Resolved before any commit; rolled into Task 1.
**2. [Rule 3 — Blocking lint]** Spotless reformatted plan-08 files (multi-line
function param lists, multi-line `remember` blocks). The wider repo has 38
pre-existing spotless violations in unrelated files; per scope boundary, only
the in-scope formatting was committed (`a6f0d46`). The pre-existing violations
were confirmed to predate this plan via `git stash` + `spotlessCheck` before
the plan's edits.
## RecipeTheme.kt edit (final form)
The single in-scope change was adding the `LocalGlassBackend provides glassBackend`
entry alongside the existing four `LocalRecipe*` entries:
```kotlin
@Composable
public fun RecipeTheme(content: @Composable () -> Unit) {
val dark = isSystemInDarkTheme()
val recipeColors = if (dark) DarkRecipeColors else LightRecipeColors
val materialColors = if (dark) LegacyMaterialDarkColors else LegacyMaterialLightColors
val glassBackend = koinInject<GlassBackend>()
MaterialTheme(colorScheme = materialColors) {
androidx.compose.runtime.CompositionLocalProvider(
LocalRecipeColors provides recipeColors,
LocalRecipeTypography provides DefaultRecipeTypography,
LocalRecipeSpacing provides DefaultRecipeSpacing,
LocalRecipeShapes provides DefaultRecipeShapes,
LocalRecipeGlass provides DefaultRecipeGlass,
LocalGlassBackend provides glassBackend,
content = content,
)
}
}
```
The `MaterialTheme(colorScheme = materialColors)` wrapper is unchanged (Open
Question Q3 RESOLVED — legacy auth screens still depend on it).
## Settings registration check
`com.russhwolf.settings.Settings` is bound as a `single<Settings>` in:
- `composeApp/src/iosMain/kotlin/dev/ulfrx/recipe/auth/IosAuthModule.kt:25`
- `composeApp/src/androidMain/kotlin/dev/ulfrx/recipe/auth/AndroidAuthModule.kt:19`
Phase 2 introduced this for `SecureAuthStateStore`. shellModule reuses the same
binding — no commonMain `single<Settings>` was required.
## PostLoginPlaceholderScreen / PostLoginViewModel preservation
Both source files remain on disk:
```
composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/ui/screens/auth/PostLoginPlaceholderScreen.kt
composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/ui/screens/auth/PostLoginViewModel.kt
```
They are no longer reachable from `App.kt` — kept as a logout-bridge possibility
per CONTEXT line 101 / Open Question Q3 (RESOLVED). A future phase may revive
or repurpose them.
## Manual smoke (V-08 / V-09 / V-10 / V-11)
Manual iOS-simulator smoke deferred — no simulator in this autonomous run.
Static checks performed:
- `./gradlew :composeApp:compileKotlinIosSimulatorArm64 -q` → exits 0
- `./gradlew :composeApp:compileDebugKotlinAndroid -q` → exits 0
- `./gradlew :composeApp:iosSimulatorArm64Test --tests "...AppShellGateTest"` → tests pass
- `grep -r '@Ignore' composeApp/src/commonTest/` → 0 results
`./gradlew :composeApp:check` is RED only because of pre-existing spotless
violations in 38 unrelated files (predates this plan; confirmed via stash).
## Self-Check: PASSED
- composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/di/ShellModule.kt — FOUND
- composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/di/AppModule.kt — FOUND (modified)
- composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/App.kt — FOUND (modified)
- composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/navigation/RootNavHost.kt — FOUND (modified)
- composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/ui/theme/RecipeTheme.kt — FOUND (modified)
- composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/ui/screens/shell/AppShellGateTest.kt — FOUND (un-ignored)
- Commit 9714765 — FOUND (Task 1)
- Commit 20e840e — FOUND (Task 2)
- Commit 2639244 — FOUND (Task 3)
- Commit 26392df — FOUND (Task 4)
- Commit a6f0d46 — FOUND (style)