cv-builder.md (variant model, rendering pipeline, API, builder workflow, extension points, known deep-link limitation) + cv-theme-engine.md (how a theme is data and how to add one). Roadmap Phase 4 marked foundation-shipped with the remaining polish itemised. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2.8 KiB
CV Theme Engine
Phase 4 (2026-07-18). How CV themes work and how to add one. Companion to
docs/architecture/cv-builder.md. Verified against code.
The rule
A theme is data, not code. There is exactly one renderer — ThemedCvRenderer
(JobTrackerApi/Services/ThemedCvRenderer.cs) — with a single render path. Every visual difference
between themes is expressed by the fields of a CvTheme record (Models/CvTheme.cs). Adding a theme
never touches the renderer.
This replaces the previous approach (CvTemplateRenderer, one hand-written HTML method per template),
which is retained only for the legacy tailored-draft flow and is not used by the builder.
CvTheme fields (the knobs)
| Group | Fields |
|---|---|
| Identity | Id, Name, Category, Description |
| Layout | Layout (single | sidebar-left | sidebar-right | header-band), SidebarWidthMm, SidebarSections |
| Palette | Accent, Ink, Muted, Line, Paper, SidebarBg, SidebarInk, HeadingColor |
| Typography | HeadingFont, BodyFont, NameSizePt, HeadingSizePt, BodySizePt, LineHeight |
| Spacing | PageMarginMm, SectionGapMm, EntryGapMm |
| Styling | HeaderStyle (plain|band|centered|kicker), HeadingStyle (caps-rule|underline|plain|bar), PhotoShape (none|square|rounded|circle), DefaultIcons |
The renderer computes CSS variables from these plus the variant's runtime overrides (accent, fonts,
density, page size, photo, icons) and picks one of the layout wrappers. SidebarSections decides which
section keys move to the sidebar for the two-column layouts.
Adding a theme
- Append one
CvTheme { … }toCvThemeCatalog.Themes(Models/CvTheme.cs). Only override the fields that differ from the defaults. - Nothing else. It appears in
GET /api/cv/themes, the Customize tab picker, and renders.
Add a CvBuilderTests.Every_catalog_theme_renders_valid_html already loops the whole catalog, so a new
theme is smoke-tested automatically.
Shipped themes (8)
modern (header-band), minimal (single), executive (single, serif), technical (sidebar-left,
dense), ats-classic (single, no graphics), nordic (sidebar-right), elegant (single, editorial),
creative (sidebar-left, bold).
Deliberately not here (yet)
- Rich-text inside bullets. The render model treats bullets as plain strings, so the builder's text areas are plain text. Adding bold/italic/links means the render model carries inline runs and the renderer emits them — a self-contained extension, no theme changes.
- PDF page numbers.
ShowPageNumbersis carried through settings but the actual footer is a PlaywrightfooterTemplateconcern (the PDF exporter), not CSS paged-media (Chromium's margin boxes are unreliable). Wire it inPlaywrightCvPdfExporterwhen needed.