# Composable App Cookbook

> Deploy a full-stack application with multiple components using composable monorepo templates.

## Overview

This cookbook creates a full-stack application by composing components:

```
POST /project
    ↓
Creates: Monorepo skeleton with shared packages
    ↓
POST /projects/{id}/components (service)
    ↓
Adds: Go API backend with CI step
    ↓
POST /projects/{id}/components (app)
    ↓
Adds: React/Astro frontend with CI step
    ↓
Git push triggers Woodpecker CI
    ↓
CI builds and deploys all components to K8s
```

**Composable. Template-driven. CI auto-configured.**

---

## Prerequisites

### API Access
```bash
export RDEV_API_URL="https://rdev.masq-ops.orchard9.ai"
export RDEV_API_KEY="<your-api-key>"
```

### Infrastructure Required
- rdev-api running with embedded worker
- Gitea at https://git.threesix.ai
- Woodpecker CI at https://ci.threesix.ai

---

## Step 1: Create Project (Monorepo Skeleton)

```bash
curl -X POST "$RDEV_API_URL/project" \
  -H "X-API-Key: $RDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "taskapp",
    "description": "Task management application"
  }'
```

**Response:**
```json
{
  "data": {
    "project_id": "taskapp",
    "name": "taskapp",
    "domain": "xyz789ab.threesix.ai",
    "url": "https://xyz789ab.threesix.ai",
    "git": {
      "owner": "jordan",
      "name": "taskapp",
      "html_url": "https://git.threesix.ai/jordan/taskapp"
    }
  }
}
```

This creates:
```
taskapp/
├── CLAUDE.md                    # AI routing
├── README.md                    # Project docs
├── Procfile                     # Local dev (empty)
├── docker-compose.yml           # Postgres, Redis
├── go.work                      # Go workspace
├── .woodpecker.yml              # CI pipeline (template-provided)
├── .golangci.yml                # Go linting
├── scripts/                     # Discovery scripts
│   ├── discover.sh
│   ├── install.sh
│   ├── quality.sh
│   └── dev.sh
├── pkg/                         # Shared Go packages
│   ├── app/                     # Service bootstrapper
│   ├── middleware/              # HTTP middleware
│   ├── httpresponse/            # JSON responses
│   └── ...
├── services/                    # (empty, ready for components)
├── apps/                        # (empty, ready for components)
├── workers/                     # (empty, ready for components)
└── cli/                         # (empty, ready for components)
```

---

## Step 2: Add Backend Service

```bash
curl -X POST "$RDEV_API_URL/projects/taskapp/components" \
  -H "X-API-Key: $RDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "service",
    "name": "api",
    "template": "service"
  }'
```

**Response:**
```json
{
  "data": {
    "type": "service",
    "name": "api",
    "path": "services/api",
    "port": 8001,
    "template": "service"
  }
}
```

This adds:
```
services/api/
├── cmd/server/main.go           # Entry point using pkg/app
├── internal/
│   ├── api/routes.go            # Chi router setup
│   ├── api/handlers/health.go   # Health endpoints
│   └── config/config.go         # Configuration
├── migrations/                  # Database migrations
├── Makefile                     # Build targets
├── Dockerfile                   # Multi-stage Go build
├── component.yaml               # Port, dependencies
└── .env.example                 # Environment template
```

And updates:
- `.woodpecker.yml` - adds `build-api` step
- `Procfile` - adds `api: make -C services/api run`
- `go.work` - adds `use ./services/api`

---

## Step 3: Add Frontend App

```bash
curl -X POST "$RDEV_API_URL/projects/taskapp/components" \
  -H "X-API-Key: $RDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "app-react",
    "name": "dashboard"
  }'
```

**Response:**
```json
{
  "data": {
    "type": "app-react",
    "name": "dashboard",
    "path": "apps/dashboard",
    "port": 3001
  }
}
```

This adds:
```
apps/dashboard/
├── src/
│   ├── App.tsx
│   ├── main.tsx
│   └── components/
├── package.json
├── vite.config.ts
├── tsconfig.json
├── Dockerfile                   # Multi-stage Node build
└── component.yaml               # Port, dependencies
```

---

## Step 4: Customize with Claude (Optional)

Submit a build task to customize the components:

```bash
curl -X POST "$RDEV_API_URL/projects/taskapp/builds" \
  -H "X-API-Key: $RDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "Implement a task management system:\n\nBACKEND (services/api):\n- Add Task model with id, title, description, status, created_at\n- Endpoints: GET /api/tasks, POST /api/tasks, PATCH /api/tasks/{id}, DELETE /api/tasks/{id}\n- In-memory storage for now\n\nFRONTEND (apps/dashboard):\n- Task list with status badges\n- Add task form\n- Mark complete/delete buttons\n- Dark theme with Tailwind\n- Fetch from /api/tasks",
    "auto_commit": true,
    "auto_push": true
  }'
```

Monitor the build:
```bash
curl -s "$RDEV_API_URL/builds/{task_id}" \
  -H "X-API-Key: $RDEV_API_KEY" | jq '.data.status'
```

---

## Step 5: Deployment (CI-Driven)

