Documente les lots Q à Y, et corrige ce que la série a rendu faux

Nouvelle section « Lots Q à Y » dans CLAUDE.md : les décisions et les pièges,
pas la description du code. Elle dit aussi, en tête, ce qui n'est pas éprouvé —
aucun écran de la série n'a été vérifié en navigateur.

⚠️ Quatre passages étaient devenus FAUX, et un fichier qui fait autorité et qui
ment est pire qu'un fichier incomplet :

- le lot H actait « le retour passe par l'historique du navigateur, jamais par
  une destination calculée » et décrivait js/navigation.js, supprimé depuis. Le
  texte d'origine est conservé en citation — son motif reste vrai — sous le
  renversement qui explique pourquoi il n'a pas suffi, et pourquoi toute route
  nouvelle doit entrer dans la table de RemonteeRoutes ;
- « Cinq instantanés » : ils sont neuf, et une vue d'API sans instantané est un
  écran mort hors-ligne ;
- le modèle de données ignorait Livre.NombrePages, Livre.UrlNotice et l'entité
  RevueSouhaitee, dont Utilisateur est une frontière comme pour LivreSouhaite ;
- la fiche d'une série ne porte plus de barre d'actions flottante en
  consultation ; elle ne subsiste que sur l'écran d'ordre.

