7.3 KiB
CLAUDE.md
Guidance for Claude Code 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
.sqfiles, 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: AppAuth on both Android (Kotlin actual) and iOS (Swift
AuthBridgeover AppAuth-iOS via SwiftPM, called fromiosMainthrough Koin); KMP interface incommonMain. iOS dropped CocoaPods on 2026-04-28 — see.planning/phases/02-authentication-foundation/DECISION-drop-cocoapods.md
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-jwtvalidating 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 (20–30s 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
- Sync timestamps come from the server, never the device.
updated_atis assigned server-side; pulling uses lexicographic(updated_at, id)cursor. - Row identity is always UUIDs, never composite natural keys.
(date, slot)is not a primary key. See ARCHITECTURE.md § Anti-Patterns. - Household scope is enforced in 3 layers: client query filter + server
PrincipalResolverderivinghouseholdIdfrom JWTsub+ DBhousehold_idcolumn. Never accepthousehold_idfrom request body. - All sync I/O goes through the
SyncEngineKoin singleton. Features write to SQLDelight + outbox, never to HTTP directly. See ARCHITECTURE.md § Pattern 2. - Exposed DSL only, never DAO. Active-record pattern has footguns with JSONB and coroutines.
newSuspendedTransactionfor every coroutine-touching handler. Plaintransaction {}inside asuspendblock exhausts the connection pool.- iOS binary flags on day 1:
kotlin.native.binary.objcDisposeOnMain=false,kotlin.native.binary.gc=cms. shared/commonMainstays light. No Ktor, Compose, or SQLDelight imports.- Strings externalized from day 1 — Polish-only content, but resources are multi-locale-ready.
- 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.