535 lines
17 KiB
Markdown
535 lines
17 KiB
Markdown
# DEVELOPMENT_PRINCIPLES.md — Expria Frontend
|
|
|
|
> **Document de référence — Version 1.0**
|
|
> 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.
|
|
|
|
---
|
|
|
|
## INSTRUCTION OBLIGATOIRE POUR CLAUDE CODE
|
|
|
|
**Avant d'écrire la moindre ligne de code, tu dois :**
|
|
|
|
1. Lire ce fichier en entier
|
|
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".**
|
|
|
|
---
|
|
|
|
## 1. Le cycle de travail obligatoire
|
|
|
|
Identique au backend :
|
|
|
|
```
|
|
ÉTAPE 1 — ANALYSE
|
|
Lire les fichiers concernés
|
|
Identifier tous les fichiers qui seront impactés
|
|
Identifier les risques de régression
|
|
|
|
ÉTAPE 2 — PLAN
|
|
Produire un plan détaillé :
|
|
— Fichiers modifiés (liste exhaustive)
|
|
— Fichiers créés (liste exhaustive)
|
|
— Fichiers supprimés (liste exhaustive)
|
|
— Risques identifiés
|
|
— Étapes dans l'ordre
|
|
Attendre la validation de Hermann
|
|
|
|
ÉTAPE 3 — IMPLÉMENTATION
|
|
Exécuter le plan validé
|
|
Ne pas s'écarter du plan sans signaler le changement
|
|
Maximum 3 fichiers touchés par étape
|
|
|
|
ÉTAPE 4 — VÉRIFICATION
|
|
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 (Golden Dataset)
|
|
Proposer un message de commit
|
|
```
|
|
|
|
---
|
|
|
|
## 2. Règles absolues — ne jamais enfreindre
|
|
|
|
### Règle A — Plan avant code
|
|
Claude Code ne commence jamais à coder sans plan validé.
|
|
Même pour une modification "simple" d'une ligne.
|
|
|
|
### 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.
|
|
|
|
### 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.
|
|
|
|
### Règle D — Jamais de logique de plan en dur dans le code
|
|
```typescript
|
|
// ❌ JAMAIS
|
|
if (user.plan === 'premium') { ... }
|
|
if (user.plan !== 'free') { ... }
|
|
|
|
// ✅ TOUJOURS
|
|
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
|
|
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`
|
|
|
|
### 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()
|
|
|
|
// ❌ Interdit
|
|
supabase.from('productions').select()
|
|
supabase.from('profiles').update(...)
|
|
```
|
|
|
|
Toute lecture/écriture de données métier passe par le backend Hono.
|
|
|
|
### 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.
|
|
|
|
### Règle L — Tokens du design system, jamais de valeurs brutes
|
|
Toutes les couleurs dans le JSX passent exclusivement par les tokens Direction H :
|
|
- Utilitaires Tailwind : `bg-canvas`, `text-ink-2`, `border-line`, `bg-expria`, `text-danger`, etc.
|
|
- Jamais de classes couleur Tailwind par défaut : `bg-slate-100`, `text-gray-500`, `blue-600`…
|
|
- Jamais de valeurs inline brutes : `#1B4FD8`, `oklch(…)`, `rgb(…)` dans les className ou style
|
|
- Pour les inline styles dynamiques uniquement : `style={{ background: 'var(--color-expria)' }}`
|
|
- Tout nouveau token est ajouté exclusivement dans `@theme {}` (et `.dark {}`) de `src/index.css`
|
|
|
|
```tsx
|
|
// ❌ JAMAIS
|
|
<div className="bg-blue-600 text-gray-500" />
|
|
<div style={{ color: '#1B4FD8' }} />
|
|
|
|
// ✅ TOUJOURS
|
|
<div className="bg-expria text-ink-3" />
|
|
<div style={{ color: 'var(--color-expria)' }} /> {/* inline style dynamique uniquement */}
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Structure du code — conventions
|
|
|
|
### Nommage des fichiers
|
|
|
|
```
|
|
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 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 { }
|
|
|
|
// Constantes globales : SCREAMING_SNAKE_CASE
|
|
const MAX_FREE_SIMULATIONS = 5
|
|
const PLANS = { ... }
|
|
```
|
|
|
|
### Structure d'un composant de feature
|
|
|
|
```typescript
|
|
// features/dashboard/pages/DashboardPage.tsx
|
|
|
|
import { usePlan } from '../hooks/usePlan'
|
|
import { hasAccess } from '@/entities/user/lib'
|
|
|
|
export function DashboardPage() {
|
|
// 1. Hooks en premier
|
|
const { plan, isLoading, error } = usePlan()
|
|
|
|
// 2. Gestion des états de chargement et d'erreur
|
|
if (isLoading) return <Spinner />
|
|
if (error) return <ErrorView error={error} />
|
|
|
|
// 3. Logique de permission via hasAccess
|
|
if (!hasAccess(plan.plan, 'dashboard')) {
|
|
return <DashboardFreeAperçu />
|
|
}
|
|
|
|
// 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
|
|
})
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Gestion des erreurs
|
|
|
|
### Côté api-client
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
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. Communication avec Hermann
|
|
|
|
### Format du plan (avant implémentation)
|
|
|
|
```
|
|
📋 PLAN — [nom de la tâche]
|
|
|
|
Fichiers modifiés :
|
|
- src/features/dashboard/pages/DashboardPage.tsx (ajouter affichage conditionnel)
|
|
- src/entities/user/lib.ts (ajouter helper formatRemainingSimulations)
|
|
|
|
Fichiers créés :
|
|
- src/features/dashboard/components/RemainingSimulationsBadge.tsx
|
|
- src/features/dashboard/components/__tests__/RemainingSimulationsBadge.test.tsx
|
|
|
|
Fichiers supprimés : aucun
|
|
|
|
Risques identifiés :
|
|
- 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 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/features/dashboard/pages/DashboardPage.tsx : intégration badge ligne 42
|
|
|
|
Créé :
|
|
- src/features/dashboard/components/RemainingSimulationsBadge.tsx
|
|
- src/entities/user/lib.ts : fonction formatRemainingSimulations
|
|
+ 2 fichiers de tests (3 tests supplémentaires)
|
|
|
|
Typecheck : OK
|
|
Tests : 27/27 passés ✅
|
|
|
|
Tests manuels à rejouer :
|
|
- 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
|
|
|
|
---
|
|
|
|
## 6. Checklist de démarrage de session
|
|
|
|
Avant chaque session Claude Code, vérifier :
|
|
|
|
```
|
|
[ ] Les documents de référence ont été lus
|
|
(ARCHITECTURE.md, DEVELOPMENT_PRINCIPLES.md,
|
|
PLANS_TARIFAIRES.md, PARCOURS_UTILISATEURS.md)
|
|
|
|
[ ] 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 typecheck && npm run test → 0 échec)
|
|
|
|
[ ] Un commit Git propre existe avant de commencer
|
|
|
|
[ ] La tâche de la session est clairement définie
|
|
(une seule tâche par session)
|
|
```
|
|
|
|
---
|
|
|
|
## 7. Checklist de fin de session
|
|
|
|
```
|
|
[ ] Les tests automatisés sont tous verts
|
|
(npm run typecheck && npm run test → 0 échec)
|
|
|
|
[ ] Le résumé de session a été produit
|
|
|
|
[ ] Les tests manuels du Golden Dataset ont été rejoués
|
|
|
|
[ ] Un commit Git a été fait avec un message clair
|
|
Format : "feat(<scope>): ...", "fix(<scope>): ...", "refactor(<scope>): ..."
|
|
Scopes possibles : auth, dashboard, simulation, t2-live, billing, shared, docs
|
|
|
|
Exemples :
|
|
"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é"
|
|
|
|
[ ] docs/CHANGELOG.md mis à jour avec un résumé de la session
|
|
|
|
[ ] ARCHITECTURE.md / SECURITY.md mis à jour si une décision a changé
|
|
```
|
|
|
|
---
|
|
|
|
## 8. Ce que Claude Code ne doit jamais faire
|
|
|
|
```
|
|
❌ 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
|
|
❌ 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
|
|
❌ 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 |
|
|
| 1.1 | 2026-04-18 | Ajout Règle L — tokens du design system (Sprint 0.5) |
|
|
| 1.2 | 2026-04-21 | Ajout section 10 — Session Clean obligatoire après chaque sprint |
|
|
|
|
---
|
|
|
|
## 10. Session Clean (obligatoire après chaque sprint)
|
|
|
|
> Session séparée du sprint de dev — jamais en cours d'implémentation.
|
|
|
|
### Déclenchement
|
|
- Le sprint est terminé
|
|
- Tous les tests automatisés sont verts
|
|
- Un commit propre existe (point de retour sûr)
|
|
|
|
### Prompt standard à donner à Claude Code
|
|
|
|
Lis dans l'ordre :
|
|
1. docs/ARCHITECTURE.md
|
|
2. docs/DEVELOPMENT_PRINCIPLES.md
|
|
3. docs/DESIGN_SYSTEM.md
|
|
|
|
Sprint [X] terminé, tests au vert, commit propre effectué.
|
|
Agis comme un ingénieur senior.
|
|
Analyse uniquement les fichiers modifiés ce sprint.
|
|
|
|
Objectif : réduire la complexité sans changer aucune fonctionnalité.
|
|
|
|
Règles :
|
|
- 1 fichier modifié à la fois
|
|
- npm run typecheck + npm run test après chaque fichier
|
|
- Si un test échoue : annuler la modification, passer au suivant
|
|
- Ne pas toucher aux fichiers non modifiés ce sprint
|
|
- Ne pas supprimer de code sans vérifier au préalable
|
|
qu'il n'est pas référencé ailleurs dans le projet
|
|
(grep obligatoire avant toute suppression)
|
|
- Aucune décision architecturale — si un doute,
|
|
signaler et attendre
|
|
|
|
Produis un plan (liste des fichiers à nettoyer, ordre)
|
|
et attends le GO.
|
|
|
|
### Séquence obligatoire
|
|
1. Claude Code propose le plan (fichiers + ordre)
|
|
2. Validation dans le Project avant GO
|
|
3. Claude Code factorise — 1 fichier à la fois
|
|
4. npm run typecheck + npm run test verts après chaque fichier
|
|
5. Tests manuels Golden Dataset — groupes concernés
|
|
6. Si tout vert → commit : refactor(<scope>): nettoyage Sprint [X]
|
|
7. CHANGELOG.md mis à jour
|
|
|
|
### Règle absolue
|
|
Un test manuel qui échoue après refactor = annuler toute
|
|
la session Clean, revenir au commit du sprint,
|
|
diagnostiquer avant de retenter.
|