expria-frontend/docs/DEVELOPMENT_PRINCIPLES.md

14 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.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

// ❌ 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 :

// ✅ 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.


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_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