diff --git a/.gitignore b/.gitignore index 1baedb9c7..b2a60d174 100644 --- a/.gitignore +++ b/.gitignore @@ -196,3 +196,43 @@ veza-backend-api/backend*.log # AI tooling session state (not code) .cursor/ + +# ============================================================ +# Post-audit J2 (2026-04-20) — branch chore/v1.0.7-cleanup +# ============================================================ + +# Tracked audio fixtures — use git-lfs or fixtures repo, never commit raw audio +veza-backend-api/uploads/ + +# TLS/SSL certificates committed pre-2026-04 (regen with scripts/generate-ssl-cert.sh) +config/ssl/*.pem +config/ssl/*.key +config/ssl/*.crt + +# Playwright MCP session debris +.playwright-mcp/ + +# AI session artefacts / context dumps +CLAUDE_CONTEXT.txt +UI_CONTEXT_SUMMARY.md +*.context.txt +*.ai-session.txt + +# One-off generated tooling scripts (should live in scripts/ if kept) +/generate_page_fix_prompts.sh +/build-archive.log + +# Apps/web stale audit reports (generated, never tracked) +apps/web/AUDIT_ISSUES.json +apps/web/audit_remediation.json +apps/web/lint_comprehensive.json +apps/web/storybook-roadmap.json +apps/web/storybook-*.json + +# Root PNG screenshots — move to docs/screenshots/ if historical value +/design-system-*.png +/forgot-password-*.png +/register-*.png +/reset-password-*.png +/settings-*.png +/storybook-*.png diff --git a/generate_page_fix_prompts.sh b/generate_page_fix_prompts.sh deleted file mode 100644 index 911d1f5c2..000000000 --- a/generate_page_fix_prompts.sh +++ /dev/null @@ -1,980 +0,0 @@ -#!/usr/bin/env python3 -""" -VEZA — Générateur de prompts Claude Code pour audit exhaustif de toutes les pages. -Génère un prompt par route, prêt à être copié-collé dans Claude Code avec Playwright MCP. - -Usage: - python veza-prompt-generator.py # Génère tous les prompts dans ./prompts/ - python veza-prompt-generator.py --route /settings # Génère un seul prompt - python veza-prompt-generator.py --list # Liste toutes les routes - python veza-prompt-generator.py --batch 1 # Génère le batch 1 (routes groupées) - python veza-prompt-generator.py --combined # Un seul fichier avec tous les prompts -""" - -import argparse -import os -import sys -from dataclasses import dataclass, field -from datetime import datetime -from typing import Optional - - -# ───────────────────────────────────────────── -# Configuration -# ───────────────────────────────────────────── - -OUTPUT_DIR = "./prompts" -DOMAIN = "veza.fr" - -TEST_ACCOUNTS = [ - {"role": "admin", "email": "admin@veza.music", "password": "Admin123!"}, - {"role": "creator", "email": "artist@veza.music", "password": "Artist123!"}, - {"role": "user", "email": "user@veza.music", "password": "User123!"}, - {"role": "moderator", "email": "mod@veza.music", "password": "Mod123!"}, - {"role": "user_new", "email": "new@veza.music", "password": "New123!"}, -] - -E2E_TEST_DIR = "veza-e2e/" - - -# ───────────────────────────────────────────── -# Route definitions -# ───────────────────────────────────────────── - -@dataclass -class Route: - number: int - path: str - name: str - category: str - auth_required: bool = True - role: str = "user" - params: str = "" - test_with_accounts: list = field(default_factory=list) - specific_checks: list = field(default_factory=list) - - -ROUTES = [ - # ── Pages publiques ── - Route(1, "/login", "Connexion", "public", auth_required=False, - specific_checks=[ - "Formulaire email + password fonctionnel", - "Validation des champs (email invalide, champs vides)", - "Messages d'erreur (mauvais credentials)", - "Lien 'Mot de passe oublié' fonctionnel", - "Lien 'Inscription' fonctionnel", - "Redirection après login réussi", - "Protection contre brute-force (rate limiting visible)", - "Autocomplete attributes sur les inputs", - "Accessibilité: labels, focus order, aria", - ]), - Route(2, "/register", "Inscription", "public", auth_required=False, - specific_checks=[ - "Tous les champs du formulaire (username, email, password, confirm)", - "Validation temps réel (email format, password strength, username dispo)", - "Messages d'erreur clairs pour chaque règle de validation", - "Soumission et retour serveur (201, 409 duplicate, 422 validation)", - "Lien vers /login fonctionnel", - "CGU / checkbox conditions d'utilisation si présent", - "Accessibilité: labels, focus, aria, autocomplete", - ]), - Route(3, "/forgot-password", "Récupération mot de passe", "public", auth_required=False, - specific_checks=[ - "Formulaire email fonctionnel", - "Message de confirmation après soumission", - "Gestion email inexistant (pas de leak d'info)", - "Lien retour vers /login", - "Rate limiting sur les soumissions", - ]), - Route(4, "/verify-email", "Vérification email", "public", auth_required=False, - specific_checks=[ - "Comportement avec token valide vs invalide vs expiré", - "Message de succès / erreur approprié", - "Redirection après vérification", - "Gestion du cas sans token dans l'URL", - ]), - Route(5, "/reset-password", "Réinitialisation mot de passe", "public", auth_required=False, - specific_checks=[ - "Formulaire nouveau password + confirmation", - "Validation password strength côté client", - "Comportement avec token valide vs invalide vs expiré", - "Message de succès + redirection vers /login", - "Autocomplete attributes", - ]), - Route(6, "/launch", "Landing page pré-lancement", "public", auth_required=False, - specific_checks=[ - "Rendu visuel complet (hero, sections, CTA)", - "Tous les liens/CTA fonctionnels", - "Responsive (mobile/tablet/desktop)", - "Performance de chargement", - "SEO: title, meta description, OG tags", - ]), - Route(7, "/design-system", "Démo du design system", "public", auth_required=False, - specific_checks=[ - "Tous les composants se rendent sans erreur", - "Pas d'erreurs console", - "Interactivité des composants démo (boutons, toggles, modals)", - ]), - Route(8, "/u/:username", "Profil public utilisateur", "public", auth_required=False, - params="username", - specific_checks=[ - "Affichage avec username existant (ex: tester avec chaque compte)", - "Page 404 avec username inexistant", - "Infos publiques affichées (avatar, bio, tracks publiques)", - "Pas de fuite d'infos privées (email, settings)", - "Boutons follow/unfollow si connecté", - "Liste des tracks/playlists publiques", - "Liens fonctionnels vers les tracks", - ]), - Route(9, "/playlists/shared/:token", "Playlist partagée", "public", auth_required=False, - params="token", - specific_checks=[ - "Affichage avec token valide", - "Gestion token invalide / expiré", - "Lecture des tracks depuis le partage", - "Infos playlist (titre, description, nombre de tracks)", - ]), - - # ── Navigation principale ── - Route(10, "/dashboard", "Tableau de bord", "main_nav", - test_with_accounts=["user", "creator", "admin"], - specific_checks=[ - "Widgets / cartes de stats se chargent", - "Données cohérentes avec le rôle (creator voit ses stats artiste)", - "Liens rapides fonctionnels", - "Activité récente / feed", - "Pas d'erreurs réseau dans la console", - "Responsive layout", - ]), - Route(11, "/discover", "Découverte musicale", "main_nav", - specific_checks=[ - "Sections de découverte (trending, new releases, genres)", - "Cartes de tracks/albums cliquables", - "Lecture depuis la page discover", - "Filtres / catégories fonctionnels", - "Infinite scroll ou pagination", - "État vide si aucun contenu", - ]), - Route(12, "/feed", "Fil d'actualité", "main_nav", - specific_checks=[ - "Posts / activités des artistes suivis", - "Infinite scroll ou pagination", - "Interactions (like, comment, share)", - "État vide (aucun artiste suivi)", - "Chargement sans erreurs réseau", - ]), - Route(13, "/library", "Bibliothèque musicale", "main_nav", - test_with_accounts=["user", "creator"], - specific_checks=[ - "Liste des tracks / albums / playlists de l'utilisateur", - "Tri et filtres fonctionnels", - "Recherche dans la bibliothèque", - "Actions sur les items (play, add to playlist, delete)", - "État vide pour un nouveau compte", - "Pagination / infinite scroll", - ]), - Route(14, "/queue", "File de lecture", "main_nav", - specific_checks=[ - "Affichage de la queue actuelle", - "Drag & drop pour réordonner", - "Suppression d'items de la queue", - "Bouton clear queue", - "État vide", - "Persistance après navigation", - ]), - Route(15, "/search", "Recherche globale", "main_nav", - specific_checks=[ - "Barre de recherche fonctionnelle", - "Résultats par catégorie (tracks, artists, playlists, albums)", - "Recherche en temps réel / debounce", - "Résultats cliquables et navigation correcte", - "État 'aucun résultat'", - "Historique de recherche si présent", - ]), - - # ── Playlists & Tracks ── - Route(16, "/playlists", "Liste des playlists", "playlists", - specific_checks=[ - "Liste des playlists de l'utilisateur", - "Bouton créer une playlist", - "Cartes playlist cliquables", - "Infos affichées (titre, nombre de tracks, durée, cover)", - "Actions (edit, delete, share)", - "État vide", - ]), - Route(17, "/playlists/favoris", "Playlist favoris", "playlists", - specific_checks=[ - "Liste des tracks favorites", - "Bouton unfavorite fonctionnel", - "Lecture depuis les favoris", - "Tri (date ajoutée, titre, artiste)", - "État vide", - ]), - Route(18, "/playlists/:id", "Détail playlist", "playlists", - params="id", - specific_checks=[ - "Header playlist (titre, description, cover, auteur)", - "Liste des tracks avec numérotation", - "Boutons play / shuffle", - "Actions par track (play, add to queue, remove)", - "Boutons share / edit / delete (si owner)", - "Playlist inexistante → 404", - "Playlist privée d'un autre user → 403 ou 404", - ]), - Route(19, "/playlists/:id/edit", "Édition playlist", "playlists", - params="id", - specific_checks=[ - "Formulaire pré-rempli (titre, description)", - "Upload / changement de cover", - "Réordonnement des tracks (drag & drop)", - "Ajout / suppression de tracks", - "Sauvegarde fonctionnelle", - "Validation (titre requis)", - "Accès interdit si pas owner → redirect ou 403", - ]), - Route(20, "/tracks/:id", "Détail track", "playlists", - params="id", - specific_checks=[ - "Infos track (titre, artiste, album, durée, cover)", - "Bouton play fonctionnel", - "Boutons like / add to playlist / share", - "Waveform / visualisation audio si présent", - "Commentaires si présent", - "Tracks similaires / recommandations", - "Track inexistante → 404", - "Métadonnées (genre, BPM, key si affichés)", - ]), - - # ── Social & Communication ── - Route(21, "/social", "Communauté", "social", - specific_checks=[ - "Liste des utilisateurs / artistes", - "Recherche / filtres", - "Boutons follow / unfollow", - "Profils cliquables", - "Compteurs followers/following", - "Sections (trending artists, new members, etc.)", - ]), - Route(22, "/chat", "Messagerie", "social", - test_with_accounts=["user", "creator"], - specific_checks=[ - "Liste des conversations", - "Envoi de message", - "Réception en temps réel (WebSocket)", - "Création de nouvelle conversation", - "Recherche dans les conversations", - "État vide (aucune conversation)", - "Indicateurs read/unread", - "Envoi de médias si supporté", - ]), - Route(23, "/chat/join/:token", "Rejoindre un chat", "social", - params="token", - specific_checks=[ - "Token valide → rejoint le chat + redirect", - "Token invalide → message d'erreur", - "Token expiré → message d'erreur", - "Déjà membre → redirect vers le chat existant", - ]), - Route(24, "/live", "Streams en direct", "social", - specific_checks=[ - "Liste des streams en cours", - "Cartes stream (titre, artiste, viewers, thumbnail)", - "Clic → page de visionnage", - "État vide (aucun stream)", - "Compteurs viewers", - ]), - Route(25, "/live/go-live", "Lancer un stream", "social", - role="creator", - test_with_accounts=["creator", "admin"], - specific_checks=[ - "Formulaire de configuration stream (titre, description, catégorie)", - "Prévisualisation caméra/micro", - "Bouton Go Live", - "Vérification permissions (role creator/admin requis)", - "Utilisateur standard → redirect ou message d'erreur", - "Configuration RTMP/HLS si affichée", - ]), - Route(26, "/listen-together/:sessionId", "Écoute collaborative", "social", - params="sessionId", - specific_checks=[ - "Session valide → rejoint l'écoute", - "Session invalide → erreur", - "Synchronisation audio entre participants", - "Liste des participants", - "Chat intégré si présent", - "Contrôles de lecture (host only vs all)", - ]), - - # ── Marketplace & Commerce ── - Route(27, "/marketplace", "Place de marché", "marketplace", - specific_checks=[ - "Grille / liste de produits", - "Filtres (catégorie, prix, type)", - "Recherche produits", - "Cartes produits (image, titre, prix, vendeur)", - "Pagination / infinite scroll", - "Tri (prix, date, popularité)", - ]), - Route(28, "/marketplace/products/:id", "Détail produit", "marketplace", - params="id", - specific_checks=[ - "Infos produit (images, titre, description, prix)", - "Bouton acheter / ajouter au panier", - "Avis / ratings si présent", - "Infos vendeur", - "Produit inexistant → 404", - "Produits similaires", - ]), - Route(29, "/wishlist", "Liste de souhaits", "marketplace", - specific_checks=[ - "Liste des produits wishlistés", - "Bouton retirer de la wishlist", - "Bouton acheter directement", - "État vide", - "Infos produit à jour (prix, disponibilité)", - ]), - Route(30, "/purchases", "Historique d'achats", "marketplace", - specific_checks=[ - "Liste des achats passés", - "Détails par achat (date, montant, produit, statut)", - "Téléchargement si digital", - "État vide", - "Pagination si beaucoup d'achats", - ]), - Route(31, "/checkout/complete", "Confirmation de commande", "marketplace", - specific_checks=[ - "Message de confirmation", - "Récapitulatif de la commande", - "Liens vers les achats / téléchargements", - "Comportement si accès direct sans commande → redirect", - ]), - Route(32, "/sell", "Dashboard vendeur", "marketplace", - role="creator", - test_with_accounts=["creator", "admin"], - specific_checks=[ - "Stats de vente (revenus, nombre de ventes)", - "Liste des produits en vente", - "Bouton ajouter un produit", - "Gestion des produits (edit, delete, toggle visibility)", - "Accès interdit pour les non-creators", - ]), - - # ── Créateur & Analytics ── - Route(33, "/analytics", "Tableau de bord analytics", "creator", - role="creator", - test_with_accounts=["creator", "admin"], - specific_checks=[ - "Graphiques de stats (plays, listeners, revenue)", - "Période sélectionnable (7j, 30j, 90j, 1an)", - "Top tracks / Top playlists", - "Données démographiques si présent", - "Export de données si présent", - "État vide pour un nouveau creator", - "Accès interdit pour user standard", - ]), - Route(34, "/cloud", "Stockage cloud", "creator", - role="creator", - test_with_accounts=["creator"], - specific_checks=[ - "Explorateur de fichiers", - "Upload de fichiers audio", - "Progress bar upload", - "Organisation en dossiers", - "Actions (rename, delete, move, download)", - "Quota de stockage affiché", - "Types de fichiers acceptés / rejetés", - ]), - Route(35, "/gear", "Inventaire équipement", "creator", - role="creator", - specific_checks=[ - "Liste de l'équipement", - "Ajout / édition / suppression d'items", - "Catégories (instruments, software, hardware)", - "Photos d'équipement si supporté", - "État vide", - ]), - Route(36, "/distribution", "Distribution externe", "creator", - role="creator", - specific_checks=[ - "Liste des plateformes de distribution", - "Statut de distribution par track/album", - "Bouton distribuer", - "Configuration des métadonnées requises", - "Historique des distributions", - ]), - - # ── Compte & Paramètres ── - Route(37, "/profile", "Profil (redirect)", "account", - specific_checks=[ - "Redirection vers /u/:username du user connecté", - "Redirection correcte pour chaque rôle", - ]), - Route(38, "/settings", "Paramètres du compte", "account", - test_with_accounts=["user", "creator", "admin"], - specific_checks=[ - "Onglet Account: changement de password fonctionnel", - "Onglet Account: setup/disable 2FA (Authenticator + SMS)", - "Onglet Account: statut 2FA correct", - "Onglet Account: Delete Account (validation 'DELETE', password requis)", - "Onglet Préférences: thème (radio buttons mutuellement exclusifs)", - "Onglet Préférences: langue (changement effectif)", - "Onglet Préférences: timezone", - "Onglet Notifications: tous les toggles fonctionnels", - "Onglet Confidentialité: tous les toggles fonctionnels", - "Onglet Playback: audio quality, crossfade, etc.", - "Bouton Save Config fonctionnel (pas d'erreur validation)", - "Accessibilité: labels sur tous les checkboxes/toggles", - "Pas de mélange i18n (tout FR ou tout EN)", - "Pas de fuite d'infos (email dans search bar, VAPID key)", - ]), - Route(39, "/settings/sessions", "Sessions actives", "account", - specific_checks=[ - "Liste des sessions actives (device, IP, date)", - "Session courante identifiée", - "Bouton révoquer une session", - "Bouton révoquer toutes les autres sessions", - "Confirmation avant révocation", - ]), - Route(40, "/notifications", "Centre de notifications", "account", - specific_checks=[ - "Liste des notifications", - "Marquage lu/non-lu", - "Bouton marquer tout comme lu", - "Filtres par type de notification", - "Clic sur notification → navigation correcte", - "État vide", - "Pagination / infinite scroll", - ]), - Route(41, "/subscription", "Abonnements & plans", "account", - specific_checks=[ - "Affichage des plans disponibles", - "Plan actuel mis en évidence", - "Boutons upgrade / downgrade", - "Comparaison des features par plan", - "Historique de facturation si présent", - "Gestion du moyen de paiement", - ]), - - # ── Apprentissage & Support ── - Route(42, "/education", "Formation & ressources", "learning", - specific_checks=[ - "Liste des cours / ressources", - "Catégories / filtres", - "Progression si présent", - "Contenu vidéo / texte se charge", - "Liens fonctionnels", - ]), - Route(43, "/support", "Support & aide", "learning", - specific_checks=[ - "FAQ / base de connaissances", - "Formulaire de contact / ticket", - "Chat support si présent", - "Recherche dans l'aide", - "Liens vers documentation", - ]), - - # ── Administration ── - Route(44, "/admin", "Dashboard admin", "admin", role="admin", - test_with_accounts=["admin"], - specific_checks=[ - "Stats globales plateforme (users, tracks, revenue)", - "Graphiques / KPIs", - "Accès interdit pour non-admin → redirect ou 403", - "Liens rapides vers sous-sections admin", - ]), - Route(45, "/admin/moderation", "Modération contenu", "admin", role="admin", - test_with_accounts=["admin", "moderator"], - specific_checks=[ - "Queue de modération (signalements)", - "Actions (approve, reject, ban)", - "Filtres par type de contenu / statut", - "Détail du signalement", - "Historique des actions de modération", - ]), - Route(46, "/admin/platform", "Administration plateforme", "admin", role="admin", - test_with_accounts=["admin"], - specific_checks=[ - "Configuration plateforme", - "Gestion des features flags", - "Stats système (storage, bandwidth)", - "Logs si présent", - ]), - Route(47, "/admin/transfers", "Gestion transferts/paiements", "admin", role="admin", - test_with_accounts=["admin"], - specific_checks=[ - "Liste des transferts / paiements", - "Statuts (pending, completed, failed)", - "Détail par transfert", - "Actions (approve, reject, retry)", - "Filtres et recherche", - ]), - Route(48, "/admin/roles", "Gestion des rôles", "admin", role="admin", - test_with_accounts=["admin"], - specific_checks=[ - "Liste des rôles existants", - "Permissions par rôle", - "Assignation de rôles aux utilisateurs", - "Création / modification de rôles", - "Protection contre la suppression du rôle admin", - ]), - - # ── Développeur ── - Route(49, "/developer", "Dashboard développeur", "developer", - specific_checks=[ - "Clés API (création, révocation, copie)", - "Documentation API intégrée ou liens", - "Stats d'utilisation API", - "Webhooks configurés", - "Rate limits affichés", - ]), - Route(50, "/webhooks", "Gestion webhooks", "developer", - specific_checks=[ - "Liste des webhooks configurés", - "Ajout d'un webhook (URL, events, secret)", - "Test de webhook", - "Logs de delivery (succès/échec)", - "Actions (edit, delete, toggle active)", - ]), - - # ── Redirections ── - Route(51, "/", "Redirect → /launch", "redirect", auth_required=False, - specific_checks=[ - "Redirection automatique vers /launch", - "Status code 301/302 approprié", - "Pas de flash de contenu avant redirect", - ]), - Route(52, "/tracks", "Redirect → /library", "redirect", - specific_checks=[ - "Redirection automatique vers /library", - "Fonctionne quand connecté", - ]), - Route(53, "/community", "Redirect → /social", "redirect", - specific_checks=[ - "Redirection automatique vers /social", - ]), - Route(54, "/favorites", "Redirect → /playlists/favoris", "redirect", - specific_checks=[ - "Redirection automatique vers /playlists/favoris", - ]), - Route(55, "/home", "Redirect → /dashboard", "redirect", - specific_checks=[ - "Redirection automatique vers /dashboard", - ]), - - # ── Pages d'erreur ── - Route(56, "/404", "Page non trouvée", "error", auth_required=False, - specific_checks=[ - "Affichage du message 404", - "Lien retour vers l'accueil", - "Design cohérent avec le reste du site", - "Pas d'erreurs console", - ]), - Route(57, "/500", "Erreur serveur", "error", auth_required=False, - specific_checks=[ - "Affichage du message d'erreur serveur", - "Lien retour / bouton réessayer", - "Pas de stack trace exposée", - ]), - Route(58, "/random-nonexistent-path", "Catch-all → /404", "error", auth_required=False, - specific_checks=[ - "Redirection vers la page 404", - "URL arbitraire correctement interceptée", - ]), -] - - -# ───────────────────────────────────────────── -# Prompt template -# ───────────────────────────────────────────── - -def build_test_accounts_block() -> str: - lines = [] - lines.append("Comptes de test :") - lines.append("") - lines.append("┌───────────┬───────────────────┬──────────────┐") - lines.append("│ Rôle │ Email │ Mot de passe │") - lines.append("├───────────┼───────────────────┼──────────────┤") - for i, acc in enumerate(TEST_ACCOUNTS): - role = acc["role"].ljust(9) - email = acc["email"].ljust(17) - pwd = acc["password"].ljust(12) - lines.append(f"│ {role} │ {email} │ {pwd} │") - if i < len(TEST_ACCOUNTS) - 1: - lines.append("├───────────┼───────────────────┼──────────────┤") - lines.append("└───────────┴───────────────────┴──────────────┘") - lines.append(f"Domaine : {DOMAIN}") - return "\n".join(lines) - - -def build_prompt(route: Route) -> str: - """Génère le prompt Claude Code pour une route donnée.""" - - # Déterminer les comptes à utiliser - if route.test_with_accounts: - accounts_to_use = route.test_with_accounts - elif route.role == "admin": - accounts_to_use = ["admin"] - elif route.role == "creator": - accounts_to_use = ["creator"] - elif not route.auth_required: - accounts_to_use = ["(pas de connexion)", "user"] - else: - accounts_to_use = ["user", "creator"] - - accounts_str = ", ".join(accounts_to_use) - - # Checks spécifiques - checks_block = "" - if route.specific_checks: - checks_lines = [f" - {c}" for c in route.specific_checks] - checks_block = "\n".join(checks_lines) - - # Paramètres dynamiques - params_note = "" - if route.params: - params_note = f""" -Note sur les paramètres dynamiques : - Cette route utilise le paramètre `{route.params}`. - - Teste avec une valeur VALIDE (existante en base) - - Teste avec une valeur INVALIDE (inexistante) → comportement d'erreur attendu - - Teste avec une valeur malformée (injection, caractères spéciaux) -""" - - prompt = f"""ok maintenant il faut dresser une liste exhaustive de toutes les erreurs qui existent dans la page "{route.name}" ({route.path}). -Il faut tester chaque fonctionnalité une par une avec le MCP Playwright pour simuler un utilisateur réel. - -{build_test_accounts_block()} - -Comptes à utiliser pour cette page : {accounts_str} -{params_note} -─── INSTRUCTIONS ─── - -1. AUDIT EXHAUSTIF avec Playwright MCP : - - Pour la page {route.path} ("{route.name}"), teste systématiquement : - - a) CHARGEMENT & RENDU - - La page se charge sans erreur (pas de crash, pas de blank screen) - - Pas d'erreurs dans la console navigateur (JS errors, failed network requests) - - Tous les éléments visuels attendus sont présents et visibles - - Le layout est correct (pas d'overflow, pas d'éléments qui se chevauchent) - - b) FONCTIONNALITÉS SPÉCIFIQUES À CETTE PAGE -{checks_block} - - c) RÉSEAU & API - - Tous les appels API réussissent (pas de 4xx/5xx inattendus) - - Les données affichées correspondent aux réponses API - - Gestion correcte du loading state - - Gestion correcte des erreurs réseau - - d) SÉCURITÉ - - Pas de fuite d'informations sensibles (tokens, emails dans l'URL, clés API) - - Les données d'un autre utilisateur ne sont pas accessibles - - Les actions protégées nécessitent bien l'authentification - - CSRF / XSS : vérifier les inputs utilisateur -{" - Vérifier que les rôles non-autorisés reçoivent bien un 403 ou redirect" if route.role != "user" else ""} - - e) ACCESSIBILITÉ (a11y) - - Tous les éléments interactifs ont un accessible name descriptif (pas juste "Checkbox" ou "Button") - - Les labels sont associés aux inputs - - Le focus order est logique (Tab navigation) - - Les aria-labels/aria-describedby sont présents et pertinents - - Contraste suffisant sur les textes - - f) INTERNATIONALISATION (i18n) - - Pas de mélange de langues (tout FR ou tout EN, pas les deux) - - Les traductions sont complètes (pas de clés i18n brutes affichées) - - g) RESPONSIVE - - Tester en viewport mobile (375px), tablet (768px), desktop (1280px) - - Pas d'éléments qui débordent - - Navigation mobile fonctionnelle - -2. FORMAT DU RAPPORT : - - Produis un rapport structuré exactement comme suit : - - ``` - Rapport exhaustif des erreurs — Page {route.name} ({route.path}) - Testé avec le(s) compte(s) : {accounts_str} - Date : [date du test] - - ─── - ERREURS CRITIQUES (bloquantes) - - BUG #1 — [Titre court] - - Sévérité: CRITIQUE - - Section: [section de la page] - - Repro: [étapes de reproduction] - - Erreur: [description technique] - - Source: [fichier:ligne si identifiable] - - Impact: [impact utilisateur] - - ─── - ERREURS HAUTES - - ... - - ─── - ERREURS MOYENNES - - ... - - ─── - ERREURS FAIBLES - - ... - - ─── - RÉSUMÉ - - ┌────────────────────────────┬─────────┬──────────────┐ - │ Catégorie │ Nombre │ Sévérité max │ - ├────────────────────────────┼─────────┼──────────────┤ - │ ... │ ... │ ... │ - └────────────────────────────┴─────────┴──────────────┘ - ``` - -3. CORRECTION DES BUGS : - - Après avoir dressé la liste, corrige TOUS les problèmes trouvés pour que la page soit - entièrement fonctionnelle. Pour chaque fix : - - Identifie le fichier source exact - - Applique le correctif - - Vérifie avec Playwright que le bug est résolu - -4. TRANSFORMATION EN TESTS E2E : - - Transforme toute la suite de vérification que tu viens de faire pour la page {route.path} en suite de - tests Playwright que tu peux ajouter directement à ceux existants dans {E2E_TEST_DIR}. - - Spécifications des tests : - - Fichier : {E2E_TEST_DIR}tests/{_route_to_filename(route.path)}.spec.ts - - Framework : Playwright Test (@playwright/test) - - Pattern : Page Object Model si la page est complexe - - Chaque bug trouvé ET corrigé = au moins 1 test de non-régression - - Chaque fonctionnalité testée manuellement = 1 test automatisé - - Tests organisés par describe() : "Chargement", "Fonctionnalités", "Sécurité", "a11y", "i18n" - - Les tests doivent être indépendants (setup/teardown propre) - - Utiliser les test accounts définis ci-dessus pour l'auth dans les fixtures - - Structure attendue : - ```typescript - import {{ test, expect }} from '@playwright/test'; - - test.describe('{route.name} ({route.path})', () => {{ - test.describe('Chargement & Rendu', () => {{ - test('la page se charge sans erreur', async ({{ page }}) => {{ - // ... - }}); - }}); - - test.describe('Fonctionnalités', () => {{ - // Un test par fonctionnalité - }}); - - test.describe('Sécurité', () => {{ - // Tests de sécurité - }}); - - test.describe('Accessibilité', () => {{ - // Tests a11y - }}); - - test.describe('Régression', () => {{ - // Un test par bug corrigé - }}); - }}); - ``` - - Assure-toi que les nouveaux tests s'intègrent avec la config Playwright existante - et ne dupliquent pas des tests déjà présents. -""" - return prompt.strip() - - -def _route_to_filename(path: str) -> str: - """Convertit un path comme /admin/moderation en admin-moderation.""" - clean = path.strip("/").replace("/", "-").replace(":", "") - if not clean: - clean = "root-redirect" - return clean - - -# ───────────────────────────────────────────── -# Output functions -# ───────────────────────────────────────────── - -def write_single_prompt(route: Route, output_dir: str) -> str: - """Écrit un prompt dans un fichier et retourne le chemin.""" - os.makedirs(output_dir, exist_ok=True) - filename = f"{route.number:02d}-{_route_to_filename(route.path)}.md" - filepath = os.path.join(output_dir, filename) - prompt = build_prompt(route) - - with open(filepath, "w", encoding="utf-8") as f: - f.write(prompt) - - return filepath - - -def write_combined(routes: list[Route], output_dir: str) -> str: - """Écrit tous les prompts dans un seul fichier.""" - os.makedirs(output_dir, exist_ok=True) - filepath = os.path.join(output_dir, "ALL-PROMPTS.md") - - with open(filepath, "w", encoding="utf-8") as f: - f.write(f"# VEZA — Prompts d'audit exhaustif pour les {len(routes)} routes\n") - f.write(f"# Généré le {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n") - f.write(f"# Usage : copier-coller chaque section dans Claude Code une par une.\n\n") - - for route in routes: - f.write(f"\n{'='*80}\n") - f.write(f"# ROUTE {route.number:02d}/{len(routes)} — {route.path} ({route.name})\n") - f.write(f"{'='*80}\n\n") - f.write(build_prompt(route)) - f.write("\n\n") - - return filepath - - -def list_routes(routes: list[Route]): - """Affiche toutes les routes.""" - categories = {} - for r in routes: - categories.setdefault(r.category, []).append(r) - - cat_names = { - "public": "Pages publiques", - "main_nav": "Navigation principale", - "playlists": "Playlists & Tracks", - "social": "Social & Communication", - "marketplace": "Marketplace & Commerce", - "creator": "Créateur & Analytics", - "account": "Compte & Paramètres", - "learning": "Apprentissage & Support", - "admin": "Administration", - "developer": "Développeur", - "redirect": "Redirections", - "error": "Pages d'erreur", - } - - for cat, cat_routes in categories.items(): - print(f"\n ── {cat_names.get(cat, cat)} ──") - for r in cat_routes: - auth = "🔓" if not r.auth_required else f"🔒 {r.role}" - print(f" {r.number:2d}. {r.path:<30s} {r.name:<35s} {auth}") - - print(f"\n Total : {len(routes)} routes") - - -def get_batch(routes: list[Route], batch_num: int, batch_size: int = 5) -> list[Route]: - """Retourne un sous-ensemble de routes par batch.""" - start = (batch_num - 1) * batch_size - end = start + batch_size - return routes[start:end] - - -# ───────────────────────────────────────────── -# CLI -# ───────────────────────────────────────────── - -def main(): - parser = argparse.ArgumentParser( - description="VEZA — Générateur de prompts Claude Code pour audit exhaustif", - formatter_class=argparse.RawDescriptionHelpFormatter, - epilog=""" -Exemples : - python veza-prompt-generator.py # Tous les prompts → ./prompts/ - python veza-prompt-generator.py --route /settings # Un seul prompt - python veza-prompt-generator.py --route /admin # Un seul prompt - python veza-prompt-generator.py --list # Liste les routes - python veza-prompt-generator.py --batch 3 # Routes 11-15 - python veza-prompt-generator.py --batch 3 --size 10 # Routes 21-30 - python veza-prompt-generator.py --combined # Tout dans un fichier - python veza-prompt-generator.py --category admin # Toutes les routes admin - python veza-prompt-generator.py --out ./my-prompts # Dossier custom - python veza-prompt-generator.py --print /settings # Affiche dans le terminal - """, - ) - parser.add_argument("--route", type=str, help="Génère le prompt pour une route spécifique (ex: /settings)") - parser.add_argument("--list", action="store_true", help="Liste toutes les routes") - parser.add_argument("--batch", type=int, help="Numéro de batch (1-indexed)") - parser.add_argument("--size", type=int, default=5, help="Taille d'un batch (défaut: 5)") - parser.add_argument("--combined", action="store_true", help="Génère un seul fichier avec tous les prompts") - parser.add_argument("--category", type=str, help="Filtre par catégorie (public, admin, social, ...)") - parser.add_argument("--out", type=str, default=OUTPUT_DIR, help=f"Dossier de sortie (défaut: {OUTPUT_DIR})") - parser.add_argument("--print", dest="print_route", type=str, help="Affiche le prompt dans le terminal au lieu de l'écrire") - parser.add_argument("--critical-only", action="store_true", help="Uniquement les routes avec auth/rôle spécifique") - - args = parser.parse_args() - - # ── List ── - if args.list: - list_routes(ROUTES) - return - - # ── Print single ── - if args.print_route: - route = next((r for r in ROUTES if r.path == args.print_route), None) - if not route: - print(f"❌ Route '{args.print_route}' non trouvée.", file=sys.stderr) - print(" Utilise --list pour voir toutes les routes.", file=sys.stderr) - sys.exit(1) - print(build_prompt(route)) - return - - # ── Single route ── - if args.route: - route = next((r for r in ROUTES if r.path == args.route), None) - if not route: - print(f"❌ Route '{args.route}' non trouvée.", file=sys.stderr) - print(" Utilise --list pour voir toutes les routes.", file=sys.stderr) - sys.exit(1) - filepath = write_single_prompt(route, args.out) - print(f"✅ Prompt généré : {filepath}") - return - - # ── Filter by category ── - routes = ROUTES - if args.category: - routes = [r for r in ROUTES if r.category == args.category] - if not routes: - print(f"❌ Catégorie '{args.category}' non trouvée.", file=sys.stderr) - cats = sorted(set(r.category for r in ROUTES)) - print(f" Catégories disponibles : {', '.join(cats)}", file=sys.stderr) - sys.exit(1) - - if args.critical_only: - routes = [r for r in routes if r.role != "user" or not r.auth_required] - - # ── Batch ── - if args.batch: - routes = get_batch(routes, args.batch, args.size) - if not routes: - total_batches = (len(ROUTES) + args.size - 1) // args.size - print(f"❌ Batch {args.batch} vide. Batches disponibles : 1-{total_batches}", file=sys.stderr) - sys.exit(1) - print(f"📦 Batch {args.batch} ({len(routes)} routes)") - - # ── Combined ── - if args.combined: - filepath = write_combined(routes, args.out) - print(f"✅ Fichier combiné généré : {filepath} ({len(routes)} prompts)") - return - - # ── All individual files ── - os.makedirs(args.out, exist_ok=True) - for route in routes: - filepath = write_single_prompt(route, args.out) - print(f" ✅ {route.number:2d}. {route.path:<30s} → {filepath}") - - print(f"\n🎉 {len(routes)} prompts générés dans {args.out}/") - print(f" Copie chaque prompt dans Claude Code un par un.") - total_batches = (len(routes) + args.size - 1) // args.size - print(f" Ou utilise --batch N (1-{total_batches}) pour y aller par groupes de {args.size}.") - - -if __name__ == "__main__": - main()