Files
YesChef/CLAUDE.md
T
Josh Rogers a398f8cf44 Fix form control consistency; add frontend style guide
- 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>
2026-05-14 20:13:36 -05:00

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) + 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

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.jsonnpm 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<User>. 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.csGetUserId() / 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/<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.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.tsapi<T>(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.