- QuantityInput unit select: add bg-white (browser defaults to gray without it) and capitalize default option to "Unit" - Shopping list section dropdowns: rename "Uncategorized" → "No section" for consistent title-case phrasing across all default options - src/frontend/STYLE_GUIDE.md: documents form control classes, button variants, text casing rules, icon usage, color tokens, and spacing rhythm so all future UI work stays consistent - CLAUDE.md: link to the style guide so it is always consulted Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
7.5 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
UI consistency: all form controls, buttons, colors, icons, spacing, and text casing are governed by src/frontend/STYLE_GUIDE.md. Read it before adding or modifying any UI component — deviating without a documented reason is a bug.
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.