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

89 lines
6.2 KiB
Markdown

---
phase: 02.1
plan: 06
subsystem: ui-search
tags: [kotlin, compose-multiplatform, search, viewmodel, glass, accessibility, phase-5-extension-hook]
requires:
- 02.1-03 # GlassSurface
- 02.1-04 # search_clear_a11y / search_close_a11y resource keys
provides:
- RecipesSearchViewModel (open/close/onQueryChange/clear)
- PantrySearchViewModel (open/close/onQueryChange/clear)
- SearchState data class
- SearchSource placeholder interface
- SearchPill composable (44dp inline pill on GlassSurface)
affects:
- 02.1-05 # AppShell consumes SearchPill + Search VMs
- 02.1-08 # ShellModule registers VMs in Koin
tech-stack:
added: []
patterns:
- "RESEARCH § Pattern 4: per-tab Search VM with SearchState(isOpen, query)"
- "Phase 5/8 extension hook: nullable SearchSource constructor parameter"
- "BasicTextField as renderless TextField primitive (Compose Unstyled fallback)"
key-files:
created:
- composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/ui/screens/recipes/RecipesSearchViewModel.kt
- composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/ui/screens/pantry/PantrySearchViewModel.kt
- composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/ui/components/search/SearchPill.kt
modified:
- composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/ui/screens/recipes/RecipesSearchViewModelTest.kt
- composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/ui/screens/pantry/PantrySearchViewModelTest.kt
decisions:
- "Used BasicTextField from compose-foundation rather than Compose Unstyled TextField — BasicTextField is already on the classpath, is renderless (no Material 3 chrome), and provides equivalent IME/a11y plumbing. Compose Unstyled was the originally specified primitive but adds no value here."
- "SearchState and SearchSource live in ui.screens.recipes package; PantrySearchViewModel imports them. Single source of truth prevents drift between the two VMs."
- "SearchPill's clear and close icons both use Icons.Outlined.Close glyph; UI-SPEC accessibility distinguishes via contentDescription only. Distinct glyphs deferred to Phase 10 polish."
metrics:
tasks-completed: 3
files-created: 3
files-modified: 2
completed-date: 2026-05-08
---
# Phase 02.1 Plan 06: Search Foundation Summary
Per-tab Search ViewModels (Recipes + Pantry) with locked SearchState shape and SearchPill composable rendering a 44dp inline GlassSurface pill — search affordance functional before catalog data exists (UI-10).
## What Was Built
- `SearchState(isOpen, query)` data class + `SearchSource` placeholder interface in `ui.screens.recipes`.
- `RecipesSearchViewModel` and `PantrySearchViewModel`: identical 4-action API (`open`, `close`, `onQueryChange`, `clear`). `close()` clears query (D-08); `clear()` preserves `isOpen` (D-07). Both accept nullable `searchSource: SearchSource? = null` for Phase 5/8 dependency injection without VM refactor.
- `SearchPill`: 44dp-height pill on `GlassSurface(cornerRadius = 22.dp)`, leading search icon + `BasicTextField` query input + conditional clear button (visible only when `query.isNotEmpty()`) + always-visible close button. A11y descriptions resolved from `search_clear_a11y` / `search_close_a11y`.
- Replaced `@Ignore` stubs in `RecipesSearchViewModelTest` (5 cases — V-05 + V-06 + edge cases) and `PantrySearchViewModelTest` (3 cases — V-07 parity).
## Output Spec Answers
- **Compose Unstyled TextField vs BasicTextField:** Used `BasicTextField` from `compose-foundation`. It is renderless, already on the classpath, and provides the IME/a11y plumbing the pill needs. Compose Unstyled `TextField` would have added a dependency surface for no gain in this phase.
- **Resource keys:** `search_clear_a11y` and `search_close_a11y` were both present in `composeResources/values/strings.xml` from plan 02.1-04 before SearchPill compilation (verified via `grep -c` returning 2).
- **SearchSource placement:** Declared in `ui.screens.recipes` as planned. PantrySearchViewModel imports it (alongside `SearchState`) to keep a single canonical shape.
- **AppShell handoff (02.1-05):** AppShell from plan 02.1-05 was already shipped before this plan; on inspection it stubs the search affordance internally. AppShell will be rewired to consume this plan's `SearchPill` + per-tab Search ViewModels in plan 02.1-08 (ShellModule wiring) — that's the natural integration point because Koin registration of the new VMs happens there. No regression: SearchPill + VMs are pure additions; nothing in AppShell breaks.
## Verification
- `./gradlew :composeApp:compileKotlinIosSimulatorArm64 -q` → exit 0.
- `./gradlew :composeApp:iosSimulatorArm64Test --tests "...RecipesSearchViewModelTest" --tests "...PantrySearchViewModelTest" -q` → exit 0; all 8 cases pass.
- Material 3 boundary: 0 `androidx.compose.material3` imports across the 3 new commonMain files.
- Liquid / Haze imports: 0 across the new search package and search VMs.
## Deviations from Plan
None substantive. Two minor cosmetic deviations:
1. The plan's example code referenced an internal helper named `BasicTextWithStyle` defined to call `BasicText`. Renamed to `PlaceholderText` and imported `BasicText` directly at top-level for cleaner reading — semantics unchanged.
2. The plan's import list included `KeyboardOptions`, `KeyboardCapitalization`, and `ImeAction`, but the spec'd implementation does not actually use them (no `keyboardOptions = ...` argument is set on `BasicTextField`). Omitted to keep the import list honest. If future work configures the keyboard explicitly, those imports come back.
## Self-Check: PASSED
Verified files and commits exist:
- composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/ui/screens/recipes/RecipesSearchViewModel.kt — FOUND
- composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/ui/screens/pantry/PantrySearchViewModel.kt — FOUND
- composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/ui/components/search/SearchPill.kt — FOUND
- composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/ui/screens/recipes/RecipesSearchViewModelTest.kt — FOUND (no @Ignore)
- composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/ui/screens/pantry/PantrySearchViewModelTest.kt — FOUND (no @Ignore)
Commits:
- d40aeef feat(02.1-06): add per-tab search ViewModels
- 9c193d7 feat(02.1-06): add SearchPill inline search input
- b8100cb test(02.1-06): assert search VM state-machine semantics