Files
recipe/AGENTS.md

119 lines
7.5 KiB
Markdown
Raw Permalink 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.
# 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` | 73 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 secondary; Desktop/Wasm app targets removed from v1)
- 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`
- Components: Composables / Compose Unstyled from `composables.com` for new shared controls; avoid expanding the app around Material 3
- Glass effects: Liquid (`io.github.fletchmckee.liquid:liquid`) first for menu/search/button chrome; Haze only as a fallback/simple blur tool if needed
- 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 mobile app: commonMain + androidMain + iosMain
├── iosApp/ # iOS bootstrap (Swift/SwiftUI thin shell)
├── server/ # Ktor + Exposed + Postgres + Flyway
├── shared/ # commonMain domain + DTOs; jvm target exists for server dependency
├── 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, Liquid glass style tokens
│ ├── components/ # Shared Recipe-styled composables built on Compose Unstyled where useful
│ └── 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. **Liquid/glass effects on chrome only** (menu, tab/nav/search/button chrome), never over fast-scrolling content; Haze is fallback only.
## 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 2.1 App shell/navigation/search → 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 polish → 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.*