diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..5d8fab5 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,82 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Overview + +YesChef is a family shopping list and recipe app. Single-tenant per deployment, gated by a shared `FAMILY_CODE` invite phrase at registration. Real-time list collaboration is delivered over SignalR. + +The repo is a small monorepo with two deployables plus an e2e harness at the root: + +- `src/backend/YesChef.Api/` — ASP.NET Core (net10.0) minimal API + EF Core (Npgsql) + SignalR +- `src/frontend/` — SvelteKit 2 (Svelte 5 runes) + TypeScript + Tailwind v4, deployed via `@sveltejs/adapter-node` +- `docker-compose.yml` (root) — production-style stack: Postgres 17 + backend + frontend, fronted by Traefik (TLS via Let's Encrypt). Frontend and backend share the same `DOMAIN`; Traefik routes `/api`, `/hubs`, and `/health` to the backend, everything else to the frontend. +- `test-e2e.mjs` / `test-e2e-multiuser.mjs` — Playwright scripts run against `http://localhost:5173` (Vite dev) hitting a local backend. + +## Common commands + +Frontend (run from `src/frontend`, use `npm --prefix src/frontend ` to avoid `cd`): + +```powershell +npm --prefix src/frontend install +npm --prefix src/frontend run dev # Vite dev server on :5173 +npm --prefix src/frontend run build # adapter-node build → src/frontend/build +npm --prefix src/frontend run check # svelte-kit sync + svelte-check (type/lint check) +``` + +Backend (use `dotnet --project` to avoid `cd`): + +```powershell +dotnet run --project src/backend/YesChef.Api # listens per launchSettings.json +dotnet build src/backend/YesChef.Api/YesChef.Api.csproj +dotnet ef migrations add --project src/backend/YesChef.Api +dotnet ef database update --project src/backend/YesChef.Api +``` + +Note: `Program.cs` calls `db.Database.MigrateAsync()` on startup, so running the API auto-applies pending migrations against the configured `ConnectionStrings:DefaultConnection`. + +Full stack via Docker (requires `.env` populated from `.env.example`): + +```powershell +docker compose up -d --build +docker compose logs -f backend +``` + +E2E (requires frontend running on :5173 and backend reachable): + +```powershell +node test-e2e.mjs +node test-e2e-multiuser.mjs +``` + +There is no backend test project and no test runner configured in the root `package.json` — `npm test` is a no-op stub. + +## Architecture + +### Backend — feature-folder minimal API + +`Program.cs` is the composition root. It wires EF Core (`YesChefDb`), JWT bearer auth, SignalR, runs migrations, then maps endpoints by feature folder: + +- `Auth/` — `/api/auth` register/login/me. Registration requires the `FamilyCode` config value (env `FamilyCode`). Passwords use `PasswordHasher`. JWTs are signed with HS256 from `Jwt:Secret`; issuer/audience are NOT validated. The `OnMessageReceived` event also accepts `?access_token=` for the `/hubs/*` SignalR paths. +- `Features/Stores/`, `Features/ShoppingLists/`, `Features/Recipes/` — each exposes a static `Map…Endpoints(this RouteGroupBuilder)` extension. All are mapped under `RequireAuthorization()`. +- `Features/ShoppingLists/ShoppingListHub.cs` — SignalR hub at `/hubs/shopping-list` for live list updates. +- `Data/YesChefDb.cs` — single `DbContext`. Notable model rules: `User.Name` and `Store.Name` are unique; `ShoppingList → Items` cascade-deletes; `ShoppingListItem.CheckedByUser` and `ShoppingListItem.Recipe` set null on delete; `Recipe → Ingredients` cascades. +- `Auth/ClaimsPrincipalExtensions.cs` — `GetUserId()` / `GetUserName()` helpers used by endpoints to scope writes. +- `Migrations/` — EF Core migrations. Add new ones with `dotnet ef migrations add` (see commands above). + +When adding a feature: create `Features//Endpoints.cs` with a `MapEndpoints` extension, register the group in `Program.cs`, and add any new entities to `Entities/` plus configuration in `YesChefDb.OnModelCreating`. + +### Frontend — SvelteKit with runes + +- `svelte.config.js` forces `runes: true` for all non-`node_modules` files. Use Svelte 5 runes (`$state`, `$derived`, `$effect`); do not use legacy reactive `$:` syntax. +- `src/lib/api.ts` — `api(path, opts)` helper. Stores the JWT in `localStorage` under `token`, attaches `Authorization: Bearer …`, and on 401 it clears the token and `goto('/login')`. +- `src/lib/auth.svelte.ts` — runes-based auth state. +- `src/lib/signalr.ts` — SignalR client wiring (passes the JWT via the `?access_token=` query string the backend's `OnMessageReceived` handler expects). +- `src/routes/` — file-based routes: `/login`, `/lists`, `/lists/[id]`, `/recipes`, `/recipes/new`, `/recipes/[id]`, `/stores`. +- `src/service-worker.ts` — PWA service worker. +- `$lib` is aliased to `src/lib` (see `svelte.config.js`). +- The frontend calls relative paths like `/api/...`; in production Traefik routes those to the backend on the same host. For local dev against a backend on a different port, configure a Vite proxy or run the backend behind the same origin. + +### Configuration & secrets + +`.env.example` documents the four required environment variables for the Docker stack: `POSTGRES_PASSWORD`, `JWT_SECRET`, `FAMILY_CODE`, `DOMAIN`. The backend reads `Jwt:Secret`, `FamilyCode`, and `ConnectionStrings:DefaultConnection` via `IConfiguration` — these can come from `appsettings.Development.json` for local runs or from environment variables (double-underscore form, e.g. `ConnectionStrings__DefaultConnection`) in containers.