Files
jobtrackingapp/docs/architecture/cv-theme-engine.md
T
cesnimda 5203ddea72
CI and Deploy / test (pull_request) Failing after 3m0s
CI and Deploy / deploy (pull_request) Has been skipped
feat(cv): harden multi-page builder
Wrap pathological content, paginate oversized entries, measure A4 and Letter previews correctly, unify section ordering, and gate stored-output actions on saved state.
2026-08-15 14:09:51 +02:00

3.6 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: 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.