diff --git a/README.md b/README.md
new file mode 100644
index 0000000..73d1dd5
--- /dev/null
+++ b/README.md
@@ -0,0 +1,293 @@
+# artdag-common
+
+Shared components for Art-DAG L1 (celery) and L2 (activity-pub) servers.
+
+## Features
+
+- **Jinja2 Templating**: Unified template environment with shared base templates
+- **Reusable Components**: Cards, tables, pagination, DAG visualization, media preview
+- **Authentication Middleware**: Cookie and JWT token parsing
+- **Content Negotiation**: HTML/JSON/ActivityPub format detection
+- **Utility Functions**: Hash truncation, file size formatting, status colors
+
+## Installation
+
+```bash
+pip install -e /path/to/artdag-common
+
+# Or add to requirements.txt
+-e file:../common
+```
+
+## Quick Start
+
+```python
+from fastapi import FastAPI, Request
+from artdag_common import create_jinja_env, render
+
+app = FastAPI()
+
+# Initialize templates with app-specific directory
+templates = create_jinja_env("app/templates")
+
+@app.get("/")
+async def home(request: Request):
+ return render(templates, "home.html", request, title="Home")
+```
+
+## Package Structure
+
+```
+artdag_common/
+├── __init__.py # Package exports
+├── constants.py # CDN URLs, colors, configs
+├── rendering.py # Jinja2 environment and helpers
+├── middleware/
+│ ├── auth.py # Authentication utilities
+│ └── content_negotiation.py # Accept header parsing
+├── models/
+│ ├── requests.py # Shared request models
+│ └── responses.py # Shared response models
+├── utils/
+│ ├── formatting.py # Text/date formatting
+│ ├── media.py # Media type detection
+│ └── pagination.py # Pagination helpers
+└── templates/
+ ├── base.html # Base layout template
+ └── components/
+ ├── badge.html # Status/type badges
+ ├── card.html # Info cards
+ ├── dag.html # Cytoscape DAG visualization
+ ├── media_preview.html # Video/image/audio preview
+ ├── pagination.html # HTMX pagination
+ └── table.html # Styled tables
+```
+
+## Jinja2 Templates
+
+### Base Template
+
+The `base.html` template provides:
+- Dark theme with Tailwind CSS
+- HTMX integration
+- Navigation slot
+- Content block
+- Optional Cytoscape.js block
+
+```html
+{% extends "base.html" %}
+
+{% block title %}My Page{% endblock %}
+
+{% block content %}
+
Hello World
+{% endblock %}
+```
+
+### Reusable Components
+
+#### Card
+
+```html
+{% include "components/card.html" %}
+```
+
+```html
+
+
+ {% block card_title %}Title{% endblock %}
+ {% block card_content %}Content{% endblock %}
+
+```
+
+#### Badge
+
+Status and type badges with appropriate colors:
+
+```html
+{% from "components/badge.html" import status_badge, type_badge %}
+
+{{ status_badge("completed") }}
+{{ status_badge("failed") }}
+{{ type_badge("video") }}
+```
+
+#### DAG Visualization
+
+Interactive Cytoscape.js graph:
+
+```html
+{% include "components/dag.html" %}
+```
+
+Requires passing `nodes` and `edges` data to template context.
+
+#### Media Preview
+
+Responsive media preview with format detection:
+
+```html
+{% include "components/media_preview.html" %}
+```
+
+Supports video, audio, and image formats.
+
+#### Pagination
+
+HTMX-powered infinite scroll pagination:
+
+```html
+{% include "components/pagination.html" %}
+```
+
+## Template Rendering
+
+### Full Page Render
+
+```python
+from artdag_common import render
+
+@app.get("/runs/{run_id}")
+async def run_detail(run_id: str, request: Request):
+ run = get_run(run_id)
+ return render(templates, "runs/detail.html", request, run=run)
+```
+
+### Fragment Render (HTMX)
+
+```python
+from artdag_common import render_fragment
+
+@app.get("/runs/{run_id}/status")
+async def run_status_fragment(run_id: str):
+ run = get_run(run_id)
+ html = render_fragment(templates, "components/status.html", status=run.status)
+ return HTMLResponse(html)
+```
+
+## Authentication Middleware
+
+### UserContext
+
+```python
+from artdag_common.middleware.auth import UserContext, get_user_from_cookie
+
+@app.get("/profile")
+async def profile(request: Request):
+ user = get_user_from_cookie(request)
+ if not user:
+ return RedirectResponse("/login")
+ return {"username": user.username, "actor_id": user.actor_id}
+```
+
+### Token Parsing
+
+```python
+from artdag_common.middleware.auth import get_user_from_header, decode_jwt_claims
+
+@app.get("/api/me")
+async def api_me(request: Request):
+ user = get_user_from_header(request)
+ if not user:
+ raise HTTPException(401, "Not authenticated")
+ return {"user": user.username}
+```
+
+## Content Negotiation
+
+Detect what response format the client wants:
+
+```python
+from artdag_common.middleware.content_negotiation import wants_html, wants_json, wants_activity_json
+
+@app.get("/users/{username}")
+async def user_profile(username: str, request: Request):
+ user = get_user(username)
+
+ if wants_activity_json(request):
+ return ActivityPubActor(user)
+ elif wants_json(request):
+ return user.dict()
+ else:
+ return render(templates, "users/profile.html", request, user=user)
+```
+
+## Constants
+
+### CDN URLs
+
+```python
+from artdag_common import TAILWIND_CDN, HTMX_CDN, CYTOSCAPE_CDN
+
+# Available in templates as globals:
+# {{ TAILWIND_CDN }}
+# {{ HTMX_CDN }}
+# {{ CYTOSCAPE_CDN }}
+```
+
+### Node Colors
+
+```python
+from artdag_common import NODE_COLORS
+
+# {
+# "SOURCE": "#3b82f6", # Blue
+# "EFFECT": "#22c55e", # Green
+# "OUTPUT": "#a855f7", # Purple
+# "ANALYSIS": "#f59e0b", # Amber
+# "_LIST": "#6366f1", # Indigo
+# "default": "#6b7280", # Gray
+# }
+```
+
+### Status Colors
+
+```python
+STATUS_COLORS = {
+ "completed": "bg-green-600",
+ "cached": "bg-blue-600",
+ "running": "bg-yellow-600",
+ "pending": "bg-gray-600",
+ "failed": "bg-red-600",
+}
+```
+
+## Custom Jinja2 Filters
+
+The following filters are available in all templates:
+
+| Filter | Usage | Description |
+|--------|-------|-------------|
+| `truncate_hash` | `{{ hash\|truncate_hash }}` | Shorten hash to 16 chars with ellipsis |
+| `format_size` | `{{ bytes\|format_size }}` | Format bytes as KB/MB/GB |
+| `status_color` | `{{ status\|status_color }}` | Get Tailwind class for status |
+
+Example:
+
+```html
+
+ {{ run.status }}
+
+
+{{ content_hash|truncate_hash }}
+
+{{ file_size|format_size }}
+```
+
+## Development
+
+```bash
+cd /root/art-dag/common
+
+# Install in development mode
+pip install -e .
+
+# Run tests
+pytest
+```
+
+## Dependencies
+
+- `fastapi>=0.100.0` - Web framework
+- `jinja2>=3.1.0` - Templating engine
+- `pydantic>=2.0.0` - Data validation