Documente les lots I à P, et ce qui reste volontairement de côté

Les décisions, les pièges, et ce qui n'a pas été vérifié : les écrans reposent
sur la compilation et la relecture, pas sur une exécution en navigateur.

Restent écartés, avec leur motif : l'allègement des couvertures (à mesurer
d'abord, même règle que l'AOT), la photo de couverture (le projet ne stocke pas
de fichiers), l'URL finale après redirections, les homonymes d'auteur, et la
confirmation de l'EAN-2, qui demande un magazine devant la caméra.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mathieu
2026-08-21 11:25:58 +02:00
co-authored by Claude Opus 5
parent 7c050cb410
commit 41e5c7a3fb
2 changed files with 229 additions and 5 deletions
+191
View File
@@ -2602,3 +2602,194 @@ implémente donc `IAsyncDisposable` et libère aussi lorsqu'elle change d'URL
d'image, donc `loading="lazy"` ne déclenche jamais le décodage. Ce n'est pas un défaut de la
couverture — le blob a été décodé à la main par `createImageBitmap` pour le prouver. Ne pas
partir en chasse là-dessus.
## Lots I à P — retours d'usage du 2026-08-20, traités le 2026-08-21
Onze items, cinq familles. Ce qui suit ne redit pas ce que le code montre : seulement les
décisions, et ce qui a failli être fait de travers.
### K2 — l'ISSN prend son tiret, et c'est la forme RANGÉE EN BASE
C'est le seul point où le projet s'écarte de la règle « la valeur stockée reste nue », tenue
pour l'ISBN, et il y a une raison précise : `CodePeriodique.IssnDepuis` produit **déjà** un ISSN
à tiret depuis le code-barres, et la BnF interroge `bib.issn` avec le tiret. Un ISSN tapé
« 24666718 » ne se rapprochait donc de **rien** — ni de la revue déjà créée par un scan, ni
d'une notice.
⚠️ La canonisation a lieu **avant la recherche** dans `ServiceRevues`, pas seulement à
l'écriture. Vérifié en exécution : `POST /api/revues` avec `24666718` puis avec `2466-6718`
retombent sur la **même fiche** (id 1), là où le second aurait créé une seconde revue.
`FormatageIssn` est bien plus simple que `FormatageIsbn`, et il faut voir pourquoi : un ISSN se
coupe **toujours** au même endroit, alors que les tranches d'un ISBN dépendent du groupe puis de
l'éditeur. Aucune table embarquée, aucune coupure ne peut être fausse. Comme pour l'ISBN, ce qui
n'est pas un ISSN ressort **intact** — un code mal recopié doit se voir mal recopié.
⚠️ `ServiceRenormalisation` rattrape les ISSN existants, et **pas** par le mécanisme générique :
la clé est *nullable*, or un jeu de clés confondrait tous les `NULL` en une seule valeur et la
deuxième revue sans ISSN bloquerait la première. D'où `CanoniserLesIssn`, avec la même règle de
collision que partout ailleurs — la fiche dont la forme canonique est déjà prise **garde la
sienne**, et le serveur démarre.
Les champs de **saisie** gardent la valeur tapée : découper à la frappe se battrait avec le
curseur, et c'est la règle déjà actée pour l'ISBN.
### O1 — la couverture d'un numéro est une URL, jamais des octets
**Tranché avec l'utilisateur le 2026-08-21.** `NumeroRevue.CoverUrl` se colle à la main :
aucune source ne peut la fournir, l'ISSN désignant la **revue** et non la parution. **K1 (photo
de couverture depuis la caméra) n'est pas fait** : ce serait le premier stockage de fichiers du
projet, ce qu'« ebooks : fiches uniquement » écarte — espace disque YunoHost, sauvegardes plus
lourdes, et un cache hors-ligne dont la clé est une URL.
⚠️ **Le piège, et il est silencieux** : le garde de `GET /api/couvertures` n'autorisait que
`Livres.CoverUrl` et `LivresSouhaites.CoverUrl`. Oublier `NumerosRevue.CoverUrl` n'aurait produit
**aucune erreur visible** — seulement une image qui s'affiche en ligne et jamais hors-ligne,
c'est-à-dire exactement le défaut corrigé le 2026-08-20 pour les hébergeurs sans CORS. Un test
le verrouille, et le relais a été vérifié en exécution sur une couverture de numéro (**200
image/jpeg, 43 921 o**, deux redirections traversées).
⚠️ Un `PUT /api/revues/numeros/{id}` a dû être ajouté, sans quoi couverture et une n'auraient
existé **qu'à la création, c'est-à-dire jamais** : on note un numéro le jour où on le range, on
en recopie le sommaire plus tard.
### O2 — les articles à la une : table à part, séparateur point-virgule
**Sur le NUMÉRO**, jamais sur la revue (déjà tranché) : les articles à la une changent à chaque
parution.
**Table `ArticleUne` à part, et non `Theme`.** Un thème est un vocabulaire qu'on *réutilise*
« dark fantasy » revient sur dix livres — alors qu'un titre d'article est unique à sa parution.
Rangés dans `Themes`, ils rempliraient de bruit un vocabulaire qui sert le catalogue.
⚠️ Conséquence directe qu'on manque en recopiant le modèle voisin : **pas de n-n**. Une simple
clé étrangère vers le numéro suffit, puisque rien ne se partage. Unicité `(NumeroRevueId,
TitreNormalise)` — deux parutions peuvent parfaitement titrer pareil, un test le verrouille.
**Le séparateur est le point-virgule**, et la question posée dans IDEES.md a été tranchée avec
l'utilisateur : **les thèmes de livres s'alignent dessus**, la virgule restant acceptée en repli.
| Champ | Sépare sur | Pourquoi |
|---|---|---|
| Auteurs | `;` | déjà le cas depuis toujours |
| Thèmes | `;` **et** `,` | un thème ne contient jamais de virgule ; la virgule était l'habitude |
| Articles à la une | `;` **seul** | « Ukraine, deux ans après » serait coupé en deux |
Trois champs voisins du même formulaire ne doivent pas se saisir de trois façons. `ListeSaisie`
porte le découpage une seule fois, avec ce paramètre pour unique différence.
⚠️ Le service **remplace** les articles, il ne les fusionne pas : la ligne de saisie *est* la
liste. Un titre effacé du champ disparaît, comme pour les thèmes.
### P — ranger une envie dans une série : le geste est du côté de la SÉRIE
Choisi avec l'utilisateur, pour la raison donnée dans IDEES.md : c'est là qu'on voit l'ordre de
lecture, donc là qu'on sait quelle position donner.
⚠️ **Rien ne relie l'envie à la place en base, et c'est tout le sujet.** Les séries sont
**communes** au foyer, la liste d'envies est **personnelle**, et son sens même est de préparer
un cadeau sans que l'autre le voie venir. Une `ElementSerie.LivreSouhaiteId` afficherait « tome 3
souhaité par untel » à tout le monde. Le geste crée donc une place **ordinaire**`LivreId` à
`NULL`, titre repris de l'envie — et **n'épargne qu'une resaisie**, ce qui est exactement la
demande.
Ce qui doit rester invisible est le **lien**, pas l'existence du tome : on l'aurait saisi à la
main de toute façon.
**L'envie survit au rattachement**, dans le prolongement exact de « une envie déjà au catalogue
est signalée, jamais supprimée » — et à plus forte raison ici, puisque rien n'a été acheté.
L'écran le dit, sans quoi on croirait avoir déplacé quelque chose.
⚠️ Les envies dont le titre est **déjà** un tome de la série ne sont pas proposées : la place
n'étant identifiée que par son titre, les offrir mènerait droit au doublon.
### J1 et J2 — le scan là où il manquait
`ScannerCodeBarres` était déjà un composant autonome : les deux items sont donc du raccordement,
pas du décodage.
- **J1, depuis une place vide d'une série** : le scanner s'ouvre **dans la place visée**, sous le
champ ISBN existant. ⚠️ C'est ce qui répond à l'exigence « le retour doit ramener sur la série
**et** sur la place » — il n'y a pas de retour, on n'a jamais quitté la place. Passer par
`/ajout` obligeait à revenir rattacher à la main, donc à risquer le mauvais tome. Les revues
(`977`) restent hors du flux, `ElementSerie.LivreId` ne pointant que vers `Livre`.
- **J2, depuis « ajouter une envie »** : même composant, sans création de `Livre` au bout. ⚠️
L'add-on EAN-2 est **ignoré** ici, et ce n'est pas un oubli : il ne concerne que les revues,
qui ne se souhaitent pas.
### I1 et I2 — la bibliographie
- **I1, « Tout cocher »** vit **à côté du filtre**, et non dans la barre de sélection — celle-ci
n'apparaît qu'une fois une case cochée, c'est-à-dire trop tard pour rendre service. ⚠️ « Tout »
signifie **ce qui est actuellement visible** : après filtre, et selon que les masquées sont
affichées. Cocher les 200 notices remontées alors que l'écran n'en montre que trente serait
précisément ce que les compteurs par bouton cherchent à éviter.
- **I2, la couverture d'une œuvre non possédée** n'est rendue **qu'au dépliage**. Une
bibliographie compte des dizaines de lignes : les charger d'avance ferait payer des images que
personne ne regarde. La règle « sans ISBN, pas de couverture, et on n'en invente pas » est
intacte — la formule OpenLibrary s'applique à l'ISBN déjà repris du `dc:identifier`.
### L1 — l'attente se voit
`Patience.razor` : une rondelle et une phrase, `role="status"`. Posé sur le lookup ISBN, les
recherches BnF, la bibliographie et les nouveautés.
⚠️ **Un indicateur ne remplace pas un message d'échec** : une source muette garde ses
`EtatSourceBibliographie` et ses motifs. Ici on dit « ça travaille », jamais « ça a marché ».
Le besoin n'est pas décoratif, et le lot H l'avait montré : un écran qui ne dit rien pousse à
recliquer, donc à **relancer** l'appel. L'animation est neutralisée sous
`prefers-reduced-motion`, l'indicateur restant visible — c'est lui qui porte l'information, pas
sa rotation.
### M1 — champ et bouton sur une ligne
⚠️ `min-width: 0` sur le champ est ce qui fait tenir la ligne à 320 px : sans lui, un élément de
formulaire refuse de rétrécir sous sa largeur intrinsèque et pousse le bouton hors de l'écran. Le
bouton ne rétrécit pas et son libellé ne se coupe pas — un bouton tronqué ne se lit plus. Le
libellé est passé à « Créer », plus court, la phrase d'aide au-dessus disant déjà de quoi il
s'agit.
### ⚠️ `dotnet ef` n'a plus besoin de démarrer l'application
`FabriqueDbContextConception` (un `IDesignTimeDbContextFactory`) a été ajouté. Sans elle,
`dotnet ef` exécute `Program.cs` pour retrouver les services — or `Program.cs` appelle
`Database.Migrate()` au démarrage. **Écrire** une migration supposait donc d'ouvrir la base de
développement : au mieux inutile, au pire bloquant. Constaté ici, sur un système de fichiers
réseau où le verrou SQLite ne se prend jamais — l'outil attendait cinq minutes puis renonçait,
en laissant un processus qui verrouillait les binaires.
⚠️ Sa chaîne de connexion ne sert **qu'à la génération du code** : produire une migration
n'ouvre aucune base. Elle n'a pas à correspondre à quoi que ce soit, et surtout pas à la
production, configurée par `ConnectionStrings__MaBibli` dans l'unité systemd.
### Ce qui n'est délibérément pas fait, et pourquoi
| Item | Raison |
|---|---|
| **A5** — alléger les couvertures (WebP, redimensionnement) | à **mesurer** avant de s'y engager, même règle que l'AOT WASM. Aucune mesure sur un fonds réel |
| **K1** — photographier la couverture | premier stockage de fichiers du projet ; écarté avec l'utilisateur au profit de l'URL collée |
| **N2** — stocker l'URL finale après redirections | plus urgent depuis que le cache est consulté en ligne ; et rien ne garantit qu'une URL d'`archive.org` reste stable |
| **Homonymes** (« Between two worlds ») | une œuvre en trop se voit et s'ignore, contrairement à une œuvre manquante. Demanderait de garder les dates de vie du `dc:creator` |
| **OpenLibrary en second rideau** | la justification est retombée (voir « Une source muette n'est pas une bibliographie vide ») ; les œuvres remonteraient en langue originale, donc non rapprochables par `CleOeuvre` |
| **EAN-2 confirmé en kiosque** | demande un magazine réel devant la caméra : rien de ce qui se fait au clavier ne le remplace |
### Vérifié en exécution le 2026-08-21
API lancée sur une base neuve, migration `ImagesEtUnesDesNumeros` appliquée au démarrage :
| Cas | Résultat |
|---|---|
| `POST /api/revues` avec `24666718` | fiche créée, `issn` = **`2466-6718`** |
| `POST /api/revues` avec `2466-6718` | **même fiche** (id 1), aucune seconde revue |
| `POST …/numeros` avec couverture et deux articles | les deux ressortent dans `NumeroRevueDto` |
| `PUT /api/revues/numeros/1` | la une est **remplacée**, la note apprise après coup |
| `GET /api/couvertures?url=…` (couverture de **numéro**) | **200 image/jpeg, 43 921 o** |
| `GET /api/couvertures?url=…` (URL inconnue) | 404 |
**506 tests au vert** (479 avant ce lot).
⚠️ **Ce qui n'a PAS été vérifié en navigateur** : les écrans. Le rendu de la fiche revue, du
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.