Regarde enfin les écrans s'afficher, et trouve ce que 618 tests taisaient

La 7ᵉ série s'était close sur un aveu : aucun écran n'avait été regardé.
Balayage des vingt-deux routes à 320, 375 et 1280 px. Les huit lots tiennent
leurs promesses ; trois défauts en sortent, qu'aucun test ne pouvait voir.

L'arbre des séries débordait de 8 px à 320 px : sa ligne est elle-même un
élément de flex, et « min-width: auto » lui interdisait de rétrécir. C'est
mot pour mot la leçon du lot M, non appliquée à cet étage de la cascade.

L'ISBN à tirets faisait défiler toute la fiche — 395 px pour un écran de 320.
« white-space: nowrap » y annulait le « word-break » dont le dd est muni
justement pour cela. Le défaut de CSS était déjà le bon comportement : une
ligne quand il y a la place, une coupure au tiret sinon.

Le troisième est le sérieux : le relais de couvertures rendait 404 une fois
sur trois sur une couverture qui existe. archive.org extrait l'image d'un ZIP
à la volée et met 5 à 15 s, le délai coupait à 10. Rien n'était alors mis en
cache, donc CORS puis relais se rejouaient à chaque affichage — le défaut même
que ce relais existe pour corriger. Un 404 ne prouve rien ici, par
construction : le relais répond 404 à tout refus pour ne pas être un oracle.
Il a désormais son propre délai, personne n'attendant derrière un cache qui
se remplit en tâche de fond.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mathieu
2026-08-21 17:44:00 +02:00
co-authored by Claude Opus 5
parent d7ea12ddbf
commit 323b50676b
4 changed files with 139 additions and 7 deletions
+98
View File
@@ -3058,3 +3058,101 @@ n'affirme rien — comparer avec `RangDesEnvies`, qui avait dû reconduire un or
| `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.
## La 7ᵉ série vérifiée en navigateur — trois défauts, tous invisibles aux tests (2026-08-21)
La série Q à Y s'était close sur un aveu : « **aucun** écran n'a été regardé s'afficher ». Le
balayage l'a été le 2026-08-21, à **320, 375 et 1280 px**, sur les vingt-deux routes de
l'application. Les 618 tests ne pouvaient rien en dire — ils couvrent les services et l'API, pas
la mise en page ni le réseau réel.
⚠️ **Le panneau du navigateur d'automatisation ne compose pas d'image** : aucune capture d'écran
n'est possible ici. Ce n'est pas un obstacle, et c'est même instructif — la vérification s'est
faite sur la **géométrie réelle** (`getBoundingClientRect`, `scrollWidth`) et les styles calculés,
qui prouvent un débordement de 8 px qu'un œil sur une capture aurait laissé passer.
### Ce que la vérification a confirmé
| Lot | Vérifié |
|---|---|
| Q | ordre titre → type → thèmes → auteurs → rôles → reste ; les rôles n'apparaissent **qu'à deux auteurs** |
| R | à 375 px menu **plein écran** (375×812, `fixed`, focalisé, Échap ferme) ; à 1280 px rangée collante sous un en-tête `sticky top:0` |
| S | remontée d'un cran sur **cinq branches** éprouvées, et l'arrêt au catalogue : `/revues/3/edition → /revues/3 → /revues → / → /` |
| T | actions à droite sur la ligne du titre à 1280 px (bord droit commun) ; elles passent à la ligne à 375 px, ce qui est la bonne dégradation |
| U | champ « Nombre de pages (facultatif) » en `<input number>` |
| W | actions en tête, ISSN à tiret, numéros du plus récent au plus ancien, **sans date en dernier** ; l'édition d'un numéro rouvre bien ses articles joints par « ; », « Ukraine, deux ans après » restant entier |
| X | deux sections, « 2 livres et 2 revues en envie », « Éditer » sur chacune |
| Y | manuel, contact, version (« version de développement », dit honnêtement), licence AGPL v3 |
Toutes les routes du client (`@page`) figurent bien dans la table de `RemonteeRoutes` — contrôle
refait à la main, la table étant explicite et rien ne garantissant en machine qu'on n'en oublie
pas une.
### ⚠️ Défaut 1 — l'arbre des séries débordait de 8 px à 320 px
`.arbre-ligne` est elle-même un **élément de flex** (son `<li>` l'est), et `min-width: auto` lui
interdisait de descendre sous sa largeur intrinsèque : un titre de sous-série indenté poussait le
compte « 0 sur 2 tomes » à 328 px sur un écran de 320.
C'est **mot pour mot la leçon du lot M**, déjà écrite ici — « le point qui fait tenir la ligne à
320 px n'est pas le `flex` mais `min-width: 0` » — simplement non appliquée à ce composant-là.
`.arbre-titre` l'avait, la **ligne** ne l'avait pas. La règle vaut à chaque étage de la
cascade de flex, pas au seul étage où l'on a été mordu.
### ⚠️ Défaut 2 — l'ISBN à tirets faisait défiler toute la fiche
`.code-isbn` portait `white-space: nowrap`. Un ISBN en monospace mesure **147 px**, là où la
colonne des valeurs de la fiche en offre 111 à 375 px et **56 à 320 px** : le code débordait sa
colonne, et avec lui la page entière — **395 px de contenu pour un écran de 320**.
⚠️ Deux choses à retenir, au-delà du symptôme :
- **Une règle tardive annulait un garde-fou délibéré.** `.fiche-champs dd` porte `min-width: 0`
et `word-break: break-word` *précisément* pour cela ; `nowrap`, plus spécifique, les défaisait
sur la seule valeur qui en avait besoin.
- **Le défaut de CSS était déjà le bon comportement.** Sans `nowrap`, le code tient sur une ligne
quand il y a la place (vérifié à 1280 px) et ne se coupe **qu'à un tiret** quand il n'y en a
pas — le tiret étant le seul endroit où une coupure se relit. Deux lignes à 375 px, trois à
320 px : c'est le prix, et il est sans commune mesure avec une page qui défile de travers.
Décidé avec l'utilisateur. Les deux autres pistes ont été écartées : donner sa pleine largeur à
la ligne ISBN **ne suffit pas à 320 px** (144 px disponibles pour 147 nécessaires), et réduire la
police rendrait illisible, sur le petit écran, un code qu'on y recopie chez le libraire.
### ⚠️ Défaut 3 — le relais de couvertures rendait 404 sur une couverture qui existe
Le plus sérieux des trois, et le seul qu'aucune inspection du code n'aurait donné.
`GET /api/couvertures` partageait le délai de 10 s des sources bibliographiques. Or les
couvertures OpenLibrary aboutissent sur `archive.org`, qui les **extrait d'une archive ZIP à la
volée** : mesuré le 2026-08-21, 5,6 s / 14,9 s / 5,5 s en amont, et **8,3 s → 200, 7,3 s → 200,
10,003 s → 404** à travers le relais. Une fois sur trois, l'image existante était déclarée
introuvable.
⚠️ **La conséquence n'est pas une image manquante, elle est cumulative** : un 404 signifie que
rien n'est rangé en IndexedDB, donc que le client rejoue CORS **puis** relais à *chaque* affichage
de la fiche — exactement le défaut que ce relais existe pour corriger, et que CLAUDE.md notait
comme réglé (« l'erreur CORS n'apparaît qu'une fois par couverture et par appareil »).
⚠️ **Et il était indistinguable d'une URL inconnue**, par construction : tout refus du relais
répond 404 pour ne pas faire de lui un oracle. Cette règle reste juste, mais elle a un revers à
connaître — **le 404 ne prouve rien**, et un défaut du relais ne se diagnostique qu'en le
chronométrant. C'est la même leçon que « Robert A. Harper » : une source intermittente produit
exactement les symptômes d'une source lacunaire.
**Correction : un délai propre au relais, `timeoutRelaisCouvertures` = 30 s.** Les deux appels
n'ont pas le même témoin — un lookup ISBN fait patienter quelqu'un devant un formulaire, d'où les
10 s et la bascule sur la source suivante ; le relais remplit un cache **en tâche de fond**, et
abandonner n'y fait rien gagner, cela perd la couverture. La lecture reste bornée à 5 Mio.
Vérifié après correction : **cinq appels sur cinq en 200**, 49 153 octets.
### Ce que le balayage n'a PAS trouvé
Aucun débordement horizontal sur les vingt-deux routes, aux trois largeurs, après correction.
Aucune erreur de console autre que les erreurs CORS attendues (première tentative directe avant
repli sur le relais, documentée). 618 tests toujours au vert.
⚠️ **La base de développement a été garnie pour l'occasion** — un cycle de deux sous-séries et
cinq tomes, une revue à trois numéros, deux envies de livres et deux de revues. Sans elle, la
moitié des écrans de la série s'affichait vide et ne prouvait rien. Elle est restée en place :
le prochain balayage en aura le même besoin.
+14 -5
View File
@@ -707,11 +707,20 @@ Aux items déjà ouverts plus haut — **A5** (alléger les couvertures), **K1**
couverture), **N2** (stocker l'URL finale après redirections), les **homonymes**, **OpenLibrary
en second rideau** et l'**EAN-2 à confirmer en kiosque** — la série du 2026-08-21 en ajoute trois.
- **Vérifier en navigateur tous les écrans touchés.** ⚠️ C'est le plus important : **aucun** n'a
été regardé s'afficher. Formulaire livre réordonné, menu collant et plein écran, flèche de
retour sur chaque branche, actions en tête des fiches série et revue, édition d'un numéro,
deux sections de la liste d'envies, page « À propos ». Les services et l'API sont couverts par
618 tests ; le rendu ne l'est par rien.
- ~~**Vérifier en navigateur tous les écrans touchés.**~~**Fait le 2026-08-21** — voir
`CLAUDE.md`, section « La 7ᵉ série vérifiée en navigateur ». Vingt-deux routes, à 320, 375 et
1280 px. Les huit lots se comportent comme décrit, et **trois défauts** que les 618 tests ne
pouvaient pas voir en sont sortis : l'arbre des séries débordait de 8 px à 320 px (`min-width`
manquant sur la ligne — la leçon du lot M, non appliquée à cet étage), l'ISBN à tirets faisait
défiler toute la fiche (`white-space: nowrap` annulant le garde-fou de `.fiche-champs dd`), et
surtout **le relais de couvertures rendait 404 une fois sur trois** sur une couverture qui
existe, `archive.org` mettant 5 à 15 s là où le délai coupait à 10.
⚠️ Ce que l'exercice a montré au-delà des trois correctifs : **le rendu ne se vérifie pas à
l'œil**. Le débordement de 8 px n'était visible que par `scrollWidth`, et le 404 intermittent du
relais que par un chronométrage — aucune capture d'écran n'aurait donné l'un ni l'autre. La
bonne question n'est pas « est-ce que ça ressemble à quelque chose ? » mais « qu'est-ce que la
géométrie et le réseau disent réellement ? ».
- **Réordonner les envies de revues.** Elles ont un `Rang` et une section à elles, mais aucun
écran pour les déplacer — les livres, eux, ont `/souhaits/ordre`. Le besoin n'est pas constaté :
la section est courte par nature. À faire le jour où elle ne l'est plus.
+14 -1
View File
@@ -36,6 +36,19 @@ builder.Services.AddScoped<IFournisseurUtilisateur, FournisseurUtilisateurSsowat
// Timeout court : une source lente ne doit pas bloquer la cascade, l'appelant bascule sur la suivante.
var timeoutSources = TimeSpan.FromSeconds(10);
// ⚠️ Le relais de couvertures a son PROPRE délai, et bien plus long. Ce n'est pas une largesse :
// les deux appels n'ont pas le même témoin. Un lookup ISBN fait patienter quelqu'un devant un
// formulaire — d'où les 10 s ci-dessus, et la bascule sur la source suivante. Le relais, lui,
// remplit un cache en tâche de fond : personne n'attend derrière, et abandonner ne fait pas
// gagner du temps, cela perd la couverture.
//
// Mesuré le 2026-08-21 : les couvertures OpenLibrary aboutissent sur `archive.org`, qui les
// EXTRAIT D'UNE ARCHIVE ZIP à la volée et met 5 à 15 s. À 10 s, le relais répondait 404 une fois
// sur trois — indistinguable, pour l'appelant, d'une URL inconnue. L'image n'était alors jamais
// mise en cache, et le client rejouait CORS puis relais à CHAQUE affichage : exactement le
// défaut que ce relais existe pour corriger.
var timeoutRelaisCouvertures = TimeSpan.FromSeconds(30);
builder.Services.AddHttpClient<IBnfClient, BnfClient>(http =>
{
http.BaseAddress = new Uri(BnfClient.UrlBase);
@@ -67,7 +80,7 @@ builder.Services.AddScoped<IIsbnLookupService, IsbnLookupService>();
// le SSRF.
builder.Services.AddHttpClient<IServiceCouvertures, ServiceCouvertures>(http =>
{
http.Timeout = timeoutSources;
http.Timeout = timeoutRelaisCouvertures;
http.DefaultRequestHeaders.UserAgent.ParseAdd("MaBibli/0.1 (bibliotheque personnelle auto-hebergee)");
})
.ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler
+13 -1
View File
@@ -1080,9 +1080,16 @@ body {
/* Un ISBN se relit chiffre à chiffre : une chasse fixe évite de confondre 1 et l,
0 et O, et rend les tranches séparées par des tirets alignées d'une ligne à l'autre. */
/* PAS de « white-space: nowrap » ici, et ce n'est pas un oubli. Un ISBN à tirets mesure
147 px en monospace, la colonne des valeurs de la fiche n'en offre que 111 à 375 px et
56 à 320 px : « nowrap » le faisait déborder, et avec lui TOUTE la page 395 px de contenu
pour un écran de 320, mesuré le 2026-08-21. Il annulait au passage le « word-break » dont
« .fiche-champs dd » est justement muni pour cela.
Le défaut de CSS est exactement ce qu'on veut : le code tient sur une seule ligne quand il y
a la place, et ne se coupe qu'à un tiret quand il n'y en a pas le tiret étant le seul
endroit une coupure se relit. */
.code-isbn {
font-family: ui-monospace, "SFMono-Regular", "Menlo", "Consolas", monospace;
white-space: nowrap;
}
/* Titre d'écran et son bouton de réglages sur la même ligne : le bouton « Filtrer » du
@@ -1173,6 +1180,11 @@ body {
gap: 0.5rem;
min-height: 2.25rem;
padding: 0.2rem 0;
/* Même piège qu'au lot M : la ligne est elle-même un élément de flex (son <li> l'est),
et « min-width: auto » lui interdit de descendre sous sa largeur intrinsèque. Sans cette
ligne, un titre de sous-série indenté pousse le compte « 0 sur 2 tomes » hors de l'écran
et fait défiler la page horizontalement (mesuré à 320 px : 328 px de large). */
min-width: 0;
}
.arbre-plier,