Files
recipe/.planning/PROJECT.md

16 KiB
Raw Blame History

Recipe (working title)

What This Is

A mobile-first meal planning app for a small household — pick recipes for the week, fill a calendar across five meal slots per day, and watch pantry gaps + shopping lists emerge from the plan. Kotlin Multiplatform targeting iOS primarily, with Android, Desktop, and Wasm as secondary targets. Built for me + my partner (shared household plan) with a handful of family/friends as authorized users on the same self-hosted backend.

Core Value

"My week is planned." I pick recipes, the calendar fills up, and I know what we're eating. Everything else — pantry tracking, shopping list, nutrition numbers — exists to reinforce that one moment.

Requirements

Validated

(None yet — ship to validate)

Active

Authentication & identity

  • Users sign in via the user's self-hosted Authentik instance (OIDC)
  • Sessions persist across app launches; offline access works with cached credentials

Household sharing

  • Users can join a household: one plan, one pantry, one shopping list
  • Changes by either user converge on all devices when online

Recipes (browse & detail)

  • User can browse a curated recipe catalog with a grid view
  • User can filter recipes by meal slot, tags, cooking time, and ingredients
  • User can search recipes by title/tag
  • User can open a recipe detail view with ingredients, steps, and nutrition per serving

Meal planner (hero feature)

  • User can view a calendar of days and see planned meals per day
  • User can add recipes to any of 5 slots/day (śniadanie, drugie śniadanie, obiad, przekąska, kolacja)
  • User can remove or replace a meal entry
  • User can adjust servings on a meal entry
  • User can customize a meal entry: swap ingredients (substitutions), exclude ingredients, add extras, override amounts
  • User can select a specific product (pack size) for an ingredient in a meal entry
  • User can mark a meal slot as "skipped" for a day
  • User sees daily nutrition totals (kcal, protein, fat, carbs) computed from the plan

Pantry

  • User can view current pantry inventory grouped by category
  • User can add/update quantities manually in the pantry
  • User sees which ingredients fall short over a chosen planning horizon
  • User can filter pantry by category and by shortfall status

Shopping list

  • User can select days from the plan to generate a shopping list
  • Shopping list aggregates ingredient needs across selected days minus pantry
  • Shopping list groups items by category for an efficient store trip
  • User can mark items as bought during a shopping session; marked items are removed from active needs and added to pantry

Offline + sync

  • App is fully usable offline: read and write plans, pantry, shopping list
  • Local changes sync to the backend when connectivity returns, without data loss
  • Conflicts between two household members' concurrent edits resolve deterministically (last-write-wins for MVP; revisit if it hurts)

Polish UI foundation

  • All user-facing strings are externalized into resource files (i18n-ready), even though v1 ships Polish only
  • UI uses a Liquid-Glass-inspired visual language (translucent surfaces, blur, soft depth) implemented in Compose Multiplatform
  • Visual hierarchy is less cramped than the mockup (more breathing room, calmer typography)
  • iOS app feels iOS-idiomatic within Compose's constraints (tab bar placement, navigation patterns, safe areas, dark mode)

Out of Scope

For v1 (deferred to later phases / milestones):

  • In-app recipe authoring — v1 seeds the DB manually; authoring in-app comes next phase
  • Recipe sharing between users/households — future feature; households are isolated in v1
  • Nutrition goal tracking (targets, streaks, deficits) — v1 shows numbers informationally only
  • English and other language copy — code is i18n-ready but v1 ships Polish only
  • True native iOS 26 Liquid Glass via SwiftUI interop — Compose approximation for v1; revisit only if real-device chrome feels clearly inadequate
  • Desktop and Wasm as shipped products — Desktop useful for hot-reload dev; Wasm is a possible future target, neither is a v1 deliverable
  • Sign in with Apple as a first-class button — user's Authentik handles auth; Apple can be federated upstream in Authentik if needed later

Permanently out of scope (explicit exclusions):

  • Social features: comments, ratings, recipe feeds, public profiles — this is a private household app, not a community product
  • Meal-plan marketplaces / paid plans — personal-use product
  • Grocery delivery integrations (Instacart, etc.) — Polish market + small scope; not worth the integration cost
  • Barcode scanning / receipt OCR for pantry updates — manual entry is fine for a 2-person household
  • AI-generated recipes — curated catalog is the value

Deliberately not carried forward from the mockup:

  • The mockup's seed data (~80 ingredients, ~30 recipes) — user chose to start the catalog fresh
  • The mockup's visual design — full visual rebuild; mockup is functional reference only
  • The mockup's localStorage data model — server-backed with local cache replaces it

Context

