Files

220 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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)