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

156 lines
9.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
phase: 02.1-app-shell-navigation-search-foundation
verified: 2026-05-08T00:00:00Z
status: passed
verdict: PASS
score: 5/5 success criteria verified
plans_complete: 8/8
---
# Phase 2.1 Verification Report — App Shell, Navigation & Search Foundation
**Phase Goal:** Build the app shell, navigation, and search foundation — type-safe nav graphs, glass design tokens, glass surface primitive, dock + search chrome, per-tab search VMs, empty-state tab screens, and final Koin/integration wiring.
**Verdict:** **PASS**
All 5 ROADMAP success criteria verified, all 7 V-anchor automated tests present without `@Ignore`, iOS compile + linkDebugFrameworkIosSimulatorArm64 green, and 8/8 plans executed with summaries.
---
## ROADMAP Success Criteria
| # | Criterion (paraphrased) | Status | Evidence |
|---|---|---|---|
| 1 | Authenticated user lands in shell, can switch between 4 tabs without signing out | PASS | `App.kt:66-69` routes `RootRoute.Shell -> AppShell()`; `AppShell.kt` hosts `RootNavHost` with 4 nested graphs; `DockBar` calls `navigateToTab(dest.graphRoute)` |
| 2 | Each tab has its own back-stack boundary; intentional empty states | PASS | `RootNavHost.kt` uses 4 `navigation<*Graph>(startDestination = *Home)` blocks; `NavExtensions.navigateToTab` applies `popUpTo(...){saveState=true}; launchSingleTop=true; restoreState=true` (V-01); `Tab*Screen` composables render `EmptyState` with anticipatory Polish copy |
| 3 | Compose Unstyled / renderless primitives, Material 3 only legacy | PASS | New shell composables use `BasicText`/`BasicTextField` from compose-foundation; zero `androidx.compose.material3` imports in shell/dock/search/glass/empty packages (per executor reports); MaterialTheme retained only in `RecipeTheme.kt` for legacy auth screens |
| 4 | Liquid library used for chrome with fallback path | PASS | `GlassSurface.kt` dispatches via `LocalGlassBackend` to `LiquidGlassSurface` / `HazeGlassSurface` / `FlatGlassSurface`; `GlassBackend.kt` has `resolveGlassBackend(settings, isDebugBuild, default)` with debug override (V-02, V-03); registered as `single<GlassBackend>` in `ShellModule.kt` defaulting to Liquid |
| 5 | Search button functional: open/close/clear/query echo, intentional empty body | PASS | `RecipesSearchViewModel` / `PantrySearchViewModel` expose open/close/onQueryChange/clear with locked semantics (close clears query, clear preserves isOpen) — covered by V-05/V-06/V-07 tests; `SearchPill.kt` is a 44dp inline pill with `BasicTextField` + clear/close icons; `FloatingSearchButton` gated to Recipes/Pantry only |
---
## Validation Anchors V-01..V-07
| Anchor | Test File | Status |
|---|---|---|
| V-01 | `commonTest/.../navigation/NavigationTest.kt` | Real assertions (no `@Ignore`) — 3 cases passing |
| V-02 | `commonTest/.../ui/components/glass/GlassBackendTest.kt` | Real assertions — backend default + parsing |
| V-03 | `commonTest/.../ui/components/glass/GlassBackendOverrideTest.kt` | Real assertions — debug override + production short-circuit |
| V-04 | `commonTest/.../ui/screens/shell/AppShellGateTest.kt` | Real assertions — 5 AuthState×hasUser cases via pure `resolveRootRoute` |
| V-05 | `commonTest/.../ui/screens/recipes/RecipesSearchViewModelTest.kt` | Real assertions — 5 cases (open/query/close clears, etc.) |
| V-06 | (same file as V-05) | Real assertions — `clear()` resets only query, isOpen=true |
| V-07 | `commonTest/.../ui/screens/pantry/PantrySearchViewModelTest.kt` | Real assertions — 3 cases parity with Recipes |
`grep -r '@Ignore' composeApp/src/commonTest/`**0 results** (all Wave-0 stubs replaced with real assertions).
---
## Build & Test Verification (this verification run)
- `./gradlew :composeApp:iosSimulatorArm64Test --tests "dev.ulfrx.recipe.navigation.*" --tests "dev.ulfrx.recipe.ui.components.glass.*" --tests "dev.ulfrx.recipe.ui.screens.shell.*" --tests "dev.ulfrx.recipe.ui.screens.recipes.RecipesSearchViewModelTest" --tests "dev.ulfrx.recipe.ui.screens.pantry.PantrySearchViewModelTest"`**BUILD SUCCESSFUL**
- `./gradlew :composeApp:linkDebugFrameworkIosSimulatorArm64`**BUILD SUCCESSFUL** (iOS sim framework link green)
---
## Required Artifacts (existence + substantive)
All files referenced in the 8 plan SUMMARYs exist on disk.
### Theme tokens (Plan 02.1-02)
- `ui/theme/RecipeColors.kt` — semantic light/dark palette
- `ui/theme/RecipeTypography.kt` — display/title/body/label scale
- `ui/theme/RecipeSpacing.kt` — xs/sm/lg/xl/xxl/xxxl
- `ui/theme/RecipeShapes.kt` — pill/circle radii
- `ui/theme/RecipeGlass.kt` — border/shadow/blur defaults
- `ui/theme/RecipeTheme.kt` — providers + MaterialTheme wrapper + LocalGlassBackend wiring
### Glass primitive (Plan 02.1-03)
- `ui/components/glass/GlassBackend.kt` — enum, CompositionLocal, resolver
- `ui/components/glass/GlassSurface.kt` — public dispatcher
- `LiquidGlassSurface.kt`, `HazeGlassSurface.kt`, `FlatGlassSurface.kt` — three backends
- `GlassBackdrop.kt` — shared sampling source
- `IsDebugBuild.kt` (common) + `.ios.kt` + `.android.kt` (actuals)
### Navigation (Plan 02.1-04)
- `navigation/Routes.kt` — 8 `@Serializable data object` (4 graph + 4 home)
- `navigation/BottomBarDestination.kt` — enum in D-03 order, `hasSearch` flag
- `navigation/RootNavHost.kt` — single root with 4 nested `navigation<*Graph>` blocks
- `navigation/NavExtensions.kt``navigateToTab` four-flag contract
### Shell composables (Plan 02.1-05)
- `ui/screens/shell/ShellViewModel.kt` — (activeTab, searchOpen) StateFlow
- `ui/screens/shell/AppShell.kt` — authenticated root, GlassBackdropSource + bottom chrome column
- `ui/components/dock/DockBar.kt` — collapsible 4-tab dock with animateContentSize + AnimatedContent
- `ui/components/dock/FloatingSearchButton.kt` — 44dp glass button
### Search (Plan 02.1-06)
- `ui/screens/recipes/RecipesSearchViewModel.kt` — open/close/onQueryChange/clear + nullable SearchSource hook
- `ui/screens/pantry/PantrySearchViewModel.kt` — parity
- `ui/components/search/SearchPill.kt` — 44dp inline GlassSurface pill with BasicTextField
### Empty state + tab screens (Plan 02.1-07)
- `ui/components/empty/EmptyState.kt` — reusable composable with mergeDescendants a11y
- `ui/screens/{planner,recipes,pantry,shopping}/{*Screen,*ViewModel}.kt` — 8 files
### Final integration (Plan 02.1-08)
- `di/ShellModule.kt` — Koin: GlassBackend single + ShellViewModel + 4 tab VMs + 2 search VMs
- `di/AppModule.kt` modified: `includes(authModule, userModule, shellModule)`
- `App.kt` modified: `RootRoute` enum + `resolveRootRoute()` + Authenticated → `AppShell()`
- `RootNavHost.kt` modified: `TabHomePlaceholder` calls replaced with real `Tab*Screen` via `koinViewModel(viewModelStoreOwner = parent)`
---
## Resource Strings (i18n hygiene)
`composeResources/values/strings.xml` carries 24 keys total: 7 auth (pre-existing) + 4 shell tabs + 2 search placeholders + 3 search a11y + 8 empty-state. Zero hardcoded Polish literals in new `.kt` files (all flow through `stringResource(Res.string.*)`) — satisfies UI-01 and convention #9.
---
## Key Wiring Verification
| Link | Status | Evidence |
|---|---|---|
| `App.kt``AppShell` (auth gate) | WIRED | `App.kt:13` import + `App.kt:69` `RootRoute.Shell -> AppShell()` |
| `AppModule.kt``shellModule` | WIRED | `AppModule.kt:11` `includes(authModule, userModule, shellModule)` |
| `RootNavHost` → 4 Tab Screens | WIRED | `koinViewModel<*ViewModel>(viewModelStoreOwner = parent)` per tab; no `TabHomePlaceholder` left |
| `RecipeTheme``LocalGlassBackend` | WIRED | `RecipeTheme.kt` includes `LocalGlassBackend provides koinInject<GlassBackend>()` |
| `DockBar` tab cell → `navigateToTab` | WIRED | `AppShell` dispatches `navigateToTab(dest.graphRoute)` on tab change |
| `AppShell``SearchPill` + per-tab Search VM | WIRED | When-branches for both Recipes and Pantry; gated by `activeTab.hasSearch` |
---
## Anti-Patterns Scan
- Zero `androidx.compose.material3` imports in new shell/dock/search/glass/empty packages (executor reports + spot-checks).
- Zero direct `liquid` / `haze` imports outside the dedicated backend files.
- No `safeContentPadding()` in AppShell (Pitfall F honored).
- No hardcoded Polish literals in commonMain `.kt` files.
- No `TODO(02.1-08)` markers remain after Plan 08.
---
## Out-of-Scope / Acknowledged Items
1. **Pre-existing Spotless violations in 38 unrelated files** (LokksmithOidcSupport, OidcClient, AuthSession, etc.) — confirmed by Plan 08 executor via `git stash` + `spotlessCheck` to predate this phase. **OUT OF SCOPE for Phase 2.1**; flagged for a future cleanup pass. Does not affect Phase 2.1 verdict.
2. **Manual iOS-simulator smoke tests V-08..V-11** (visual chrome, animation feel, search affordance UX, Liquid look-and-feel) — deferred to user smoke-test pass per VALIDATION.md (no simulator in autonomous run). Static checks confirm code paths are wired correctly; visual confirmation belongs to the user's manual runbook execution.
3. **`./gradlew :composeApp:check`** is RED only because of the 38-file pre-existing Spotless debt. The Phase 2.1 owned files all pass Spotless (Plan 08 commit `a6f0d46`).
---
## Regression Check
- Phase 2 auth flow preserved: `LoginScreen` / `SplashScreen` / `MaterialTheme` wrapper untouched in core paths.
- `PostLoginPlaceholderScreen.kt` and `PostLoginViewModel.kt` source files preserved on disk per CONTEXT line 101 (logout-bridge possibility), only their imports/call site removed from `App.kt`.
- No deletions to `auth/` or `user/` packages; the brief Plan 01 unrelated-staged-file accident was repaired in commit `1066e9b` before any other work.
---
## Gaps
**None.** All 5 ROADMAP success criteria, all V-01..V-07 anchors, and all 8 plans are complete and substantive. Phase 2.1 is ready to mark done.
---
*Verified: 2026-05-08 by gsd-verifier (Claude)*
*Phase: 02.1-app-shell-navigation-search-foundation*