---
phase: 02-authentication-foundation
plan: 07
subsystem: auth
tags: [kmp, compose-multiplatform, material3, koin-viewmodel, compose-resources, auth-gate]

requires:
  - phase: 02-authentication-foundation
    provides: 02-06 AuthSession StateFlow, AuthState model, authModule Koin singletons
provides:
  - SplashScreen / LoginScreen / PostLoginPlaceholderScreen Phase 2 auth gate
  - LoginViewModel + LoginScreenState + PostLoginViewModel mapping AuthSession results to Compose Resources
  - Compose Resources strings for the seven Phase 2 auth keys
  - RecipeTheme Material 3 light/dark seed with primary `#3B6939` / `#A2D597`
affects: [phase-03-households]

tech-stack:
  added:
    - kotlinx-coroutines-test (commonTest only) for the multiplatform `runTest` runtime
  patterns:
    - "App.kt observes AuthSession.state via collectAsStateWithLifecycle and renders one of three screens; no manual navigation."
    - "LoginViewModel.onSignInClick() returns the launched Job so commonTest can join() deterministically without dragging in a TestDispatcher."
    - "ViewModels registered in authModule via org.koin.core.module.dsl.viewModel; consumed via koinViewModel<T>()."
    - "All commonTest coroutine tests use kotlinx.coroutines.test.runTest so wasmJs can compile (runBlocking is JVM/Native-only)."

key-files:
  created:
    - composeApp/src/commonMain/composeResources/values/strings.xml
    - composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/ui/theme/RecipeTheme.kt
    - composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/ui/screens/auth/SplashScreen.kt
    - composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/ui/screens/auth/LoginScreen.kt
    - composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/ui/screens/auth/LoginViewModel.kt
    - composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/ui/screens/auth/PostLoginPlaceholderScreen.kt
    - composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/ui/screens/auth/PostLoginViewModel.kt
    - composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/ui/screens/auth/LoginViewModelTest.kt
    - .planning/phases/02-authentication-foundation/deferred-items.md
  modified:
    - composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/App.kt
    - composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/auth/AuthModule.kt
    - composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/auth/AuthSessionTest.kt
    - composeApp/build.gradle.kts
    - gradle/libs.versions.toml

key-decisions:
  - "ViewModels registered in authModule (alongside AuthSession) instead of a new uiModule — keeps the single Koin module that owns AuthSession also owning its UI consumers."
  - "LoginViewModel.onSignInClick() returns Job rather than swallowing it so tests deterministically join without a TestDispatcher; production callers ignore the returned Job."
  - "AuthSession.initialize() is launched from a LaunchedEffect in App.kt rather than a Koin lifecycle hook; keeps Phase 2 startup explicit and easy to trace."
  - "Pre-existing ./gradlew check failures (Android JVM SecureAuthStateStoreContractTest, ios SecureAuthStateStore ktlint) are out of scope for 02-07 and tracked in deferred-items.md per scope-boundary rule."

requirements-completed: [AUTH-01, AUTH-04, AUTH-05]

duration: 10m
completed: 2026-04-28
---

# Phase 02 Plan 07: Auth UI Gate Summary

**Phase 2 auth UI gate — SplashScreen / LoginScreen / PostLoginPlaceholderScreen wired to AuthSession via koinViewModel, with externalized Polish strings and a Material 3 seed theme.**

## Performance

- **Duration:** ~10 min (automated tasks)
- **Started:** 2026-04-28T15:31:20Z
- **Automated work completed:** 2026-04-28T15:41:31Z
- **Tasks completed:** 2 of 3 (Task 3 awaits manual iOS Authentik UAT)
- **Files created:** 9
- **Files modified:** 5

## Accomplishments

- Added all seven Phase 2 Compose Resources keys with the Polish scaffold copy from `02-UI-SPEC.md`.
- Added `RecipeTheme` with light/dark Material 3 schemes seeded by `#3B6939` / `#A2D597` and `isSystemInDarkTheme()`.
- Replaced the JetBrains template `App()` body with the auth-gate `when` over `AuthSession.state`, observing via `collectAsStateWithLifecycle` and kicking `AuthSession.initialize()` from `LaunchedEffect`.
- Implemented `SplashScreen`, `LoginScreen`, and `PostLoginPlaceholderScreen` using Material 3 stdlib only — no Scaffold, no Haze, all strings via `stringResource(Res.string.*)`.
- Implemented `LoginViewModel` (mapping AuthSession failures → `auth_error_*` `StringResource` keys, clearing stale errors on retry) and trivial `PostLoginViewModel.onSignOutClick()` delegating to `AuthSession.logout()`.
- Registered both ViewModels in `authModule` via `org.koin.core.module.dsl.viewModel`.
- Added `kotlinx-coroutines-test` to `commonTest` so the wasmJs target can compile coroutine tests (replacing JVM-only `runBlocking` with multiplatform `runTest` in both `LoginViewModelTest` and the existing `AuthSessionTest`).

