RenderLoop — documentation intégrale (llms-full.txt) ===================================================== Source : https://renderloop.jnk.ovh/ — pages : https://renderloop.jnk.ovh/docs/, https://renderloop.jnk.ovh/agents/ et https://renderloop.jnk.ovh/demo/ English full text : https://renderloop.jnk.ovh/en/llms-full.txt Dépôt : https://github.com/linquant/render-loop Version : 0.4.0 — dernière mise à jour : 2026-10-20 Licence : MIT. Auteur : Johann Nowacki. Paquet Composer : linquant/render-loop. Ce document est autonome : il contient de quoi répondre aux questions d'usage sans lire le HTML du site. 1. En une phrase ---------------- RenderLoop est une boucle de retour visuelle pour applications PHP : capturer le HTML rendu d'une page avant modification, puis le comparer après, pour ne voir que le changement réel — le bruit (DebugBar, scripts, jetons CSRF, horodatages, chrome du site) étant masqué automatiquement. 2. Le problème -------------- Après une modification de code, la question utile n'est pas « qu'ai-je écrit ? » mais « qu'est-ce que la page rendue a changé ? ». Un `git diff` montre l'édition source ; il ne montre ni les effets indirects (un include partagé, une fonction commune, une requête qui vide une table), ni une modification faite en parallèle sur la même page. Sur une grosse base de code legacy où une page n'a aucun test, ces effets passent inaperçus. RenderLoop compare l'état rendu avant et après. Tout ce qui change en dehors de l'édition voulue est une régression. git diff RenderLoop montre VOTRE édition -> montre le changement RENDU - $price = 1295; p.price : "1295.00" -> "995.00" + $price = 995; + un include partagé a vidé le panier 3. La boucle ------------ 1. Capturer l'état rendu avant de toucher au code : `-a capture -u -t before`. 2. Modifier le code (à la main ou via un agent), et s'assurer que le serveur sert bien le nouveau fichier (opcache, étapes de build). 3. Relancer `-a recheck -u `. Le diff doit contenir uniquement le changement voulu. Tout le reste est une régression. Sans capture « before », il n'y a rien à comparer : c'est le piège numéro un. 4. Installation et prérequis ---------------------------- composer require linquant/render-loop Le binaire s'appelle alors `vendor/bin/render-loop` (dans un checkout du dépôt : `bin/render-loop`). Prérequis : PHP 8.2 ou plus, avec `ext-dom`, `ext-libxml`, `ext-json` et `ext-curl`. Composer exige `ext-curl` à l'installation ; à l'exécution, seule la capture par requête HTTP l'utilise, la stratégie d'envoi de HTML déjà rendu n'appelle pas curl. Composer installe symfony/console (6.4, 7.x ou 8.x), symfony/css-selector et sebastian/diff. Pour développer : PHPUnit 11 ou 12 et PHPStan 2 (niveau 10 / max sur `src` et `tests`). Dans les exemples, `render-loop` désigne `vendor/bin/render-loop` lorsque `vendor/bin` est dans le `PATH`. 5. Démarrage rapide ------------------- # 1. capturer l'état AVANT la modification vendor/bin/render-loop -a capture -u "http://localhost:8000/my_page.php" -t before # 2. modifier la page (à la main ou via un agent) # 3. revérifier : le diff ne doit contenir QUE le changement voulu vendor/bin/render-loop -a recheck -u "http://localhost:8000/my_page.php" 6. Référence de la CLI ---------------------- Forme générale : render-loop -a [options] L'action par défaut est `recheck`. Deux formats de sortie : `table` (défaut, lisible par un humain) et `json` (`-f json`, destiné aux machines et aux agents) pour les actions qui le prennent en charge — voir la liste au §8. 6.1 capture ----------- Récupère une page par HTTP (curl) et enregistre son HTML rendu comme instantané. render-loop -a capture -u "http://localhost:8000/page.php" -t before Options : `-u/--url` (requis), `-t/--tag` (libellé, ex. `before`), `-d/--dir`, `-c/--config`, `--allow-host`, `--cookie-name`, `--method`, `--header`, `--data`, `--ajax`. Sortie texte : `Snapshot saved: `, où l'identifiant a la forme `_` (par exemple `page.php_1792491901.2345`). Code de retour 0 en cas de succès, non nul en cas d'erreur (URL refusée par le garde-fou, statut HTTP supérieur ou égal à 400, échec curl). Capture d'un endpoint AJAX interne (fragment) sans navigateur : render-loop -a capture -u "http://localhost/ajax/price.php?id=42" --ajax -t before `--ajax` ajoute l'en-tête `X-Requested-With: XMLHttpRequest` (sans doublon s'il est déjà fourni via `--header`). Pour un endpoint en POST : render-loop -a capture -u "http://localhost/search" --method POST --data "q=bag&page=2" --ajax -t before `--data` implique POST si `--method` n'est pas fourni. `--header` est répétable, au format `"Nom: valeur"`. 6.2 store --------- Enregistre un HTML déjà rendu, lu sur l'entrée standard. Sert aux pages JS/SPA dont le HTML est produit par un navigateur headless ou un autre outil. cat rendered.html | render-loop -a store -u "http://localhost/app" -t before Options : `-u/--url` (requis), `-t/--tag` (défaut `store`), `-d/--dir`, `-c/--config`. Un STDIN vide provoque une erreur et un code de retour non nul. 6.3 recheck ----------- Recapture la page et la compare à sa dernière référence : la capture la plus récente qui n'est pas elle-même un recheck (ou la baseline explicitement désignée). C'est l'action par défaut : plusieurs rechecks successifs ne font donc pas dériver la base de comparaison. render-loop -a recheck -u "http://localhost/page.php" render-loop -a recheck -u "http://localhost/page.php" -f json Options : `-u/--url` (requis), `-d/--dir`, `-c/--config`, `-f/--format`, `--max-hunks`, `--exit-code`, `--expect-selector`, `--allow-host`, `--cookie-name`, `--method`, `--header`, `--data`, `--ajax`. Code de retour : 0 même si le diff est non vide, sauf avec `--exit-code` (comportement de `git diff --exit-code`). Avec `--expect-selector`, c'est le verdict de confinement qui pilote le code de retour. 6.4 compare ----------- Compare deux instantanés précis, désignés par leur identifiant. Un identifiant a la forme `_`, par exemple `page.php_1792491901.2345`. render-loop -a list -f json render-loop -a compare --from=page.php_1792491901.2345 --to=page.php_1792491902.6789 Options : `--from` et `--to` (requis), `-d/--dir`, `-c/--config`, `-f/--format`, `--max-hunks`, `--exit-code`, `--expect-selector`. Même forme de sortie et mêmes codes de retour que `recheck`. 6.5 list -------- Liste les instantanés enregistrés, éventuellement filtrés par URL. render-loop -a list render-loop -a list -u "http://localhost/page.php" -f json Options : `-u/--url`, `-d/--dir`, `-f/--format`. Sortie JSON : tableau d'objets `{"id": string, "url": string, "tag": string|null, "datetime": string}`. Code de retour 0. [{"id": "page.php_1792491901.2345", "url": "http://localhost/page.php", "tag": "before", "datetime": "2026-10-20 10:15:00"}] 6.6 calibrate ------------- Capture la même page plusieurs fois sans modification : tout ce qui diffère entre des captures identiques est du bruit (horodatages, compteurs, jetons). L'action propose les sélecteurs à masquer, ou les écrit dans la configuration avec `--apply`. render-loop -a calibrate -u "http://localhost/page.php" render-loop -a calibrate -u "http://localhost/page.php" --samples=4 --apply -c normalizer.json Options : `-u/--url` (requis), `--samples` (nombre de captures, minimum 2, défaut 2), `--apply`, `-c/--config`, `-d/--dir`, `-f/--format`, `--allow-host`, `--cookie-name`, `--method`, `--header`, `--data`, `--ajax`. Deux captures sont séparées d'environ 1,1 seconde pour franchir une frontière de seconde et faire varier les horodatages. Plus d'échantillons attrapent un bruit plus lent. Sortie JSON : `{"candidates": [string], "applied": [string]}`. Code de retour 0, y compris quand aucun bruit n'est détecté. 6.7 mask-add ------------ Ajoute des sélecteurs de bruit à la configuration sans passer par la calibration. Utile quand on sait déjà qu'un widget de chat ou une bannière de cookies est du bruit. render-loop -a mask-add --selector='.chat-widget' --selector='.cookie-banner' -c normalizer.json Options : `--selector` (requis, répétable), `-c/--config`, `-f/--format`. L'opération est idempotente : aucun doublon n'est ajouté. Sortie JSON : `{"applied": [string]}` avec uniquement les sélecteurs réellement ajoutés. Code de retour non nul si aucun `--selector` n'est fourni. 6.8 capabilities ---------------- Renvoie le contrat machine de toutes les actions : nom, résumé, options requises et optionnelles, forme de la sortie JSON, sémantique des codes de retour. render-loop -a capabilities -f json Options : `-f/--format`. Sans `-f json`, affiche un résumé lisible qui renvoie vers la version JSON. Code de retour 0. C'est le point d'entrée recommandé pour un agent qui ne veut pas analyser le texte d'aide. 6.9 advise ---------- Renvoie le mode opératoire destiné à un agent IA : la boucle à suivre, comment lire la sortie, et les pièges. Là où `capabilities` décrit le « quoi », `advise` transmet le « comment ». render-loop -a advise render-loop -a advise -f json Options : `-f/--format`. Sortie JSON : `{"audience": string, "one_liner": string, "workflow": [string], "reading_the_output": [string], "pitfalls": [string]}`. Code de retour 0. Le contenu fait foi à l'exécution ; le fichier AGENTS.md du dépôt le reflète pour les agents qui lisent le dépôt d'abord, mais en cas de divergence, la CLI gagne. 6.10 golden-record ------------------ Épingle la sortie rendue actuelle d'une suite de cas représentatifs, avant un refactor (approval testing / characterization testing). render-loop -a golden-record --suite=product Options : `--suite` (requis), `--golden-dir` (défaut `golden`), `-c/--config`, `-d/--dir`, `--allow-host`, `--cookie-name`, `--method`, `--header`, `--data`, `--ajax`. Le manifeste `golden/.json` déclare les cas et le masque : { "name": "shop", "cases": [ { "id": "catalog", "url": "http://localhost:8123/index.php" }, { "id": "product-aeron", "url": "http://localhost:8123/product.php?sku=aeron" } ], "mask": [".phpdebugbar", ".server-time", "input[name=csrf_token]"] } La sortie normalisée est écrite dans `golden//.html`. Ce fichier doit être commité : le diff se relit dans git comme un fichier `.approved`. Sortie texte : nombre de cas épinglés et taille du masque. Le masque du manifeste est enrichi automatiquement à l'enregistrement, puis réécrit dans le fichier. 6.11 golden-verify ------------------ Recapture chaque cas d'une suite et le compare à son golden. Échoue en cas de dérive du rendu. render-loop -a golden-verify --suite=product Options : `--suite` (requis), `--golden-dir`, `-c/--config`, `-d/--dir`, `--allow-host`, `--cookie-name`, `--method`, `--header`, `--data`, `--ajax`. La sortie est toujours en texte : `-f json` n'est pas pris en charge par cette action. Sortie par cas : `OK`, `CHANGED` (avec résumé et diff unifié) ou golden manquant. Code de retour non nul si au moins un cas a changé ou manque ; 0 si tout correspond. Point important : capturer contre des données figées, pour que le diff reflète le code et non les données. 7. Options transverses ---------------------- - `-a/--action` : capture, store, recheck, compare, list, calibrate, mask-add, golden-record, golden-verify, capabilities, advise. Défaut : recheck. - `-u/--url` : URL de la page. - `-t/--tag` : libellé de l'instantané (ex. `before`). - `--from`, `--to` : identifiants des instantanés pour `compare`. - `--suite` : nom de la suite golden (manifeste `/.json`). - `--golden-dir` : répertoire des suites golden. Défaut : `golden`. - `-d/--dir` : répertoire de stockage des instantanés. Défaut : `.render-loop/snapshots`. - `-c/--config` : fichier de configuration du normaliseur (sélecteurs de bruit). Défaut : le `config/normalizer.json` fourni avec le paquet. - `-f/--format` : `table` ou `json`. Défaut : `table`. - `--max-hunks` : nombre maximal de hunks affichés. Défaut : 40, minimum 1. - `--allow-host` : hôte autorisé en plus de `localhost` et `127.0.0.1`. Répétable. - `--cookie-name` : nom du cookie de session. Défaut : `PHPSESSID`. - `--samples` : nombre de captures de calibration (2 minimum). Défaut : 2. - `--apply` : `calibrate` écrit les sélecteurs trouvés dans la configuration. - `--selector` : sélecteur de bruit pour `mask-add`. Répétable. - `--expect-selector` : pour `recheck` et `compare`, portée autorisée du changement. Répétable. - `--exit-code` : code de retour non nul quand le diff est non vide (`recheck`, `compare`), comme `git diff --exit-code`. - `--method` : méthode HTTP de capture (`POST`, etc.). Défaut : GET, ou POST si `--data` est fourni. `--header` ajoute un en-tête `"Nom: valeur"` (répétable), `--data` fournit le corps, `--ajax` ajoute `X-Requested-With: XMLHttpRequest`. 8. Format JSON -------------- Les actions `recheck`, `compare`, `list`, `calibrate`, `mask-add`, `capabilities` et `advise` acceptent `-f json`. Seuls `recheck` et `compare` renvoient le résumé de diff ; les autres ont leur propre forme, décrite ci-dessous. `capture`, `store`, `golden-record` et `golden-verify` écrivent toujours du texte : le flag y est ignoré. Les sorties JSON sont lisibles (indentation, Unicode et slashs non échappés). 8.1 Diff (`recheck`, `compare`) { "summary": { "lines_added": 1, "lines_removed": 1, "hunks": 1, "truncated": false, "tags_touched": ["p"], "changes": [ { "kind": "text", "selector": "article.product-detail > p.price", "context": "Aeron Chair", "before": "$1295.00", "after": "$995.00" } ] }, "diff": "--- ...\n+++ ...\n@@ ..." } - `lines_added`, `lines_removed`, `hunks` : compteurs du diff unifié. - `truncated` : vrai si le diff affiché a été plafonné par `--max-hunks`. - `tags_touched` : noms des balises présentes dans les lignes modifiées. - `changes` : couche sémantique, une entrée par changement localisé. - `kind` : `text` (contenu modifié), `added` (nœud apparu) ou `removed` (nœud disparu) ; `before` et `after` portent les valeurs selon le type. - `selector` : chemin CSS au mieux, dégradant de `#id` vers `tag.class` puis vers `tag:nth-of-type(n)`. - `context` : texte du titre ou libellé le plus proche, ancre lisible sur du HTML legacy sans id ni classe (le `

