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)

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: 16 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:
    • <html> 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-<lastName>-<YYYYMMDD>.pdf
  • Client: Export button calls endpoint; stream download; show progress & errors.

Persistence & Autosave

  • Autosave: debounce 12s 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.

Description
No description provided
Readme 188 KiB
Languages
TypeScript 84.9%
JavaScript 14.4%
CSS 0.5%
HTML 0.2%