Each component's CI step includes a `deploy-{name}` phase that runs `kubectl set image` on merge to `main`. Pushing code triggers the full build-and-deploy pipeline automatically.

To manually deploy a single component outside of CI:

```bash
# Example: deploy the api service with a specific image tag
kubectl set image deployment/taskapp-api \
  api=registry.threesix.ai/taskapp/api:<tag> \
  -n projects
```

---

## Step 6: Monitor CI Pipeline

```bash
curl -s "$RDEV_API_URL/projects/taskapp/pipelines" \
  -H "X-API-Key: $RDEV_API_KEY" | jq '.data[0]'
```

The pipeline builds both components in parallel then deploys.

---

## Step 7: Verify Deployment

```bash
# Check site is live
curl -I https://xyz789ab.threesix.ai

# Test API
curl https://xyz789ab.threesix.ai/api/tasks | jq .

# View the app
open https://xyz789ab.threesix.ai
```

---

## Adding More Components

### Add a Background Worker

```bash
curl -X POST "$RDEV_API_URL/projects/taskapp/components" \
  -H "X-API-Key: $RDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "worker",
    "name": "notifications",
    "template": "worker"
  }'
```

### Add a CLI Tool

```bash
curl -X POST "$RDEV_API_URL/projects/taskapp/components" \
  -H "X-API-Key: $RDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "cli",
    "name": "taskctl",
    "template": "cli"
  }'
```

### List All Components

```bash
curl -s "$RDEV_API_URL/projects/taskapp/components" \
  -H "X-API-Key: $RDEV_API_KEY" | jq '.data'
```

---

## Component Types

| Type | Directory | Stack | Default Port |
|------|-----------|-------|--------------|
| service | `services/` | go | 8001+ |
| worker | `workers/` | go | N/A |
| app-astro | `apps/` | astro | 3001+ |
| app-react | `apps/` | react | 3001+ |
| cli | `cli/` | go | N/A |

Ports auto-increment as you add components of the same type.

---

## Teardown

```bash
curl -X DELETE "$RDEV_API_URL/project/taskapp" \
  -H "X-API-Key: $RDEV_API_KEY"
```

Removes: DNS records, K8s deployments, project metadata. Gitea repo preserved.

---

## E2E Test Script

Run the full flow:
```bash
./cookbooks/scripts/composable-test.sh run my-test-app
```

Check status:
```bash
./cookbooks/scripts/composable-test.sh status my-test-app
```

Cleanup:
```bash
./cookbooks/scripts/composable-test.sh teardown my-test-app
```

---

## Architecture

```
┌─────────────────────────────────────────────────────────────────────┐
│                  Composable Full-Stack Deployment                   │
│                                                                     │
│  POST /project                                                      │
│       │                                                             │
│       └──► Creates monorepo skeleton with:                          │
│            - Shared pkg/ (8 packages)                               │
│            - .woodpecker.yml (base CI)                              │
│            - Discovery scripts                                      │
│                                                                     │
│  POST /projects/{id}/components (× N)                               │
│       │                                                             │
│       └──► For each component:                                      │
│            - Renders template to services/|apps/|workers/|cli/      │
│            - Inserts CI step into .woodpecker.yml                   │
│            - Updates Procfile, go.work                              │
│                                                                     │
│  POST /projects/{id}/builds (optional)                              │
│       │                                                             │
│       └──► Claude customizes existing components                    │
│                                                                     │
│  Git push → Woodpecker CI:                                          │
│       ├──► build-api (Kaniko → registry)                            │
│       ├──► deploy-api (kubectl set image)                           │
│       ├──► build-dashboard (Kaniko → registry)                      │
│       ├──► deploy-dashboard (kubectl set image)                     │
│       └──► verify (confirm deployments)                             │
│                                                                     │
│  Components live at https://{slug}.threesix.ai                      │
│       ├──► /api/* → services/api                                    │
│       └──► /* → apps/dashboard                                      │
└─────────────────────────────────────────────────────────────────────┘
```

---

## Troubleshooting

### Component addition fails
```bash
# Check project exists
curl -s "$RDEV_API_URL/project/taskapp" \
  -H "X-API-Key: $RDEV_API_KEY" | jq '.data'

# Check available component templates
curl -s "$RDEV_API_URL/templates/components" \
  -H "X-API-Key: $RDEV_API_KEY" | jq '.data.components'
```

### Build stuck in pending
```bash
# Check worker status
curl -s "$RDEV_API_URL/workers" -H "X-API-Key: $RDEV_API_KEY" | jq '.data.summary'
```

### Pipeline fails
```bash
# Get pipeline details
curl -s "$RDEV_API_URL/projects/taskapp/pipelines/1" \
  -H "X-API-Key: $RDEV_API_KEY" | jq '.data'

# Check Woodpecker UI
open https://ci.threesix.ai/jordan/taskapp
```

### Deployment not updating
```bash
# Check K8s pods
kubectl get pods -n projects -l app=taskapp

# Check deployment events
kubectl describe deployment taskapp-api -n projects
```

---

## Related

- [Landing Page Cookbook](./landing-page.md) - Simple single-component site
- [Composable Monorepo Guide](../.claude/guides/services/composable-monorepo.md)
- [Component Templates](../.claude/guides/services/templates.md)
