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

149 lines
16 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 2.1: App Shell, Navigation & Search Foundation - Context
**Gathered:** 2026-05-08
**Status:** Ready for planning
<domain>
## Phase Boundary
Replace the post-login placeholder with the real app shell before household and domain data lands. Deliver four persistent top-level destinations (Planer, Przepisy, Spiżarnia, Zakupy) with independent per-tab back-stack boundaries, a Liquid-glass floating pill dock as the primary chrome, deliberate anticipatory empty states for every tab, and a functional search affordance (open/close + query echo only this phase) on Przepisy and Spiżarnia. Also introduce the first shared visual foundation built on Composables / Compose Unstyled + Liquid instead of expanding around Material 3 — including a full theme token scaffold (colors, typography, spacing, glass-surface) and a layered Liquid → Haze → flat fallback chain.
**Out of scope for this phase** (carried by later phases):
- Real search results or catalog data (Phase 5)
- Household onboarding / membership (Phase 3)
- SyncEngine wiring (Phase 4)
- Per-screen feature content beyond empty states (Phases 59)
- Real-device Liquid tuning + cross-screen polish (Phase 10)
- Full Polish copy pass and i18n delivery (Phase 11) — but all strings introduced in this phase MUST go through resource lookup, not hardcoded literals
</domain>
<decisions>
## Implementation Decisions
### Tab bar shape & chrome placement
- **D-01:** Bottom-anchored floating pill dock implemented as a Liquid-glass capsule, centered above the safe-area inset. No edge-to-edge bottom bar.
- **D-02:** All four tabs render icon + label at all times (active and inactive). Active tab is wider and visually emphasized; inactive tabs remain readable, not icon-only.
- **D-03:** Tab order — `Planer` / `Przepisy` / `Spiżarnia` / `Zakupy`. Default landing tab on first sign-in is `Planer` (matches the "my week is planned" core value; departs from the literal UI-03 listing order, which research confirmed is non-binding).
- **D-04:** No top app bar in v1. Tab title (where useful) lives inline at the top of each screen body. All chrome is bottom-anchored — one surface to design well.
- **D-05:** When search is opened (on tabs that have search — see D-06), the dock collapses to a single circular button showing only the active tab's icon (no label, slightly reduced height). Tapping that collapsed button closes the search and re-expands the dock. The transition is a single coordinated animation, not two independent ones. This matches the Apple-app pattern the user explicitly endorsed.
### Search affordance behavior
- **D-06:** Search button is per-tab and only present on `Przepisy` and `Spiżarnia` (the two tabs that will have searchable content in v1). `Planer` and `Zakupy` have no search button and no search surface. The button renders as a separate floating circular icon adjacent to the dock (not inside it), matching the mockup.
- **D-07:** This phase delivers open/close, query input echo, and clear/close actions only. The body of the search surface renders nothing (no placeholder list, no empty-state body) — Phase 5 wires real result rendering for Przepisy, and the corresponding pantry phase wires Spiżarnia. UI-10 is satisfied by demonstrating the affordance is functional, not by faking content.
- **D-08:** Closing the search clears the query. Reopening starts blank. No persistence across close, tab-switch, or app launch.
- **D-09:** Search is an inline bottom pill, not a full-screen sheet. The search input expands across the bottom chrome row alongside the collapsed dock toggle (D-05). Body content stays visible behind it.
### Empty state design language
- **D-10:** Visual treatment is icon + headline + subline. Icon is tab-themed (calendar for Planer, book for Przepisy, warehouse for Spiżarnia, cart for Zakupy), rendered in a calm, low-saturation theme color. No bespoke illustrations in this phase.
- **D-11:** Tone is anticipatory in Polish — copy signals the feature is real but waiting (e.g. "Wkrótce zobaczysz tu swój plan tygodnia"). Avoid neutral "Brak danych" and avoid chatty onboarding copy.
- **D-12:** No CTA buttons in empty states this phase. Households and catalog don't exist yet, so any CTA would either no-op or navigate to another empty screen. CTAs are added in feature phases as actions become real.
- **D-13:** Single reusable `EmptyState(icon, title, subtitle, action?)` composable in `ui/components/`. The `action` slot is optional and unused this phase but reserved so feature phases can add CTAs without a new component.
### Theme tokens + Liquid fallback
- **D-14:** Full theme scaffold this phase — semantic color roles (background, surface, surfaceGlass, content, contentMuted, accent, separator, borderCard), a typography scale with named text styles (display/title/body/caption), a spacing scale (4/8/12/16/24/32), and a `GlassSurface` token primitive consumed by the dock, search pill, and search/filter buttons. Phase 5 inherits cleanly; Phase 10 tunes on real hardware.
- **D-15:** Both light and dark color schemes are defined and follow the system setting. UI-05 fully lands in Phase 5 but the foundation must be correct now so Phase 5 doesn't retrofit. The mockup's CSS palette (`--app-bg-rgb`, `--card-rgb`, `--sunken-rgb`, etc.) is a useful reference but is NOT directly ported — the visual rebuild owns its own palette.
- **D-16:** `GlassSurface` is a layered primitive with a Liquid → Haze → flat translucent fallback chain. All three paths consume the same token API (color + opacity + radius). Liquid is the preferred path for chrome/buttons; Haze is the secondary blur path; the flat path is a solid translucent surface using theme tokens for the worst case.
- **D-17:** Fallback engagement is compile-time per-target plus a runtime debug toggle. Compile-time: if Liquid does not compile or ship for a given target, the build picks the fallback at build time (no runtime guards in production binaries). Runtime: a debug-build-only toggle (via `multiplatform-settings`, surfaced through a hidden settings entry or build flag) lets the user switch GlassSurface between Liquid / Haze / flat to compare on-device. No automatic perf detection in v1 — Phase 10 may revisit.
### Claude's Discretion
- Exact Liquid library API usage and effect parameters (radius, blur amount, refraction strength) — to be researched against the Liquid library's current docs by gsd-phase-researcher
- Nav graph topology: single root NavHost vs nested NavHosts per tab. Recommendation in research SUMMARY.md is nested per tab for independent back stacks; planner should default to that unless research surfaces a CMP-specific blocker
- Whether to migrate the Phase 2 Material 3 auth screens to the new component foundation now or leave them as legacy until a later phase. Default: leave auth screens as-is; do not expand Material 3 into new code
- Specific empty-state copy strings (subject to Phase 11 copy pass; placeholders this phase must still go through resource lookup)
- Icon source — Compose Material Icons vs a calmer custom icon set. Default to Material Icons Outlined for v1 unless research surfaces a clearly better option that fits the Liquid aesthetic
- Animation curves and durations for the search-open dock collapse (D-05) — should feel iOS-native; planner can pick a reasonable default and Phase 10 tunes
- Accessibility specifics: tab bar `Role.Tab` semantics, search button label, focus order between collapsed dock and search input — pick reasonable defaults aligned with iOS VoiceOver expectations
- Whether to expose the runtime fallback toggle (D-17) as an in-app debug-build affordance or as a build flag only
</decisions>
<canonical_refs>
## Canonical References
**Downstream agents MUST read these before planning or implementing.**
### Project source of truth
- `.planning/PROJECT.md` — Locked tech decisions; especially § Key Decisions (Components: Composables/Compose Unstyled; Glass: Liquid first, Haze fallback; Real app shell before household/domain work; Polish-only strings, i18n-ready)
- `.planning/REQUIREMENTS.md` § UI foundation — UI-01, UI-03, UI-04, UI-05, UI-09, UI-10 (UI-03 / UI-04 / UI-09 / UI-10 are the requirements this phase closes; UI-01 must be honored for any new strings; UI-05 lands in Phase 5 but tokens are scaffolded here)
- `.planning/ROADMAP.md` § Phase 2.1 — Goal, success criteria, requirements mapping
### Architecture & pitfalls research
- `.planning/research/SUMMARY.md` — Executive synthesis; especially § Architecture Approach (nested NavHosts per tab for independent back stacks, Koin scoping to NavBackStackEntry via `koinViewModel()`)
- `.planning/research/ARCHITECTURE.md` — Component structure (UI + Navigation layer), build-order reasoning
- `.planning/research/PITFALLS.md` — iOS infra hygiene (Pitfall 5: Liquid/Haze on chrome only, never over scrolling content; single ComposeUIViewController instance)
### Repository conventions
- `CLAUDE.md` § Tech stack (locked) — JetBrains Navigation Compose, Koin scoping, Compose Unstyled foundation, Liquid first / Haze fallback
- `CLAUDE.md` § Module structure — `composeApp/commonMain` package layout (`app/`, `navigation/`, `ui/{theme,components,screens/{recipes,planner,pantry,shopping}}`)
- `CLAUDE.md` § Non-negotiable conventions — #8 (`shared/commonMain` light), #9 (strings externalized day 1), #10 (Liquid/glass on chrome only)
### Functional reference (visual NOT carried forward; structural pattern IS)
- `~/dev/repo/recipe-mockup/js/ui/bottomNav.js` — Reference implementation of the floating pill dock: the active-tab-expand pattern, the collapse-to-single-button transition when search opens, tab order rationale (Planer first), tab-specific action button slots adjacent to the dock. Mine the structural pattern; do NOT port the CSS or animation timings literally
- `~/dev/repo/recipe-mockup/js/ui/recipeSearchField.js` — Reference for the inline search pill shape, placeholder/clear/filter slot semantics
- `~/dev/repo/recipe-mockup/index.html` — CSS for the bottom dock states (`is-collapsed-tab`, `is-nav-menu-open`, `is-inline-search-open`) is the reference for state machine transitions, not visual styling
### External library docs (for gsd-phase-researcher)
- JetBrains Navigation Compose: https://www.jetbrains.com/help/kotlin-multiplatform-dev/compose-navigation.html — type-safe `@Serializable` routes, nested NavHost setup
- Koin Compose ViewModel: https://insert-koin.io/docs/reference/koin-compose/compose/ — `koinViewModel()` scoping with NavBackStackEntry
- Liquid (fletchmckee): https://github.com/fletchmckee/liquid — modifier-node pixel-sampling API for Compose Multiplatform; check current artifact ID and KMP target matrix
- Haze (chrisbanes): https://github.com/chrisbanes/haze — fallback blur primitive; check CMP/iOS support
</canonical_refs>
<code_context>
## Existing Code Insights
### Reusable Assets
- `composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/App.kt` — Current root composable; will host the new shell after auth gate. Currently routes to `LoginScreen` / `PostLoginPlaceholderScreen` based on `AuthSession` state.
- `composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/ui/theme/RecipeTheme.kt` — Theme entry point exists but is minimal. This phase expands it into the full token scaffold (D-14, D-15).
- `composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/di/AppModule.kt` — Koin app module; new screen ViewModels register here (or in a new `ui/UiModule.kt`).
- `composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/ui/screens/auth/PostLoginPlaceholderScreen.kt` — The placeholder this phase replaces. Should be retired (or reduced to a degenerate "Authenticating…" sliver) once the shell exists; `PostLoginViewModel.kt` may continue to drive the bridge.
- `composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/auth/AuthSession.kt` — State machine the shell observes to decide whether to render auth flow or shell. No changes expected here; the shell sits downstream.
### Established Patterns
- ViewModel + StateFlow + method-per-action — every Phase 2 screen follows this; new shell screens MUST follow it (`PlannerViewModel`, `RecipesViewModel`, `PantryViewModel`, `ShoppingViewModel`, plus a `SearchViewModel` per searchable tab).
- Koin module-per-feature — `AuthModule.kt`, `UserModule.kt`. New shell adds `NavigationModule.kt` (or folds into `AppModule.kt`) and one ViewModel module per tab area.
- Strings externalized via Compose Resources — Phase 2 already established this; new shell must NOT introduce hardcoded literals (UI-01 / convention #9).
- Material 3 used in auth screens only — do NOT extend Material 3 into shell code; build new components on Compose Unstyled (PROJECT.md decision).
- iOS Kotlin/Native binary flags already set (`objcDisposeOnMain=false`, `gc=cms`) per Phase 1.
### Integration Points
- Auth gate: shell renders only when `AuthSession.state == Authenticated`. The shell becomes the new "authenticated root" — replacing `PostLoginPlaceholderScreen` as the destination of the auth gate transition in `App.kt`.
- Navigation: introduces `composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/navigation/` package — root NavHost + per-tab nested NavHosts + serializable route definitions. Phase 3 (households) will hook onboarding into this graph; Phase 5 (catalog) will populate the Recipes nested graph.
- Theme tokens: every later phase reads these. Get the API right now — colors as semantic roles, not raw hex; typography as named styles, not raw `TextStyle`; spacing as named ints, not magic numbers.
- Search ViewModel surface: this phase delivers the open/close/query state machine for Recipes + Pantry search. Phase 5 plugs results in by injecting a search-results-source dependency into the same ViewModel — design the API for that injection point now.
- GlassSurface primitive: lives in `ui/components/` (or `ui/theme/glass/`). The dock, search pill, and floating action buttons all consume it. Future polish chrome (Phase 10) tunes here without touching call sites.
</code_context>
<specifics>
## Specific Ideas
- "When search bar is shown then from the menu only active button is visible and without label but then the whole is a little bit smaller in height" — verbatim user intent for the dock-collapse-on-search transition (D-05). The transition is a single coordinated motion, not two independent ones.
- "I've seen it in some Apple apps and I like it" — re: dock collapsing into a single button when search opens. Reference point is iOS native apps (Mail, Notes, Settings) where the bottom chrome morphs as the search context activates. The Liquid library's pixel-sampling capabilities are the right tool to make this feel native rather than mechanical.
- "All tabs show labels" — explicit departure from a typical iOS tab bar where inactive labels can be hidden. The user wants every tab readable at all times; the active tab differentiates by width and emphasis, not by being the only labeled one.
- The mockup's `app-bottom-nav` is the structural reference — a floating capsule with adjacent floating circular action buttons, not a flat edge-to-edge nav bar. Visual styling is being rebuilt; the floating-pill geometry and the "search open collapses the dock" state machine are what's being preserved.
</specifics>
<deferred>
## Deferred Ideas
- Per-tab dock collapse to a single button on certain tabs/scroll states (independent of search) — mockup has this for some views; defer to Phase 10 if real-device feel demands it. Not in scope here; this phase only collapses the dock for the search-open transition.
- Profile / settings entry point in chrome — no top bar this phase (D-04) means there's no obvious slot. Households/profile UI lands in Phase 3; revisit chrome placement then.
- Cross-tab CTAs in empty states (e.g. "Browse recipes" on empty Planer) — deferred until target tabs have content (Phase 5+).
- Custom illustrations for empty states — deferred; icon-based v1 (D-10).
- Material 3 migration of Phase 2 auth screens — leave as legacy; revisit when Phase 10 polishes chrome or when a phase touches login flow visually.
- Runtime perf detection that auto-downgrades GlassSurface — deferred to Phase 10. Compile-time + debug toggle is enough for v1 (D-17).
- Persisting search query across sessions — explicitly rejected (D-08). Per-tab session-level persistence is also out of scope.
- Real-device Liquid tuning (refraction strength, specular highlights, animation curves) — that's Phase 10's job; this phase ships a working approximation with sensible defaults.
- Localization (full Polish copy pass) — Phase 11. Strings introduced this phase go through resource lookup but the catalog of copy is not finalized.
</deferred>
---
*Phase: 02.1-app-shell-navigation-search-foundation*
*Context gathered: 2026-05-08*