7.5 KiB
7.5 KiB
phase, slug, status, nyquist_compliant, wave_0_complete, created
| phase | slug | status | nyquist_compliant | wave_0_complete | created |
|---|---|---|---|---|---|
| 2.1 | app-shell-navigation-search-foundation | draft | false | false | 2026-05-08 |
Phase 2.1 — Validation Strategy
Per-phase validation contract for feedback sampling during execution. Sourced from
02.1-RESEARCH.md§ Validation Architecture.
Test Infrastructure
| Property | Value |
|---|---|
| Framework | kotlin.test (commonTest) — already used in Phase 2 (AuthSessionTest, LoginViewModelTest) |
| Config file | none — convention plugins handle recipe.kotlin.multiplatform |
| Quick run command | ./gradlew :composeApp:commonTest --tests "dev.ulfrx.recipe.ui.screens.shell.*" --tests "dev.ulfrx.recipe.ui.screens.recipes.*Search*" --tests "dev.ulfrx.recipe.navigation.*" --tests "dev.ulfrx.recipe.ui.components.glass.*" |
| Full suite command | ./gradlew :composeApp:check |
| Estimated runtime | ~30-90 seconds (commonTest); ~3-6 min (full check incl. iOS sim klib link) |
Compose UI Test on KMP iOS is not introduced this phase — feasibility is too low. Visible chrome is verified by a manual smoke runbook (see § Manual-Only Verifications).
Sampling Rate
- After every task commit: Run
./gradlew :composeApp:commonTest - After every plan wave: Run
./gradlew :composeApp:check - Before
/gsd-verify-work: Full suite green AND manual iOS-simulator smoke runbook executed - Max feedback latency: ~90 seconds (commonTest)
Per-Task Verification Map
Task IDs are filled in by the planner. The rows below are the requirement-level verification anchors that any plan task must map onto via its
verifyblock.
| Anchor | Plan | Wave | Requirement | Threat Ref | Secure Behavior | Test Type | Automated Command | File Exists | Status |
|---|---|---|---|---|---|---|---|---|---|
| V-01 | TBD | 1 | UI-03 | — | navigateToTab() applies popUpTo(graph.findStartDestination().id) { saveState = true }; launchSingleTop = true; restoreState = true |
unit | ./gradlew :composeApp:commonTest --tests "*NavigationTest*" |
❌ W0 | ⬜ pending |
| V-02 | TBD | 1 | UI-04 | — | GlassSurface selects Liquid backend on iOS source set at compile time |
unit | ./gradlew :composeApp:commonTest --tests "*GlassBackend*" |
❌ W0 | ⬜ pending |
| V-03 | TBD | 1 | UI-04 | — | GlassSurface debug-toggle flow honors multiplatform-settings value via MapSettings test impl |
unit | ./gradlew :composeApp:commonTest --tests "*GlassBackendOverride*" |
❌ W0 | ⬜ pending |
| V-04 | TBD | 1 | UI-09 | — | App.kt AuthState.Authenticated + currentUser != null resolves to AppShell, not PostLoginPlaceholderScreen |
unit | ./gradlew :composeApp:commonTest --tests "*AppShellGateTest*" |
❌ W0 | ⬜ pending |
| V-05 | TBD | 1 | UI-10 | — | RecipesSearchViewModel: open() → onQueryChange("foo") → close() clears query and resets isOpen |
unit | ./gradlew :composeApp:commonTest --tests "*RecipesSearchViewModelTest*" |
❌ W0 | ⬜ pending |
| V-06 | TBD | 1 | UI-10 | — | RecipesSearchViewModel: clear() resets only query, keeps isOpen=true |
unit | (same target) | ❌ W0 | ⬜ pending |
| V-07 | TBD | 1 | UI-10 | — | PantrySearchViewModel: parity with recipes (open/close/clear semantics) |
unit | ./gradlew :composeApp:commonTest --tests "*PantrySearchViewModelTest*" |
❌ W0 | ⬜ pending |
| V-08 | TBD | 1 | UI-09 / UI-03 | — | Each tab renders its own empty state on first launch without flash | manual smoke (iOS) | n/a | manual | ⬜ pending |
| V-09 | TBD | 1 | UI-03 | — | Bottom-tab reselect preserves nested back stack | manual smoke (iOS) | n/a | manual | ⬜ pending |
| V-10 | TBD | 1 | UI-10 | — | Search affordance visible on Recipes + Pantry tabs only (D-06) | manual smoke + screenshot per tab | n/a | manual | ⬜ pending |
| V-11 | TBD | 1 | UI-04 | — | Liquid dock/menu chrome animates on iOS device path; flat fallback path activates when override is set | manual smoke (iOS) | n/a | manual | ⬜ pending |
Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky
Wave 0 Requirements
composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/navigation/NavigationTest.kt— stubs for V-01 (UI-03 navigateToTab semantics; usesTestNavHostControllerif available, else asserts on the option-builder lambda)composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/ui/components/glass/GlassBackendTest.kt— stubs for V-02 (UI-04 compile-time backend selection)composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/ui/components/glass/GlassBackendOverrideTest.kt— stubs for V-03 (UI-04 settings-driven debug override)composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/ui/screens/shell/AppShellGateTest.kt— stubs for V-04 (UI-09 App.kt routing)composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/ui/screens/recipes/RecipesSearchViewModelTest.kt— stubs for V-05/V-06 (UI-10)composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/ui/screens/pantry/PantrySearchViewModelTest.kt— stubs for V-07 (UI-10 mirror)- iOS-simulator smoke runbook (see § Manual-Only Verifications) committed alongside the phase artifacts so V-08…V-11 have a repeatable check
- No new framework install —
kotlin.testis already wired throughrecipe.kotlin.multiplatformconvention plugin - Wave 0 dependency-resolution checks for the three load-bearing assumptions A1/A2/A3 (Liquid iOS klib resolves, Material Icons Outlined available without
material-icons-extended, nav-compose 2.9.2 K/N back-stack save/restore on iOS)
Manual-Only Verifications
| Behavior | Requirement | Why Manual | Test Instructions |
|---|---|---|---|
| Each tab's empty state renders without flash on first launch | UI-09 | Compose Multiplatform on iOS lacks mature snapshot/UI testing for chrome-level visual verification | iOS sim cold launch → land on Planer (default tab) → confirm intentional empty illustration + copy → no spinner/flash |
| Tab back-stack preserved across reselection | UI-03 | Real navigation behavior across nested NavHosts is best validated visibly on the simulator | Navigate Przepisy → tap any future stub detail nav → switch to Spiżarnia → switch back to Przepisy → expect previous state restored, not start dest |
| Search affordance is functional and scoped | UI-10 | UX of opening/closing/clearing must be felt, not just unit-asserted | On Recipes tab: tap search icon → surface opens → type "abc" → confirm query state → tap clear → query empty, surface still open → tap close → surface dismissed. Repeat on Pantry. Confirm Planer/Zakupy do NOT show search affordance. |
| Liquid dock/menu chrome on iOS device path | UI-04 | Glass aesthetic and performance can only be judged by eye | iOS sim run with default config → confirm Liquid menu/dock renders with the expected glass treatment → toggle debug override via multiplatform-settings storage → confirm flat fallback activates |
| Dock collapse animation on tab change | UI-04 / UI-09 | Animation feel | Tab between all four destinations → confirm dock animation runs smoothly, no jank |
Validation Sign-Off
- All tasks have
<automated>verify or Wave 0 dependencies - Sampling continuity: no 3 consecutive tasks without automated verify
- Wave 0 covers all MISSING references (test file stubs above)
- No watch-mode flags
- Feedback latency < 90s for commonTest
nyquist_compliant: trueset in frontmatter
Approval: pending