# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview Basil is a full-stack recipe manager built with TypeScript. It features a REST API backend, React web frontend, and PostgreSQL database. The application supports importing recipes from URLs via schema.org markup parsing, local/S3 image storage, and comprehensive recipe management (CRUD operations, search, tagging). ## Architecture **Monorepo Structure:** - `packages/shared/` - Shared TypeScript types and interfaces used across all packages - `packages/api/` - Express.js REST API server with Prisma ORM - `packages/web/` - React + Vite web application - Future: `packages/mobile/` - React Native mobile apps **Key Technologies:** - Backend: Node.js, TypeScript, Express, Prisma ORM, PostgreSQL - Frontend: React, TypeScript, Vite, React Router - Infrastructure: Docker, Docker Compose, nginx - Recipe Scraping: Cheerio for HTML parsing, Schema.org Recipe markup support **Database Schema:** - `Recipe` - Main recipe entity with metadata (prep/cook time, servings, ratings) - `Ingredient` - Recipe ingredients with amounts, units, and ordering - `Instruction` - Step-by-step instructions with optional images - `RecipeImage` - Multiple images per recipe - `Tag` / `RecipeTag` - Many-to-many tagging system **Storage Architecture:** - Primary: Local filesystem storage (`./uploads`) - Optional: S3 storage (configurable via environment variables) - Storage service implements strategy pattern for easy switching ## Development Commands ```bash # Install all dependencies (root + all packages) npm install # Development mode (starts all packages with hot reload) npm run dev # Build all packages npm run build # Lint all packages npm run lint # Testing npm test # Run all unit tests npm run test:e2e # Run E2E tests with Playwright npm run test:e2e:ui # Run E2E tests with Playwright UI # Docker commands npm run docker:up # Start all services (PostgreSQL, API, web) npm run docker:down # Stop all services npm run docker:build # Rebuild Docker images ``` ### Backend-Specific Commands ```bash cd packages/api # Development with auto-reload npm run dev # Database migrations npm run prisma:migrate # Create and apply migration npm run prisma:generate # Generate Prisma client npm run prisma:studio # Open Prisma Studio GUI # Build for production npm run build npm start ``` ### Frontend-Specific Commands ```bash cd packages/web # Development server npm run dev # Production build npm run build npm run preview # Preview production build locally ``` ## Configuration **Environment Variables (packages/api/.env):** ``` PORT=3001 NODE_ENV=development DATABASE_URL=postgresql://basil:basil@localhost:5432/basil?schema=public STORAGE_TYPE=local # or 's3' LOCAL_STORAGE_PATH=./uploads BACKUP_PATH=./backups CORS_ORIGIN=http://localhost:5173 ``` For S3 storage, add: ``` S3_BUCKET=basil-recipes S3_REGION=us-east-1 S3_ACCESS_KEY_ID=your-key S3_SECRET_ACCESS_KEY=your-secret ``` For remote PostgreSQL database, update: ``` DATABASE_URL=postgresql://username:password@remote-host:5432/basil?schema=public ``` ## Key Features ### Recipe Import from URL - `POST /api/recipes/import` - Scrapes recipe from URL using schema.org markup - Extracts: title, description, ingredients, instructions, times, images - Handles JSON-LD structured data (primary) with fallback to manual parsing - Downloads and stores images locally or on S3 ### Recipe Management - Full CRUD operations on recipes - Pagination and search (by title, description) - Filtering by cuisine, category - Image upload with multiple images per recipe - Tagging system for organization ### Storage Service - Abstracted storage interface in `packages/api/src/services/storage.service.ts` - Local storage: Saves to filesystem with timestamped filenames - S3 storage: Placeholder for AWS SDK implementation - Easy to extend for other storage providers ### Backup & Restore - Complete data backup to single ZIP file including database and uploaded files - Backup service in `packages/api/src/services/backup.service.ts` - REST API for creating, listing, downloading, and restoring backups - Automatic backup of all recipes, cookbooks, tags, and relationships - Configurable backup storage location via `BACKUP_PATH` environment variable ## Adding New Features ### Adding a New API Endpoint 1. Add route handler in `packages/api/src/routes/*.routes.ts` 2. Update shared types in `packages/shared/src/types.ts` if needed 3. Rebuild shared package: `cd packages/shared && npm run build` 4. Use Prisma client for database operations ### Adding a New Frontend Page 1. Create component in `packages/web/src/pages/` 2. Add route in `packages/web/src/App.tsx` 3. Create API service methods in `packages/web/src/services/api.ts` 4. Import and use shared types from `@basil/shared` ### Database Schema Changes 1. Edit `packages/api/prisma/schema.prisma` 2. Run `npm run prisma:migrate` to create migration 3. Run `npm run prisma:generate` to update Prisma client 4. Update TypeScript types in `packages/shared/src/types.ts` to match ## Docker Deployment The project includes full Docker support for production deployment: ```bash docker-compose up -d ``` This starts: - PostgreSQL database (port 5432) - API server (port 3001) - Web frontend served via nginx (port 5173) Persistent volumes: - `postgres_data` - Database storage - `uploads_data` - Uploaded images - `backups_data` - Backup files ### Using a Remote Database To use a remote PostgreSQL database instead of the local Docker container: 1. Set the `DATABASE_URL` environment variable to point to your remote database 2. Update `docker-compose.yml` to pass the environment variable or create a `.env` file in the root 3. Optionally, remove or comment out the `postgres` service and its dependency in `docker-compose.yml` Example `.env` file in project root: ``` DATABASE_URL=postgresql://username:password@remote-host:5432/basil?schema=public ``` The docker-compose.yml is configured to use `${DATABASE_URL:-default}` which will use the environment variable if set, or fall back to the local postgres container. ## API Reference **Recipes:** - `GET /api/recipes` - List all recipes (supports pagination, search, filters) - `GET /api/recipes/:id` - Get single recipe with all relations - `POST /api/recipes` - Create new recipe - `PUT /api/recipes/:id` - Update recipe - `DELETE /api/recipes/:id` - Delete recipe and associated images - `POST /api/recipes/:id/images` - Upload image for recipe - `POST /api/recipes/import` - Import recipe from URL **Query Parameters:** - `page`, `limit` - Pagination - `search` - Search in title/description - `cuisine`, `category` - Filter by cuisine or category **Backups:** - `POST /api/backup` - Create a new backup (returns backup metadata) - `GET /api/backup` - List all available backups - `GET /api/backup/:filename` - Download a specific backup file - `POST /api/backup/restore` - Restore from backup (accepts file upload or existing filename) - `DELETE /api/backup/:filename` - Delete a backup file ## Important Implementation Details ### Prisma Relations - All related entities (ingredients, instructions, images, tags) use cascade delete - Ingredients and instructions maintain ordering via `order` and `step` fields - Tags use many-to-many relationship via `RecipeTag` join table ### Recipe Scraping - Primary: Parses JSON-LD `application/ld+json` scripts for Schema.org Recipe data - Fallback: Extracts basic info from HTML meta tags and headings - Handles ISO 8601 duration format (PT30M, PT1H30M) for cook times - Downloads images asynchronously and stores them locally ### Frontend Routing - Uses React Router v6 for client-side routing - Vite proxy forwards `/api` and `/uploads` requests to backend during development - Production uses nginx reverse proxy to backend API ### TypeScript Workspace - Root `package.json` defines npm workspaces - Packages can reference each other (e.g., `@basil/shared`) - Must rebuild shared package when types change for other packages to see updates ## CI/CD and Deployment Basil includes a complete CI/CD pipeline with Gitea Actions for automated testing, building, and deployment. **Quick Start:** - See [CI/CD Setup Guide](docs/CI-CD-SETUP.md) for full documentation - See [Deployment Quick Start](docs/DEPLOYMENT-QUICK-START.md) for quick reference **Pipeline Overview:** 1. **Test Stage**: Runs unit tests (Vitest) and E2E tests (Playwright) 2. **Build Stage**: Builds Docker images for API and Web (main branch only) 3. **Deploy Stage**: Pushes images to registry and triggers webhook deployment **Deployment Options:** - **Automatic**: Push to main branch triggers full CI/CD pipeline - **Manual**: Run `./scripts/manual-deploy.sh` for interactive deployment - **Webhook**: Systemd service listens for deployment triggers **Key Files:** - `.gitea/workflows/ci-cd.yml` - Main CI/CD workflow - `scripts/deploy.sh` - Deployment script - `scripts/webhook-receiver.sh` - Webhook server - `.env.deploy.example` - Deployment configuration template **Required Secrets (Gitea):** - `DOCKER_USERNAME` - Docker Hub username - `DOCKER_PASSWORD` - Docker Hub access token - `DEPLOY_WEBHOOK_URL` - Webhook endpoint for deployments ## Version Management **IMPORTANT**: Increment the version with **every production deployment**. ### Version Format Basil uses calendar versioning with the format: `YYYY.MM.PPP` - `YYYY` - Four-digit year (e.g., 2026) - `MM` - Two-digit month with zero-padding (e.g., 01 for January, 12 for December) - `PPP` - Three-digit patch number with zero-padding that increases with every deployment. **Does not reset at month boundaries** — it is a monotonically increasing counter across the lifetime of the project. ### Examples - `2026.01.006` - Sixth deployment (in January 2026) - `2026.04.007` - Seventh deployment (in April 2026 — patch continues from previous month, does not reset) - `2026.04.008` - Eighth deployment (still in April 2026) - `2026.05.009` - Ninth deployment (in May 2026 — patch continues, does not reset) ### Version Update Process When deploying to production: 1. **Update version files:** ```bash # Update both version files with new version # packages/api/src/version.ts # packages/web/src/version.ts export const APP_VERSION = '2026.01.002'; ``` 2. **Commit the version bump:** ```bash git add packages/api/src/version.ts packages/web/src/version.ts git commit -m "chore: bump version to 2026.01.002" git push origin main ``` 3. **Create Git tag and release:** ```bash # Tag should match version with 'v' prefix git tag v2026.01.002 git push origin v2026.01.002 # Or use Gitea MCP to create tag and release ``` 4. **Document in release notes:** - Summarize changes since last version - List bug fixes, features, and breaking changes - Reference related pull requests or issues ### Version Display The current version is displayed in: - API: `GET /api/version` endpoint returns `{ version: '2026.01.002' }` - Web: Footer or about section shows current version - Both packages export `APP_VERSION` constant for internal use ## UI Design System - Thumbnail Cards ### Responsive Column Layout System All recipe and cookbook thumbnail displays support a responsive column system (3, 5, 7, or 9 columns) with column-specific styling for optimal readability at different densities. **Column-Responsive Font Sizes:** - **Column 3** (Largest cards): Title 0.95rem, Description 0.8rem (2 lines), Meta 0.75rem - **Column 5** (Medium cards): Title 0.85rem, Description 0.75rem (2 lines), Meta 0.7rem - **Column 7** (Compact): Title 0.75rem, Description hidden, Meta 0.6rem - **Column 9** (Most compact): Title 0.75rem, Description hidden, Meta 0.6rem **Implementation Pattern:** 1. Add `gridClassName = \`recipes-grid columns-${columnCount}\`` or `\`cookbooks-grid columns-${columnCount}\`` 2. Apply className to grid container: `
` 3. Use column-specific CSS selectors: `.columns-3 .recipe-info h3 { font-size: 0.95rem; }` ### Recipe Thumbnail Display Locations All locations use square aspect ratio (1:1) cards with 60% image height. 1. **Recipe List Page** (`packages/web/src/pages/RecipeList.tsx`) - Class: `recipe-grid-enhanced columns-{3|5|7|9}` - CSS: `packages/web/src/styles/RecipeList.css` - Features: Main recipe browsing with pagination, search, filtering - Displays: Image, title, description, time, rating - Status: ✅ Responsive column styling applied 2. **Cookbooks Page - Recent Recipes** (`packages/web/src/pages/Cookbooks.tsx`) - Class: `recipes-grid columns-{3|5|7|9}` - CSS: `packages/web/src/styles/Cookbooks.css` - Features: Shows 6 most recent recipes below cookbook list - Displays: Image, title, description, time, rating - Status: ✅ Responsive column styling applied 3. **Cookbook Detail - Recipes Section** (`packages/web/src/pages/CookbookDetail.tsx`) - Class: `recipes-grid columns-{3|5|7|9}` - CSS: `packages/web/src/styles/CookbookDetail.css` - Features: Paginated recipes within a cookbook, with remove button - Displays: Image, title, description, time, rating, remove button - Status: ✅ Responsive column styling applied 4. **Add Meal Modal - Recipe Selection** (`packages/web/src/components/meal-planner/AddMealModal.tsx`) - Class: `recipe-list` with `recipe-item` - CSS: `packages/web/src/styles/AddMealModal.css` - Features: Selectable recipe list for adding to meal plan - Displays: Small thumbnail, title, description - Status: ⚠️ Needs responsive column styling review 5. **Meal Card Component** (`packages/web/src/components/meal-planner/MealCard.tsx`) - Class: `meal-card` with `meal-card-image` - CSS: `packages/web/src/styles/MealCard.css` - Features: Recipe thumbnail in meal planner (compact & full views) - Displays: Recipe image as part of meal display - Status: ⚠️ Different use case - calendar/list view, not grid-based ### Cookbook Thumbnail Display Locations All locations use square aspect ratio (1:1) cards with 50% image height. 1. **Cookbooks Page - Main Grid** (`packages/web/src/pages/Cookbooks.tsx`) - Class: `cookbooks-grid` - CSS: `packages/web/src/styles/Cookbooks.css` - Features: Main cookbook browsing with pagination - Displays: Cover image, name, recipe count, cookbook count - Status: ✅ Already has compact styling (description/tags hidden) - Note: Could benefit from column-responsive font sizes 2. **Cookbook Detail - Nested Cookbooks** (`packages/web/src/pages/CookbookDetail.tsx`) - Class: `cookbooks-grid` with `cookbook-card nested` - CSS: `packages/web/src/styles/CookbookDetail.css` - Features: Child cookbooks within parent cookbook - Displays: Cover image, name, recipe count, cookbook count - Status: ✅ Already has compact styling (description/tags hidden) - Note: Could benefit from column-responsive font sizes ### Key CSS Classes - `recipe-card` - Individual recipe card - `recipe-grid-enhanced` or `recipes-grid` - Recipe grid container - `cookbook-card` - Individual cookbook card - `cookbooks-grid` - Cookbook grid container - `columns-{3|5|7|9}` - Dynamic column count modifier class ### Styling Consistency Rules 1. **Image Heights**: Recipes 60%, Cookbooks 50% 2. **Aspect Ratio**: All cards are square (1:1) 3. **Border**: 1px solid #e0e0e0 (not box-shadow) 4. **Border Radius**: 8px 5. **Hover Effect**: `translateY(-2px)` with `box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1)` 6. **Description Display**: - Columns 3 & 5: Show 2 lines - Columns 7 & 9: Hide completely 7. **Font Scaling**: Larger fonts for fewer columns, smaller for more columns 8. **Text Truncation**: Use `-webkit-line-clamp` with `text-overflow: ellipsis`