Files
tribunal-app/CLAUDE.md
T
Valentin ROBIN e6f4463cb0 Design responsive, logo, et correctif RLS upload photo
Design & UX :
- Renomme "Administration" en "Admin" (nav, page, docs).
- Logo original (marteau de juge stylisé navy/or) en favicon (src/app/icon.svg,
  remplace le favicon.ico par défaut de create-next-app) et dans la navbar
  (components/logo.tsx).
- Navbar : menu hamburger sur mobile (les liens débordaient à partir de
  ~4 entrées sur petit écran).
- Leaderboard : lignes en deux niveaux sur mobile (infos membre / contrôles
  juges) au lieu d'un seul flex-wrap qui devenait illisible.
- Contrôles de points juges (JudgePointControls) : boutons et champ montant
  réduits en mode compact pour tenir dans les colonnes du podium sur petit
  écran.
- Podium : scroll horizontal de secours (overflow-x-auto) si le contenu
  dépasse malgré tout sur très petits écrans.
- Table admin : pseudo/avatar sur une ligne, rôle/statut/actions sur une
  autre en mobile (le flex-1 sur l'input pseudo n'avait aucun effet en
  layout colonne).

Correctif sécurité/fonctionnel :
- storage.objects n'avait que des policies INSERT et UPDATE pour le bucket
  avatars, pas de policy SELECT. Postgres a besoin de lire la ligne
  existante pour évaluer la clause USING d'un UPDATE : sans SELECT, tout
  remplacement de photo (upsert) échouait avec "new row violates row-level
  security policy", même si les policies INSERT/UPDATE étaient correctes.
  Ajout d'une policy SELECT publique sur le bucket avatars (cohérent avec
  le fait que le bucket est déjà public en lecture).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-11 16:47:43 +02:00

7.7 KiB

@AGENTS.md

Le Tribunal

Application web pour un groupe d'amis (~12 personnes) qui anime un jeu et des olympiades pendant une semaine de vacances : comptes, rôles, leaderboard en direct, attribution de points par des juges, journal public des événements. Ré-thématisable par édition (un thème différent chaque année).

1. État actuel

V1, V2 et V3 sont livrées :

  • V1 : comptes, profils, leaderboard statique.
  • V2 : authentification par email réel (plus de pseudo+email interne), rôles public/judge, verrou de pseudo (modifiable une fois), section Admin.
  • V3 : attribution de points par les juges (RPC sécurisée, +/- avec confirmation explicite), journal public « le crieur », podium top 3, flèches de progression, recadrage/compression photo côté client.

Hors périmètre pour l'instant (voir roadmap en fin de doc) : éditions/thèmes, le jeu du Tribunal lui-même (Char, roulette, ostracisme, timer du Gardien), mode grand écran.


2. Stack technique

  • Framework : Next.js 16 (App Router) + TypeScript
  • Style : Tailwind CSS v4 (design tokens en variables CSS, voir globals.css)
  • Auth + Base + Stockage + Temps réel : Supabase (Auth, Postgres, Storage, Realtime)
  • Recadrage photo : react-easy-crop + canvas natif (resize/compression client, jamais d'upload de l'image brute)
  • Déploiement : Vercel (front) + Supabase (managé) — pas encore déployé, tourne en local pour l'instant

Next.js 16 a renommé middleware.ts en proxy.ts (fichier src/proxy.ts, fonction proxy). src/lib/supabase/middleware.ts est un nom de fichier utilitaire interne, sans rapport avec cette convention.


3. Modèle de données (supabase/schema.sql, source de vérité)

Le script est idempotent : toujours le ré-exécuter en entier après une modification, jamais de migration incrémentale séparée.

public.profiles (liée à auth.users, créée automatiquement par un trigger sur auth.users)

Colonne Notes
id = auth.users.id
pseudo affiché partout, unique (insensible à la casse)
avatar_url URL publique Storage ({user_id}.webp)
points jamais modifiable directement — uniquement via RPC award_points
role public (défaut) ou judge
pseudo_locked passe à true automatiquement au 1er changement de pseudo
previous_rank repère de classement, mis à jour par reset_rank_reference()

public.points_log (journal, lecture seule côté client)

id, target_id, judge_id, delta, reason, created_at — écrit uniquement par la RPC award_points.

Colonnes sensibles : RLS + trigger, jamais confiance au client

