Tasks 1+2 (auto) complete: Compose Resources strings, RecipeTheme seed, SplashScreen/LoginScreen/PostLoginPlaceholderScreen, LoginViewModel + PostLoginViewModel registered in authModule, App.kt auth gate via collectAsStateWithLifecycle. Task 3 (checkpoint:human-verify) requires manual iOS Authentik UAT per docs/authentik-setup.md before Phase 2 can be marked complete.
12 KiB
phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, requirements-completed, duration, completed
| phase | plan | subsystem | tags | requires | provides | affects | tech-stack | key-files | key-decisions | requirements-completed | duration | completed | ||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 02-authentication-foundation | 07 | auth |
|
|
|
|
|
|
|
|
10m | 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
RecipeThemewith light/dark Material 3 schemes seeded by#3B6939/#A2D597andisSystemInDarkTheme(). - Replaced the JetBrains template
App()body with the auth-gatewhenoverAuthSession.state, observing viacollectAsStateWithLifecycleand kickingAuthSession.initialize()fromLaunchedEffect. - Implemented
SplashScreen,LoginScreen, andPostLoginPlaceholderScreenusing Material 3 stdlib only — no Scaffold, no Haze, all strings viastringResource(Res.string.*). - Implemented
LoginViewModel(mapping AuthSession failures →auth_error_*StringResourcekeys, clearing stale errors on retry) and trivialPostLoginViewModel.onSignOutClick()delegating toAuthSession.logout(). - Registered both ViewModels in
authModuleviaorg.koin.core.module.dsl.viewModel. - Added
kotlinx-coroutines-testtocommonTestso the wasmJs target can compile coroutine tests (replacing JVM-onlyrunBlockingwith multiplatformrunTestin bothLoginViewModelTestand the existingAuthSessionTest).
Task Commits
- Task 1 (RED): Compose Resources, theme seed, failing LoginViewModel tests —
466e4c7(test) - Task 2 (GREEN): Auth screens, ViewModels, App auth gate —
88f4898(feat) - Task 2 follow-up: switch commonTest to runTest for wasmJs compatibility —
570652c(fix) - 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-gatewhenbody.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—commonTestkotlinx-coroutines-testdependency.gradle/libs.versions.toml— addedkotlinx-coroutinesTestlibrary 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 checkran the wasmJs test target for the first time. - Issue:
kotlinx.coroutines.runBlockingis JVM/Native-only and breaks:composeApp:compileTestKotlinWasmJs. The pre-existingAuthSessionTest(committed in Plan 02-06) used the same pattern and was never wasmJs-tested —02-06only ran:composeApp:jvmTest. Phase 02-07's verification gate is the first one to catch it. - Fix: Added
org.jetbrains.kotlinx:kotlinx-coroutines-testtocommonTest, switched bothAuthSessionTestand the newLoginViewModelTestfromrunBlockingtorunTest. - Files modified:
composeApp/build.gradle.kts,gradle/libs.versions.toml,AuthSessionTest.kt,LoginViewModelTest.kt. - Verification:
./gradlew :composeApp:jvmTest :composeApp:compileDebugKotlinAndroid :composeApp:compileKotlinIosSimulatorArm64 :composeApp:compileTestKotlinWasmJsexits 0. - Committed in:
570652c.
Out-of-scope discoveries (not fixed; logged)
See deferred-items.md:
SecureAuthStateStoreContractTest(Android JVM unit) fails onmasterHEAD before any 02-07 change — Android Keystore unavailable in plain JVM unit tests; needs Robolectric orandroidTest.composeApp/src/iosMain/.../SecureAuthStateStore.ios.kt:L31ktlintproperty-namingviolation pre-exists onmaster.
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 spotlessApplyreformatted 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 unrelatedSecureAuthStateStore.ios.ktktlint rule is logged indeferred-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:
- Fresh install opens Splash then
LoginScreen. - Tap
Zaloguj się przez Authentik— hosted Authentik login opens and returns throughrecipe://callback. - App shows
Witaj, {displayName}!. - Restart after access-token expiry (or short token lifetime) — app returns to authenticated screen without credentials.
- Tap
Wyloguj się— app returns to LoginScreen; restart does not silently authenticate. GET /api/v1/mereturns 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_buttonPolish copy present,0xFF3B6939inRecipeTheme,auth_error_cancelledreferenced inLoginViewModelTest, noClick me!inApp.kt,collectAsState+SplashScreenpresent inApp.kt,auth_welcome_formatinPostLoginPlaceholderScreen, no raw Polish strings in any.ktsource underdev/ulfrx/recipe/.
Automated — pre-existing failures (not introduced by 02-07; tracked in deferred-items.md)
:composeApp:testDebugUnitTest— 2 failures inSecureAuthStateStoreContractTest.:composeApp:spotlessKotlinCheck— 1 ktlint violation inSecureAuthStateStore.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,570652cexist ingit log. - Acceptance grep checks all pass (run inline above).
./gradlew :composeApp:jvmTest :composeApp:compileDebugKotlinAndroid :composeApp:compileKotlinIosSimulatorArm64 :composeApp:compileTestKotlinWasmJsexits 0.- Pre-existing failures unrelated to 02-07 are documented in
deferred-items.md(verified viagit stashreproduction onmasterHEAD).
Phase: 02-authentication-foundation Status: Tasks 1+2 complete; Task 3 (manual iOS Authentik UAT) awaiting user verification.