`, un ``, la `` précédente d'une ligne clé/valeur). `null` si rien de proche ne convient. - `diff` : diff unifié du HTML normalisé, toujours présent. C'est le socle fiable ; `changes` ne fait que l'enrichir. Pour un lecteur à budget de tokens : lire `summary` d'abord, ne dérouler `diff` que si le résumé est non vide, et décider à partir du résumé JSON plutôt que du code de retour (`recheck` et `compare` renvoient 0 même avec un diff, sauf `--exit-code`). Avec `--expect-selector`, la réponse gagne un bloc `assertion` : "assertion": { "expected_selectors": [".price"], "satisfied": false, "inside_scope_changed": true, "reason": "1 change(s) outside the expected scope", "out_of_scope": { "lines_added": 1, "lines_removed": 1, "hunks": 1, "diff": "..." } } `out_of_scope` est un résumé de diff (mêmes champs que `summary`, plus `diff`). 8.2 Autres sorties JSON - `list` : `[{"id": string, "url": string, "tag": string|null, "datetime": string}]`. - `calibrate` : `{"candidates": [string], "applied": [string]}`. - `mask-add` : `{"applied": [string]}`. - `capabilities` : manifeste `{"tool": "render-loop", "formats": ["table", "json"], "exit_codes": string, "actions": [...]}` ; chaque action porte `name`, `summary`, `required`, `optional`, `output`, `exit_code`. - `advise` : `{"audience", "one_liner", "workflow", "reading_the_output", "pitfalls"}`. 9. Masquage du bruit et calibration ------------------------------------ Avant comparaison, le HTML est normalisé : les blocs `