# Requirements: Recipe

**Defined:** 2026-04-24
**Core Value:** "My week is planned." I pick recipes, the calendar fills up, and I know what we're eating.

## v1 Requirements

### Authentication & identity

- [x] **AUTH-01**: User can sign in via the self-hosted Authentik instance using OIDC (authorization code flow with PKCE)
- [x] **AUTH-02**: Client stores access + refresh tokens securely (iOS Keychain / Android EncryptedSharedPreferences)
- [x] **AUTH-03**: Ktor server validates incoming access tokens via Authentik's JWKS endpoint (issuer, audience, expiry, signature, clock skew leeway)
- [x] **AUTH-04**: User session persists across app launches without re-authentication (token refresh handled transparently)
- [x] **AUTH-05**: User can sign out, which revokes local tokens and returns to the login screen
- [x] **AUTH-06**: Users are JIT-provisioned in the server database on first successful login (by OIDC `sub` claim)

### Household sharing

- [ ] **HSHD-01**: On first login, user is prompted to create a new household or join an existing one via invite code
- [ ] **HSHD-02**: User can create a new household and become its first member
- [ ] **HSHD-03**: User can generate an invite code for their household (short-lived, single-use)
- [ ] **HSHD-04**: Another user can redeem an invite code to join the household
- [ ] **HSHD-05**: All household members see the same plan, pantry, and shopping list
- [ ] **HSHD-06**: Server-side tenancy is enforced: every household-scoped query filters by `household_id` derived from the authenticated principal, never from the request body
- [ ] **HSHD-07**: Client queries for household data filter locally by active `household_id` (defense in depth)

### Recipes — browse & detail

- [ ] **RCPE-01**: User can view a grid of the household's recipe catalog
- [ ] **RCPE-02**: User can filter recipes by meal slot (śniadanie / drugie śniadanie / obiad / przekąska / kolacja)
- [ ] **RCPE-03**: User can filter recipes by tag (e.g., "szybkie", "wegetariańskie", "wysokobiałkowe")
- [ ] **RCPE-04**: User can filter recipes by cooking time range (minutes)
- [ ] **RCPE-05**: User can search recipes by title or tag text
- [ ] **RCPE-06**: User can open a recipe detail view showing ingredients (with amounts + units), steps, nutrition per serving, and cooking time
- [ ] **RCPE-07**: Recipe detail shows ingredient alternatives (substitutions) where defined in the catalog
- [ ] **RCPE-08**: Recipe catalog is seeded via server-side SQL fixtures or admin CLI (no in-app authoring in v1)

### Meal planner (hero feature)