## Task Commits

1. **Task 1 (RED): Compose Resources, theme seed, failing LoginViewModel tests** — `466e4c7` (test)
2. **Task 2 (GREEN): Auth screens, ViewModels, App auth gate** — `88f4898` (feat)
3. **Task 2 follow-up: switch commonTest to runTest for wasmJs compatibility** — `570652c` (fix)
4. **Task 3 (manual UAT): pending — see Awaiting User UAT below**

## Files Created/Modified

### Created
- `composeApp/src/commonMain/composeResources/values/strings.xml` — Phase 2 auth strings (Polish scaffold).
- `composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/ui/theme/RecipeTheme.kt` — Material 3 seed theme.
- `composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/ui/screens/auth/SplashScreen.kt` — wordmark + progress.
- `composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/ui/screens/auth/LoginScreen.kt` — wordmark + button + inline error.
- `composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/ui/screens/auth/LoginViewModel.kt` — `LoginScreenState` + `onSignInClick()` mapping.
- `composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/ui/screens/auth/PostLoginPlaceholderScreen.kt` — welcome + logout.
- `composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/ui/screens/auth/PostLoginViewModel.kt` — `onSignOutClick()` → `AuthSession.logout()`.
- `composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/ui/screens/auth/LoginViewModelTest.kt` — five tests covering cancelled/network/unknown/success and clear-error-on-retry.
- `.planning/phases/02-authentication-foundation/deferred-items.md` — log of pre-existing failures.

### Modified
- `composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/App.kt` — auth-gate `when` body.
- `composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/auth/AuthModule.kt` — `viewModel { ... }` registrations.
- `composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/auth/AuthSessionTest.kt` — `runBlocking` → `runTest`.
- `composeApp/build.gradle.kts` — `commonTest` `kotlinx-coroutines-test` dependency.
- `gradle/libs.versions.toml` — added `kotlinx-coroutinesTest` library entry.

## Decisions Made

See frontmatter `key-decisions`.

## Deviations from Plan

### Auto-fixed Issues

**1. [Rule 3 — Blocking] commonTest coroutine tests must use `runTest`, not `runBlocking`**

- **Found during:** Task 2 verification, when `./gradlew check` ran the wasmJs test target for the first time.
- **Issue:** `kotlinx.coroutines.runBlocking` is JVM/Native-only and breaks `:composeApp:compileTestKotlinWasmJs`. The pre-existing `AuthSessionTest` (committed in Plan 02-06) used the same pattern and was never wasmJs-tested — `02-06` only ran `:composeApp:jvmTest`. Phase 02-07's verification gate is the first one to catch it.
- **Fix:** Added `org.jetbrains.kotlinx:kotlinx-coroutines-test` to `commonTest`, switched both `AuthSessionTest` and the new `LoginViewModelTest` from `runBlocking` to `runTest`.
- **Files modified:** `composeApp/build.gradle.kts`, `gradle/libs.versions.toml`, `AuthSessionTest.kt`, `LoginViewModelTest.kt`.
- **Verification:** `./gradlew :composeApp:jvmTest :composeApp:compileDebugKotlinAndroid :composeApp:compileKotlinIosSimulatorArm64 :composeApp:compileTestKotlinWasmJs` exits 0.
- **Committed in:** `570652c`.

### Out-of-scope discoveries (not fixed; logged)

See `deferred-items.md`:

- `SecureAuthStateStoreContractTest` (Android JVM unit) fails on `master` HEAD before any 02-07 change — Android Keystore unavailable in plain JVM unit tests; needs Robolectric or `androidTest`.
- `composeApp/src/iosMain/.../SecureAuthStateStore.ios.kt:L31` ktlint `property-naming` violation pre-exists on `master`.

Both originate in Plans 02-04 / 02-05 and are out of scope for this UI plan per the executor scope-boundary rule.

