a398f8cf44
- 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>
105 lines
7.5 KiB
Markdown
105 lines
7.5 KiB
Markdown
# 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):
|
|
|
|
```powershell
|
|
./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`):
|
|
|
|
```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 <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`:
|
|
|
|
```powershell
|
|
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`):
|
|
|
|
```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<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.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/<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.ts` — `api<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.
|