Codebase state. The ~/dev/repo/recipe directory is a freshly-generated Kotlin Multiplatform Compose template from IntelliJ with four modules: composeApp (Android + Desktop + iOS shared UI), iosApp (iOS bootstrap), server (Ktor, not yet written), and shared (common code). No app logic exists yet — this is effectively greenfield with the build infra in place.

Reference implementation. The user built a working PWA at ~/dev/repo/recipe-mockup/ (vanilla JS + Tailwind CDN + nginx/Docker). It implements the same four views (Recipe List, Meal Planner, Pantry, Shopping List) and has mature logic worth mining as a functional spec — particularly planner entry customization (substitutions, amount overrides, product selection), shortfall computation over a horizon, and shopping-list aggregation with "bought" session tracking. The mockup's UI design is not being carried forward; the user is redesigning visuals around a Liquid-Glass-inspired language.

Users. Authorized users only, behind the user's Authentik. Primary user is the author; secondary is their partner (household sharing from day 1); a handful of family/friends may use their own household accounts. Not an App Store public launch — personal / close-circle use.

Infra. User runs a homelab. Authentik is already installed. The Ktor backend will run on the same server (containerized). No managed cloud dependencies planned.

Language & platform. Polish-only UI for v1 (strings externalized for future i18n). iOS is the primary daily driver; Android deployed later for friends; Desktop useful for development (hot reload); Wasm is aspirational.

Liquid Glass decision. True iOS 26 Liquid Glass (refractive material, specular highlights, morphing chrome) is a SwiftUI-native feature that Compose on iOS cannot reproduce exactly (Compose uses Skia, not Metal-native glass material). The v1 plan is: Compose-only approximation (blur + translucency + gradients) everywhere, measure real-device performance and visual quality, and only selectively add SwiftUI interop for the chrome (tab bar, nav bar) if the approximation feels insufficient. This avoids upfront interop complexity for 90%+ of the UI.

Constraints

  • Tech stack: Kotlin Multiplatform + Compose Multiplatform for UI, Ktor for server, Authentik OIDC for auth — Locked; aligns with user's skills + self-hosted infra
  • Primary platform: iOS — Must feel good here first; other platforms are secondary
  • Hosting: Self-hosted on user's existing homelab (alongside Authentik) — No managed cloud; implies containerized deploy, self-managed DB, reverse proxy
  • Offline: Full offline read/write is required — User will use the shopping list in-store where signal is unreliable; online-only is unacceptable
  • Audience size: ~510 authenticated users total — Don't over-engineer multi-tenancy, rate limiting, or horizontal scaling
  • Language: Polish UI for v1, i18n-ready code — All strings must be externalized from day 1 to avoid costly retrofit later
  • Data seeding: Catalog starts empty; user will author recipes directly in DB for MVP — Need admin-friendly seeding path (SQL migrations, JSON fixtures, or CLI tool)
  • Visual direction: Liquid-Glass-inspired (Compose approximation) — Bright mockup palette is being replaced; design needs to be reworked as part of the rebuild

Key Decisions

Product & scope

