Skip to content

feat: editorial CV pipeline — real HTML/PDF rendering, Lighthouse 100#3

Merged
CybotTM merged 4 commits into
mainfrom
redesign/editorial-cv
May 10, 2026
Merged

feat: editorial CV pipeline — real HTML/PDF rendering, Lighthouse 100#3
CybotTM merged 4 commits into
mainfrom
redesign/editorial-cv

Conversation

@CybotTM

@CybotTM CybotTM commented May 10, 2026

Copy link
Copy Markdown
Owner

Why

PDFs were broken (the CI workflow deliberately forced a fallback Python script that emitted a 1-page text-only PDF stub) and the HTML had none of the metadata/semantics needed for decent Lighthouse scores. This rebuild swaps the whole pipeline for a Python + Jinja2 + Markdown + WeasyPrint setup with an editorial visual treatment.

What changes

Pipeline (replaces forced-fallback build):

  • scripts/build.py — single entrypoint, reads src/cv-*.de.md (YAML front-matter + Markdown), renders through Jinja2 templates, writes public/cv-*.de.html and matching PDFs via WeasyPrint, plus public/index.html landing page.
  • templates/base.html.j2 + cv.html.j2 + index.html.j2 own the full <head>: title, description, canonical, OG profile tags, Twitter card, JSON-LD schema.org/Person, favicon, font preloads.
  • Drops the __missing_pandoc__ / __missing_weasyprint__ overrides; deletes scripts/render_html.py and scripts/render_pdf.py.
  • Drops committed public/* — it's now gitignored and built fresh in CI on every push/PR.

Visual (editorial / serif / typographic):

  • Self-hosted variable fonts: Source Serif 4 (body) + Inter (UI), both OFL, ~50 KB each.
  • Warm off-white page #fbfaf6, high-contrast text (>16:1), deep wine accent #7d2d3a (>5.7:1).
  • Section labels in small-caps Inter; bullet markers in accent color; hairline rules between sections; no boxes/cards.
  • Print stylesheet: A4 portrait, 18mm margins, page numbers via @page footer, break-inside: avoid on roles, link URLs revealed on print.

Accessibility / SEO / best-practices wins:

  • Skip-link, semantic <main> / <header> / <footer>, single <h1> per page, descriptive aria-label on PDF links, aria-current on active variant, rel=me on profile links.
  • <title>, <meta name=description>, theme-color, link rel=canonical, link rel=icon favicon.svg, link rel=alternate type=application/pdf.
  • Open Graph profile tags + JSON-LD Person for richer LinkedIn/Twitter/Google previews.

CI:

  • New lighthouse job runs treosh/lighthouse-ci-action@v12 on every PR with the built public/ as fixture.
  • .lighthouserc.json asserts a11y / BP / SEO at 1.0 and performance at 0.95 (will fail the PR check if any drops).
  • Pages deploy is unchanged otherwise.

Local Lighthouse results (desktop preset)

Verified on all three pages before opening this PR:

Page Performance A11y Best Practices SEO
index.html 100 100 100 100
cv-executive.de.html 100 100 100 100
cv-technical.de.html 100 100 100 100

Compare to the previous numbers (a11y 87, SEO 80, best-practices 96).

PDFs

Both PDFs now render through real WeasyPrint, not the 1-page text stub. ~36 KB each, 2 pages, with running headers, page numbers, and the same editorial typography as the screen version.

How to verify locally

sudo apt-get install -y libpango-1.0-0 libpangoft2-1.0-0 libharfbuzz0b libcairo2 libgdk-pixbuf-2.0-0
make install build serve            # http://localhost:8000
make lighthouse                     # writes lighthouse-*.html per page

Notes / trade-offs

  • Repo grew by ~200 KB of self-hosted fonts (4 woff2 files). In exchange we drop external CDN requests, get full CSP/best-practices score, and avoid Google Fonts privacy footprint.
  • public/ is no longer in git history going forward (already removed in this PR; old commits keep their copies — fine).

Replaces the deliberately-broken fallback renderers with a Python +
Jinja2 + Markdown + WeasyPrint pipeline. Drops committed public/ and
builds it fresh in CI.

Pipeline
- scripts/build.py reads src/cv-*.de.md (YAML front-matter + Markdown),
  renders through templates/, writes public/*.html and public/*.pdf,
  plus a public/index.html landing page.
- requirements.txt pins markdown, jinja2, weasyprint, pyyaml.
- Makefile gains install / build / serve / lighthouse / clean targets.

Templates
- templates/base.html.j2 emits a complete <head>: title, description,
  author, robots, theme-color, canonical, favicon, alternate (PDF),
  Open Graph profile tags, Twitter card, JSON-LD schema.org/Person,
  font preloads, stylesheet.
- templates/cv.html.j2 wraps body in semantic <header>/<main>/<footer>
  with a skip-link, descriptive aria-labels on PDF links, aria-current
  on the active variant link, rel=me on profile links.
- templates/index.html.j2 is a proper landing page with lede,
  versions list, contact section, all sectioned with aria-labelledby.

Visual (editorial / serif / typographic)
- Self-hosted variable fonts (OFL): Source Serif 4 (body), Inter (UI).
- Tokens: warm off-white page (#fbfaf6), high-contrast text (>16:1),
  deep wine accent (#7d2d3a, >5.7:1), hairline rules.
- Generous whitespace, single-column max 36em, small-caps section
  labels, bullet markers in accent color, descriptive link underlines.
- Print: A4, 18mm margins, page numbers via @page footer, page-break
  control on h2/h3/li, link URLs revealed after link text on print.

CI / Lighthouse
- .github/workflows/build.yml installs Python + WeasyPrint native deps,
  runs build.py, uploads public/ as Pages artifact for main, and as a
  workflow artifact for PRs.
- New 'lighthouse' job runs treosh/lighthouse-ci-action against the
  built artifact for every PR; .lighthouserc.json asserts a11y / BP /
  SEO at 1.0 and performance at 0.95.
- Pages deploy job is unchanged behavior, just decoupled.

Cleanup
- Drops scripts/render_html.py, scripts/render_pdf.py (the stub
  generators that produced the unstyled HTML and the 1-page text-only
  PDFs).
- Drops committed public/*.html and public/*.pdf (.gitignored now).
- Removes the PANDOC=__missing_pandoc__ WEASYPRINT=__missing_weasyprint__
  override that forced the fallbacks in CI.

Verified locally with Lighthouse desktop preset on all three pages
(index, executive, technical): performance 100, accessibility 100,
best-practices 100, seo 100.

Signed-off-by: Sebastian Mendel <info@sebastianmendel.de>
Copilot AI review requested due to automatic review settings May 10, 2026 14:54
Lighthouse rejects file:// URLs with INVALID_URL. The treosh action's
staticDistDir input spins up a local server for the public/ folder and
runs the audits against http://localhost:<port>/<path>.

Signed-off-by: Sebastian Mendel <info@sebastianmendel.de>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR replaces the prior “forced fallback” CV build with a Python-based pipeline that renders Markdown + YAML front-matter through Jinja2 templates into SEO/a11y-friendly HTML and real PDFs via WeasyPrint, and adds CI Lighthouse gating for PRs.

Changes:

  • Introduces scripts/build.py to generate public/ (HTML + PDF + landing page) from src/cv-*.de.md using Jinja2 + Markdown + WeasyPrint.
  • Adds new Jinja2 templates with richer <head> metadata (canonical/OG/Twitter/JSON-LD) and a redesigned editorial CSS with self-hosted fonts.
  • Updates CI to build via Python + system libs and run Lighthouse CI on PRs; stops committing public/ artifacts.

Reviewed changes

Copilot reviewed 16 out of 25 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
templates/index.html.j2 New landing page template listing variants + contact links.
templates/cv.html.j2 New CV page template with header nav and injected rendered Markdown body.
templates/base.html.j2 Shared <head> metadata + font preloads + skip-link wrapper.
src/cv-technical.de.md Adds YAML front-matter for technical variant metadata.
src/cv-executive.de.md Adds YAML front-matter for executive variant metadata.
scripts/render_pdf.py Removes legacy PDF stub generator.
scripts/render_html.py Removes legacy Markdown-to-HTML fallback renderer.
scripts/build.py New build entrypoint: parse front-matter, render templates, write HTML/PDF, copy assets.
requirements.txt Pins Python dependencies for the new pipeline.
README.md Documents new layout, build steps, Lighthouse, and publishing behavior.
public/index.html Removes committed generated artifact.
public/cv-technical.de.html Removes committed generated artifact.
public/cv-executive.de.html Removes committed generated artifact.
Makefile Replaces Pandoc/WeasyPrint CLI flow with venv + scripts/build.py and Lighthouse helper target.
assets/style.css Replaces styling with editorial screen/print CSS and self-hosted font usage.
assets/favicon.svg Adds SVG favicon asset.
.lighthouserc.json Adds Lighthouse CI configuration and assertions.
.gitignore Ignores public/, venv, and Lighthouse artifacts.
.github/workflows/build.yml Updates CI to use Python deps, build with scripts/build.py, and run Lighthouse on PRs.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread scripts/build.py Outdated
Comment thread templates/cv.html.j2 Outdated
Comment thread templates/index.html.j2
Comment thread .lighthouserc.json
CybotTM added 2 commits May 10, 2026 16:59
The treosh action's configPath input takes precedence over the
staticDistDir/urls inputs, so the action saw staticDistDir as null and
tried to load /index.html as an absolute URL (INVALID_URL).

Putting both into the .lighthouserc.json collect block keeps a single
source of truth that local 'lhci autorun' and the GH Actions step both
honor.

Signed-off-by: Sebastian Mendel <info@sebastianmendel.de>
- scripts/build.py: drop the leading '../' on the CV asset() prefix.
  Both index.html and cv-*.de.html sit at the root of public/ next to
  assets/, so plain 'assets/...' resolves correctly under any subpath
  (including the GitHub Pages /cv/ project URL). The '../assets/...'
  form happened to work locally because python http.server collapses
  '..' past root, but on Pages it would escape outside /cv/.

- templates/cv.html.j2 + templates/index.html.j2: outer <article>
  becomes <div class="cv">. The HTML spec forbids <main> as a
  descendant of <article>; in cv.html.j2 the body content is now
  wrapped in <article class="cv-body"> *inside* <main id="main">.

- .lighthouserc.json: switch performance assertion from 'warn' to
  'error' (warn does not fail the run, which contradicted the PR
  description). a11y/BP/SEO already at error.

Signed-off-by: Sebastian Mendel <info@sebastianmendel.de>
Copilot AI review requested due to automatic review settings May 10, 2026 15:04
@CybotTM
CybotTM merged commit 469c17b into main May 10, 2026
5 checks passed
@CybotTM
CybotTM deleted the redesign/editorial-cv branch May 10, 2026 15:06

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 25 changed files in this pull request and generated 2 comments.

Comment thread .lighthouserc.json
Comment on lines +6 to +8
"http://localhost/index.html",
"http://localhost/cv-executive.de.html",
"http://localhost/cv-technical.de.html"
Comment thread scripts/build.py
Comment on lines +205 to +208
favicon = ASSETS / "favicon.svg"
if favicon.exists():
shutil.copy2(favicon, PUBLIC / "favicon.svg")

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants