# Story 2.4: HTML Formatting with Multiple Templates
## Status
Completed
## Story
**As a developer**, I want a module that takes the generated text content and formats it into a standard HTML file using one of a few predefined CSS templates, assigning one template per bucket/subdomain, so that all deployed content has a consistent look and feel per site.
## Acceptance Criteria
- A directory of multiple, predefined HTML/CSS templates exists.
- The master JSON configuration file maps a specific template to each deployment target (e.g., S3 bucket, subdomain).
- A function accepts the generated content and a target identifier (e.g., bucket name).
- The function correctly selects and applies the appropriate template based on the configuration mapping.
- The content is structured into a valid HTML document with the selected CSS.
- The final HTML content is stored and associated with the project in the database.
## Dependencies
- **Story 2.5**: Deployment Target Assignment must run before this story to set `site_deployment_id` on GeneratedContent
- If `site_deployment_id` is null, a random template will be selected
## Tasks / Subtasks
### 1. Create Template Infrastructure
**Effort:** 3 story points
- [x] Create template file structure under `src/templating/templates/`
- Basic template (default)
- Modern template
- Classic template
- Minimal template
- [x] Each template should include:
- HTML structure with placeholders for title, meta, content
- Embedded or inline CSS for styling
- Responsive design (mobile-friendly)
- SEO-friendly structure (proper heading hierarchy, meta tags)
### 2. Implement Template Loading Service
**Effort:** 3 story points
- [x] Implement `TemplateService` class in `src/templating/service.py`
- [x] Add `load_template(template_name: str)` method that reads template file
- [x] Add `get_available_templates()` method that lists all templates
- [x] Handle template file not found errors gracefully with fallback to default
- [x] Cache loaded templates in memory for performance
### 3. Implement Template Selection Logic
**Effort:** 2 story points
- [x] Add `select_template_for_content(site_deployment_id: Optional[int])` method
- [x] If `site_deployment_id` exists:
- Query SiteDeployment table for custom_hostname
- Check `master.config.json` templates.mappings for hostname
- If mapping exists, use it
- If no mapping, randomly select template and save to config
- [x] If `site_deployment_id` is null: randomly select template
- [x] Return template name
### 4. Implement Content Formatting
**Effort:** 5 story points
- [x] Create `format_content(content: str, title: str, meta_description: str, template_name: str)` method
- [x] Parse HTML content and extract components
- [x] Replace template placeholders with actual content
- [x] Ensure proper escaping of HTML entities where needed
- [x] Validate output is well-formed HTML
- [x] Return formatted HTML string
### 5. Database Integration
**Effort:** 2 story points
- [x] Add `formatted_html` field to `GeneratedContent` model (Text type, nullable)
- [x] Add `template_used` field to `GeneratedContent` model (String(50), nullable)
- [x] Add `site_deployment_id` field to `GeneratedContent` model (FK to site_deployments, nullable, indexed)
- [x] Create database migration script
- [x] Update repository to save formatted HTML and template_used alongside raw content
### 6. Integration with Content Generation Flow
**Effort:** 2 story points
- [x] Update `src/generation/service.py` to call template service after content generation
- [x] Template service reads `site_deployment_id` from GeneratedContent
- [x] Store formatted HTML and template_used in database
- [x] Handle template formatting errors without breaking content generation
### 7. Unit Tests
**Effort:** 3 story points
- [x] Test template loading with valid and invalid names
- [x] Test template selection with site_deployment_id present
- [x] Test template selection with site_deployment_id null (random)
- [x] Test content formatting with different templates
- [x] Test fallback behavior when template not found
- [x] Test error handling for malformed templates
- [x] Achieve >80% code coverage for templating module
### 8. Integration Tests
**Effort:** 2 story points
- [x] Test end-to-end flow: content generation → template application → database storage
- [x] Test with site_deployment_id assigned (consistent template per site)
- [x] Test with site_deployment_id null (random template)
- [x] Verify formatted HTML is valid and renders correctly
- [x] Test new site gets random template assigned and persisted to config
## Dev Notes
### Current State
- `master.config.json` already has templates section with mappings (lines 52-59)
- `src/templating/service.py` exists but is empty (only 2 lines)
- `src/templating/templates/` directory exists but only contains `__init__.py`
- `GeneratedContent` model stores raw content in Text field but no formatted HTML field yet
### Dependencies
- Story 2.2/2.3: Content must be generated before it can be formatted
- Story 2.5: Deployment target assignment (optional - defaults to random if not assigned)
- Configuration system: Uses existing master.config.json structure
### Technical Decisions
1. **Template format:** Jinja2 or simple string replacement (to be decided during implementation)
2. **CSS approach:** Embedded `