Files
recipe/AGENTS.md
2026-04-29 20:54:13 +02:00

7.1 KiB
Raw Blame History

AGENTS.md

Guidance for Codex when working in this repository.

Project

Recipe (working title) — a household meal planning + pantry + shopping list app built with Kotlin Multiplatform (iOS-primary) and a self-hosted Ktor server. Offline-first with last-write-wins sync; household sharing (me + partner); auth via self-hosted Authentik (OIDC).

Core value: "My week is planned." I pick recipes, the calendar fills up, and I know what we're eating.

Planning workflow — always start here

This project uses GSD (Get Shit Done). All product scope, tech decisions, requirements, and phase structure live in .planning/. Read these files before doing any implementation work.

File What it is When to read
.planning/PROJECT.md Product scope, locked tech decisions, constraints, out-of-scope Every session — source of truth
.planning/REQUIREMENTS.md 72 v1 requirements with REQ-IDs grouped by category, plus v2 / out-of-scope When touching any feature area
.planning/ROADMAP.md 11 phases with goals, mapped requirements, success criteria To know which phase we're in
.planning/STATE.md Current phase + high-level pointer Fast orientation
.planning/config.json Workflow settings (YOLO mode, fine granularity, quality models) Rarely — set once
.planning/research/SUMMARY.md Executive synthesis of architecture + pitfalls research When planning a phase
.planning/research/ARCHITECTURE.md Component structure, data flow, build-order reasoning When structuring code
.planning/research/PITFALLS.md 14 critical pitfalls specific to this stack Before touching auth, sync, or iOS specifics

Tech stack (locked — see PROJECT.md § Key Decisions for full rationale)

Client (composeApp/):

  • Kotlin Multiplatform + Compose Multiplatform (iOS-primary; Android, Desktop, Wasm secondary)
  • Navigation: org.jetbrains.androidx.navigation:navigation-compose (JetBrains-official CMP port of Jetpack Navigation)
  • State: ViewModel + StateFlow, method-per-action; org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose
  • DI: Koin (koin-core, koin-compose, koin-compose-viewmodel)
  • Local DB: SQLDelight 2.x (raw .sq files, generated type-safe Kotlin)
  • HTTP: Ktor Client
  • Serialization: kotlinx.serialization
  • Date/time: kotlinx.datetime
  • Logging: Kermit (Touchlab)
  • Images: Coil 3 (io.coil-kt.coil3:coil-compose)
  • Settings/KV: com.russhwolf:multiplatform-settings
  • Glass/blur: Haze (dev.chrisbanes.haze:haze)
  • Mobile OIDC: Lokksmith on Android/iOS (KMP interface; ASWebAuthenticationSession underneath on iOS)

Server (server/):

  • Ktor Server 3.x on the user's homelab (alongside Authentik)
  • Postgres
  • Exposed (DSL only — never the DAO / active-record API)
  • Flyway for migrations
  • Auth: io.ktor:ktor-server-auth-jwt validating Authentik tokens via JWKS

Shared (shared/commonMain):

  • Domain models + API DTOs only
  • No UI, no HTTP, no DB code — keep dependency graph minimal

Sync: Last-write-wins with server-assigned updated_at; HTTP polling (2030s foreground) + pull-to-refresh + debounced push after writes. POST /api/v1/sync/push, GET /api/v1/sync/pull?since=....

Module structure

recipe/
├── composeApp/          # KMP: commonMain + androidMain + iosMain + jvmMain (desktop)
├── iosApp/              # iOS bootstrap (Swift/SwiftUI thin shell)
├── server/              # Ktor + Exposed + Postgres + Flyway
├── shared/              # commonMain: domain + DTOs, no UI/HTTP/DB
├── build-logic/         # Convention plugins (Kotlin/Compose/test config)
├── gradle/libs.versions.toml  # Single source of truth for versions
└── .planning/           # GSD planning artifacts (see above)

Package layout inside composeApp/commonMain:

dev.ulfrx.recipe/
├── app/                 # App entry, Koin init, theme
├── navigation/          # NavHost, routes, nav graph (nested NavHosts per tab)
├── ui/
│   ├── theme/           # Colors, typography, Haze glass styles
│   ├── components/      # Shared composables
│   └── screens/{recipes,planner,pantry,shopping}/  # Each with screen + ViewModel
├── data/{local,remote,repository}/
└── domain/              # Client-only logic; shared/ handles cross-cutting

Rule: No feature modules in v1. Flat composeApp/commonMain with the package layout above.

Non-negotiable conventions

  1. Sync timestamps come from the server, never the device. updated_at is assigned server-side; pulling uses lexicographic (updated_at, id) cursor.
  2. Row identity is always UUIDs, never composite natural keys. (date, slot) is not a primary key. See ARCHITECTURE.md § Anti-Patterns.
  3. Household scope is enforced in 3 layers: client query filter + server PrincipalResolver deriving householdId from JWT sub + DB household_id column. Never accept household_id from request body.
  4. All sync I/O goes through the SyncEngine Koin singleton. Features write to SQLDelight + outbox, never to HTTP directly. See ARCHITECTURE.md § Pattern 2.
  5. Exposed DSL only, never DAO. Active-record pattern has footguns with JSONB and coroutines.
  6. newSuspendedTransaction for every coroutine-touching handler. Plain transaction {} inside a suspend block exhausts the connection pool.
  7. iOS binary flags on day 1: kotlin.native.binary.objcDisposeOnMain=false, kotlin.native.binary.gc=cms.
  8. shared/commonMain stays light. No Ktor, Compose, or SQLDelight imports.
  9. Strings externalized from day 1 — Polish-only content, but resources are multi-locale-ready.
  10. Haze blur on chrome only (tab bar, nav bar), never over fast-scrolling content.

Current phase

See .planning/STATE.md. The roadmap has 11 phases; you must work within the currently active one. Don't jump ahead.

Build order (load-bearing — do not reorder): Phase 1 Infra → Phase 2 Auth → Phase 3 Households → Phase 4 SyncEngine skeleton → Phase 5 Recipe catalog → Phase 6 Planner core → Phase 7 Planner customization/nutrition → Phase 8 Pantry → Phase 9 Shopping → Phase 10 UI chrome (Haze) → Phase 11 Localization + deployment.

GSD commands you'll use

  • /gsd-progress — show current state and suggest next action
  • /gsd-discuss-phase N — socratic phase clarification before planning
  • /gsd-plan-phase N — produce detailed PLAN.md for phase N
  • /gsd-execute-phase N — execute the plans in phase N
  • /gsd-next — automatically advance to the next logical step

Functional reference

The legacy PWA mockup at ~/dev/repo/recipe-mockup/ is the functional reference (logic, data shapes, user flows). It is not a visual reference — UI is being rebuilt around a Liquid-Glass-inspired language. Mine it for planner customization logic (substitutions, amount overrides, product selections), shortfall computation, and shopping aggregation. Do not port its vanilla-JS data or Tailwind styling.


Initialized: 2026-04-24. Update when .planning/PROJECT.md § Key Decisions gains load-bearing new entries.