La RLS est au niveau ligne : elle ne peut pas exprimer « cette colonne seulement si tel rôle ». Le verrouillage fin des colonnes (role, points, pseudo_locked, pseudo figé, previous_rank) passe par le trigger enforce_profile_update() :

  • points : bloqué pour tout le monde, y compris les juges, en écriture directe. Seule la RPC award_points() (SECURITY DEFINER) peut la modifier, via un flag de session (app.bypass_points_lock) qui autorise explicitement CETTE écriture précise.
  • role, previous_rank : modifiables uniquement par un judge.
  • pseudo : modifiable une fois par le propriétaire (auto-lock ensuite), ou à tout moment par un judge.
  • auth.uid() is null (SQL Editor, migrations) bypass le trigger : ces accès sont déjà fiables par construction.

Ce pattern (RLS pour l'accès à la ligne + trigger BEFORE UPDATE pour l'accès aux colonnes) est la convention du projet — le reproduire pour toute nouvelle colonne sensible plutôt que d'inventer autre chose.

RPC exposées

  • is_pseudo_taken(p_pseudo) — anon + authenticated, ne renvoie qu'un booléen (l'anon ne peut pas lire profiles).
  • award_points(p_target_id, p_delta, p_reason) — authenticated, vérifie role = 'judge' côté serveur, jamais côté client.
  • reset_rank_reference() — authenticated, vérifie role = 'judge', fige le classement courant dans previous_rank.

4. Authentification

Email + mot de passe (Supabase Auth impose l'unicité de l'email). Confirmation d'email activée — nécessite un SMTP custom configuré côté Supabase (le service intégré est trop limité en volume, voir README). Le pseudo est un simple nom d'affichage stocké dans profiles, jamais l'email n'est montré aux autres membres.


5. Pages & navigation

Route Contenu
/ Redirige vers /leaderboard si connecté, sinon /login
/login Email + mot de passe
/signup Email + mot de passe + pseudo + photo (recadrée) ; gère l'attente de confirmation email
/leaderboard Podium top 3 + liste, Realtime, contrôles de points pour les juges
/journal « Le crieur » — fil live des attributions de points, lecture pour tous
/profile Pseudo (verrouillable), photo, déconnexion
/admin Juges uniquement, vérifié côté serveur (Server Component) — gestion des membres, reset du repère de classement

/leaderboard, /profile, /admin, /journal sont protégées par src/proxy.ts (redirection /login si non connecté). L'accès juge-only de /admin n'est pas géré par le proxy (il n'a pas facilement le rôle) — c'est la page elle-même qui vérifie et redirige.


6. Look & feel

Design tokens en variables CSS (globals.css) : --navy #0F2748, --gold #C9A227, --ivory #F4ECD8, --ink #2A2116. Ne jamais coder une couleur en dur dans un composant — passer par ces tokens pour que le re-thème par édition reste possible.

Convention de carte : rounded-xl border border-navy/10 bg-white shadow-sm (formulaires, podium) ou rounded-lg pour les lignes de liste. Bouton primaire : bg-navy text-ivory hover:bg-navy/90. Bouton secondaire : border border-navy/20 hover:bg-navy/5.


7. Conventions

  • Toujours passer par les policies RLS + trigger ; ne jamais exposer la service_role key côté client.
  • Toute nouvelle colonne sensible sur profiles (ou une future table) suit le pattern RLS+trigger de la section 3, pas un grant/revoke par colonne (qui ne peut pas distinguer les rôles applicatifs).
  • Aperçus locaux d'image avant upload (crop, fichier choisi) = blob:/data: URL → toujours unoptimized sur next/image ou une balise <img> classique (next/image ne sait pas résoudre ces schémas côté serveur).
  • Commandes : npm run dev, npm run build, npm run lint.
  • Le schéma SQL (supabase/schema.sql) est la seule source de vérité pour la base — le tenir à jour à chaque changement de données/sécurité, et le garder idempotent.

8. Roadmap (pas encore codé)

  • Éditions & thèmes : table editions (année, thème, mood board, vocabulaire) ; les points deviendront rattachables à une édition.
  • Le jeu du Tribunal : le Char (zone de pénalité), batailles de cul sec, ostracisme, roulette, timer du Gardien.
  • Mode grand écran : vue leaderboard optimisée pour vidéoprojecteur.
  • Déploiement : pas encore fait (Vercel + Supabase managé prévus, voir README).