Files
jobtrackingapp/docs/architecture/cv-theme-engine.md
T
2026-08-24 20:21:23 +02:00

3.9 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 (JobTrackerApi/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 from these plus normalized variant overrides: printable palette, curated fonts, body/heading size, line height, spacing, page size, layout, sidebar width/content, header and heading treatments, skills presentation, photo and icons. SidebarSections decides which section keys move to the sidebar for the two-column layouts. Changing a template or override never transforms the structured career content.

Adding a theme

  1. Append one CvTheme { … } to CvThemeCatalog.Themes (JobTrackerApi/Models/CvTheme.cs). Only override the fields that differ from the defaults.
  2. Nothing else. It appears in GET /api/cv/themes, the Template tab and dashboard create flow. GET /api/cv/themes/{id}/preview renders isolated in-memory sample data for a real visual thumbnail; preview data is never persisted.

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).

Rich text, ATS, and print (2026-07-18)

  • Rich-text bullets are shipped: bullets/summary support **bold**, *italic*, __underline__, [text](url) via ThemedCvRenderer.Inline — everything is HTML-escaped first, then a whitelist is re-applied, so no user tag survives. Storage stays plain text (no HTML, no sanitiser dependency).
  • ATS-friendliness is a data flag: CvTheme.AtsFriendly (set on the single-column themes), surfaced in GET /api/cv/themes and badged in the Customize tab. Two-column (sidebar) themes are not flagged, as sidebar layouts can trip naive resume parsers.
  • Print quality: ordinary entries use break-inside: avoid-page; content classified as too tall for a page is allowed to flow between bullets/paragraphs so Chromium cannot clip an unsplittable block. Section headings avoid a following break, list items carry widow/orphan rules, and long names/titles/contact values/URLs/tags wrap within minmax(0, …) columns. Typography is not scaled down to mask overflow.

Deliberately not here (yet)

  • PDF page numbers. ShowPageNumbers is carried through settings but the actual footer is a Playwright footerTemplate concern (the PDF exporter), not CSS paged-media (Chromium's margin boxes are unreliable). Wire it in PlaywrightCvPdfExporter when needed.