docs: enrichir ARCHITECTURE, DEVELOPMENT_PRINCIPLES, GOLDEN_DATASET, TESTS_AUTOMATISES, TEST_ENVIRONMENT

This commit is contained in:
Hermann_Kitio 2026-04-17 18:35:50 +03:00
parent 52b8e9d011
commit f343fb4696
5 changed files with 1649 additions and 1476 deletions

View file

@ -1,10 +1,8 @@
# DEVELOPMENT_PRINCIPLES.md — Expria / Coach TCF Canada
# DEVELOPMENT_PRINCIPLES.md — Expria Frontend
> **Document de référence — Version 1.0**
> Ce document définit les règles que Claude Code doit lire et respecter
> à chaque session de développement, sans exception.
> Il est conçu pour un projet maintenu par un fondateur non-technique
> assisté par l'IA.
> Ce document définit les règles que toute session Claude Code sur le frontend doit lire et respecter, sans exception.
> Adapté de `DEVELOPMENT_PRINCIPLES.md` du backend — les principes sont les mêmes, les règles spécifiques sont ajustées.
---
@ -13,10 +11,12 @@
**Avant d'écrire la moindre ligne de code, tu dois :**
1. Lire ce fichier en entier
2. Lire ARCHITECTURE.md
3. Lire PLANS_TARIFAIRES.md
4. Annoncer : "Documents lus. Voici mon plan pour cette session."
5. Produire un plan détaillé et attendre la validation
2. Lire `ARCHITECTURE.md` frontend
3. Lire `PLANS_TARIFAIRES.md` (copie du backend)
4. Lire `PARCOURS_UTILISATEURS.md` (section du plan concerné)
5. Consulter les ADRs pertinents dans `docs/adr/`
6. Annoncer : "Documents lus. Voici mon plan pour cette session."
7. Produire un plan détaillé et attendre la validation
**Tu ne passes à l'implémentation que quand Hermann dit "GO".**
@ -24,7 +24,7 @@
## 1. Le cycle de travail obligatoire
Chaque modification, petite ou grande, suit ce cycle sans exception :
Identique au backend :
```
ÉTAPE 1 — ANALYSE
@ -47,13 +47,15 @@ Chaque modification, petite ou grande, suit ce cycle sans exception :
Maximum 3 fichiers touchés par étape
ÉTAPE 4 — VÉRIFICATION
Lancer npm run test (backend)
Annoncer le résultat : "X tests passés, 0 échecs"
Lancer npm run typecheck
Lancer npm run test
Annoncer le résultat : "Typecheck OK. X tests passés, 0 échecs."
Si un test échoue : STOP — corriger avant de continuer
ÉTAPE 5 — RÉSUMÉ
Lister exactement ce qui a été modifié
Indiquer les tests manuels à rejouer (référence au Golden Dataset)
Indiquer les tests manuels à rejouer (Golden Dataset)
Proposer un message de commit
```
---
@ -63,141 +65,184 @@ Chaque modification, petite ou grande, suit ce cycle sans exception :
### Règle A — Plan avant code
Claude Code ne commence jamais à coder sans plan validé.
Même pour une modification "simple" d'une ligne.
La simplicité apparente est trompeuse dans un projet avec des permissions par plan.
### Règle B — Maximum 3 fichiers par étape
Si une tâche nécessite de modifier plus de 3 fichiers,
elle est découpée en plusieurs étapes avec validation intermédiaire.
Chaque étape est testée avant de passer à la suivante.
Si une tâche nécessite de modifier plus de 3 fichiers, elle est découpée en plusieurs étapes avec validation intermédiaire.
### Règle C — Tests verts avant de continuer
Après chaque étape d'implémentation :
`npm run typecheck` doit retourner 0 erreur.
`npm run test` doit retourner 0 échec.
Si un test échoue, on corrige avant de passer à l'étape suivante.
On ne livre jamais une étape avec des tests rouges.
### Règle D — Jamais de logique de plan en dur dans le code
Toutes les vérifications de permissions lisent depuis `lib/access.ts`.
Exemple interdit :
```typescript
// ❌ JAMAIS
if (user.plan === 'premium') { ... }
if (user.plan !== 'free') { ... }
```
Exemple correct :
```typescript
// ✅ TOUJOURS
const perms = getPlanPermissions(user.plan)
if (perms.exam_mode) { ... }
import { hasAccess } from '@/entities/user/lib'
if (hasAccess(user.plan, 'exam_mode')) { ... }
```
Voir ADR 005 pour le détail.
### Règle E — Jamais de clé privée dans le frontend
Les variables suivantes n'existent que dans le backend :
Seules ces variables sont autorisées dans le frontend :
- `VITE_API_URL`
- `VITE_SUPABASE_URL`
- `VITE_SUPABASE_ANON_KEY` (clé publique)
- `VITE_ENABLE_T2_LIVE` (flag)
- `VITE_SENTRY_DSN` (si monitoring actif)
Les variables suivantes **n'existent jamais** dans le frontend :
- `SUPABASE_SERVICE_ROLE_KEY`
- `GEMINI_API_KEY`
- `DEEPSEEK_API_KEY`
- `STRIPE_SECRET_KEY`
- `STRIPE_WEBHOOK_SECRET`
Le frontend n'a accès qu'à :
- `VITE_SUPABASE_ANON_KEY` (clé publique Supabase)
- `VITE_API_URL` (URL du backend)
### Règle F — Jamais d'appel direct à Supabase pour des données métier
Supabase côté frontend est **exclusivement** utilisé pour l'auth :
```typescript
// ✅ Autorisé
supabase.auth.signInWithPassword(...)
supabase.auth.signOut()
supabase.auth.getSession()
### Règle F — Jamais de modification de la base de données sans migration SQL
Toute modification du schéma Supabase passe par un fichier de migration
dans `supabase/migrations/`.
Jamais de modification directe dans le dashboard Supabase sans fichier de migration correspondant.
// ❌ Interdit
supabase.from('productions').select()
supabase.from('profiles').update(...)
```
### Règle G — Jamais de suppression de données utilisateur
Aucune opération `DELETE` sur les tables `productions` ou `profiles`
sauf dans les scripts de test explicitement balisés.
En cas de doute : archiver, ne pas supprimer.
Toute lecture/écriture de données métier passe par le backend Hono.
### Règle H — Signaler tout écart par rapport au plan
Si pendant l'implémentation Claude Code réalise que le plan doit être modifié,
il STOP, signale le changement, explique pourquoi, et attend une nouvelle validation.
### Règle G — access.ts doit rester identique au backend
`src/entities/user/access.ts` est une **copie bit-à-bit** de `expria-backend/src/lib/access.ts`.
Toute modification se fait dans les deux dépôts, dans le même commit logique.
Voir ADR 004.
### Règle H — Jamais de logique métier dans `features/`
La logique métier (permissions, quotas, floutage, règles de validation) vit exclusivement dans `src/entities/<domaine>/lib.ts`.
Les composants React de `features/` appellent ces fonctions, sans les réimplémenter.
### Règle I — Signaler tout écart par rapport au plan
Si pendant l'implémentation Claude Code réalise que le plan doit être modifié, il STOP, signale le changement, explique pourquoi, et attend une nouvelle validation.
Il ne prend jamais de décision architecturale de sa propre initiative.
### Règle J — Sécurité
- Jamais `dangerouslySetInnerHTML` sans `DOMPurify`
- Jamais `eval`, `new Function`, `setTimeout(string, ...)`
- Jamais de secret dans `localStorage` ou `sessionStorage`
- Jamais de `console.log` de données utilisateur (email, JWT, payload API)
- Toujours passer par `apiFetch` (jamais `fetch` nu)
Voir `SECURITY.md` pour le détail.
### Règle K — Pas de worktree Git
Claude Code ne crée jamais de worktree Git.
Toutes les modifications se font directement dans le dossier du projet principal.
---
## 3. Structure du code — conventions
### Nommage des fichiers
```
frontend/src/
pages/ → PascalCase.tsx (Dashboard.tsx, Simulation.tsx)
components/ → PascalCase.tsx (RapportCard.tsx, PaywallModal.tsx)
hooks/ → camelCase.ts (useAuth.ts, usePlan.ts)
api/ → camelCase.ts (simulations.ts, corrections.ts)
lib/ → camelCase.ts (access.ts, supabase.ts)
types/ → camelCase.ts (plans.ts, simulation.ts)
backend/src/
routes/ → camelCase.ts (simulations.ts, stripe.ts)
controllers/ → camelCase.ts (simulationController.ts)
middleware/ → camelCase.ts (auth.ts, plan.ts)
lib/ → camelCase.ts (access.ts, deepseek.ts)
__tests__/ → camelCase.test.ts (canUserSimulate.test.ts)
```
src/
app/ → camelCase.tsx (providers.tsx, router.tsx)
entities/
<domaine>/
types.ts → interfaces et types
lib.ts → fonctions pures métier
api.ts → fonctions d'appel API
access.ts → cas spécial : identique au backend
features/
<feature>/
components/ → PascalCase.tsx (LoginForm.tsx, PaywallModal.tsx)
pages/ → PascalCase.tsx (LoginPage.tsx, DashboardPage.tsx)
hooks/ → camelCase.ts (useAuth.ts, usePlan.ts)
lib/ → camelCase.ts (ws-client.ts, audio.ts)
state/ → kebab-case.ts (t2-machine.ts)
shared/
components/ → PascalCase.tsx
hooks/ → camelCase.ts
lib/ → kebab-case.ts (api-client.ts, auth-client.ts)
types/ → camelCase.ts
config/ → camelCase.ts
__tests__/ → <nom-du-fichier-testé>.test.ts
```
### Nommage des variables et fonctions
```typescript
// Variables : camelCase
const userPlan = 'premium'
const simulationsUsed = 5
// Fonctions : camelCase, verbe en premier
function getUserPlan(userId: string) { }
function canUserSimulate(profile: Profile) { }
function updateUserPlan(userId: string, plan: Plan) { }
function hasAccess(plan: Plan, feature: BooleanPermission): boolean { }
function canSimulate(plan: Plan, used: number): SimulationCheck { }
// Composants React : PascalCase
function DashboardPage() { }
function PaywallModal() { }
// Hooks : use + PascalCase (convention React)
function useAuth() { }
function usePlan() { }
// Types et interfaces : PascalCase
type Plan = 'free' | 'standard' | 'premium'
interface UserProfile { }
interface SimulationResult { }
// Constantes globales : SCREAMING_SNAKE_CASE
const MAX_FREE_SIMULATIONS = 5
const PLANS = { free: {...}, standard: {...}, premium: {...} }
const PLANS = { ... }
```
### Structure d'une route Hono (backend)
### Structure d'un composant de feature
```typescript
// Pattern obligatoire pour toutes les routes
app.post('/simulations', authMiddleware, planMiddleware('simulation'), async (c) => {
// 1. Récupérer et valider les données entrantes
const body = await c.req.json()
// features/dashboard/pages/DashboardPage.tsx
// 2. Logique métier (déléguer au controller)
const result = await simulationController.create(body, c.get('user'))
import { usePlan } from '../hooks/usePlan'
import { hasAccess } from '@/entities/user/lib'
// 3. Retourner la réponse
return c.json(result, 201)
})
```
### Structure d'un composant React (frontend)
```typescript
// Pattern obligatoire pour tous les composants
interface Props {
// Toujours typer les props
}
export function NomDuComposant({ prop1, prop2 }: Props) {
export function DashboardPage() {
// 1. Hooks en premier
const { user } = useAuth()
const perms = usePlan()
const { plan, isLoading, error } = usePlan()
// 2. Handlers
const handleClick = () => { }
// 2. Gestion des états de chargement et d'erreur
if (isLoading) return <Spinner />
if (error) return <ErrorView error={error} />
// 3. Rendu conditionnel selon le plan
if (!perms.dashboard) return <PaywallModal feature="dashboard" />
// 3. Logique de permission via hasAccess
if (!hasAccess(plan.plan, 'dashboard')) {
return <DashboardFreeAperçu />
}
// 4. JSX
return (
<div>...</div>
)
// 4. Rendu principal
return <DashboardComplete plan={plan} />
}
```
### Structure d'un hook custom
```typescript
// features/dashboard/hooks/usePlan.ts
import { useQuery } from '@tanstack/react-query'
import { getPlanStatus } from '@/entities/user/api'
export function usePlan() {
return useQuery({
queryKey: ['plan'],
queryFn: getPlanStatus,
staleTime: 5 * 60 * 1000, // 5 min
})
}
```
@ -205,147 +250,160 @@ export function NomDuComposant({ prop1, prop2 }: Props) {
## 4. Gestion des erreurs
### Backend — toujours retourner une erreur structurée
### Côté api-client
```typescript
// ✅ Format d'erreur standard
return c.json({
error: true,
code: 'QUOTA_REACHED',
message: 'Quota de simulations atteint pour ce plan',
}, 403)
// shared/lib/api-client.ts
// En cas de succès : retourne le payload typé T directement (pas d'enveloppe)
// En cas d'erreur : throw une ApiError typée
// TanStack Query catch automatiquement et rend l'erreur disponible dans .error
// ❌ Jamais
return c.json({ error: 'error' }, 500)
throw new Error('Something went wrong')
```
### Codes d'erreur standardisés
```
AUTH_REQUIRED → 401 Pas de JWT ou JWT invalide
PLAN_INSUFFICIENT → 403 Feature non disponible pour ce plan
QUOTA_REACHED → 403 Quota de simulations épuisé
INVALID_PLAN → 400 Valeur de plan inconnue
SIMULATION_NOT_FOUND → 404 Simulation inexistante ou non accessible
STRIPE_WEBHOOK_INVALID → 400 Signature webhook invalide
INTERNAL_ERROR → 500 Erreur serveur inattendue
```
### Frontend — toujours gérer les erreurs API
```typescript
// ✅ Pattern obligatoire pour tout appel API
try {
const result = await api.simulations.create(data)
// succès
} catch (error) {
if (error.code === 'QUOTA_REACHED') {
// afficher modal upgrade
} else if (error.code === 'PLAN_INSUFFICIENT') {
// afficher paywall
} else {
// afficher message d'erreur générique
}
export async function apiFetch<T>(
path: string,
options: RequestInit & { timeoutMs?: number } = {}
): Promise<T> {
// 1. Ajouter headers (Authorization Bearer, X-API-Version, Content-Type)
// 2. Appliquer AbortController pour timeout
// 3. Retry avec backoff exponentiel sur erreurs réseau + 5xx
// 4. Parser la réponse :
// - Si 2xx → retourner le JSON tel quel typé T
// - Si 4xx/5xx → parser l'erreur backend { error: true, code, message } → throw ApiError
}
```
Le format exact des erreurs backend est documenté dans `ARCHITECTURE.md` section 5 (confirmé par audit du 2026-04-17).
### Côté hook (utilisation)
```typescript
const mutation = useMutation({
mutationFn: submitProduction,
onError: (err: ApiError) => {
switch (err.code) {
case 'QUOTA_REACHED':
openUpgradeModal()
break
case 'PLAN_INSUFFICIENT':
openPaywall()
break
case 'AUTH_REQUIRED':
redirectToLogin()
break
case 'VALIDATION_ERROR':
case 'INVALID_BODY':
toast.error('Données invalides. Vérifiez votre saisie.')
break
default:
toast.error('Une erreur est survenue. Réessayez dans quelques instants.')
}
},
})
```
> **Note** : `VALIDATION_ERROR` et `INVALID_BODY` sont deux codes backend pour la même classe d'erreur (corps invalide). Côté frontend, on les gère de la même manière. L'unification côté backend est tracée dans `TECH_DEBT.md` backend (TD-15).
### Messages utilisateur
- En français
- Clairs et non techniques
- Orientés action
```
// ✅ Bons messages
"Vous avez utilisé vos 5 simulations gratuites. Passez en Standard pour continuer."
"Le mode Examen est réservé au plan Premium."
"Une erreur est survenue. Réessayez dans quelques instants."
// ❌ Mauvais messages
"Erreur 403 : quota_reached"
"TypeError: Cannot read property 'plan' of undefined"
```
---
## 5. Sécurité — vérifications obligatoires
### Toute route backend qui touche à des données utilisateur doit :
```typescript
// 1. Vérifier l'authentification
app.use(authMiddleware) // vérifie le JWT Supabase
// 2. Vérifier le plan si nécessaire
app.use(planMiddleware('exam_mode')) // vérifie la permission
// 3. Vérifier que la ressource appartient à l'utilisateur
const simulation = await db.getSimulation(id)
if (simulation.user_id !== currentUser.id) {
return c.json({ error: true, code: 'AUTH_REQUIRED' }, 401)
}
```
### La vérification des permissions se fait TOUJOURS côté backend
Le frontend peut masquer des boutons selon le plan — c'est de l'UX.
Mais la vérification réelle se fait dans le middleware backend.
Un utilisateur malveillant peut appeler l'API directement sans passer par le frontend.
---
## 6. Communication avec Hermann
## 5. Communication avec Hermann
### Format du plan (avant implémentation)
```
📋 PLAN — [nom de la tâche]
Fichiers modifiés :
- src/routes/simulations.ts (ajouter la vérification de quota)
- src/lib/access.ts (ajouter la fonction canUserSimulate)
- src/features/dashboard/pages/DashboardPage.tsx (ajouter affichage conditionnel)
- src/entities/user/lib.ts (ajouter helper formatRemainingSimulations)
Fichiers créés :
- src/lib/__tests__/canUserSimulate.test.ts
- src/features/dashboard/components/RemainingSimulationsBadge.tsx
- src/features/dashboard/components/__tests__/RemainingSimulationsBadge.test.tsx
Fichiers supprimés :
- aucun
Fichiers supprimés : aucun
Risques identifiés :
- La modification de access.ts peut affecter toutes les routes
qui utilisent getPlanPermissions → vérifier après modification
- La modification de DashboardPage peut affecter l'affichage pour les 3 plans
→ vérifier les 3 comptes de test après implémentation
Étapes :
1. Créer la fonction canUserSimulate dans access.ts
2. Écrire le test canUserSimulate.test.ts
3. Ajouter la vérification dans la route POST /simulations
1. Créer le composant RemainingSimulationsBadge + son test
2. Créer le helper formatRemainingSimulations dans entities/user/lib.ts + test
3. Intégrer dans DashboardPage
En attente de validation avant de commencer.
```
### Format du résumé (après implémentation)
```
✅ RÉSUMÉ — [nom de la tâche]
Modifié :
- src/routes/simulations.ts : ajout vérification quota ligne 42
- src/lib/access.ts : ajout fonction canUserSimulate
- src/features/dashboard/pages/DashboardPage.tsx : intégration badge ligne 42
Créé :
- src/lib/__tests__/canUserSimulate.test.ts (7 tests)
- src/features/dashboard/components/RemainingSimulationsBadge.tsx
- src/entities/user/lib.ts : fonction formatRemainingSimulations
+ 2 fichiers de tests (3 tests supplémentaires)
Tests : 41/41 passés ✅
Typecheck : OK
Tests : 27/27 passés ✅
Tests manuels à rejouer :
- Golden Dataset Groupe 2 (B2, B7) — quota Free
- Golden Dataset Groupe 3 (C2) — illimité Standard
- Golden Dataset Groupe 2 (B1) — Dashboard Free avec compteur
- Golden Dataset Groupe 3 (C1) — Dashboard Standard sans compteur
Message de commit proposé :
feat(dashboard): afficher le compteur de simulations restantes (Free)
```
### Quand Claude Code doit s'arrêter et demander
- Un test automatisé échoue après modification
- La tâche nécessite de modifier plus de 3 fichiers par étape
- Une décision architecturale non documentée est requise
- Une ambiguïté sur le comportement attendu selon un plan tarifaire
- Le backend doit être modifié (session différente, dépôt différent)
- Une clé privée serait nécessaire côté frontend
---
## 7. Checklist de démarrage de session
## 6. Checklist de démarrage de session
Avant chaque session Claude Code, vérifier :
```
[ ] Les 4 documents de référence ont été lus
(DEVELOPMENT_PRINCIPLES.md, ARCHITECTURE.md,
[ ] Les documents de référence ont été lus
(ARCHITECTURE.md, DEVELOPMENT_PRINCIPLES.md,
PLANS_TARIFAIRES.md, PARCOURS_UTILISATEURS.md)
[ ] L'environnement de test est dans l'état attendu
(voir TEST_ENVIRONMENT.md — script de réinitialisation)
[ ] Les ADRs pertinents ont été consultés
(docs/adr/)
[ ] L'environnement de dev fonctionne
(npm run dev, pas d'erreur au démarrage)
[ ] Les tests automatisés sont tous verts
(npm run test → 0 échec)
(npm run typecheck && npm run test → 0 échec)
[ ] Un commit Git propre existe avant de commencer
(état de repli en cas de régression)
[ ] La tâche de la session est clairement définie
(une seule tâche par session)
@ -353,70 +411,53 @@ Avant chaque session Claude Code, vérifier :
---
## 8. Checklist de fin de session
Avant de clôturer chaque session Claude Code :
## 7. Checklist de fin de session
```
[ ] Les tests automatisés sont tous verts
(npm run test → 0 échec)
(npm run typecheck && npm run test → 0 échec)
[ ] Le résumé de session a été produit
(fichiers modifiés, tests passés)
[ ] Les tests manuels du Golden Dataset ont été rejoués
(groupes concernés par les modifications)
[ ] Un commit Git a été fait avec un message clair
Format : "feat: [description]" ou "fix: [description]"
Format : "feat(<scope>): ...", "fix(<scope>): ...", "refactor(<scope>): ..."
Scopes possibles : auth, dashboard, simulation, t2-live, billing, shared, docs
Exemples :
"feat: ajout vérification quota simulations free"
"fix: correction rapport flouté plan découverte"
"refactor: extraction logique permissions dans access.ts"
"feat(dashboard): affichage conditionnel selon le plan"
"fix(auth): corriger la redirection après logout"
"refactor(entities/user): extraire hasAccess dans un fichier dédié"
[ ] DEVELOPMENT_PRINCIPLES.md et ARCHITECTURE.md
sont à jour si une décision a changé
[ ] docs/CHANGELOG.md mis à jour avec un résumé de la session
[ ] ARCHITECTURE.md / SECURITY.md mis à jour si une décision a changé
```
---
## 9. Messages d'erreur utilisateur — ton et format
Les messages affichés à l'utilisateur doivent être :
- En français
- Clairs et non techniques
- Orientés action (que faire ensuite)
- Non condescendants
## 8. Ce que Claude Code ne doit jamais faire
```
// ✅ Bons messages
"Vous avez utilisé vos 5 simulations gratuites.
Passez en Standard pour continuer votre préparation."
"Le mode Examen est réservé au plan Premium.
Passez en Premium pour vous entraîner en conditions réelles."
"Une erreur est survenue. Veuillez réessayer dans quelques instants."
// ❌ Mauvais messages
"Erreur 403 : quota_reached"
"Vous n'êtes pas autorisé à effectuer cette action."
"Internal server error"
```
---
## 10. Ce que Claude Code ne doit jamais faire
```
❌ Modifier lib/access.ts sans signaler que TOUTES les permissions sont impactées
❌ Modifier entities/user/access.ts sans prévenir (source de vérité partagée avec le backend)
❌ Ajouter une dépendance npm sans demander la validation
Modifier le schéma Supabase directement dans le dashboard
❌ Appeler Supabase pour autre chose que l'auth
❌ Écrire une logique de plan en dur (if plan === 'premium')
❌ Exposer une clé privée dans le frontend
❌ Supprimer des données utilisateur (productions, profiles)
❌ Modifier plus de 3 fichiers sans validation intermédiaire
❌ Passer à l'étape suivante si un test est rouge
❌ Prendre une décision architecturale sans la documenter
❌ Coder sans plan validé, même pour "juste une petite modification"
❌ Utiliser dangerouslySetInnerHTML sans DOMPurify
❌ Créer un worktree Git
❌ Modifier le backend depuis une session frontend (dépôt différent)
```
---
## 9. Historique
| Version | Date | Changements |
|---|---|---|
| 1.0 | 2026-04-17 | Création, adaptée de la version backend |