Skip to content

Handover: publishpipe CV Tool — 2026-04-13

Branch

claude/plan-cv-tool-sokBV on SvenRelijveld1995/portfolio All work is committed and pushed. Working tree is clean.


What was built

A full pipeline that generates PDF CVs from Markdown source files using publishpipe (thesethtruth/publishpipe), covering:

Layer Description
Static build cv/build.ts renders both English and Dutch CVs to cv/dist/
Runtime API GET /api/cv/generate?lang=english\|nederlands — feature-flag gated
Feature flag ENABLE_CV_GENERATION=false env var; frontend polls /api/cv/status
Frontend cv-download-dialog.tsx shows generate buttons only when flag is on
E2E tests e2e/cv-download.spec.ts covers flag-off, flag-on, generate, download

The existing static download flow (/api/download-cv, PDFs in client/public/) is untouched — publishpipe is purely additive.


Key files

File Role
cv/build.ts Build script (use this, not publishpipe CLI directly)
cv/package.json Bun workspace; scripts call build.ts
cv/content/english.md Static English CV source
cv/content/dutch.md Static Dutch CV source
cv/templates/english.md.j2 Jinja2 template for API runtime generation
cv/templates/dutch.md.j2 Jinja2 template (Dutch)
cv/dist/ Generated PDFs (gitignored output directory)
api/main.py generate_cv endpoint + /api/cv/status + Jinja2 rendering
client/src/components/cv-download-dialog.tsx Frontend dialog
e2e/cv-download.spec.ts Playwright E2E tests
Dockerfile.backend Multi-stage: bun-stage installs cv/node_modules
.env.example Documents ENABLE_CV_GENERATION=false
docs/planning/cv-generation.md Full 6-phase implementation plan

How to build CVs locally

bash
cd cv
bun install            # first time only
PUPPETEER_SKIP_DOWNLOAD=true \
PUPPETEER_EXECUTABLE_PATH=/path/to/chromium \
  bun run build        # both languages → cv/dist/
  bun run build:en     # English only
  bun run build:nl     # Dutch only

On Linux without internet, Playwright's bundled Chromium works:

bash
PUPPETEER_EXECUTABLE_PATH=/opt/pw-browsers/chromium-1194/chrome-linux/chrome

build.ts automatically strips Google Fonts <link> elements and passes --no-sandbox to Chromium — both are required in sandboxed/root environments.


Known issues fixed in this session

Problem Root cause Fix
18 pages (1 job per page) --- in publishpipe markdown = forced page break Removed all --- between job entries
Duplicate header on page 1 publishpipe renders frontmatter as header; body also had # Sven Relijveld h1 Removed h1 + bold contact line from markdown body
Chrome hangs indefinitely waitForNetworkIdle never resolves; Google Fonts <link rel="preconnect"> creates pending connections build.ts strips those links before calling pagedjs-cli
Chrome crashes as root pagedjs-cli doesn't pass --no-sandbox build.ts calls pagedjs-cli directly with --no-sandbox,--disable-setuid-sandbox

Current PDF output

Both CVs render to 7 pages in ~120ms. Structure: header (from frontmatter) → Profile → Work Experience (13 entries) → Skills → Certifications → Awards & Recognition. No duplicate header. No spurious page breaks between jobs.


What still needs attention

1. Promote generated PDFs to client/public/

The E2E tests and /api/download-cv still serve the old hand-crafted PDFs from client/public/. To replace them with publishpipe-generated ones:

bash
# After verifying the output looks right:
cd cv && bun run build
cp dist/CV_Sven_Relijveld_English.pdf \
   "../client/public/CV Sven Relijveld 2026-04-13_English.pdf"
cp dist/CV_Sven_Relijveld_Nederlands.pdf \
   "../client/public/CV Sven Relijveld 2026-04-13_Nederlands.pdf"

Then update the filename references in api/main.py (STATIC_PDF_DIR) and any hardcoded filenames in the frontend or E2E tests.

2. CI workflow

.github/workflows/generate-cv.yml exists but references npm run cv:generate (old root package.json script). Update it to:

yaml
- run: cd cv && bun install --frozen-lockfile
- run: cd cv && bun run build

And set PUPPETEER_EXECUTABLE_PATH to the Chromium installed by the CI runner.

3. API runtime — Jinja2 templates need live data wiring check

The api/main.py renders cv/templates/english.md.j2 with experience, skills, certifications, and awards fetched from the DB. Verify the Jinja2 output looks correct end-to-end by enabling ENABLE_CV_GENERATION=true and hitting GET /api/cv/generate?lang=english against a running instance with seeded data.

4. Dutch content review

The Dutch cv/content/dutch.md was restructured this session to fix layout issues. Give it a final read to ensure all translations are accurate.


Environment notes

  • Chromium in this dev environment: /opt/pw-browsers/chromium-1194/chrome-linux/chrome (Playwright-bundled, must pass --no-sandbox as root)
  • bun: Available system-wide, also copied into Docker image from oven/bun:1-slim
  • PUBLISHPIPE_MOCK_PDF=1: Set this env var to skip Chromium entirely and write a stub PDF — useful for unit testing the API endpoint

Commit log for this feature

81b400a fix(cv): replace publishpipe CLI with build.ts wrapper to fix network-restricted builds
185fc5c fix(cv): remove duplicate header and page-break separators from all CV sources
5849c40 feat(cv): add publishpipe CV generation with feature flag
a9dbcfb docs(planning): add Phase 6 — runtime on-demand CV generation
1bfa6e5 docs(planning): add publishpipe CV generation plan