|
| 1 | +# OpenAdapt Viewer Architecture |
| 2 | + |
| 3 | +This document describes the architecture of the openadapt-viewer package for LLM assistants and developers. |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +openadapt-viewer generates standalone HTML files for visualizing ML training results, benchmark evaluations, and capture recordings. The architecture prioritizes: |
| 8 | + |
| 9 | +1. **Maintainability** - Small, focused files that fit in LLM context windows |
| 10 | +2. **Separation of concerns** - Data loading, processing, and presentation are separate |
| 11 | +3. **Standalone capability** - Generated HTML files work offline without a server |
| 12 | +4. **No build step** - CDN-loaded libraries, no webpack/vite required |
| 13 | + |
| 14 | +## Technology Stack |
| 15 | + |
| 16 | +| Layer | Technology | Rationale | |
| 17 | +|-------|------------|-----------| |
| 18 | +| Data Processing | Pure Python + Pydantic | Type-safe, testable | |
| 19 | +| HTML Structure | Jinja2 templates | Industry standard, well-understood | |
| 20 | +| Visualization | Plotly | Best standalone export support | |
| 21 | +| Styling | Tailwind CSS (CDN) | Utility-first, no build step | |
| 22 | +| Interactivity | Alpine.js (CDN) | Lightweight (~15KB), declarative | |
| 23 | + |
| 24 | +## Directory Structure |
| 25 | + |
| 26 | +``` |
| 27 | +openadapt-viewer/ |
| 28 | +├── pyproject.toml |
| 29 | +├── README.md |
| 30 | +├── ARCHITECTURE.md # This file |
| 31 | +├── src/ |
| 32 | +│ └── openadapt_viewer/ |
| 33 | +│ ├── __init__.py # Package exports |
| 34 | +│ ├── cli.py # CLI entry point |
| 35 | +│ │ |
| 36 | +│ ├── core/ # Shared utilities |
| 37 | +│ │ ├── __init__.py |
| 38 | +│ │ ├── types.py # Pydantic models, type definitions |
| 39 | +│ │ ├── data_loader.py # Common data loading utilities |
| 40 | +│ │ └── html_builder.py # Jinja2 environment setup |
| 41 | +│ │ |
| 42 | +│ ├── templates/ # Jinja2 templates |
| 43 | +│ │ ├── base.html # Base template with CDN imports |
| 44 | +│ │ └── components/ # Reusable HTML components |
| 45 | +│ │ ├── header.html |
| 46 | +│ │ └── navigation.html |
| 47 | +│ │ |
| 48 | +│ └── viewers/ # Vertical slices by viewer type |
| 49 | +│ ├── __init__.py |
| 50 | +│ └── benchmark/ # Benchmark viewer |
| 51 | +│ ├── __init__.py |
| 52 | +│ ├── data.py # Data models and loading |
| 53 | +│ └── generator.py # HTML generation logic |
| 54 | +``` |
| 55 | + |
| 56 | +## Key Design Patterns |
| 57 | + |
| 58 | +### 1. Vertical Slice Architecture |
| 59 | + |
| 60 | +Each viewer type (benchmark, training, recording) is a self-contained module with: |
| 61 | +- **data.py** - Pydantic models and data loading functions |
| 62 | +- **generator.py** - HTML generation logic |
| 63 | +- **templates/** (optional) - Viewer-specific templates |
| 64 | + |
| 65 | +This allows LLMs to understand and modify one viewer without loading the entire codebase. |
| 66 | + |
| 67 | +### 2. Template Inheritance |
| 68 | + |
| 69 | +All templates inherit from `base.html`, which provides: |
| 70 | +- CDN imports for Tailwind CSS, Alpine.js, and Plotly |
| 71 | +- Common header and navigation components |
| 72 | +- Dark mode support |
| 73 | +- Responsive layout |
| 74 | + |
| 75 | +```html |
| 76 | +{% extends "base.html" %} |
| 77 | +{% block title %}My Page{% endblock %} |
| 78 | +{% block content %} |
| 79 | + <h1>Page content here</h1> |
| 80 | +{% endblock %} |
| 81 | +``` |
| 82 | + |
| 83 | +### 3. Data/Presentation Separation |
| 84 | + |
| 85 | +Data loading and HTML generation are strictly separated: |
| 86 | + |
| 87 | +```python |
| 88 | +# data.py - Pure data operations |
| 89 | +from pydantic import BaseModel |
| 90 | + |
| 91 | +class BenchmarkTask(BaseModel): |
| 92 | + task_id: str |
| 93 | + status: str |
| 94 | + metrics: dict |
| 95 | + |
| 96 | +def load_benchmark_data(path: str) -> list[BenchmarkTask]: |
| 97 | + # Load and validate data, no HTML concerns |
| 98 | + ... |
| 99 | +``` |
| 100 | + |
| 101 | +```python |
| 102 | +# generator.py - HTML generation only |
| 103 | +from .data import load_benchmark_data |
| 104 | + |
| 105 | +def generate_benchmark_html(data_path: str, output_path: str) -> None: |
| 106 | + tasks = load_benchmark_data(data_path) |
| 107 | + # Render templates with data |
| 108 | + ... |
| 109 | +``` |
| 110 | + |
| 111 | +### 4. Standalone HTML Generation |
| 112 | + |
| 113 | +Generated HTML files are fully self-contained: |
| 114 | +- Plotly.js can be embedded or loaded from CDN |
| 115 | +- Data is embedded as inline JSON |
| 116 | +- No external dependencies required for viewing |
| 117 | + |
| 118 | +```python |
| 119 | +# CDN mode (smaller file, requires internet) |
| 120 | +fig.to_html(include_plotlyjs='cdn') |
| 121 | + |
| 122 | +# Standalone mode (larger file, works offline) |
| 123 | +fig.to_html(include_plotlyjs=True) |
| 124 | +``` |
| 125 | + |
| 126 | +## File Size Guidelines |
| 127 | + |
| 128 | +To maintain LLM-friendliness: |
| 129 | +- Keep files under **500 lines** |
| 130 | +- One responsibility per file |
| 131 | +- Use type hints throughout |
| 132 | +- Add docstrings explaining intent, not mechanics |
| 133 | + |
| 134 | +## Adding a New Viewer |
| 135 | + |
| 136 | +1. Create a new directory under `viewers/`: |
| 137 | + ``` |
| 138 | + viewers/myviewer/ |
| 139 | + ├── __init__.py |
| 140 | + ├── data.py |
| 141 | + └── generator.py |
| 142 | + ``` |
| 143 | + |
| 144 | +2. Define Pydantic models in `data.py` |
| 145 | + |
| 146 | +3. Implement generation logic in `generator.py` |
| 147 | + |
| 148 | +4. Add CLI command in `cli.py` |
| 149 | + |
| 150 | +5. Update this document |
| 151 | + |
| 152 | +## CDN Resources |
| 153 | + |
| 154 | +The following CDN resources are loaded in `base.html`: |
| 155 | + |
| 156 | +| Library | CDN URL | Version | Size | |
| 157 | +|---------|---------|---------|------| |
| 158 | +| Tailwind CSS | cdn.tailwindcss.com | 3.x | ~100KB (JIT) | |
| 159 | +| Alpine.js | cdn.jsdelivr.net/npm/alpinejs | 3.x | ~15KB | |
| 160 | +| Plotly.js | cdn.plot.ly/plotly-2.32.0.min.js | 2.32.0 | ~3.5MB | |
| 161 | + |
| 162 | +For offline/standalone mode, Plotly.js is embedded directly in the HTML. |
| 163 | + |
| 164 | +## Testing |
| 165 | + |
| 166 | +Run tests with: |
| 167 | +```bash |
| 168 | +uv run pytest |
| 169 | +``` |
| 170 | + |
| 171 | +Test files should mirror the source structure: |
| 172 | +``` |
| 173 | +tests/ |
| 174 | +├── test_core/ |
| 175 | +│ ├── test_types.py |
| 176 | +│ ├── test_data_loader.py |
| 177 | +│ └── test_html_builder.py |
| 178 | +└── test_viewers/ |
| 179 | + └── test_benchmark/ |
| 180 | + ├── test_data.py |
| 181 | + └── test_generator.py |
| 182 | +``` |
| 183 | + |
| 184 | +## Related Projects |
| 185 | + |
| 186 | +- **openadapt-ml** - ML training pipeline (source of training data) |
| 187 | +- **openadapt-evals** - Benchmark evaluation infrastructure |
| 188 | +- **openadapt-capture** - Recording capture tool |
0 commit comments