119 lines
7.6 KiB
Markdown
119 lines
7.6 KiB
Markdown
# 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` | 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 through the KMP `OidcClient` interface. iOS uses ASWebAuthenticationSession underneath without a Swift auth bridge.
|
||
|
||
**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 (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 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.*
|