# CV Editor Engine — Development Guide This guide turns the CV editor engine plan into concrete, incremental tasks you can implement. It defines scope, architecture, data model, workflows, templates, preview/export, validation, accessibility, security, performance, testing, and milestones with acceptance criteria. ## Objectives - Deliver a fast, accessible CV builder with live preview and high-quality PDF export. - Keep data structured and templates decoupled for ATS-friendly and visual layouts. - Ensure printable parity between inline preview and final PDF output. ## Tech Stack - Client: React, TailwindCSS, Zustand, TanStack Query, Tiptap (+ DOMPurify) - Server: Node.js + Express for export, Puppeteer/Playwright for PDF rendering - Validation: Zod (or Yup) - Testing: Vitest/Jest, React Testing Library, Playwright for E2E ## Repo Layout (proposed) ``` src/ components/ # UI building blocks (forms, lists, stepper) editors/ # Step editors (Heading, Work, Education, Skills, Summary) templates/ # Inline templates (ATS, Visual) + shared formatters printable/ # Printable HTML builder and styles (@page, A4) store/ # Zustand store, mutations and selectors services/ # API calls (save/load/export) and helpers utils/ # escapeHtml, sanitizers, validators, formatters hooks/ # autosave, debounced change tracking, accessibility server/ export/ # Express route for PDF export tests/ # Unit, integration, E2E, visual regression ``` ## Conventions - Strictly sanitize user HTML via `DOMPurify` and escape all text in templates. - Keep templates pure: accept CV JSON and return JSX/HTML; no side effects. - Scope styles via CSS modules or Tailwind; avoid global leakage. - Maintain preview ↔ printable parity using shared formatters and data. ## Data Model (Zod-style) ```ts const CvSchema = z.object({ personal: z.object({ firstName: z.string().min(1), lastName: z.string().min(1), email: z.string().email(), phone: z.string().optional(), street: z.string().optional(), city: z.string().optional(), country: z.string().optional(), postcode: z.string().optional(), links: z.array(z.object({ label: z.string(), url: z.string().url() })).optional(), extras: z.array(z.string()).optional(), // e.g., LinkedIn, Website, Driving licence }), summary: z.string().max(600).optional(), work: z.array(z.object({ id: z.string(), title: z.string().min(1), company: z.string().min(1), location: z.string().optional(), startDate: z.string(), // ISO or YYYY-MM endDate: z.string().optional(), bullets: z.array(z.string().max(160)).max(6), employmentType: z.enum(['full_time','part_time','contract','intern','freelance']).optional(), })), education: z.array(z.object({ id: z.string(), degree: z.string().min(1), school: z.string().min(1), startDate: z.string(), endDate: z.string().optional(), notes: z.string().optional(), })), skills: z.array(z.object({ name: z.string(), level: z.enum(['Beginner','Intermediate','Advanced']).optional() })), languages: z.array(z.object({ name: z.string(), level: z.enum(['Basic','Conversational','Fluent','Native']) })).optional(), certifications: z.array(z.object({ name: z.string(), issuer: z.string().optional(), date: z.string().optional() })).optional(), templateId: z.string().default('ats'), }); ``` Validation rules - Required: `firstName`, `lastName`, `email`; at least one of `work` or `education`. - Bullets: 1–6 per role; each ≤160 chars; avoid trailing punctuation. - Summary: ≤600 chars; discourage emojis; sanitize on save/render. - Links: normalize to `https://`; restrict protocols to `http`, `https`. ## Editor Flow - Stepper: Heading → Work → Education → Skills → Summary → Finalize - Each step runs field validation on blur and pre-navigation; critical errors block Next (e.g., invalid email). - Drag-and-drop reorder for Work and Skills; inline error messages with `aria-describedby`. ## Template System - Template API: `{ id, name, renderer(cv), thumbnail? }` - Shared formatters: `formatDate`, `formatLocation`, `escapeText`, `sanitizeHtml`. - Templates - ATS: semantic lists, neutral styles, no sidebars, simple typographic accents. - Visual: sidebar contact, accented headings, subtle color blocks (similar to sample CV). ## Inline Preview - Render chosen template directly from CV JSON; debounce heavy updates. - Memoize template components; isolate styles via Tailwind scopes or modules. ## Printable Preview (Iframe) - `buildPrintableHtml(cv)` returns a complete HTML document: - `` with embedded CSS: `@page { size: A4; margin: 18mm }` and print-safe font stack. - No external scripts; all assets inline; background printing enabled. - Load via `iframe srcDoc`; maintain parity with inline by sharing formatters and sanitized summary. ## Export Service - Express `POST /export/pdf` - Input: CV JSON (validated on server) - Compose: `buildPrintableHtml(cv)` - Render: Puppeteer `page.setContent(html, { waitUntil: 'networkidle0' })`, `page.pdf({ format: 'A4', printBackground: true })` - Output: `application/pdf` stream with filename `cv--.pdf` - Client: Export button calls endpoint; stream download; show progress & errors. ## Persistence & Autosave - Autosave: debounce 1–2s after idle; mark dirty while saving; show last-saved timestamp. - API - `GET /cv/:id` → load draft - `PUT /cv/:id` → save draft - `POST /export/pdf` → export - Store integration via TanStack Query mutations and selectors in Zustand. ## Accessibility - Labels with `htmlFor`; inputs with `aria-describedby` for errors; ensure WCAG AA contrast. - Keyboard navigation for stepper and list CRUD; focus management after add/delete. - Templates use semantic elements: `header`, `section`, `ul`, avoiding purely presentational markup. ## Security - Sanitize summary on input and before render; escape all template-bound text. - Restrict allowed URL protocols; validate `mailto:` only for email display links (never user-provided). - No inline scripts; printable builder is self-contained HTML/CSS. ## Performance - Debounce preview/autosave; throttle rapid list operations. - Lazy-load noncritical components (template gallery); memoize heavy subtrees. - Avoid large assets; prefer CSS accents; keep DOM size lean. ## Testing Strategy - Unit - `escapeHtml`, `sanitizeHtml`, formatters, Zod validators, store selectors - Integration - Inline ↔ printable parity across templates - Autosave flows and error handling - Export success/failure - E2E (Playwright) - Create CV → navigate steps → validate errors → switch templates → preview → export PDF - Visual Regression - Printable HTML screenshots per template and key data permutations ## Error Handling - Centralized error boundary; step-level error summaries; non-blocking for non-critical issues. - Graceful server errors with retry; maintain local draft if save/export fails. ## Milestones & Tasks ### M1 — Foundations Tasks - Initialize client app and Tailwind; set up routing. - Create `CvSchema`, types, and helpers (normalization, IDs). - Implement Zustand store: CV draft, active step, templateId, UI flags. - Build Stepper and Heading editor form with validation. - Implement ATS inline template and preview panel. Acceptance - User can enter heading details with inline validation and see preview update. - Data stored in Zustand; template renders from CV JSON. ### M2 — Content Steps Tasks - Work editor: CRUD, reorder, bullet editor with length guidance. - Education editor: CRUD; reuse shared list components. - Skills editor: tag input + quick-add chips; optional level. Acceptance - Add/update/delete/reorder across steps; errors appear in-place; preview updates live. ### M3 — Summary & Printable Tasks - Integrate Tiptap (StarterKit + Placeholder); toolbar with basic marks. - Sanitize output; store HTML + optional JSON. - Implement `buildPrintableHtml(cv)` and iframe preview with A4 styles. Acceptance - Printable preview matches inline layout (content, spacing, formatting); summary sanitized. ### M4 — Persistence Tasks - Wire autosave with debounced mutations; load on mount. - Add last-saved timestamp and dirty state. - Handle save/load errors gracefully. Acceptance - Draft persists across reloads; autosave feels responsive and safe. ### M5 — Export Tasks - Server: Express route for `POST /export/pdf` using Puppeteer. - Client: Export button; download with progress; error UI. Acceptance - PDF export produces a visually faithful A4 document with correct filename. ### M6 — Templates Tasks - Add Visual template; gallery picker with thumbnails and filters (ATS vs Visual). - Persist `templateId`; ensure both inline and printable support switching. Acceptance - Users switch templates instantly; parity holds across preview and PDF. ### M7 — QA & Polish Tasks - Accessibility pass; performance tuning; code cleanup. - Unit, integration, E2E, visual tests; CI workflow. Acceptance - Tests green; meets accessibility and performance targets. ### M8 — Enhancements (optional) Tasks - Import structured CV (JSON); version history; i18n; AI assist. Acceptance - Optional features are behind flags and do not regress core flows. ## Setup (placeholder) Commands to run once you scaffold the app: ``` npm create vite@latest cv-engine -- --template react-ts cd cv-engine npm i tailwindcss @tiptap/react @tiptap/starter-kit dompurify zustand @tanstack/react-query zod puppeteer express npx tailwindcss init -p ``` Adapt paths to your chosen structure. This repo currently contains planning documents only. ## Acceptance Criteria Checklist (global) - Structured CV JSON validated end-to-end - Live inline preview with ATS template by M1 - Printable HTML parity by M3 (layout, spacing, typography, sanitization) - Autosave with clear feedback by M4 - Reliable PDF export by M5 - At least two templates with instant switching by M6 - Accessibility AA and test coverage by M7 ## User Journey - User starts by selecting a template from the gallery. - They then navigate through the stepper to fill out their personal information, work experience, education, skills, and summary. - Photo upload should only show if the selected template has a placeholder for a profile photo. - After completing the steps, they can preview their CV in real-time with the selected template. - If they are satisfied with the preview, they can export their CV as a PDF file. ## Glossary - ATS: Applicant Tracking System; favors semantic, minimal styling. - Parity: Inline preview and exported PDF display equivalent content/layout. - Printable builder: Function that returns a self-contained HTML document for PDF rendering. --- Use this README as the source of truth during implementation. Update sections as architecture evolves, but keep template parity, sanitization, and accessibility as non-negotiable constraints.