# CV-Maker — Documentation technique Application web de création de CV en temps réel : saisie **Markdown** dans un panneau latéral, **preview A4** instantanée, sauvegarde locale automatique et export **PDF** — le tout sans backend, 100 % côté navigateur. ## Sommaire 1. [Prérequis](#prérequis) 2. [Installation et lancement](#installation-et-lancement) 3. [Scripts](#scripts) 4. [Stack technique](#stack-technique) 5. [Structure du projet](#structure-du-projet) 6. [Modèle de données](#modèle-de-données) 7. [Fonctionnement clé](#fonctionnement-clé) - [Rendu Markdown sécurisé](#rendu-markdown-sécurisé) - [Persistance locale](#persistance-locale) - [Gestion des expériences](#gestion-des-expériences) - [Impression PDF](#impression-pdf) - [PWA](#pwa) 8. [Conception détaillée](#conception-détaillée) 9. [Limitations connues](#limitations-connues) --- ## Prérequis | Outil | Version minimale | | --- | --- | | Node.js | 20.9+ | | pnpm | 12.x (`corepack enable` puis `pnpm -v` — figé via `packageManager`) | ## Installation et lancement ```bash # 1. Installer les dépendances pnpm install # 2. Lancer en développement pnpm dev # → http://localhost:3000 # 3. Construire + servir en production pnpm build pnpm start ``` ## Scripts | Script | Commande | Description | | --- | --- | --- | | `dev` | `pnpm dev` | Serveur de développement (Turbopack) | | `dev:sw` | `concurrently …` | Dev + re-build du service worker en continu (`--watch`) | | `build` | `next build && serwist build` | Build de production + génération du service worker | | `start` | `pnpm start` | Lancement du build de production | | `lint` | `pnpm lint` | Vérification ESLint (Next.js) | | `typecheck` | `tsc --noEmit` | Validation TypeScript strict | ## Stack technique | Brique | Choix | Rôle | | --- | --- | --- | | Framework | Next.js (App Router) | Routage, composants serveur/client, métadonnées | | Langage | TypeScript (strict) | Typage du modèle de données | | Styles | Tailwind CSS | Design tokens, responsive, print | | Markdown | `marked` | Conversion Markdown → HTML | | Sécurité | `dompurify` | Assainissement du HTML avant injection (anti-XSS) | | Persistance | `localStorage` | Données locales, aucune transmission | | PWA | `@serwist/next` + `@serwist/cli` | Service worker (mode configurator), manifest, installabilité | | Icônes | `lucide-react` | Iconographie | | Conteneurisation | Docker (multi-stage) | Image de production, distribution Docker Hub | ## Structure du projet ``` cv-maker/ ├── app/ │ ├── layout.tsx # Layout racine (lang, meta, manifest) │ ├── page.tsx # Éditeur principal │ ├── globals.css # Tokens Tailwind + @media print │ ├── documentation/ │ │ └── page.tsx # Page d'aide (statique) │ ├── mentions-legales/ │ │ └── page.tsx # Page Mentions légales (statique) │ └── sw.ts # Source du service worker (Serwist) ├── components/ │ ├── ui/ # Primitives réutilisables │ │ ├── Button.tsx │ │ ├── IconButton.tsx │ │ ├── Textarea.tsx │ │ ├── Tabs.tsx │ │ └── Footer.tsx # Pied de page (lien Mentions légales) │ └── cv/ # Composants métier du CV │ ├── CvWorkspace.tsx # Assemblage 2 colonnes + toolbar │ ├── CvToolbar.tsx # Barre d'app (PDF, JSON, liens) │ ├── CvInputPanel.tsx # Panneau gauche (tabs + expériences) │ ├── CvTabs.tsx # Onglets 1..5 │ ├── MarkdownTextarea.tsx # Textarea contrôlé │ ├── ExperienceManager.tsx # Formulaire + liste expériences │ ├── ExperienceItem.tsx # Ligne « n. titre [Modifier] [×] » │ ├── CvPreview.tsx # Feuille A4 │ ├── CvHeader.tsx # Accroche (zone 1) │ ├── CvLeftColumn.tsx # Sections 2→5 (1/3) │ ├── CvSection.tsx # Section générique │ ├── CvRightColumn.tsx # Expériences pro (2/3) │ └── CvExperiencePreview.tsx ├── lib/ │ ├── types/cv.ts # Contracts du domaine │ ├── markdown.ts # markdownToHtml (marked + DOMPurify) │ ├── storage.ts # Adapter localStorage │ └── hooks/ │ ├── useLocalStorage.ts # Persistance générique │ └── useCVData.ts # Façade métier d'édition ├── public/ │ ├── manifest.webmanifest # Manifest PWA │ └── icons/ # icônes 192 / 512 ├── Dockerfile # Image multi-stage (deps/builder/runner) ├── docker-compose.yml # Service cv-maker, healthcheck ├── .dockerignore # Exclusions de contexte build ├── serwist.config.mjs # Config service worker (mode configurator) ├── next.config.ts # output: "standalone" ├── tsconfig.json # strict ├── GOAL.md # Cahier des charges source └── README.md # Ce document ``` ## Modèle de données Contenu stocké dans **une seule clé localStorage** : `cv-maker:v1` (JSON). ```ts interface CVCareerData { version: 1; accroche: string; // onglet 1 — message d'accroche etatCivil: string; // onglet 2 — état civil + contacts + site + GitHub formations: string; // onglet 3 — formations softSkills: string; // onglet 4 — soft skills centresInteret: string; // onglet 5 — centres d'intérêt experiences: Experience[]; } interface Experience { id: string; // crypto.randomUUID() — identifiant stable titre: string; // titre affiché (liste + section CV) contenu: string; // corps en Markdown createdAt: number; updatedAt: number; } ``` - Les 5 zones sont du **Markdown brut** (le HTML rendu n'est jamais stocké). - Les expériences sont affichées **dans l'ordre de saisie** : la 1ʳᵉ ajoutée en haut de la section « Expériences professionnelles », la suivante en dessous, etc. (la 1ʳᵉ porte le numéro 1). - Sauvegarde invalide/corrompue → état par défaut, sans crash. ## Fonctionnement clé ### Rendu Markdown sécurisé `lib/markdown.ts` — toute injection HTML passe par ici : ```ts const rawHtml = marked.parse(source, { async: false }) as string; return DOMPurify.sanitize(rawHtml); ``` `marked` génère le HTML ; **`DOMPurify` neutralise** tout script/événement URL dangereuse avant le `dangerouslySetInnerHTML`. Aucun HTML brut saisi n'est injecté directement. ### Persistance locale `useLocalStorage` synchronise l'état React avec localStorage **à chaque changement** (via `useEffect`) ; l'enregistrement est silencieux (aucun indicateur affiché). `useCVData` expose la façade d'édition (`setText`, `addExperience`, `updateExperience`, `removeExperience`, `resetCV`, `importCV`), et la barre d'application permet l'**export/import JSON** (`cv-maker.json`) pour partager un CV entre appareils. ### Gestion des expériences - Bouton **+** : nouvelle expérience (formulaire titre + contenu). - Liste numérotée avec **Modifier** / **×** (suppression confirmée). - Identifiants UUID : jamais d'index comme identifiant (robustesse lors des suppressions). ### Impression PDF - Bouton « Imprimer / PDF » → `window.print()`. - CSS `@media print` dans `app/globals.css` : `@page { size: A4; margin: 0 }`, panneau de saisie et toolbar masqués, seule la feuille A4 est imprimée. ### PWA - **Mode « configurator »** (`@serwist/next` + `@serwist/cli`, compatible Turbopack) : `serwist.config.mjs` décrit le service worker, compilé par `pnpm build` après Next.js (`next build && serwist build`). - `public/sw.js` pré-cache les routes `/`, `/documentation`, `/mentions-legales` et tous les assets (787 kB, 20 URL). - En développement le service worker est **désactivé** (`SerwistProvider disable`) ; utiliser `pnpm dev:sw` pour le reconstruire à chaud. - `public/manifest.webmanifest` + icônes 192/512 → installable, fonctionne hors-ligne (les données du CV restent dans localStorage). ### Dockerisation L'image est construite en **3 étapes** (`Dockerfile` multi-stage) : | Étape | Image | Contenu | | --- | --- | --- | | `deps` | `node:20-alpine` | Installation déterministe des dépendances (`pnpm install --frozen-lockfile`, incl. `pnpm-workspace.yaml`) | | `builder` | `node:20-alpine` | Compilation (`pnpm build`, sortie `standalone` + `sw.js`) | | `runner` | `node:20-alpine` | Exécution uniquement (`node server.js`, image ≈ 60 Mo) | **Build et lancement :** ```bash docker build -t cv-maker:latest . docker run --rm -p 3001:3000 cv-maker:latest # → http://localhost:3001 ``` > Si le port 3000 est déjà occupé en local, mapper un autre port hôte (`-p 3001:3000`). **Avec Docker Compose :** ```bash docker compose up -d docker compose down ``` **Publication sur Docker Hub :** ```bash docker login docker tag cv-maker:latest /cv-maker:latest docker push /cv-maker:latest ``` Voir la [documentation détaillée Docker](./docs/conception/08-deploiement-docker.md) pour le contenu complet des fichiers (`Dockerfile`, `docker-compose.yml`, `.dockerignore`) et la recette de validation. ## Conception détaillée Le dossier de conception complet (analyse fonctionnelle, architecture, modèle de données, UI, plan de réalisation pas-à-pas, sécurité/RGPD, plan de tests) se trouve dans : ``` docs/conception/ ├── README.md → sommaire et ordre de lecture ├── 01-analyse-fonctionnelle.md ├── 02-architecture-technique.md ├── 03-modele-donnees.md ├── 04-specifications-ui.md ├── 05-etapes-realisation.md ├── 06-securite-rgpd.md ├── 07-plan-tests.md └── 08-deploiement-docker.md ``` ## Limitations connues - **Desktop first** : tablette/mobile = niveau « utilisable », non optimisé. - **One page A4 à l'impression** : feuille de preview à hauteur flexible ; un contenu très long peut générer plusieurs pages PDF. - **Pas de tests automatisés** (v1) : recette manuelle documentée dans `docs/conception/07-plan-tests.md` ; tests Vitest prévus en v2. - **Pas de drag & drop** de réorganisation des expériences (évolution prévue).