---
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)
