Documente le lot H dans CLAUDE.md

Les seize retours du 2026-08-20, avec ce que chacun a appris. Deux décisions actées
sont explicitement renversées, et leur raisonnement d'origine conservé plutôt
qu'effacé : la barre d'onglets en bas (juste sur le pouce, mais sans état de repli)
et le fichier logo-bandeau.svg séparé (qu'un cache pouvait ne pas servir).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mathieu
2026-08-20 21:15:38 +02:00
co-authored by Claude Opus 5
parent 0c4babd7b9
commit 7150d3bcae
+264 -23
View File
@@ -1482,12 +1482,14 @@ ouverte dans IDEES.md.
Vérifié en exécution sur `9772466671438` : la BnF nomme « Médor », la fiche est créée avec son
ISSN, deux numéros s'y ajoutent, et **rescanner le même code retombe sur la même fiche**.
### Un sixième onglet, mesuré
### ~~Un sixième onglet, mesuré~~ — caduc depuis le 2026-08-20
`Catalogue / Auteurs / Séries / Revues / Prêts / Envies`. À 320 px le plus étroit des
téléphones réalistes — les six libellés occupent **312 px sans troncature**, huit de marge.
⚠️ C'est la limite : un septième onglet, ou un libellé plus long que « Catalogue », imposera de
regrouper plutôt que d'ajouter.
`Catalogue / Auteurs / Séries / Revues / Prêts / Envies`. À 320 px, les six libellés occupaient
**312 px sans troncature**, huit de marge — c'était la limite, et elle a été atteinte.
⚠️ **La barre d'onglets n'existe plus** : la navigation est passée en haut, dans un menu qui se
déploie. Voir « Le menu est en haut ». La mesure ci-dessus reste instructive pour une seule
raison : elle montre qu'une rangée qui « tient tout juste » ne tient en réalité pas.
## Sagas et cycles — migration `SagasEtCycles` (2026-08-19)
@@ -1558,10 +1560,11 @@ sans quoi une coupure juste après un ajout de tome rendrait la série d'avant.
### L'écran, et ce qu'il refuse de faire
- **Un cinquième onglet.** Vérifié à 320 px : les cinq libellés tiennent sans troncature
(258 px sur 320), au prix d'un `font-size` passé de 0.85 à 0.8 rem.
- **Un cinquième onglet.** (Caduc : la barre d'onglets a disparu le 2026-08-20.)
- **Des flèches, pas de glisser-déposer** : le drag & drop HTML5 ne fonctionne pas au doigt, et
c'est sur téléphone qu'on consulte une saga. Même décision que pour la liste d'envies.
⚠️ Depuis le 2026-08-20, ces flèches ne vivent plus que sur `/series/{id}/ordre` — voir
« L'ordre de lecture a son propre écran ».
- **Le titre affiché est celui du livre quand il est rattaché**, le titre saisi restant le filet.
Rattacher le mauvais livre se voit donc immédiatement — et « Détacher » restitue le nom du tome.
- **Aucune détection automatique** de série depuis un titre (« Tome 3 »), aucune source tierce.
@@ -1619,28 +1622,28 @@ sinon on créerait des fiches sans format en croyant que « rien d'affiché = ph
acquis sans code particulier (`Format.Physique` vaut 0, valeur par défaut de l'énumération) —
mais si l'énumération change d'ordre un jour, ce comportement tombe.
### La navigation tient dans quatre onglets, en bas de l'écran
### La navigation est une liste de destinations, pas une rangée d'onglets
`MainLayout` porte une barre fixe **Catalogue / Auteurs / Prêts / Envies**. Elle remplace les
listes de liens que chaque page traînait dans sa barre d'actions : cinq boutons y passaient sur
deux ou trois lignes sur un téléphone, et « Envies » ne figurait pas partout.
`MainLayout` porte les six destinations **Catalogue / Auteurs / Séries / Revues / Prêts /
Envies**. Elles remplacent les listes de liens que chaque page traînait dans sa barre d'actions :
cinq boutons y passaient sur deux ou trois lignes sur un téléphone, et « Envies » ne figurait pas
partout.
Conséquence tenue partout : **`.actions-flottantes` ne porte plus que des *actions***. Tout lien
qui doublonnait exactement une destination d'onglet a été retiré — « Retour au catalogue »,
« Retour aux auteurs », « Ma liste d'envies ». Ce qui reste est ce qu'aucun onglet ne sait faire
(« Ses livres chez vous », qui est un catalogue *restreint*).
⚠️ **En bas, pas sous le bandeau** : le pouce atteint le bas de l'écran. Les deux barres fixes
coexistent grâce à `--mb-onglets-hauteur`, partagée entre `MainLayout.razor.css` et `app.css`
sans elle, la barre d'actions recouvrirait les onglets. Vérifié à 375 px : onglets 770-812,
actions 706-770, aucun chevauchement.
qui doublonnait exactement une destination du menu a été retiré — « Retour au catalogue »,
« Retour aux auteurs », « Ma liste d'envies ». Ce qui reste est ce qu'aucune destination ne sait
faire (« Ses livres chez vous », qui est un catalogue *restreint*).
⚠️ `NavLinkMatch.All` sur « Catalogue » est **obligatoire** : son `href` est la racine, et sans
cela l'onglet resterait allumé sur les quatre écrans.
cela l'entrée resterait allumée sur tous les écrans.
Les onglets **restent actifs hors-ligne** : les quatre écrans se consultent depuis leurs
Les entrées **restent actives hors-ligne** : les six écrans se consultent depuis leurs
instantanés. Ce sont les écritures qui se désactivent, jamais la navigation.
⚠️ **La position, en revanche, a changé le 2026-08-20** — voir « Le menu est en haut », qui
renverse le « en bas, le pouce atteint le bas de l'écran » écrit ici le 2026-08-18. La variable
`--mb-onglets-hauteur` qui faisait cohabiter les deux barres fixes n'existe plus.
### Les filtres du catalogue se replient derrière un bouton
Deux rangées de segments occupaient en permanence le haut de l'écran pour un réglage qu'on
@@ -1724,8 +1727,9 @@ classe, pour ne pas diverger une troisième fois.
`favicon.png` / `icon-192.png` / `icon-512.png` étaient encore le logo « @ » violet du template
`dotnet new blazorwasm`. Remplacés par un pictogramme de livre ouvert (deux pages en V, tranche
centrale blanche), dans le bleu d'accent existant (`#1b3a5c`) — généré par script (Pillow), pas
par un outil de génération d'images IA, pour rester un simple dessin géométrique. Un petit
`logo-bandeau.svg` (blanc, net à toute résolution) accompagne « MaBibli » dans le bandeau.
par un outil de génération d'images IA, pour rester un simple dessin géométrique. Le même dessin
accompagne « MaBibli » dans le bandeau — ⚠️ **en SVG *en ligne* depuis le 2026-08-20**, et non
plus dans un `logo-bandeau.svg` séparé : voir « L'icône cassée du bandeau ».
`mabibli_ynh/logo.png` (256×256, fond transparent — convention du catalogue d'applications
YunoHost) reprend le même dessin.
@@ -2143,3 +2147,240 @@ nouveau script, et les 441 tests.
⚠️ **Un appareil déjà dans l'état dépareillé ne se répare pas tout seul** : son cache mélangé
précède le correctif. Il faut vider les données du site (ou désinstaller puis réinstaller la
PWA) **une fois**. Le correctif empêche d'y retomber, il ne défait pas ce qui est déjà en place.
## Lot H — retours d'usage du 2026-08-20 (5ᵉ série)
Seize points remontés en usage sur l'application installée. Ils se rangent en cinq
familles ; l'ordre ci-dessous est celui du traitement, pas celui de la remontée.
### Le menu est en haut — décision de 2026-08-18 renversée
Constat de l'utilisateur : la barre d'onglets du bas « s'affiche toujours très mal ».
⚠️ **Le raisonnement du 2026-08-18 reste juste, et il ne suffisait pas.** « Le pouce
atteint le bas de l'écran » est vrai ; mais une barre qui ne s'affiche pas correctement ne
se touche pas du tout. Ce qui manquait n'était pas la position, c'était **un état de
repli** : six onglets en `flex: 1 1 0` sur une barre fixe n'ont aucun comportement de
secours — dès que la place manque, ou que la feuille de style scopée n'est pas celle
attendue, les six libellés se collent, se tronquent ou s'empilent sans mise en forme.
Le remplacement suit le gabarit **Blazor par défaut**, pour cette raison précise : sans la
moindre feuille de style, il reste **une suite de liens lisibles les uns sous les autres**.
| Largeur | Menu | Bascule |
|---|---|---|
| < 40 rem (téléphone) | masqué, se déploie au clic | visible |
| ≥ 40 rem | rangée horizontale centrée sous le bandeau | masquée |
Le point de rupture est à **40 rem** et non aux 48 rem du reste de la feuille : six
libellés courts tiennent bien avant que la grille de cartes ne s'élargisse, et les cacher
derrière une bascule sur une tablette serait un clic de trop. Vérifié en exécution à
375 px (menu `none`, bascule `flex`) et à 1280 px (menu `row`, bascule `none`), sans
débordement horizontal ni dans un cas ni dans l'autre.
⚠️ Le menu **se referme sur `LocationChanged`** : `NavLink` ne referme rien de lui-même, et
un clic sur « Auteurs » laissait les six entrées empilées au-dessus de la liste atteinte.
Conséquence sur `app.css` : `--mb-onglets-hauteur` **a disparu**, et `.actions-flottantes`
se pose directement en `bottom: 0`. Plus rien ne l'accompagne en bas de l'écran.
### Un retour explicite dans le bandeau
« 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.
⚠️ `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.
### L'icône cassée du bandeau
`logo-bandeau.svg` était un fichier **séparé**, servi (ou non) indépendamment de
l'application : un appareil dont le cache précédait son ajout du 2026-08-20 recevait un
404, d'où l'icône cassée en haut à gauche. Le pictogramme est désormais **en ligne dans le
balisage** — un SVG en ligne ne peut pas manquer. Le fichier a été supprimé.
C'est la même famille de défaut que celle du lot A1 et de la mise à jour dépareillée : un
fichier statique à nom stable qu'un cache peut servir — ou ne pas servir — indépendamment
du reste.
### ⚠️ La bibliographie rappelait la BnF à chaque clic
Symptôme : « masquer/afficher prend énormément de temps, ainsi que le bouton Je le veux ».
Cause, et elle est instructive : chaque écriture appelait `ChargerAsync()`, c'est-à-dire
une **interrogation complète de la BnF** (≈1 s par page, jusqu'à **dix pages** pour les
nouveautés, qui ne sont pas mises en cache) suivie d'une **relecture de tout le catalogue**.
`ModifierBibliographieAsync` faisait de surcroît `_bibliographies.Clear()`, ce qui vidait
le cache de session mis en place au lot D.
**L'écran attendait une source distante pour apprendre un drapeau qu'il connaissait déjà.**
Masquer une œuvre ou l'ajouter à ses envies ne change *rien* à ce que la BnF sait de
l'auteur : seuls basculent des drapeaux **personnels**, que le client peut poser lui-même.
La correction est en deux endroits, et les deux sont nécessaires :
| Où | Quoi |
|---|---|
| `ServiceLivresApi.PatcherOeuvres` | corrige les bibliographies **en cache**, au lieu de les vider |
| `Bibliographie.MettreAJourOeuvre` | corrige la copie **que l'écran tient en main** |
⚠️ Le second n'est pas redondant : les **nouveautés ne sont pas dans le cache**, et c'est
précisément le cas le plus lent.
Mesuré en exécution après correction : **181 ms** pour « Je le veux », **226 ms** pour un
masquage groupé de deux œuvres — contre un rechargement BnF complet auparavant.
### La bibliographie est un écran où l'on coche
- **Case à gauche du titre, ligne centrée verticalement, boutons groupés à droite.**
Alignés en haut, la case flottait au-dessus du texte et les boutons pendaient sous lui.
Vérifié : case 1646 px, actions 279359 px, tous centrés sur la même ligne à 375 px.
- **La case est offerte sur TOUTE œuvre visible**, pas seulement sur celles à découvrir :
masquer en lot suppose de pouvoir cocher ce qu'on possède déjà.
- **La barre de sélection est en haut, et collante.** On coche en descendant : une barre
en bas obligeait soit à redescendre pour valider, soit — pire — recouvrait les dernières
lignes qu'on cherchait justement à cocher. Elle porte « Ajouter aux envies (n) »,
« Masquer (n) », « Réafficher (n) » et « Tout décocher », chaque compteur ne comptant
que ce que son bouton peut réellement traiter.
- **« Nouveautés » et « Ses livres chez vous » remontent à côté du filtre** : ce sont trois
façons de restreindre la même liste, et « Nouveautés » est ce qu'on veut *en ouvrant*
l'écran, pas après avoir parcouru cinquante lignes. Le composant `BasculeBibliographie`
évite d'en tenir deux copies (liste garnie, liste vide).
### Auteurs : chercher, trier, et aligner
- **Recherche et tri en mémoire, sans nouvel appel.** La liste des auteurs est déjà chargée
en entier — c'est elle qui sert l'instantané hors-ligne — et la filtrer côté serveur
aurait rendu la page inutilisable sans réseau. Recherche par **sous-chaîne du nom
normalisé** (comme le catalogue), tri **ordinal sur la forme normalisée** (comme
`FiltreLivresLocal`), pour qu'un appareil ne classe pas « Éluard » autrement qu'un autre.
- **Deux tris, et pas un troisième** : par nom (« où est untel ? ») et par nombre de livres
(« qui ai-je le plus ? »).
- **Trois colonnes dont une seule est élastique** : compteur à gauche sur **2,5 rem**
(trois chiffres — personne ne possède mille livres d'un même auteur), nom au milieu,
actions à droite. ⚠️ C'est la largeur **figée** des deux colonnes extrêmes qui fait tout
l'intérêt : sans elle, chaque ligne plaçait son compteur et ses boutons ailleurs, et
l'œil devait relire chaque ligne entière. Vérifié : bord droit du compteur à 56 px et
colonne d'actions à 152359 px, **identiques sur toutes les lignes**.
- Au passage, `champ-texte` du champ de renommage n'existait dans aucune feuille de style ;
il est devenu `champ-saisie`.
### Filtre de prêt au catalogue
`CritereLivres.Prete` : `true` = ce qui est dehors, `false` = ce qui est à la maison,
`null` = tout. ⚠️ **Commun au foyer**, contrairement au statut de lecture : aucune identité
n'entre dans son évaluation. L'écran « Prêts » répondait déjà à « qu'est-ce qui est
dehors ? » ; ce filtre sert l'autre moitié — « qu'est-ce que j'ai réellement sous la
main ? », avant de promettre un livre à quelqu'un.
⚠️ Toute dimension de filtre s'ajoute **aux deux implémentations** et au test qui les
confronte. Quatre jeux de critères ajoutés, et surtout un livre au **prêt clos** dans le
jeu de données : c'est exactement là que le serveur (`DateRetour == null`) et le client
(`PreteA is not null`) auraient pu diverger en silence.
La ligne se replie derrière « Filtrer », compte dans le compteur du bouton, et **disparaît
tant que rien n'est prêté** — mêmes règles et mêmes précautions que les formats et les
types (déduction sur un chargement `EstSansCritere` uniquement).
### Les exports portent le rang d'envie
Le CSV suivait déjà l'ordre choisi, mais un ordre **implicite se perd au premier tri par
titre** dans un tableur : une colonne « Rang » l'ouvre désormais.
⚠️ Le `.txt` en avait bien plus besoin : son **groupement par auteur détruit l'ordre**
d'envie — c'est le prix, assumé, d'une liste rangée comme une librairie. Le rang est donc
réinscrit devant chaque titre (` - [3] Titre`), sous une ligne qui l'explique.
Le rang imprimé est la **position dans la liste reçue**, pas la valeur brute de la colonne
`Rang` : celle-ci peut comporter des trous (une suppression ne renumérote pas), et ce qu'on
veut lire est « troisième de ma liste ».
### ⚠️ Les couvertures des envies : le défaut n'était pas où on le cherchait
« Dans la liste d'envies, aucune image n'est trouvée, ce qui est bizarre. »
Ce n'était ni le composant `Couverture`, ni OpenLibrary : **seul l'écran « ajouter une
envie » remplissait `CoverUrl`**. Or la plupart des envies arrivent par la bibliographie
d'un auteur ou par un tome manquant d'une série — deux chemins qui transmettent bien un
ISBN, mais aucune image.
Le repli est posé **à la lecture** (`ServiceSouhaits.Projeter`), et non à l'écriture : il
vaut alors aussi pour les envies **déjà enregistrées**, sans migration ni rattrapage. La
règle « sans ISBN, pas de couverture, et on n'en invente pas » est intacte — on n'invente
rien, on applique la formule OpenLibrary habituelle à un ISBN qu'on possède déjà.
### L'arbre des séries ne montre que la descendance
« Quand je parle de tree, je pensais vraiment à quelque chose de beaucoup plus simple. »
L'arbre dépliait la **liste complète des tomes** de chaque nœud, cartes de livres
comprises : un cycle de quatre séries occupait plusieurs écrans, et l'on ne voyait plus ce
qu'un arbre sert à voir — **qui contient quoi**. Il ne porte plus que le titre,
l'avancement cumulé et le lien vers la fiche ; les tomes ont leur écran.
⚠️ **Corollaire : tout est déplié par défaut**, et l'on replie ce dont on ne veut pas.
L'inverse obligeait à ouvrir chaque nœud pour découvrir s'il contenait quelque chose,
c'est-à-dire à faire à la main le travail de l'arbre.
`/series` **ne charge plus le catalogue entier** : l'arbre n'en a plus l'usage.
Vérifié en exécution sur *La Légende de Drizzt* : les trois lignes tiennent à 375 px,
indentées de 16 px et 42 px, et le cycle totalise bien « 0 sur 5 tomes » (3 + 2).
### L'ordre de lecture a son propre écran
« Je ne veux pas pouvoir modifier l'ordre sans le vouloir. »
Nouvelle route **`/series/{id}/ordre`**, sur le modèle exact de `/souhaits/ordre`. Les
flèches — tomes **et** sous-séries d'un cycle — n'existent plus qu'ici : offertes en
consultation, elles se déclenchaient au défilement du pouce, et l'ordre de lecture d'une
saga changeait sans qu'on l'ait voulu.
⚠️ Le mode est dans l'**adresse**, pas dans un booléen interne : convention du projet depuis
la fiche livre, et elle fait du bouton « retour » du navigateur une sortie naturelle. Les
trois routes partageant le même paramètre, le composant reste abonné à `LocationChanged`.
L'écran d'ordre **ne fait que réordonner** : ni actions par tome, ni ajout de tome. Le
numéro de rang, lui, reste visible en consultation — c'est une information, pas une action.
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.
### 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 »
calquée sur celle des livres, ou renommer « livre » en « ouvrage » et regrouper ?
**Tranché : une entrée unique**, atteinte depuis le catalogue **et** depuis les revues.
Le geste réel est « j'ai un truc avec un code-barres », pas « je vais cataloguer un
livre » : obliger à savoir d'avance ce qu'on tient était un détour, d'autant que le
décodage distingue déjà un `978`/`979` d'un `977`.
⚠️ **Le MODÈLE ne bouge pas, et il ne doit pas bouger** : une revue reste une `Revue`,
jamais un `Livre`. Fondre les deux tables obligerait à répéter « et qui n'est pas une
revue » à chaque lecture du catalogue, des compteurs, de la détection de doublons, des
séries et des bibliographies par auteur. C'est l'ergonomie qui est regroupée, pas le
schéma.
| Route | Rôle |
|---|---|
| `/ajout` (alias `/ajout/isbn`) | scan et saisie de code, pour livre comme pour magazine |
| `/ajout/manuel` | saisie manuelle d'un **livre** |
| `/revues/ajout` | saisie manuelle d'une **revue** (titre, ISSN, éditeur) |
⚠️ La saisie manuelle est le **seul** endroit où l'entrée unique doit reposer la question :
sans code à lire, rien ne peut trancher à la place de l'utilisateur. D'où deux boutons
distincts, et non un formulaire à bascule.
Le champ « nom d'une nouvelle revue » a quitté le milieu de `/revues`. `/revues/ajout`
enchaîne sur la **fiche** de la revue plutôt que de revenir à la liste : le geste n'est
jamais « créer une revue », c'est toujours « ranger un numéro ».
Vérifié en exécution : `9772466671438` saisi depuis « Ajouter un ouvrage » nomme toujours
« Médor » et propose d'ouvrir sa fiche.