156 lines
9.7 KiB
Markdown
156 lines
9.7 KiB
Markdown
---
|
||
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*
|