11 KiB
11 KiB
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
- AUTH-01: User can sign in via the self-hosted Authentik instance using OIDC (authorization code flow with PKCE)
- AUTH-02: Client stores access + refresh tokens securely (iOS Keychain / Android EncryptedSharedPreferences)
- AUTH-03: Ktor server validates incoming access tokens via Authentik's JWKS endpoint (issuer, audience, expiry, signature, clock skew leeway)
- AUTH-04: User session persists across app launches without re-authentication (token refresh handled transparently)
- AUTH-05: User can sign out, which revokes local tokens and returns to the login screen
- AUTH-06: Users are JIT-provisioned in the server database on first successful login (by OIDC
subclaim)
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_idderived 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-assignedupdated_at - SYNC-06: Deletes are soft deletes (
deleted_atcolumn); 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: Tab bar and nav bar use Haze-based glass/blur effects (Liquid Glass approximation)
- 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
Infrastructure & build
- INFRA-01: Gradle version catalog at
gradle/libs.versions.tomlis the single source of truth for library versions - INFRA-02:
build-logic/convention plugins centralize Kotlin/Compose/test configuration across modules - 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
- INFRA-06:
shared/commonMaincontains 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 |
|---|---|---|
| (filled by roadmapper) |
Coverage:
- v1 requirements: 72 total (AUTH=6, HSHD=7, RCPE=8, PLAN=14, PNTR=5, SHOP=6, SYNC=10, UI=9, INFRA=7)
- Mapped to phases: — (filled by roadmapper)
- Unmapped: — (filled by roadmapper)
Requirements defined: 2026-04-24 Last updated: 2026-04-24 after initial definition