# Canvas Service Architecture

## Overview
Canvas is an HTML tool showcase platform at `canvas.ephron.ren`. Unlike Blog (markdown files) and Prompt (SQLite), Canvas uses a **file-based storage** system.

## Storage Structure
```
content/pages/
├── meta.json          # Metadata for all pages
├── slug-1.html        # HTML content files
├── slug-2.html
└── .gitkeep
```

- Each canvas is a standalone HTML file (filename = slug)
- `meta.json` stores all metadata in a single JSON file
- No database involved

## meta.json Format
```json
{
  "pages": {
    "hermes-agent-ai": {
      "title": "Hermes Agent — 自我进化的 AI 智能体",
      "description": "介绍页",
      "source": "other",
      "category": "tool",
      "tags": ["AI", "Agent"],
      "draft": false,
      "created_at": "2026-05-06T00:00:00",
      "updated_at": "2026-05-06T00:00:00",
      "created_by": "svc_xxx",
      "updated_by": "svc_xxx",
      "ownership_type": "service",
      "handoff_to_human": false,
      "views": 42
    }
  }
}
```

## Valid Categories (hardcoded)
```python
CANVAS_CATEGORIES = [
    ("tool", "🔧 实用工具"),
    ("game", "🎮 小游戏"),
    ("visual", "🎨 可视化"),
    ("learning", "📚 学习教育"),
    ("productivity", "⚡ 效率提升"),
    ("fun", "🎉 趣味娱乐"),
    ("other", "📦 其他"),
]
```

Source: `canvas/src/services/canvas.py` lines 624-632.

## Route Structure
| Route file | Auth | Purpose |
|------------|------|---------|
| `pages.py` | None (public) | Homepage list, view page, raw HTML |
| `service_api.py` | Bearer Token | Draft CRUD only |
| `admin.py` | Cookie (ephron_auth) | Full CRUD + publish toggle |

## Key Endpoints

### Public (no auth)
- `GET /` — Homepage, shows non-draft canvases grouped by category
- `GET /view/{slug}` — View page with iframe embedding `/raw/{slug}`
- `GET /raw/{slug}` — Raw HTML content (iframe src target)

### Service API (Bearer Token)
- `GET /api/service/canvas` — List own drafts only
- `GET /api/service/canvas/{slug}` — Get own draft
- `POST /api/service/canvas` — Create draft
- `PATCH /api/service/canvas/{slug}` — Update own draft (fails if published)
- `DELETE /api/service/canvas/{slug}` — Delete own draft

### Admin (Cookie auth)
- `GET /admin` — Admin dashboard with all canvases (incl. drafts)
- `GET /admin/new` — New canvas form
- `POST /admin/new` — Create canvas
- `GET /admin/edit/{slug}` — Edit form
- `POST /admin/edit/{slug}` — Save edits
- `POST /admin/toggle-draft` — Toggle draft status (publish/unpublish)
- `POST /admin/delete` — Delete canvas

## Auth Flow (Admin)
1. Login at `auth.ephron.ren/api/login` with form data (`username` + `password`)
2. Response sets `ephron_auth` cookie on `.ephron.ren` domain
3. Admin routes check cookie via `is_authenticated()`
4. CSRF token required for all POST forms (generated per-request)

## iframe Embedding Issue
The `/view/{slug}` page uses `<iframe src="/raw/{slug}">` to display canvas content.
The shared `security_headers.py` middleware blocks iframe embedding with `X-Frame-Options: DENY` and `frame-ancestors 'none'`.

**Solution**: Override headers in the `/raw/{slug}` route handler:
```python
headers={
    "X-Frame-Options": "SAMEORIGIN",
    "Content-Security-Policy": "...frame-ancestors 'self'...",
}
```

## Source Files
- `canvas/src/routes/pages.py` — Public page routes
- `canvas/src/routes/service_api.py` — Service API (Bearer Token)
- `canvas/src/routes/admin.py` — Admin routes (Cookie auth)
- `canvas/src/services/canvas.py` — Core service (storage, CRUD, categories)
- `canvas/src/services/auth.py` — Auth helpers
- `canvas/src/config.py` — Config (CONTENT_DIR, COOKIE_NAME, etc.)
- `shared/security_headers.py` — Shared security middleware
