Files
recipe/.planning/phases/02.1-app-shell-navigation-search-foundation/02.1-08-SUMMARY.md

10 KiB
Raw Blame History

phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, decisions, metrics, requirements
phase plan subsystem tags requires provides affects tech-stack key-files decisions metrics requirements
02.1 08 app-shell-final-integration
koin
di
navigation
glass
app-entry
integration
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)
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)
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
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
created modified
composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/di/ShellModule.kt
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
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).
duration completed
~25 min 2026-05-08
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:

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