- [ ] **PLAN-01**: User can view a calendar showing planned meals per day
- [ ] **PLAN-02**: User can navigate between days/weeks/months in the calendar
- [ ] **PLAN-03**: User can add a recipe to any of the 5 slots on any day (śniadanie, drugie śniadanie, obiad, przekąska, kolacja)
- [ ] **PLAN-04**: User can remove a meal entry from a slot
- [ ] **PLAN-05**: User can replace a meal entry by picking a different recipe
- [ ] **PLAN-06**: User can adjust servings on a meal entry (1–12)
- [ ] **PLAN-07**: User can substitute an ingredient in a meal entry with one of the defined alternatives
- [ ] **PLAN-08**: User can exclude an ingredient from a meal entry (won't appear in shopping/pantry calculations)
- [ ] **PLAN-09**: User can add an extra ingredient to a meal entry (amount + unit from the ingredient catalog)
- [ ] **PLAN-10**: User can override the amount of an ingredient in a meal entry
- [ ] **PLAN-11**: User can select a specific product (pack size) for a given ingredient in a meal entry
- [ ] **PLAN-12**: User can mark a meal slot as skipped for a day
- [ ] **PLAN-13**: User sees daily nutrition totals (kcal, protein, fat, carbs) aggregated from all planned meals that day, respecting customizations
- [ ] **PLAN-14**: Meal entries have stable UUID identity (never composite keys like `date + slot`) to survive concurrent edits

### Pantry

- [ ] **PNTR-01**: User can view pantry inventory grouped by ingredient category (pieczywo, nabiał, mięso i ryby, warzywa, owoce, suche, przyprawy, inne)
- [ ] **PNTR-02**: User can manually add or update the quantity of an ingredient in the pantry (using its pantry unit: g, ml, szt.)
- [ ] **PNTR-03**: User sees which ingredients fall short over a user-selected planning horizon (e.g., next 7 days) based on the plan minus current pantry
- [ ] **PNTR-04**: User can filter pantry view by category
- [ ] **PNTR-05**: User can filter pantry view by shortfall status (needed / sufficient / not in plan)

### Shopping list

- [ ] **SHOP-01**: User can select a date range from the plan to generate a shopping list
- [ ] **SHOP-02**: Shopping list aggregates all ingredient needs across selected days minus current pantry quantities
- [ ] **SHOP-03**: Shopping list groups items by ingredient category for efficient in-store navigation
- [ ] **SHOP-04**: User can mark an item as bought during a shopping session; the item is removed from active needs and added to pantry in its pantry unit
- [ ] **SHOP-05**: User can undo a recently marked-bought item within the same session
- [ ] **SHOP-06**: Session log persists across app restarts until the user explicitly clears it

### Offline + sync

- [ ] **SYNC-01**: Client reads all household data from local SQLDelight without requiring network
- [ ] **SYNC-02**: Client writes all household data locally first (optimistic UI), then queues the change for sync
- [ ] **SYNC-03**: Server assigns `updated_at` (server time, monotonic) on every accepted write; clients never trust their own device clock for sync ordering
- [ ] **SYNC-04**: Client pulls household changes via `GET /sync/pull?since=...` using a lexicographic `(updated_at, id)` cursor to survive same-millisecond writes
- [ ] **SYNC-05**: Client pushes pending writes via `POST /sync/push` (batched); accepted writes come back with server-assigned `updated_at`
- [ ] **SYNC-06**: Deletes are soft deletes (`deleted_at` column); synced as state changes, not as "delete ops"
- [ ] **SYNC-07**: Sync runs on: app foreground, pull-to-refresh, debounced 2s after every local write, and polls every 20–30s while foreground
- [ ] **SYNC-08**: Sync failures (network error, 5xx) retry with exponential backoff and do not block the UI
- [ ] **SYNC-09**: Sync engine is implemented as a single Koin singleton owning the outbox queue and pull cursor — features never issue HTTP writes directly
- [ ] **SYNC-10**: When two household members edit the same row, the server-assigned later write wins; no silent data loss because writes use UUID identity, not natural keys

### UI foundation (polish + glass aesthetic)

- [ ] **UI-01**: All user-facing strings are externalized as Compose resources (i18n-ready), even though v1 ships Polish only
- [ ] **UI-02**: App ships with Polish-language copy throughout
- [ ] **UI-03**: Bottom tab navigation with 4 tabs: Przepisy / Planer / Spiżarnia / Zakupy, each preserving its own back stack independently
- [ ] **UI-04**: App chrome and primary icon buttons use the chosen Compose Liquid-Glass approximation, starting with the Liquid library for menu/search controls
- [ ] **UI-05**: App supports light and dark color schemes with translucent surfaces working in both
- [ ] **UI-06**: UI is iOS-idiomatic within Compose constraints (safe areas, swipe-back gesture where applicable, keyboard avoidance)
- [ ] **UI-07**: Visual hierarchy is less cramped than the mockup — deliberate spacing, calmer typography, readable at arm's length
- [ ] **UI-08**: Locale-aware date formatting for display (days, months, weekday names in Polish); sync wire-format stays UTC ISO-8601
- [ ] **UI-09**: App starts cleanly on first launch (no blank flash) and shows appropriate empty states when catalog / plan / pantry / shopping are empty
- [ ] **UI-10**: Main app search affordance is functional before catalog data exists: search opens, query state updates, clear/close actions work, and the no-results/empty-data state is deliberate

### Infrastructure & build

- [x] **INFRA-01**: Gradle version catalog at `gradle/libs.versions.toml` is the single source of truth for library versions
- [x] **INFRA-02**: `build-logic/` convention plugins centralize Kotlin/Compose/test configuration across modules
- [x] **INFRA-03**: iOS Kotlin/Native binary options set from day 1: `kotlin.native.binary.objcDisposeOnMain=false`, `gc=cms`
- [ ] **INFRA-04**: Server Docker image builds and deploys to user's homelab alongside Authentik
- [ ] **INFRA-05**: Flyway migrations run automatically on server startup in a known order
- [x] **INFRA-06**: `shared/commonMain` contains only domain models + API DTOs — no UI, no HTTP, no DB code
- [ ] **INFRA-07**: App is distributed to partner via TestFlight (iOS) for initial dogfooding

## v2 Requirements

Explicitly acknowledged but deferred. Not in the v1 roadmap.

### In-app recipe authoring

- **AUTHR-01**: User can create a new recipe with ingredients, steps, nutrition, allowed slots, tags
- **AUTHR-02**: User can edit an existing recipe
- **AUTHR-03**: User can archive/delete a recipe they created

### Nutrition goal tracking

- **NUTR-01**: User can set daily macro targets (kcal, protein, fat, carbs)
- **NUTR-02**: Planner shows deficit/surplus vs. targets per day
- **NUTR-03**: User can view weekly nutrition trends

### Additional platforms

- **PLAT-01**: Android app distributed to household members (APK or Play Store)
- **PLAT-02**: Web (Compose for Wasm) as a PWA replacement for the current mockup
- **PLAT-03**: English localization (full copy pass)

### Sync hardening

- **SYNC2-01**: Server-sent events (SSE) for near-realtime sync instead of polling
- **SYNC2-02**: Per-table upgrade path from LWW to operation-log sync if concurrent-edit data loss becomes observable

### iOS Liquid Glass fidelity

- **LG2-01**: Native SwiftUI interop for tab bar and nav bar (real iOS 26 Liquid Glass material) if Compose approximation proves inadequate on real hardware

## Out of Scope

Explicitly excluded. Documented to prevent scope creep.

| Feature | Reason |
|---------|--------|
| Social features (comments, ratings, public profiles, feeds) | Private household app, not a community product |
| Recipe sharing between households | Households are isolated in v1; recipe marketplace is not the point |
| Meal-plan marketplace / paid plans | Personal-use product |
| Grocery-delivery integrations (Instacart, Carrefour Online, etc.) | Polish-market + small scope; integration cost not justified |
| Barcode scanning / receipt OCR for pantry updates | Manual entry is fine for a 2-person household |
| AI-generated recipes | Curated catalog is the value |
| Apple Sign-in as a first-class button | Authentik OIDC is user's IdP, not a third-party social login |
| Port of mockup's vanilla-JS visual design | Visual rebuild around Liquid-Glass language; mockup is functional spec only |
| Port of mockup's ~80 ingredients + ~30 recipes as seed data | User explicitly chose to re-curate catalog fresh |
| Device-clock-based sync timestamps | Silent data loss under drift; server-assigned timestamps mandatory |
| True iOS 26 Liquid Glass native material in v1 | Requires SwiftUI interop; Compose approximation is v1 scope |

## Traceability

Populated during roadmap creation. Each v1 requirement maps to exactly one phase.

| Requirement | Phase | Status |
|-------------|-------|--------|
| AUTH-01 | Phase 2: Authentication Foundation | Complete |
| AUTH-02 | Phase 2: Authentication Foundation | Complete |
| AUTH-03 | Phase 2: Authentication Foundation | Pending |
| AUTH-04 | Phase 2: Authentication Foundation | Complete |
| AUTH-05 | Phase 2: Authentication Foundation | Complete |
| AUTH-06 | Phase 2: Authentication Foundation | Pending |
| HSHD-01 | Phase 3: Households, Membership & Server Data Foundation | Pending |
| HSHD-02 | Phase 3: Households, Membership & Server Data Foundation | Pending |
| HSHD-03 | Phase 3: Households, Membership & Server Data Foundation | Pending |
| HSHD-04 | Phase 3: Households, Membership & Server Data Foundation | Pending |
| HSHD-05 | Phase 3: Households, Membership & Server Data Foundation | Pending |
| HSHD-06 | Phase 3: Households, Membership & Server Data Foundation | Pending |
| HSHD-07 | Phase 3: Households, Membership & Server Data Foundation | Pending |
| RCPE-01 | Phase 5: Recipe Catalog (Read Path) | Pending |
| RCPE-02 | Phase 5: Recipe Catalog (Read Path) | Pending |
| RCPE-03 | Phase 5: Recipe Catalog (Read Path) | Pending |
| RCPE-04 | Phase 5: Recipe Catalog (Read Path) | Pending |
| RCPE-05 | Phase 5: Recipe Catalog (Read Path) | Pending |
| RCPE-06 | Phase 5: Recipe Catalog (Read Path) | Pending |
| RCPE-07 | Phase 5: Recipe Catalog (Read Path) | Pending |
| RCPE-08 | Phase 5: Recipe Catalog (Read Path) | Pending |
| PLAN-01 | Phase 6: Meal Planner — Core Write Path | Pending |
| PLAN-02 | Phase 6: Meal Planner — Core Write Path | Pending |
| PLAN-03 | Phase 6: Meal Planner — Core Write Path | Pending |
| PLAN-04 | Phase 6: Meal Planner — Core Write Path | Pending |
| PLAN-05 | Phase 6: Meal Planner — Core Write Path | Pending |
| PLAN-06 | Phase 6: Meal Planner — Core Write Path | Pending |
| PLAN-07 | Phase 7: Meal Planner — Customization & Nutrition | Pending |
| PLAN-08 | Phase 7: Meal Planner — Customization & Nutrition | Pending |
| PLAN-09 | Phase 7: Meal Planner — Customization & Nutrition | Pending |
| PLAN-10 | Phase 7: Meal Planner — Customization & Nutrition | Pending |
| PLAN-11 | Phase 7: Meal Planner — Customization & Nutrition | Pending |
| PLAN-12 | Phase 6: Meal Planner — Core Write Path | Pending |
| PLAN-13 | Phase 7: Meal Planner — Customization & Nutrition | Pending |
| PLAN-14 | Phase 6: Meal Planner — Core Write Path | Pending |
| PNTR-01 | Phase 8: Pantry | Pending |
| PNTR-02 | Phase 8: Pantry | Pending |
| PNTR-03 | Phase 8: Pantry | Pending |
| PNTR-04 | Phase 8: Pantry | Pending |
| PNTR-05 | Phase 8: Pantry | Pending |
| SHOP-01 | Phase 9: Shopping List & Session Log | Pending |
| SHOP-02 | Phase 9: Shopping List & Session Log | Pending |
| SHOP-03 | Phase 9: Shopping List & Session Log | Pending |
| SHOP-04 | Phase 9: Shopping List & Session Log | Pending |
| SHOP-05 | Phase 9: Shopping List & Session Log | Pending |
| SHOP-06 | Phase 9: Shopping List & Session Log | Pending |
| SYNC-01 | Phase 4: Sync Engine Skeleton | Pending |
| SYNC-02 | Phase 4: Sync Engine Skeleton | Pending |
| SYNC-03 | Phase 4: Sync Engine Skeleton | Pending |
| SYNC-04 | Phase 4: Sync Engine Skeleton | Pending |
| SYNC-05 | Phase 4: Sync Engine Skeleton | Pending |
| SYNC-06 | Phase 4: Sync Engine Skeleton | Pending |
| SYNC-07 | Phase 4: Sync Engine Skeleton | Pending |
| SYNC-08 | Phase 4: Sync Engine Skeleton | Pending |
| SYNC-09 | Phase 4: Sync Engine Skeleton | Pending |
| SYNC-10 | Phase 4: Sync Engine Skeleton | Pending |
| UI-01 | Phase 11: Localization & iOS Deployment | Pending |
| UI-02 | Phase 11: Localization & iOS Deployment | Pending |
| UI-03 | Phase 2.1: App Shell, Navigation & Search Foundation | Pending |
| UI-04 | Phase 2.1: App Shell, Navigation & Search Foundation | Pending |
| UI-05 | Phase 5: Recipe Catalog (Read Path) | Pending |
| UI-06 | Phase 10: UI Chrome & Liquid-Glass Polish | Pending |
| UI-07 | Phase 10: UI Chrome & Liquid-Glass Polish | Pending |
| UI-08 | Phase 5: Recipe Catalog (Read Path) | Pending |
| UI-09 | Phase 2.1: App Shell, Navigation & Search Foundation | Pending |
| UI-10 | Phase 2.1: App Shell, Navigation & Search Foundation | Pending |
| INFRA-01 | Phase 1: Project Infrastructure & Module Wiring | Complete |
| INFRA-02 | Phase 1: Project Infrastructure & Module Wiring | Complete |
| INFRA-03 | Phase 1: Project Infrastructure & Module Wiring | Complete |
| INFRA-04 | Phase 11: Localization & iOS Deployment | Pending |
| INFRA-05 | Phase 3: Households, Membership & Server Data Foundation | Pending |
| INFRA-06 | Phase 1: Project Infrastructure & Module Wiring | Complete |
| INFRA-07 | Phase 11: Localization & iOS Deployment | Pending |

**Coverage:**
- v1 requirements: **73 total** (AUTH=6, HSHD=7, RCPE=8, PLAN=14, PNTR=5, SHOP=6, SYNC=10, UI=10, INFRA=7)
- Mapped to phases: **73**
- Unmapped: **0**

---
*Requirements defined: 2026-04-24*
*Last updated: 2026-04-23 after roadmap creation*
