CV-Engine/Cv-engine.md
geulah 1bede93cd1 feat: initialize CV Engine project with core functionality
- Set up React + TypeScript + Vite project with TailwindCSS
- Implement Zustand store for CV data management
- Create personal details editor with validation
- Add ATS-friendly template for CV preview
- Build stepper navigation component
- Include schema validation with Zod
- Configure ESLint and Prettier for code quality
2025-10-05 22:53:40 +01:00

425 lines
14 KiB
Markdown

# CV Editor React Component — README
A reusable, plug-and-play React component that provides a CV/resume builder with:
* a form-based editor (stepper UI),
* a rich-text **Tiptap** summary editor,
* live preview (inline) and iframe-isolated printable preview (A4),
* structured JSON CV model (single source of truth),
* easy integration points for saving, exporting (server-side Puppeteer example), theming and templates.
This README explains how to install, integrate, customize, test and deploy the component in an existing React app or run it standalone.
---
# Table of contents
1. What this component does
2. Key features & design decisions
3. Quick start (install + run)
4. Component API (props & events)
5. File / folder structure (suggested)
6. How to plug into an existing React app (examples)
7. Templates: how they work & how to add more
8. Exporting PDFs (server-side Puppeteer example)
9. Security & sanitization (DOMPurify)
10. State management recommendations (Zustand + TanStack)
11. Styling and theming (Tailwind / CSS isolation)
12. Accessibility checklist
13. Testing & CI suggestions
14. Troubleshooting / common errors
15. Next steps & extension ideas
---
# 1. What this component does
This component provides a full CV editor UI you can drop into your React app. It stores the CV as a structured JSON model and renders templates from that model. The summary field is a rich editor (Tiptap) that stores content as HTML/JSON; previews are rendered inline and inside an iframe (print-ready HTML). For production PDF export you can reuse the same printable HTML on the server and render it with Puppeteer.
---
# 2. Key features & design decisions
* **Single source of truth**: the CV is a JSON object (easy to save, modify, export).
* **Templates as renderers**: templates are modular React components / HTML + CSS that accept the CV JSON and return DOM or printable HTML.
* **Rich summary**: Tiptap (ProseMirror) used for structured, extensible rich text with predictable HTML output.
* **Preview parity**: inline preview uses the same React template rendering to reduce export surprises; iframe preview isolates CSS for print fidelity.
* **Server-side parity**: printable HTML builder can be used on server in Puppeteer to generate pixel-perfect PDFs.
---
# 3. Quick start (install + run)
### Minimal dependencies
Add these packages to your project:
```bash
# Core
npm install react react-dom
# UI + styling (suggested)
npm install tailwindcss
# Rich text
npm install @tiptap/react @tiptap/starter-kit @tiptap/extension-placeholder
# Optional & recommended
npm install dompurify
npm install axios
npm install zustand @tanstack/react-query
```
### Example package.json dev/start scripts
```json
{
"scripts": {
"start": "vite", // or react-scripts start / next dev
"build": "vite build",
"test": "vitest"
}
}
```
### Run
Add the component file (e.g. `CVEditorWireframe.jsx`) to your app and import it into a page. Start your app as usual (`npm start` / `npm run dev`).
---
# 4. Component API (props & events)
Use this component as `<CVEditor />`. Example usage:
```jsx
import CVEditor from './components/CVEditorWireframe'
function App() {
const initialCV = { /* optional initial JSON model */ }
async function handleSave(cvJson) {
// e.g. POST /api/cv
await axios.post('/api/cv', cvJson)
}
return (
<CVEditor
initialCV={initialCV}
templates={myTemplateList}
onSave={handleSave}
exportEndpoint="/api/export/pdf" // optional: server endpoint for export jobs
allowDownload={true}
theme="light"
/>
)
}
```
### Props
* `initialCV``{}` CV JSON model to pre-populate the editor (optional).
* `templates``[{ id, name, renderer }]` list of templates. `renderer` can be a React component that accepts `{cv}` or a function that returns printable HTML.
* `onSave(cv)` — async callback called when user saves (or autosave triggers).
* `onExportRequest(cv)` — callback to call when user requests PDF export. If provided, component can call it (you can send to server for Puppeteer).
* `exportEndpoint` — optional server endpoint URL to POST CV JSON for export.
* `allowDownload``boolean`, whether to show Export/Download controls.
* `theme``'light' | 'dark'` (optional, used to add top-level class).
* `className` — additional CSS class for outer container.
* `onChange(cv)` — optional callback fired on any CV change (useful for autosave).
---
# 5. File / folder structure (suggested)
```
src/
components/
CVEditorWireframe.jsx // main component (editor + preview)
templates/
TemplateClassic.jsx
TemplateATS.jsx
editors/
RichSummaryEditor.jsx // Tiptap wrapper
utils/
printableHtml.js // buildPrintableHtml(cv)
escapeHtml.js
hooks/
useCvAutosave.js
pages/
EditorPage.jsx // where you mount CVEditor
server/
export/
exportPdf.js // Express + Puppeteer server code (optional)
```
---
# 6. How to plug into an existing React app
### A. As a child component (recommended)
1. Copy `CVEditorWireframe.jsx` and its dependencies into your project.
2. Import and render it inside an existing route or component.
3. Provide `initialCV`, `onSave` and `templates` props as needed.
```jsx
// routes/ProfileEditor.jsx
import CVEditor from '../components/CVEditorWireframe'
import TemplateClassic from '../components/templates/TemplateClassic'
const templates = [
{ id: 'classic', name: 'Classic', renderer: TemplateClassic },
// add ATS or other templates
]
export default function ProfileEditor() {
const initialCV = {...}
async function handleSave(cv) { await api.saveCv(cv) }
return <CVEditor initialCV={initialCV} templates={templates} onSave={handleSave} exportEndpoint="/api/export/pdf" />
}
```
### B. As an isolated micro-frontend
If you prefer it to be self-contained: bundle the component into its own package or iframe it from a separate deployment. But prefer simple import for easier code sharing and debugging.
---
# 7. Templates: how they work & how to add more
**Two options for templates:**
1. **React component template** — a React component that accepts `{ cv }` and renders markup. Use for inline preview and server rendering (with `renderToString`).
2. **HTML template function** — a function that receives `cv` and returns printable HTML string. Use for server-side export or when you want designers to provide standalone HTML/CSS files.
**Example React template**
```jsx
export default function TemplateClassic({ cv }) {
return (
<div className="template-root">
<h1>{cv.personal.firstName} {cv.personal.lastName}</h1>
<div dangerouslySetInnerHTML={{ __html: cv.summary }} />
{cv.work.map(w => (
<div key={w.id}>
<h3>{w.title}</h3>
<div>{w.company} {w.period}</div>
<ul>{w.bullets.map((b,i) => <li key={i}>{b}</li>)}</ul>
</div>
))}
</div>
);
}
```
**Guidelines**
* Provide both `visual` templates (fancier CSS) and `ATS` templates (semantic, simple markup).
* Keep template CSS isolated (CSS modules, scoped styles or render in iframe).
* Use the same rendering logic on the server for PDFs to ensure parity.
---
# 8. Exporting PDFs (server-side Puppeteer example)
### Why server-side?
Puppeteer produces consistent, pixel-perfect PDFs (server or worker) and you can offload CPU work off the client.
### Minimal Express endpoint (example)
```js
// server/export/exportPdf.js
import express from 'express';
import puppeteer from 'puppeteer';
const router = express.Router();
router.post('/export/pdf', async (req, res) => {
try {
const cv = req.body;
// reuse printable-html builder (same logic as client)
const html = buildPrintableHtml(cv);
const browser = await puppeteer.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'] });
const page = await browser.newPage();
await page.setContent(html, { waitUntil: 'networkidle0' });
const pdfBuffer = await page.pdf({ format: 'A4', printBackground: true });
await browser.close();
res.set({
'Content-Type': 'application/pdf',
'Content-Disposition': `attachment; filename="${cv.personal.firstName}_${cv.personal.lastName}_CV.pdf"`
});
res.send(pdfBuffer);
} catch (err) {
console.error(err);
res.status(500).json({ error: 'Failed to create PDF' });
}
});
export default router;
```
**Notes**
* Use a worker or job queue (BullMQ) for high volume.
* Reuse the same HTML builder (`buildPrintableHtml`) on the server (move the builder to a shared package or duplicate carefully).
* If using serverless (Vercel / Netlify), be aware Puppeteer needs special setup (use `chrome-aws-lambda`).
---
# 9. Security & sanitization (DOMPurify)
Because summary HTML comes from users, always sanitize before rendering into `srcDoc` or sending to server.
```bash
npm install dompurify
```
```js
import DOMPurify from 'dompurify'
const safeHtml = DOMPurify.sanitize(cv.summary)
<div dangerouslySetInnerHTML={{ __html: safeHtml }} />
```
Sanitize on both client and server if the server stores the HTML or returns it to other users.
---
# 10. State management recommendations
* **Zustand** — simple local/global client state (editor draft, UI toggles).
* **TanStack Query** — server-state (fetching/saving CVs, export job status).
* Use both: keep in-memory editing state in Zustand, and use React Query for network operations (cache, optimistic updates, retries).
Example:
```js
// useCvAutosave.js
import { useMutation } from '@tanstack/react-query';
export function useAutosave() {
return useMutation(cv => api.saveCv(cv), { /* ... */ })
}
```
---
# 11. Styling & theming
* Use **Tailwind CSS** for fast iteration: the wireframe uses tailwind utility classes.
* For templates, prefer scoped CSS (CSS modules) or provide a `template.css` imported only by the template to avoid leakage.
* For iframe preview, `srcDoc` includes template CSS directly—this isolates styles perfectly.
---
# 12. Accessibility checklist
* All form fields have accessible labels (use `label` with `htmlFor`).
* Provide keyboard navigation for stepper and drag/reorder (if implemented).
* Ensure color contrast meets WCAG AA for text.
* Use semantic elements (`<header>`, `<main>`, `<section>`, `<ul>`) in templates.
* Add `aria-live` regions for autosave status and export progress.
---
# 13. Testing & CI suggestions
* **Unit tests**: Vitest/Jest for utils (`escapeHtml`, `buildPrintableHtml`).
* **Visual regression**: Percy / Chromatic for templates and export output.
* **E2E**: Cypress or Playwright for full editor flows (create CV → preview → export).
* **CI**: run tests + lint; optionally run Puppeteer to generate sample PDFs in a job (be mindful of resource usage).
Example unit test for `escapeHtml`:
```js
import { escapeHtml } from '../utils/escapeHtml'
test('escapes special chars', () => {
expect(escapeHtml(`Tom & Jerry <script>`)).toContain('&amp;')
})
```
---
# 14. Troubleshooting / common errors
* **Unterminated string constant**: often caused by unescaped newline characters inside template literals or improperly closed quotes. Ensure:
* Use backticks `` ` `` for multi-line template strings.
* Escape `\n` inside single/double quoted strings (e.g. `join('\\n')`).
* Ensure `escapeHtml` function returns properly and ends with `}`.
* **Tiptap SSR**: Tiptap relies on DOM; rendering server-side requires guarding the editor initialisation (only run in browser). Use `if (typeof window !== 'undefined')` or dynamic import.
* **Puppeteer memory/CPU**: scale PDF generation using a worker pool (e.g. `puppeteer-cluster`) or separate service.
* **Iframe `srcDoc` not rendering**: ensure the printable HTML is a full HTML document (doctype, head, meta charset). Some browsers block certain inline scripts—best to avoid inline scripts in `srcDoc`.
---
# 15. Next steps & extension ideas
* Add **template gallery** with thumbnails and filters (ATS vs Visual).
* Add **resume parser** (import old CVs) — use Rchilli/Sovren or build simple NER workflow.
* Add **AI suggestions** (OpenAI) for summary and bullet generation — integrate as optional helper.
* Add **collaboration** (CRDT or ProseMirror-based real-time editing).
* Add **version history** and downloads per version.
* Add **internationalisation** for multi-language templates.
---
# Example: Minimal `buildPrintableHtml` and `escapeHtml` utils
```js
// utils/escapeHtml.js
export function escapeHtml(str) {
if (!str) return ''
return String(str)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/\"/g, '&quot;')
.replace(/'/g, '&#39;');
}
```
```js
// utils/printableHtml.js
import { escapeHtml } from './escapeHtml'
export function buildPrintableHtml(cv) {
const css = `@page{size:A4;margin:20mm}body{font-family:Inter,Arial...}`;
const summary = cv.summary || '';
const workHtml = (cv.work || []).map(w => `
<article>
<div style="display:flex;justify-content:space-between">
<div><strong>${escapeHtml(w.title)}</strong><div>${escapeHtml(w.company)}</div></div>
<div>${escapeHtml(w.period)}</div>
</div>
<ul>${(w.bullets || []).map(b => `<li>${escapeHtml(b)}</li>`).join('')}</ul>
</article>
`).join('');
return `<!doctype html><html><head><meta charset="utf-8"><style>${css}</style></head><body>
<header><h1>${escapeHtml(cv.personal.firstName)} ${escapeHtml(cv.personal.lastName)}</h1></header>
<section>${summary}</section>
<section>${workHtml}</section>
</body></html>`;
}
```
---
# Final notes
* This component is intentionally modular: swap the templates, storage, and export backend independently.
* For production, focus on sanitization (DOMPurify), server-side rendering parity (use same templates or same printable-html builder), and performance (worker pool for PDF generation).
If you'd like, I can:
* generate a ready-to-drop `CVEditorWireframe.jsx` file (complete component) and `TemplateClassic.jsx` for you to copy, or
* provide the Express + Puppeteer server file and a docker-compose example to run the PDF service, or
* scaffold a small repo with the full stack (client + server export).
Which would you like me to produce next?