Implement main app navigation
This commit is contained in:
@@ -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)
|
||||
Reference in New Issue
Block a user