## Issues Encountered

- `./gradlew spotlessApply` reformatted many pre-existing files unrelated to 02-07 (because the repo had pre-existing format drift). Those reformats were reverted before commit so the 02-07 commits stay scope-clean. Spotless's failure on the unrelated `SecureAuthStateStore.ios.kt` ktlint rule is logged in `deferred-items.md`.

## Known Stubs

None. The auth gate is fully wired end to end; all rendered text is sourced from Compose Resources, and ViewModels delegate to the real `AuthSession` Koin singleton.

The `PostLoginPlaceholderScreen` itself is a Phase 2 placeholder by design — Phase 3's `HouseholdGate` replaces it. This is documented in `02-UI-SPEC.md` and `02-CONTEXT.md` and is not a stub.

## Threat Flags

None beyond the plan's threat model. The new UI surfaces only render strings and dispatch to `AuthSession`; tokens are never logged or rendered (T-02-07-01). Logout (T-02-07-02) is the only state-changing action wired in `PostLoginViewModel`. Login button explicitly mentions Authentik (T-02-07-03). Refresh failures route silently to `LoginScreen` per `02-UI-SPEC.md`'s refresh-failure UX (T-02-07-04). All copy comes from `composeResources/values/strings.xml` (T-02-07-05).

## User Setup Required

**Manual iOS Authentik UAT (Task 3 — checkpoint:human-verify, blocking):**

Per `02-VALIDATION.md` § Manual-Only Verifications and `docs/authentik-setup.md`:

1. Fresh install opens Splash then `LoginScreen`.
2. Tap `Zaloguj się przez Authentik` — hosted Authentik login opens and returns through `recipe://callback`.
3. App shows `Witaj, {displayName}!`.
4. Restart after access-token expiry (or short token lifetime) — app returns to authenticated screen without credentials.
5. Tap `Wyloguj się` — app returns to LoginScreen; restart does not silently authenticate.
6. `GET /api/v1/me` returns 200 with valid token; 401 without token or with wrong-audience token.

Reply with `approved` to mark Phase 2 complete, or describe the failing step (with tokens redacted) so the gate can be re-opened.

## Verification

### Automated — passing
- `./gradlew :composeApp:jvmTest --tests "*LoginViewModelTest*"` — PASS (5 tests).
- `./gradlew :composeApp:jvmTest :composeApp:compileDebugKotlinAndroid :composeApp:compileKotlinIosSimulatorArm64 :composeApp:compileTestKotlinWasmJs` — PASS.
- Acceptance grep checks: `auth_sign_in_button` Polish copy present, `0xFF3B6939` in `RecipeTheme`, `auth_error_cancelled` referenced in `LoginViewModelTest`, no `Click me!` in `App.kt`, `collectAsState` + `SplashScreen` present in `App.kt`, `auth_welcome_format` in `PostLoginPlaceholderScreen`, no raw Polish strings in any `.kt` source under `dev/ulfrx/recipe/`.

### Automated — pre-existing failures (not introduced by 02-07; tracked in deferred-items.md)
- `:composeApp:testDebugUnitTest` — 2 failures in `SecureAuthStateStoreContractTest`.
- `:composeApp:spotlessKotlinCheck` — 1 ktlint violation in `SecureAuthStateStore.ios.kt`.

### Manual — pending
- iOS Authentik UAT (Task 3 — see User Setup Required above).

## Next Phase Readiness

Phase 3 (households) can now extend `AuthState.Authenticated.householdId` and replace `PostLoginPlaceholderScreen` with `HouseholdGate` without touching `AuthSession` or the auth-gate `when` (it already handles the `is Authenticated` branch).

## Self-Check: PASSED

- All listed created/modified files exist on disk.
- Commits `466e4c7`, `88f4898`, `570652c` exist in `git log`.
- Acceptance grep checks all pass (run inline above).
- `./gradlew :composeApp:jvmTest :composeApp:compileDebugKotlinAndroid :composeApp:compileKotlinIosSimulatorArm64 :composeApp:compileTestKotlinWasmJs` exits 0.
- Pre-existing failures unrelated to 02-07 are documented in `deferred-items.md` (verified via `git stash` reproduction on `master` HEAD).

---
*Phase: 02-authentication-foundation*
*Status: Tasks 1+2 complete; Task 3 (manual iOS Authentik UAT) awaiting user verification.*
