# Boucle proto-review (gate fonctionnel + visuel)

Le gate de convergence du proto, analogue à `code-review` pour le code. L'oracle (`design check`) et la couverture (`design coverage`) sont des regex sur le **source** : ils ne chargent rien, ne rendent rien, ne cliquent rien. Ils déclarent `CLEAN` un proto dont la modale est figée au chargement ou dont les écrans sont injoignables. Ce gate comble ce trou : il **charge le proto dans un navigateur**, vérifie qu'il marche et qu'il ressemble à l'app réelle, produit des findings, et reboucle jusqu'à passe.

> Sans ce gate, chaque proto sort cassé jusqu'à ce qu'un humain le rattrape. Le but est de déplacer ce contrôle **avant** la revue PM (le temps humain coûteux qu'on protège).

## Prérequis

- MCP navigateur `chrome-mcp` (déclaré dans `## Required tools` du SKILL).
- Le proto servi **localement** pour que `proto.css` et les assets relatifs résolvent :
  ```bash
  cd design-protos/<iid> && python -m http.server 8765 &
  ```
  Ouvrir `http://localhost:8765/index.html` (ou chaque `<écran>.html`) via `new_page`. Tuer le serveur en fin de boucle.

## A. Gate fonctionnel (assertions déterministes)

Piloter le navigateur avec `evaluate_script` / `click` / `list_console_messages`. Toutes les assertions ci-dessous sont **bloquantes** (une seule rouge => finding).

1. **Aucun overlay bloquant au chargement.** Pour chaque **racine** de modale/overlay (l'élément porteur de la classe de masquage), `el.checkVisibility() === false`. L'app s'ouvre sur l'état nominal (liste peuplée), jamais sur une modale.
   - C'est le bug de cette session : `style="display:flex"` inline écrasait `.hidden`. Le gate l'attrape parce qu'il lit la **visibilité réelle**, pas la classe.
   - **Piège (vérifié)** : ne pas tester `getComputedStyle(el).display !== 'none'` sur un nœud quelconque. Un descendant d'un parent `display:none` rapporte quand même son **propre** `display` (ex: `block`) → faux positif sur les `[role="dialog"]` internes. `checkVisibility()` (ou `offsetParent === null`) tient compte des ancêtres cachés ; cibler les **racines** d'overlay.
2. **Rendu de l'état nominal.** L'état par défaut (liste peuplée) est visible et a du contenu réel (lignes injectées, pas un tbody vide).
3. **Zéro erreur JS.** `list_console_messages` ne contient aucune entrée de niveau `error` après chargement et après chaque interaction.
4. **Atteignabilité (coverage-map).** Lire `coverage-map.md`. Pour **chaque** écran/état listé (FR → écran → état), piloter son **vrai déclencheur** (bouton primaire, icône d'action de ligne, switcher d'état, soumission de formulaire) et asserter que l'écran cible devient visible (`display !== 'none'`), puis le refermer (backdrop / ×) et asserter qu'il se cache. Un écran du coverage-map non atteignable = finding.
5. **Aucun déclencheur mort.** Chaque déclencheur listé produit un changement DOM observable au clic (ouverture, commutation d'état, message d'erreur). Un clic sans effet = finding.

Snippet d'assertion type (à adapter au proto) :

```js
() => {
  // checkVisibility() tient compte des ancêtres display:none (≠ getComputedStyle.display)
  const roots = ['modal-create','modal-edit','modal-delete-confirm','modal-delete-blocked']; // racines porteuses de la classe de masquage
  const openOnLoad = roots.filter(id => document.getElementById(id).checkVisibility()).length;
  return { openOnLoad, nominalVisible: document.getElementById('state-populated').checkVisibility(), rows: document.querySelectorAll('#depot-rows tr').length };
}
```

## B. Gate visuel (jugement vision)

Piloter le proto vers chaque écran, prendre un `take_screenshot`, puis juger. Pour chaque écran :

1. **Fidélité au design system / à l'app réelle.** Poppins partout, tokens (pas de couleur/gris hors palette), densité et espacements du vrai composant, vrais composants du catalogue `components/ui/` (pas une approximation « inspirée de »). Comparer mentalement au vrai écran de référence lu en step 4.
2. **Satisfaction des AC.** Chaque critère d'acceptation du ticket a un écran qui le rend visiblement satisfait (champ requis marqué, état d'erreur affiché, action de confirmation présente).
3. **Cohérence responsive** si la feature le prévoit (desktop + mobile).

Le jugement est **adversarial** : chercher activement ce qui cloche (« très brouillon et sommaire » est un finding, pas un détail). En cas de doute sur la fidélité, c'est un finding.

## C. Findings

Émettre une liste structurée, une entrée par défaut :

```
- [<fonctionnel|visuel>] <écran/état> : <ce qui cloche> → <correction attendue>
```

## D. Convergence

- Findings non vides → **patcher le proto** (revenir au step 4 du SKILL), puis **rejouer** `design check` (step 6) + `design coverage` (step 7) + ce gate. Régénérer est gratuit.
- **Max 5 itérations.** Non-convergence après 5 → escalader à l'humain (en mode CI : footer `DESIGN_RESULT` `escalated`, raison = `proto-review never PASS after 5`).
- Passe (fonctionnel + visuel sans findings) → publier (step 9).

## Mode auto / CI

Le gate tourne à l'identique : le jugement visuel est le même Claude, le navigateur est `chrome-mcp` dans l'image de job. Le proto n'est **jamais** publié ni passé en `design::review` tant que proto-review n'est pas vert. Le serveur local + le navigateur doivent être disponibles dans l'image CI (sinon escalader avec raison `chrome-mcp unavailable`).
