17 KiB
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.mddu 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 :
- Lire ce fichier en entier
- Lire
ARCHITECTURE.mdfrontend - Lire
PLANS_TARIFAIRES.md(copie du backend) - Lire
PARCOURS_UTILISATEURS.md(section du plan concerné) - Consulter les ADRs pertinents dans
docs/adr/ - Annoncer : "Documents lus. Voici mon plan pour cette session."
- 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
// ❌ 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_URLVITE_SUPABASE_URLVITE_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_KEYGEMINI_API_KEYDEEPSEEK_API_KEYSTRIPE_SECRET_KEYSTRIPE_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 :
// ✅ 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
dangerouslySetInnerHTMLsansDOMPurify - Jamais
eval,new Function,setTimeout(string, ...) - Jamais de secret dans
localStorageousessionStorage - Jamais de
console.logde données utilisateur (email, JWT, payload API) - Toujours passer par
apiFetch(jamaisfetchnu)
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 {}) desrc/index.css
// ❌ 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
// 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
// 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
// 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
// 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)
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_ERRORetINVALID_BODYsont 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 dansTECH_DEBT.mdbackend (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 :
- docs/ARCHITECTURE.md
- docs/DEVELOPMENT_PRINCIPLES.md
- 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
- Claude Code propose le plan (fichiers + ordre)
- Validation dans le Project avant GO
- Claude Code factorise — 1 fichier à la fois
- npm run typecheck + npm run test verts après chaque fichier
- Tests manuels Golden Dataset — groupes concernés
- Si tout vert → commit : refactor(): nettoyage Sprint [X]
- 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.