# Recipe Manager — Architecture

**Last Updated:** 2026-03-23  
**Status:** Initial Design

---

## Tech Stack

### Backend
- **Runtime:** Node.js 22+
- **Language:** TypeScript 5+
- **Framework:** Express.js (REST API)
- **Database:** SQLite 3 (sql.js — pure JavaScript, sync operations)
- **Validation:** Zod (schema validation)
- **Testing:** Vitest (unit), Supertest (integration)

### Frontend
- **Framework:** React 18+
- **Build Tool:** Vite
- **Routing:** React Router 6
- **State:** React Context + hooks (Zustand if needed)
- **Forms:** React Hook Form
- **Styling:** Tailwind CSS
- **Testing:** Vitest + Testing Library

### DevOps
- **Containerization:** Docker + Docker Compose
- **Reverse Proxy:** Caddy (auto-HTTPS, simple config)
- **Process Manager:** PM2 (for non-Docker deployments)
- **Backup:** Litestream (continuous SQLite replication)

---

## Architecture Patterns

### 1. Layered Architecture

```
┌─────────────────────────────────────┐
│         Frontend (React)            │
│  Components → Hooks → API Client    │
└──────────────┬──────────────────────┘
               │ HTTP/JSON
┌──────────────┴──────────────────────┐
│         Backend (Express)           │
│  Routes → Services → Repositories   │
└──────────────┬──────────────────────┘
               │ SQL
┌──────────────┴──────────────────────┐
│         Database (SQLite)           │
│     recipes, tags, collections      │
└─────────────────────────────────────┘
```

### 2. API Design

**RESTful endpoints:**
- `GET /api/recipes` — List recipes (with filters, pagination)
- `GET /api/recipes/:id` — Get single recipe
- `POST /api/recipes` — Create recipe
- `PUT /api/recipes/:id` — Update recipe
- `DELETE /api/recipes/:id` — Delete recipe
- `GET /api/tags` — List tags
- `POST /api/recipes/:id/tags` — Add tag to recipe

**Response format:**
```json
{
  "success": true,
  "data": { ... },
  "error": null
}
```

### 3. Data Model

```sql
CREATE TABLE recipes (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  title TEXT NOT NULL,
  description TEXT,
  ingredients TEXT NOT NULL, -- JSON array
  instructions TEXT NOT NULL, -- JSON array of steps
  source_url TEXT,
  notes TEXT,
  servings INTEGER,
  prep_time_minutes INTEGER,
  cook_time_minutes INTEGER,
  created_at INTEGER NOT NULL, -- Unix timestamp
  updated_at INTEGER NOT NULL,
  last_cooked_at INTEGER
);

CREATE TABLE tags (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  name TEXT UNIQUE NOT NULL,
  color TEXT -- Hex color for UI
);

CREATE TABLE recipe_tags (
  recipe_id INTEGER NOT NULL,
  tag_id INTEGER NOT NULL,
  PRIMARY KEY (recipe_id, tag_id),
  FOREIGN KEY (recipe_id) REFERENCES recipes(id) ON DELETE CASCADE,
  FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE
);

CREATE INDEX idx_recipes_title ON recipes(title);
CREATE INDEX idx_recipes_created_at ON recipes(created_at DESC);
CREATE INDEX idx_recipe_tags_recipe ON recipe_tags(recipe_id);
CREATE INDEX idx_recipe_tags_tag ON recipe_tags(tag_id);
```

### 4. Frontend Structure

```
src/
├── components/
│   ├── RecipeCard.tsx
│   ├── RecipeForm.tsx
│   ├── RecipeDetail.tsx
│   ├── CookMode.tsx
│   └── SearchBar.tsx
├── hooks/
│   ├── useRecipes.ts
│   ├── useRecipe.ts
│   └── useTags.ts
├── services/
│   └── api.ts           -- API client
├── types/
│   └── recipe.ts        -- TypeScript interfaces
├── pages/
│   ├── HomePage.tsx
│   ├── RecipeListPage.tsx
│   ├── RecipeDetailPage.tsx
│   └── CookModePage.tsx
├── App.tsx
└── main.tsx
```

---

## Key Decisions

### Why sql.js?
- Pure JavaScript (zero native dependencies, works on Node v22+)
- Synchronous API (simpler error handling)
- Portable (WSL2, Docker, browser-ready for future PWA)
- See docs/ADR-001-sqlite-library-choice.md for decision rationale

### Why Tailwind CSS?
- Rapid prototyping
- Agent-friendly (well-documented utility classes)
- No CSS-in-JS overhead

### Why Vite over Create-React-App?
- Faster dev server (ESM-native)
- Simpler config
- Better TypeScript support

### Why Docker Compose?
- Consistent dev/prod environment
- Easy to add services (reverse proxy, backup)
- Paul already uses Docker for fintrove

---

## Development Workflow

### 1. Agent Iteration Loop
```bash
# Agent reads PROJECT.md + ARCHITECTURE.md
# Agent plans next feature
# Agent implements + tests
# Agent commits with conventional commit message
# Agent updates docs/ADR-xxx.md
# Agent reports completion
```

### 2. Testing Strategy
- **Unit tests:** Core business logic (recipe parsing, scaling calculations)
- **Integration tests:** API endpoints
- **E2E tests:** Critical user flows (create recipe → cook mode)

### 3. Commit Conventions
```
feat: add recipe scaling
fix: correct ingredient parsing
docs: update architecture decisions
test: add cook mode E2E tests
chore: bump dependencies
```

---

## Deployment

### Docker Compose Stack

```yaml
services:
  recipe-manager:
    build: .
    ports:
      - "3000:3000"
    volumes:
      - ./data:/app/data  # SQLite database
    environment:
      NODE_ENV: production
      DATABASE_PATH: /app/data/recipes.db

  caddy:
    image: caddy:2
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
    depends_on:
      - recipe-manager

volumes:
  caddy_data:
```

### Backup Strategy
- **Litestream:** Continuous replication to S3/B2
- **Manual exports:** Weekly JSON dumps for portability
- **Git LFS:** Track database snapshots (optional)

---

## Security Considerations

### MVP (Single Household)
- No authentication (behind home network or VPN)
- HTTPS via Caddy (Let's Encrypt)
- Input validation (prevent SQL injection via Zod)

### Future (Multi-User)
- JWT-based auth
- Per-user recipe collections
- Invite-only signup

---

## Performance Goals

- **Page load:** <1s on 3G connection
- **Recipe search:** <100ms for 1000 recipes
- **Cook mode:** Wake lock prevents screen sleep
- **Offline support:** Service worker (PWA) in v1

---

## Open Questions for Agents

1. **Recipe scraping:** Puppeteer vs Playwright vs Cheerio?
2. **Image storage:** Filesystem vs base64 in DB vs S3?
3. **Serving size scaling:** Fractional math library needed?
4. **Export format:** JSON, CSV, or standard (Recipe Schema.org)?

---

_Agents: Add Architecture Decision Records (ADRs) in `docs/` as you make significant choices._