Decision Rationale Outcome
KMP with Compose Multiplatform for UI iOS-primary + Android secondary + Desktop/Wasm optional; single codebase for 90%+ of UI — Pending
Household-sharing from day 1 (me + partner share one plan) Core use case is cooking together; per-user + later-sharing would force data-model rewrite — Pending
Authentik OIDC as sole auth provider for MVP User already runs Authentik; self-hosted == aligned; Apple Sign-in likely not required for App Store since Authentik is user's own IdP, not a third-party social login — Pending
Server lives on user's homelab alongside Authentik Existing infra, zero managed-cloud cost, same ops surface — Pending
Offline-first with last-write-wins sync Grocery-store usage demands offline; conflict resolution overkill for a 2-person household — Pending
Compose-only Liquid Glass approximation for v1 Real iOS 26 Liquid Glass requires SwiftUI interop; approximation keeps single codebase; revisit only if chrome feels inadequate on real device — Pending
Polish-only strings, i18n-ready infrastructure Single-language content for v1 speed; externalized strings prevent future rewrite — Pending
Start catalog fresh (don't port mockup seed data) Mockup data is a reference, not production content; user wants to re-curate — Pending
Nutrition is informational only in v1 Keep scope tight; tracking/goals are a natural v2 if usage patterns justify — Pending
Mockup is functional spec only, not visual spec Visual direction is changing (Liquid Glass); logic is mature and worth mining — Pending

Client tech stack (composeApp)

Decision Rationale Outcome
Navigation: Jetpack Navigation Compose (CMP port) — org.jetbrains.androidx.navigation:navigation-compose:2.9.x JetBrains-official recommendation on kotlinlang.org; type-safe routes via @Serializable; works across Android/iOS/Desktop/Wasm; skill transferable to Android — Pending
Architecture: ViewModel + StateFlow + method-per-action Standard modern pattern; matches JetBrains/Google samples; lowest ceremony; upgrade individual screens to sealed-event onEvent only when they grow complex — Pending
DI: Koin — koin-core, koin-compose, koin-compose-viewmodel De facto KMP standard; smoothest koinViewModel() integration with Jetpack Nav back-stack scoping; no codegen; small surface to learn — Pending
Local DB: SQLDelight 2.x Most mature KMP DB; Wasm-ready (hedge for future Compose-for-Web target); raw SQL is a transferable skill; clear migration story via .sq files — Pending
HTTP client: Ktor Client Same team as server; first-class KMP; shared serialization config — Pending
Serialization: kotlinx.serialization (JSON) Standard; works everywhere; pairs with Ktor and SQLDelight — Pending
Date/time: kotlinx.datetime Standard; SQLDelight adapters available — Pending
Logging: Kermit (Touchlab) KMP-native logger; simple API; optional Crashlytics/Sentry bridges — Pending
Image loading: Coil 3 (io.coil-kt.coil3:coil-compose) First-class Compose Multiplatform support; modern API — Pending
Key-value settings: com.russhwolf:multiplatform-settings Small prefs (last tab, theme toggles) that don't belong in SQLDelight — Pending
Glass/blur effects: Haze (dev.chrisbanes.haze:haze) Purpose-built for glass UI in CMP; handles content capture + efficient re-blur; multiplatform — Pending
Mobile OIDC: AppAuth (Android) + ASWebAuthenticationSession wrapper (iOS), exposed via KMP interface Platform-native OAuth flows; no cross-platform auth library mature enough yet for this in 2026 — Pending

Server tech stack

Decision Rationale Outcome
Server framework: Ktor Server 3.x Same team as client HTTP; Kotlin-native; fits homelab deployment; coroutines throughout — Pending
Database: Postgres Homelab-friendly (Docker); JSONB for meal-entry extras; room to grow; standard skill — Pending
SQL: Exposed DSL Kotlin-backend standard; type-safe SQL builders; JSONB first-class; strong tutorial trail with Ktor. Avoid Exposed's DAO (active record) API — Pending
Migrations: Flyway Industry-standard numbered V__.sql files; auto-apply on startup; works with any JDBC-backed stack — Pending
Token validation: io.ktor:ktor-server-auth-jwt Built-in JWKS support with caching + rotation; direct integration with Authentik's OIDC endpoint — Pending

Sync

Decision Rationale Outcome
Strategy: last-write-wins with server-assigned updated_at per row Household of 2 has negligible concurrent-edit risk; simplest to implement and debug; upgradeable per-table to op-log later if hurt — Pending
Transport: HTTP polling (2030s when foreground) + pull-to-refresh + debounced push after local writes Sufficient freshness for a household; SSE is a v2 enhancement if polling feels laggy — Pending
API shape: REST, versioned /api/v1, two sync endpoints (POST /sync/push, GET /sync/pull?since=...) plus catalog + households/invites CRUD Versioning leaves room to evolve; minimal surface area; read-mostly catalog is heavily cached on client — Pending
Server-side data model: users, households, memberships, invites + household-scoped tables carrying household_id, updated_at, deleted_at Supports household sharing + invites, JIT user provisioning from OIDC sub, soft deletes for sync — Pending

Build & module structure

Decision Rationale Outcome
Gradle version catalog (gradle/libs.versions.toml) Single source of truth for versions; standard KMP practice — Pending
Convention plugins (build-logic/ module) from day 1 Centralizes Kotlin/Compose/test config; educational payoff; small upfront cost — Pending
Keep template modules: composeApp/, iosApp/, server/, shared/ — no feature modules in v1 Feature modules don't pay off until ~10 features or multiple devs; flat is clearer at this scale — Pending
shared/commonMain holds: domain models + API DTOs only (no UI, no HTTP, no DB) Keeps shared dep graph minimal; both client and server depend on shared/ — Pending
composeApp/commonMain package layout: app/ navigation/ ui/{theme,components,screens/{recipes,planner,pantry,shopping}} data/{local,remote,repository} domain/ Groups by UI concern + data layer; resists premature modularization — Pending

Evolution

This document evolves at phase transitions and milestone boundaries.

After each phase transition (via /gsd-transition):

  1. Requirements invalidated? → Move to Out of Scope with reason
  2. Requirements validated? → Move to Validated with phase reference
  3. New requirements emerged? → Add to Active
  4. Decisions to log? → Add to Key Decisions
  5. "What This Is" still accurate? → Update if drifted

After each milestone (via /gsd-complete-milestone):

  1. Full review of all sections
  2. Core Value check — still the right priority?
  3. Audit Out of Scope — reasons still valid?
  4. Update Context with current state

Last updated: 2026-04-24 after initial tech-stack discussion