- Add 4 new CV templates (ATS-Friendly, Classic, Modern, Minimal) with thumbnails - Implement template registry and gallery component for template selection - Add photoUrl field to personal info with URL validation - Update buildPrintableHtml to support template-specific styling - Modify ExportControls to use template registry - Add template preview thumbnails in SVG format
250 lines
11 KiB
Markdown
250 lines
11 KiB
Markdown
# 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:
|
||
- `<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 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. |