Files
cesnimda ce76046a29 feat: complete release readiness work
- consolidate API ownership and remove dead vendor code

- add Stripe billing, learning paths, and public CV hardening

- add migration, recovery, security, audit, and browser gates
2026-07-31 16:54:16 +02:00

3.4 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 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

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

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: the renderer emits break-inside: avoid on entries, break-after: avoid on section headings, and widow/orphan control, so entries don't split across a page in the Playwright PDF pass.

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.