IDEES.md marque les lots Q à Y traités, en gardant le texte qui dit le besoin,
et ouvre trois points que la série laisse : la vérification en navigateur de
tous les écrans touchés, le réordonnancement des envies de revues, et un
éventuel signalement « déjà possédée » pour elles — plus douteux qu'il n'y
paraît, une envie portant souvent la revue entière. Ce qui était déjà ouvert
(A5, K1, N2, homonymes, OpenLibrary, EAN-2) reste intact.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mathieu
2026-08-21 14:56:23 +02:00
co-authored by Claude Opus 5
parent 498579f27b
commit d7ea12ddbf
2 changed files with 332 additions and 10 deletions
+275 -10
View File
@@ -348,9 +348,13 @@ l'utilisateur, et la douchette tape des chiffres.
| `Services/FiltreLivresLocal.cs` | Recherche, filtres et tri hors-ligne sur l'instantané |
| `Services/ServiceLivresApi.cs` | Lectures avec repli sur le cache, écritures refusées |
**Cinq instantanés, un par vue de l'API**, jamais un par requête : `catalogue` (le catalogue
**entier**, sans filtre), `auteurs`, `prets-en-cours`, `utilisateur`, et `souhaits` depuis le
2026-08-18 (voir « La liste d'envies est le cinquième instantané »). C'est exactement ce qui
**Un instantané par vue de l'API**, jamais un par requête. Ils étaient cinq le 2026-08-18 ;
ils sont **neuf** depuis le 2026-08-21 : `catalogue` (le catalogue **entier**, sans filtre),
`auteurs`, `prets-en-cours`, `utilisateur`, `souhaits` (voir « La liste d'envies est le cinquième
instantané »), `series`, `revues`, `souhaits-revues` et `version`. ⚠️ **Toute nouvelle vue de
l'API doit en recevoir un**, faute de quoi l'écran correspondant est mort hors-ligne — c'est
exactement ce qui était arrivé à la liste d'envies, et le symptôme (un `404` en plein écran) ne
désignait pas la cause. C'est exactement ce qui
justifie IndexedDB plutôt que le cache du service worker : un cache de réponses HTTP ne
restituerait que les URL déjà visitées, donc **une recherche jamais tapée en ligne ne rendrait
rien**. Vérifié en exécution — hors-ligne, chercher un auteur jamais affiché auparavant remonte
@@ -717,7 +721,9 @@ Livre
├── Editeur
├── Format : Physique | Numerique
├── TypeDocument : NonPrecise | Roman | BandeDessinee (défaut = NonPrecise, PAS Roman)
├── NombrePages : int? (⚠ NULL = « on ne sait pas » ; 0 est REFUSÉ, pas stocké)
├── CoverUrl
├── UrlNotice (notice BnF ou OpenLibrary ayant prérempli la fiche)
├── DateAjout
└── AjoutePar (YNH_USER — traçabilité, PAS un cloisonnement)
⚠ pas de colonne Statut, pas de colonne Auteur
@@ -806,6 +812,20 @@ LivreSouhaite (la liste d'envies est PERSONNELLE — voir sa sec
├── Rang (ordre CHOISI, 0 = le plus désiré — personnel comme le reste)
└── UNIQUE (Utilisateur, TitreNormalise, AuteurNormalise)
une œuvre par personne ; deux personnes peuvent souhaiter le même livre
RevueSouhaitee (table SŒUR de LivreSouhaite — PERSONNELLE elle aussi)
├── Id
├── Utilisateur (YNH_USER — une FRONTIÈRE, comme pour LivreSouhaite)
├── Titre (« Médor », sans son numéro)
├── TitreNormalise (NormalisationTexte, PAS CleOeuvre : une revue n'a pas de sous-titre)
├── Numero : string? (NULL = la revue entière, et non un numéro précis)
├── NumeroNormalise (jamais NULL — chaîne vide, sinon l'unicité ne tient pas)
├── Issn (facultatif, CANONISÉ AVEC SON TIRET)
├── Note
├── DateAjout
├── Rang (numéroté indépendamment de LivreSouhaite.Rang)
└── UNIQUE (Utilisateur, TitreNormalise, NumeroNormalise)
⚠ pas de CoverUrl, pas d'auteur, aucune FK vers Revue ni NumeroRevue
```
Garder `Pret` comme table séparée (pas un champ sur `Livre`) pour conserver l'historique complet des prêts passés, pas juste l'état actuel.
@@ -2353,14 +2373,21 @@ Vérifié en exécution : `crossOrigin === "use-credentials"` sur le lien, manif
« Le déplacement est foireux » : d'un écran profond (bibliographie, fiche de tome, ajout
d'envie), il fallait deviner quelle destination du menu ramenait en arrière.
Le retour passe par l'**historique du navigateur**, jamais par une destination calculée :
un même écran est atteignable par plusieurs chemins — une fiche livre s'ouvre depuis le
catalogue, une bibliographie, une série ou une recherche.
⚠️ **Ce qui suit a été RENVERSÉ le 2026-08-21** — voir « S — le retour est une remontée
hiérarchique ». Conservé parce que le motif d'origine reste vrai, et qu'il faut savoir
pourquoi il n'a pas suffi.
⚠️ `history.back()` **seul ne suffit pas** : ouverte depuis l'écran d'accueil du téléphone,
la PWA démarre sur une pile d'un seul cran et « retour » **sortirait de l'application**.
D'où `js/navigation.js`, qui teste `history.length` — sans équivalent côté C# — et retombe
sur le catalogue.
> Le retour passe par l'**historique du navigateur**, jamais par une destination calculée :
> un même écran est atteignable par plusieurs chemins — une fiche livre s'ouvre depuis le
> catalogue, une bibliographie, une série ou une recherche.
>
> ⚠️ `history.back()` **seul ne suffit pas** : ouverte depuis l'écran d'accueil du téléphone,
> la PWA démarre sur une pile d'un seul cran et « retour » **sortirait de l'application**.
> D'où `js/navigation.js`, qui teste `history.length` — sans équivalent côté C# — et retombe
> sur le catalogue.
⚠️ `wwwroot/js/navigation.js` **n'existe plus** : le retour est calculé en C# par
`Services/RemonteeRoutes.cs`. Ne pas le recréer.
### L'icône cassée du bandeau
@@ -2516,6 +2543,10 @@ numéro de rang, lui, reste visible en consultation — c'est une information, p
Le lien « Changer l'ordre » n'apparaît qu'à partir de deux tomes ou deux sous-séries :
réordonner un élément unique n'a pas de sens.
⚠️ Depuis le 2026-08-21, « Modifier » et « Changer l'ordre » ne sont plus au **bas** de la
fiche : ils sont groupés à droite de la ligne du titre, et `.actions-flottantes` ne subsiste
sur cet écran que dans le mode ordre (le bouton « Terminé »). Voir « T et W ».
### Une seule entrée d'ajout : « Ajouter un ouvrage » (décidé avec l'utilisateur)
Question posée telle quelle par l'utilisateur : refaire une page « ajouter une revue »
@@ -2793,3 +2824,237 @@ API lancée sur une base neuve, migration `ImagesEtUnesDesNumeros` appliquée au
scan depuis une série ou depuis les envies, du « Tout cocher » et de l'indicateur d'attente
repose sur la compilation et la relecture, pas sur une exécution. À regarder au prochain
passage sur un vrai appareil.
## Lots Q à Y — retours d'usage du 2026-08-21, traités le 2026-08-21
Neuf lots, quatre commits. Ce qui suit ne redit pas ce que le code montre : seulement les
décisions, les invariants qu'elles ont mis à l'épreuve, et ce qui a failli être fait de travers.
⚠️ **Aucun écran n'a été vérifié en navigateur sur toute la série.** Le rendu — formulaire
réordonné, menu plein écran, actions en tête de fiche, page « À propos », deux sections d'envies
— repose sur la compilation et la relecture, pas sur une exécution. Ce qui **est** vérifié : les
services et l'API (618 tests au vert, 506 au début de la série) et les points d'entrée éprouvés à
la main, listés en fin de section. À regarder au prochain passage sur un vrai appareil.
### S — le retour est une remontée hiérarchique, et non plus l'historique
⚠️ **Ceci renverse la décision du 2026-08-20** (« le retour passe par l'historique du navigateur,
jamais par une destination calculée »). Le motif d'alors reste vrai — un même écran s'atteint par
plusieurs chemins — **et il ne suffisait pas** : l'historique remonte aussi les **allers-retours**
(ouvrir un filtre, changer un ordre, entrer puis sortir d'une édition). On cliquait cinq fois sans
quitter le même écran. La question posée par la flèche est « où suis-je ? », pas « qu'ai-je fait
juste avant ? ».
`MaBibli.Client/Services/RemonteeRoutes.cs` porte une **table de parenté explicite**, et une
fonction **pure** donc testable sans navigateur.
⚠️ **Pas un découpage naïf de l'URL** : toutes les routes n'ont pas la forme d'une arborescence.
`/souhaits/ordre` remonte bien à `/souhaits`, mais `/auteurs/{id}/bibliographie` remonte à
`/auteurs`**la fiche d'un auteur n'existe pas**. Un `..` d'URL aurait mené sur une page
inexistante.
⚠️ **Toute route nouvelle doit être inscrite dans la table.** Le repli — la destination de menu de
la branche — existe pour qu'une route oubliée reste utilisable, **pas** pour dispenser de la
ligne : sans elle, `/revues/{id}/edition` retombait sur la *liste* des revues au lieu de la fiche
qu'on venait de quitter (constaté, et corrigé au lot W). L'ordre de lecture de la table compte
aussi : le premier modèle qui correspond gagne, donc `/revues/ajout` est écrit **avant**
`/revues/{id}`.
Le garde-fou « ne jamais sortir de l'application » vit désormais là aussi : la fonction rend
**toujours** un chemin interne. C'est ce qui remplace le test de `history.length`, qui ne disait
pas ce qu'on croyait dans une PWA `standalone` — la pile d'une session y contient aussi ce qui
précède l'application. `wwwroot/js/navigation.js` est **supprimé**.
### R — le menu colle en haut, et occupe l'écran entier sur téléphone
**Un seul conteneur collant** portant bandeau *et* menu, et non deux éléments collants
superposés : le second aurait exigé d'écrire en dur la hauteur d'un bandeau qui **varie** avec la
pastille hors-ligne et le nom d'utilisateur. C'est la même famille de piège que
`--mb-onglets-hauteur`, disparue avec la barre du bas.
Sur téléphone, le menu déployé prend **tout l'écran** : sous le bandeau, il partageait la place
avec la liste qu'on venait de quitter, et l'on choisissait sa destination au milieu d'autre chose.
⚠️ **Un calque plein écran sans porte de sortie est un piège.** Trois s'ajoutent à la fermeture
déjà en place sur `LocationChanged` : la croix, **Échap** (le calque prend le focus à l'ouverture
— sans focus, aucun `keydown` ne lui parviendrait, exactement comme le calque d'agrandissement des
couvertures) et le clic hors des liens.
⚠️ Le plein écran est **explicitement annulé au-delà de 40 rem** : un menu ouvert au doigt puis
une fenêtre agrandie laisserait sinon un calque sans bascule pour le refermer.
⚠️ **Les règles du menu restent en feuille GLOBALE** (`wwwroot/css/app.css`) : une règle scopée
n'atteint pas ce que rend un `NavLink` (défaut du lot H, à ne pas refaire), et la navigation est
ce qui doit le moins pouvoir tomber. Le point de rupture de **40 rem** reste écrit aux **deux**
endroits (`app.css` et `MainLayout.razor.css`) et les deux doivent rester identiques.
### Q — l'ordre des champs du formulaire livre est une décision
Titre, **type de document**, thèmes, auteurs, rôles, puis le reste. Le type **commande la suite de
la saisie** — les rôles ne se posent que là — et il fallait descendre tout le formulaire pour dire
« c'est une BD », c'est-à-dire après avoir saisi ce qui en dépend.
Deux invariants **maintenus**, et il fallait résister à les entamer :
- **rien n'est présélectionné** : `NonPrecise` ne prétend toujours rien. Remonter le champ le rend
visible, ce qui est le but ; le préremplir écrirait quelque chose de faux ;
- **les rôles n'apparaissent qu'à partir de deux auteurs**, BD ou non. IDEES.md proposait « BD
**et** deux auteurs » : ç'aurait été un revirement, un album signé d'une seule personne n'ayant
pas plus de rôle à distinguer qu'un roman.
⚠️ Le champ des noms se relie **à chaque frappe** et reconstruit la liste entière : des tests
verrouillent la préservation des rôles, que ce réordonnancement ne doit pas entamer.
### T et W — les actions remontent à droite du titre, la fiche revue prend deux modes
Sur `/series/{id}` comme sur `/revues/{id}`, les actions sont **groupées à droite de la ligne du
titre**, dans le même bloc d'en-tête — disposition déjà tenue par la bibliographie et la liste des
auteurs. Reléguées en bas, les actions d'une saga de vingt tomes ou d'une collection de trente
numéros ne se découvraient qu'après avoir tout déroulé. La barre `.actions-flottantes` disparaît
donc de la **consultation** d'une série ; elle ne subsiste que sur l'écran d'ordre.
`/revues/{id}` consulte, `/revues/{id}/edition` modifie — troisième écran à prendre cette forme
après la fiche livre et la fiche série.
⚠️ **Modifier et retirer un numéro n'existent QUE dans l'édition** : en consultation, ces boutons
se déclenchaient sous le pouce en faisant défiler la liste. Même raison que les flèches d'ordre
d'une série, reléguées sur leur propre écran. Un retrait de numéro se **confirme** désormais, comme
la suppression d'un livre ou d'une revue.
⚠️ **« Ajouter un numéro » RESTE en consultation, et ce n'est pas une entorse** : le geste ne
touche pas à la fiche de la revue, il range un objet de plus — même exception que « Prêter » et le
statut de lecture sur une fiche livre. Le formulaire se replie derrière son bouton et s'ouvre de
lui-même quand on arrive du scanner avec un numéro lu sur l'add-on EAN-2 : demander un clic de plus
pour saisir ce qu'on tient en main serait un détour.
⚠️ Les deux routes partagent le paramètre `{id}` : le routeur ne redessine rien en passant de
l'une à l'autre, d'où l'abonnement à `LocationChanged` — piège déjà rencontré sur la fiche livre.
Quitter l'édition referme au passage ce qui n'a de sens que là : formulaire de numéro ouvert,
retrait ou suppression en attente de confirmation.
### V — toute la ligne d'une revue ouvre sa fiche
Comme la carte entière au catalogue. ⚠️ **Le titre reste un vrai lien** — adresse, clavier,
clic-milieu — et son clic **ne remonte pas** jusqu'à la ligne, qui naviguerait une seconde fois.
Toute action posée un jour sur cette ligne devra faire de même, sinon la ligne avalera le clic du
bouton.
### U — le nombre de pages, et pourquoi `0` est refusé
`Livre.NombrePages` est **nullable, sans valeur par défaut** : « 0 page » se lirait comme une
donnée là où l'on veut dire « on ne sait pas ». Même raison que `TypeDocument.NonPrecise` — un
défaut qui ne prétend rien n'a rien à reprendre, d'où une migration réduite à un `AddColumn`. Le
service **refuse** un zéro avec un message plutôt que de l'écrire ; **effacer le champ** reste la
façon de revenir à « inconnu ».
Le préremplissage vient de `dc:format`, que rien ne lisait jusqu'ici. ⚠️ **Ce champ n'est pas un
nombre mais une phrase décrivant le support** : « 1 vol. (113 p.) : ill., couv. ill. en coul. ;
18 cm ». Les règles de `NettoyageIsbd.NombrePages` sont donc **étroites** — un nombre suivi de
`p.` ou de `page(s)`, rien d'autre — et tout le reste rend `null`.
⚠️ **L'erreur n'est pas symétrique** : un champ vide se remplit à la main en trois secondes, un
chiffre faux s'enregistre sans que personne ne le voie. « 1 vol. » ne vaut pas 1, « 30 cm » ne vaut
pas 30, et « (p. 45-90) » — une pagination de contribution — ne vaut rien. La valeur reste
**proposée** dans un champ modifiable, et rien n'est déduit pour un ebook (le nombre de pages y
varie avec la police : s'il est saisi, c'est une indication).
### X — éditer une envie, et souhaiter une revue
#### `PUT /api/souhaits/{id}`
- **Les formes normalisées se recalculent.** Sans cela, le rapprochement « déjà au catalogue »
continuerait de se faire sur l'ancienne forme, et **le signalement mentirait sans le dire**.
- **Le filtre sur l'appelant fait partie de la clé de recherche**, pas d'une vérification
ultérieure : l'envie d'un autre est **introuvable** (404), jamais refusée (403).
- ⚠️ **Une édition peut heurter l'unicité `(Utilisateur, TitreNormalise, AuteurNormalise)`**, ce
qu'un ajout ne peut pas. Renommer une envie en une autre déjà présente répond par un message
lisible, jamais par « UNIQUE constraint failed ». **400 et non 409**, contrairement au doublon du
catalogue : là-bas posséder deux exemplaires est légitime et l'appel se reconfirme, ici l'index
l'interdit et il n'y a **rien à confirmer**.
- ⚠️ **La couverture n'est écrite que si la charge utile en porte une.** Aucun écran n'offre de
champ « URL de couverture » pour une envie (décision actée) : un remplacement inconditionnel
l'aurait effacée à la première faute de frappe corrigée.
- Le **rang n'est pas touché** — l'ordre a son propre point d'entrée.
#### `RevueSouhaitee` : une table sœur, pour la quatrième fois
Même argument que `Revue` face à `Livre`, et que `LivreSouhaite` face au catalogue. Une envie de
livre est identifiée par un couple **(œuvre, auteur)** : c'est ce que porte son index unique et ce
qui permet de la rapprocher du catalogue. Un numéro de revue **n'a pas d'auteur** et se distingue
par son **numéro** — deux notions que la clé de `LivreSouhaite` ne sait pas exprimer sans devenir
fausse pour tout le monde.
⚠️ `NumeroNormalise` est **NOT NULL avec un défaut vide** : SQLite tient deux `NULL` pour
distincts, et « Médor, sans numéro » s'ajouterait autant de fois qu'on cliquerait. Piège déjà
rencontré sur `LivreSouhaite.AuteurNormalise` — il se repose à chaque table personnelle.
⚠️ **Le coût d'une table sœur se paie partout où les deux listes se rejoignent** : affichage,
export `.txt`, export `.csv`, instantané hors-ligne `souhaits-revues`, et
`ServiceRenormalisation`. C'est là que le sujet se rate s'il est bâclé — une envie de revue
invisible en librairie ne sert à rien.
Décisions prises en chemin :
| Point | Décision | Pourquoi |
|---|---|---|
| Affichage | **deux sections**, jamais un classement entrelacé | chaque table numérote son rang **indépendamment** ; mélanger deux suites sans rapport produirait un ordre que personne n'a choisi |
| `.txt` | livres groupés par auteur, puis les revues en **section à part, à la fin** | une revue n'a pas d'auteur : la ranger sous « Auteur non précisé » la mêlerait à des **livres** dont l'auteur est simplement inconnu. Au kiosque comme en librairie, ce ne sont pas les mêmes rayons |
| `.csv` | colonne **« Type »**, en-tête **« ISBN / ISSN »** | sans elles, un tri par titre rendrait revues et livres indiscernables, et la colonne des codes mêlerait deux natures de code en silence |
| Réordonnancement des envies de revues | **non fait** | la section est courte ; à rouvrir si l'usage le demande (noté dans IDEES.md) |
| Signalement « déjà possédé » | **non fait** | demanderait de rapprocher un numéro d'un `NumeroRevue`, et l'envie porte souvent la revue entière (noté dans IDEES.md) |
| `CoverUrl` | **absent** | aucune source ne donne l'image d'un numéro souhaité — et donc **rien à ajouter au garde de `GET /api/couvertures`** |
⚠️ **L'ISSN y est canonisé à part dans `ServiceRenormalisation`** : `Renormaliser` n'applique rien
quand la clé ne bouge pas, donc un ISSN mal formé sur une ligne au titre inchangé lui échapperait.
C'est la même précaution que `CanoniserLesIssn` sur `Revue` (lot K2).
### Y — l'application dit sa version, et sous quelle licence
`/a-propos` : manuel annoncé à venir, `mailto:info@limonier.be`, site de l'auteur, version publiée
avec sa date de build, licence **AGPL v3** et lien vers le dépôt. ⚠️ **L'AGPL attend que les
utilisateurs d'un service en réseau puissent en obtenir la source** : ce lien n'est pas un
ornement, c'est ce que la licence demande.
L'application ne connaissait pas sa version : elle vivait dans le `manifest.toml` du dépôt
`mabibli_ynh` et dans les tags git, **jamais dans le binaire**. `build/publier-release.sh` pose
désormais `-p:Version` et `-p:MaBibliDateBuild` (en respectant `SOURCE_DATE_EPOCH`), et l'API rend
les deux par `GET /api/version`.
⚠️ **L'horodatage de build est le TÉMOIN de l'injection, et rien ne le calcule côté MSBuild.**
Sans lui, la version lue serait le `1.0.0` que le SDK .NET pose par défaut dans tout assembly : il
se lirait comme une vraie version alors qu'il ne désigne rien — et c'est **exactement** la valeur
qu'on ira chercher pour diagnostiquer un appareil au cache dépareillé. Mieux vaut ne rien annoncer :
un binaire compilé à la main se déclare « version de développement », ce qui est vrai.
`VersionApplication.Depuis` est une fonction **pure**, éprouvable sans fabriquer d'assembly, et
elle coupe le `+empreinte` que le SDK ajoute parfois à `AssemblyInformationalVersion`.
Trois décisions d'écran :
- **L'entrée du menu est DÉTACHÉE des six destinations** — filet au-dessus sur téléphone, à gauche
en rangée sur PC : « À propos » est une **annexe**, pas une septième destination. Ses règles
vivent en feuille **globale**, comme tout le menu.
- **Le `mailto:` reste un lien même hors-ligne** : il ne charge aucune page et passe la main au
client de messagerie, qui sait mettre un message en attente. Le site et le dépôt, eux, basculent
en **boutons désactivés portant leur motif**, comme les liens d'export des envies — un `<a>`
laissé actif quitterait l'application pour une page d'erreur du navigateur.
- **La version est mise en instantané hors-ligne** : on lit ce numéro justement quand quelque chose
ne va pas, et un appareil qu'on soupçonne est souvent celui qui n'a plus de réseau. L'écran dit
alors que c'est la **dernière version vue du serveur**, et non la version courante.
### ⚠️ `dotnet ef` et les migrations de la série
Deux migrations, toutes deux sans reprise de données : `NombreDePages` (un `AddColumn`, le défaut
`NULL` ne prétendant rien) et `EnviesDeRevues` (une table neuve). C'est ce que permet un défaut qui
n'affirme rien — comparer avec `RangDesEnvies`, qui avait dû reconduire un ordre en SQL.
### Vérifié en exécution le 2026-08-21 (API, pas navigateur)
| Cas | Résultat |
|---|---|
| ISSN d'envie de revue saisi « 24666718 » | rangé **`2466-6718`** |
| `PUT /api/souhaits/{id}` sur l'envie d'un autre | **404** |
| Exports `.txt` et `.csv` | portent bien **les deux moitiés** de la liste |
| `GET /api/version` sans injection | `{"numero":null,"publiee":false}` |
| `GET /api/version` avec `-p:Version=0.4.1 -p:MaBibliDateBuild=…` | `{"numero":"0.4.1","publiee":true}` |
**618 tests au vert** (506 au début de la série), aucun avertissement de compilation.