From 866e6e308fb4831e0d059c0e29152278b0785adb Mon Sep 17 00:00:00 2001 From: giles Date: Sun, 11 Jan 2026 09:58:43 +0000 Subject: [PATCH] Add README documentation for artdag-common library MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Document Jinja2 templating system with examples - Document authentication middleware (UserContext, cookie/header parsing) - Document content negotiation utilities - List all reusable template components - Document custom Jinja2 filters 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- README.md | 293 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 293 insertions(+) create mode 100644 README.md 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