So future agents (and humans) discover the helpers instead of stitching together the raw docker / dotnet / npm commands. Also notes that LoggingEmailSender prints invite and reset links to the API log when SMTP is unconfigured locally.
7.2 KiB
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) + SignalRsrc/frontend/— SvelteKit 2 (Svelte 5 runes) + TypeScript + Tailwind v4, deployed via@sveltejs/adapter-nodedocker-compose.yml(root) — production-style stack: Postgres 17 + backend + frontend, fronted by Traefik (TLS via Let's Encrypt). Frontend and backend share the sameDOMAIN; Traefik routes/api,/hubs, and/healthto the backend, everything else to the frontend.test-e2e.mjs/test-e2e-multiuser.mjs— Playwright scripts run againsthttp://localhost:5173(Vite dev) hitting a local backend.
Common commands
Local dev stack (preferred for everyday work):
./scripts/dev-up.ps1 # starts Postgres + API + frontend (each in its own window)
./scripts/dev-db.ps1 start # just the Postgres container (named yeschef-pg, on :5432)
./scripts/dev-db.ps1 reset # nuke the dev DB volume — next start is a fresh DB
./scripts/dev-db.ps1 status # connection string + container state
dev-db.ps1 provisions a postgres:17 container that matches the connection string baked into appsettings.json (Host=localhost;Database=yeschef;Username=yeschef;Password=yeschef). The API auto-applies migrations on startup. With no SMTP configured locally, outgoing emails (invites, password reset) print to the API log window via LoggingEmailSender — that's where to copy the join/reset URL from when manually testing those flows.
Frontend (run from src/frontend, use npm --prefix src/frontend <cmd> to avoid cd):
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):
dotnet run --project src/backend/YesChef.Api # listens per launchSettings.json
dotnet build src/backend/YesChef.Api/YesChef.Api.csproj
dotnet ef migrations add <Name> --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.
Backend tests (TUnit on Microsoft.Testing.Platform; integration tests use Testcontainers + Postgres). The src/backend/global.json opts dotnet test into MTP mode, which means project args must be passed via --project (positional project paths are rejected) and --solution is NOT a supported switch on the .NET 10 SDK. Run from src/backend so global.json is in scope — use Push-Location to avoid the blocked cd:
Push-Location src/backend; try { dotnet test --project YesChef.Api.UnitTests/YesChef.Api.UnitTests.csproj } finally { Pop-Location }
Push-Location src/backend; try { dotnet test --project YesChef.Api.IntegrationTests/YesChef.Api.IntegrationTests.csproj } finally { Pop-Location }
Integration tests start a single Postgres 17 container per test session, create a migrated yeschef_template database, then clone a fresh DB per test via CREATE DATABASE … TEMPLATE …. Tests run in parallel (capped by IntegrationTestParallelLimit) and require Docker (Rancher Desktop or Docker Desktop).
Full stack via Docker (requires .env populated from .env.example):
docker compose up -d --build
docker compose logs -f backend
E2E (requires frontend running on :5173 and backend reachable):
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/authregister/login/me. Registration requires theFamilyCodeconfig value (envFamilyCode). Passwords usePasswordHasher<User>. JWTs are signed with HS256 fromJwt:Secret; issuer/audience are NOT validated. TheOnMessageReceivedevent also accepts?access_token=for the/hubs/*SignalR paths.Features/Stores/,Features/ShoppingLists/,Features/Recipes/— each exposes a staticMap…Endpoints(this RouteGroupBuilder)extension. All are mapped underRequireAuthorization().Features/ShoppingLists/ShoppingListHub.cs— SignalR hub at/hubs/shopping-listfor live list updates.Data/YesChefDb.cs— singleDbContext. Notable model rules:User.NameandStore.Nameare unique;ShoppingList → Itemscascade-deletes;ShoppingListItem.CheckedByUserandShoppingListItem.Recipeset null on delete;Recipe → Ingredientscascades.Auth/ClaimsPrincipalExtensions.cs—GetUserId()/GetUserName()helpers used by endpoints to scope writes.Migrations/— EF Core migrations. Add new ones withdotnet ef migrations add(see commands above).
When adding a feature: create Features/<Name>/<Name>Endpoints.cs with a Map<Name>Endpoints 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.jsforcesrunes: truefor all non-node_modulesfiles. Use Svelte 5 runes ($state,$derived,$effect); do not use legacy reactive$:syntax.src/lib/api.ts—api<T>(path, opts)helper. Stores the JWT inlocalStorageundertoken, attachesAuthorization: Bearer …, and on 401 it clears the token andgoto('/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'sOnMessageReceivedhandler expects).src/routes/— file-based routes:/login,/lists,/lists/[id],/recipes,/recipes/new,/recipes/[id],/stores.src/service-worker.ts— PWA service worker.$libis aliased tosrc/lib(seesvelte.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.