Six lots, issus d'IDEES.md. Les décisions actées sont reportées dans
CLAUDE.md, et les entrées traitées retirées d'IDEES.md.
1. Douchette USB, ISSN et enchaînement du scan
- Champ ISBN focalisé à l'ouverture : c'est tout ce qu'exige une
douchette USB, qui se présente au système comme un clavier. Remède
au scan caméra, qui rate sur la webcam d'un PC (optique, pas
décodage).
- Un EAN en 977 est un périodique : il porte un ISSN, donc un titre de
revue. Intercepté AVANT la validation ISBN (c'est un EAN-13
parfaitement valide), l'ISSN est déduit du code puis nommé via
bib.issn. Auparavant la cascade s'exécutait en entier pour finir sur
« aucun résultat ».
- L'enchaînement après scan existait déjà mais était invisible : une
étape « Recherche » affiche désormais le code interrogé.
2. ISBN affichés avec des tirets
Tables extraites du RangeMessage.xml officiel — plusieurs tranches ne
sont pas celles qu'on suppose. Le francophone est découpé en entier,
ailleurs on s'arrête au groupe et à la clé : aucune coupure fausse.
Corrige au passage l'export CSV, où un ISBN nu était lu comme un
nombre par Excel.
3. Hors-ligne : la liste d'envies
ListerSouhaitsAsync était le seul point de lecture hors du dispositif
hors-ligne, d'où le « 404 Not Found » brut à l'écran. Cinquième
instantané, écritures refusées, plus aucun message HTTP. Même défaut
corrigé sur la bibliographie.
4. Navigation par onglets
Catalogue / Auteurs / Prêts / Envies dans MainLayout ; les barres
d'actions ne portent plus que des actions. Filtres repliés derrière un
bouton compteur, ligne « format » masquée quand le fonds n'a qu'un
format. Une seule entrée d'ajout, désactivée hors-ligne — pas masquée.
5. Liste d'envies : ordre, recherche, couvertures
Migration RangDesEnvies. Le remplissage reconduit l'ordre affiché
jusqu'ici : sans lui, les listes existantes se seraient réordonnées
toutes seules. Réordonnancement par flèches et glisser-déposer (le
drag & drop HTML5 ne marche pas au doigt). Ajout dans son propre
écran, avec recherche par titre (bib.title) et couvertures enfin
alimentées.
6. Bibliographie : une source muette n'est pas une liste vide
L'écran affichait « BnF injoignable » PUIS « la BnF ne connaît aucun
livre de cet auteur » — la seconde phrase étant fausse. Les deux cas
s'excluent désormais, et un bouton Réessayer est offert.
Vérifié en exécution : le diagnostic d'IDEES.md était faux sur un
point. Robert A. Harper a bien 7 œuvres à la BnF (85 notices
annoncées) ; c'était le même délai dépassé observé deux fois, pris
pour deux causes distinctes.
380 tests, dont un qui applique réellement la migration (EnsureCreated
n'en joue aucune) et un qui verrouille les messages atteignant
l'utilisateur.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Le piege que CLAUDE.md signale : en Blazor WebAssembly le code tourne dans le
navigateur alors que SQLite vit sur le serveur, et le service worker ne met en
cache que les assets. Sans travail explicite, l'application demarre hors-ligne
et affiche une bibliotheque vide.
- js/cache-hors-ligne.js : instantanes JSON dans IndexedDB, et surveillance des
bascules online/offline. Aucune logique metier.
- CacheHorsLigne / EtatReseau : lecture-ecriture des instantanes, et etat reseau
combinant navigator.onLine (fiable seulement par sa negation) avec le sort
reel des appels HTTP.
- ServiceLivresApi : les lectures retombent sur le cache, les ecritures sont
refusees. Le catalogue entier est memorise, pas les reponses filtrees : c'est
ce qui rend la recherche hors-ligne possible sur tout le fonds.
- FiltreLivresLocal : pendant navigateur de FiltreLivres, avec un test qui
confronte les deux implementations sur les memes donnees.
- Interface : pastille et bandeau d'etat avec la date de synchronisation, et
actions d'ecriture desactivees avec leur raison plutot que boutons morts.
- js/mise-a-jour.js : les empreintes WASM etant desactivees, le service worker
est le seul cache-busting du projet. L'enregistrement journalise desormais ses
echecs, et un bandeau propose la nouvelle version sans attendre la fermeture
de tous les onglets.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
La liste d'envies vit dans une table séparée (LivreSouhaite) plutôt que dans un
statut de plus sur Livre : un livre souhaité n'est pas possédé, et le loger dans
Livres l'aurait fait entrer dans le catalogue, les compteurs et les prêts, au
prix d'un « et qui n'est pas souhaité » à répéter dans chaque lecture. La portée
est personnelle, comme le statut de lecture — mais ici Utilisateur est une vraie
frontière : toute lecture filtre dessus.
Bibliographie : SRU BnF interrogé par bib.author, vérifié le 2026-08-18. Deux
filtres mesurés sur des réponses réelles sont indispensables — le type de
document (l'index mêle livres audio, jeux et spectacles) et surtout l'auteur
réel de la notice, « all » rapprochant les mots sur l'ensemble des auteurs :
« Émile Zola » remonte sinon toute l'œuvre de sa fille Denise Le Blond-Zola.
Les rééditions sont regroupées par clé d'œuvre (183 notices Werber -> 51
œuvres). Le rapprochement avec l'étagère se fait par titre, pas par ISBN, qui
désigne une édition et non une œuvre ; ses limites sont dites à l'écran.
Export en deux formats, tous deux du texte sans dépendance : .txt groupé par
auteur pour la librairie, .csv à séparateur point-virgule et BOM pour le
tableur.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
L'entite Pret existait depuis le squelette sans jamais etre exploitee. Cette
phase la met en service de bout en bout.
API
- GET /api/prets/en-cours ce qui n'est pas a la maison, du plus ancien
au plus recent : on cherche le livre oublie,
pas celui prete hier
- POST /api/livres/{id}/prets preter
- GET /api/livres/{id}/prets historique complet, du plus recent au plus
ancien
- POST /api/prets/{id}/retour clore le pret sans le supprimer
Un livre deja sorti ne peut pas etre prete une seconde fois. Le service le
verifie et nomme celui qui l'a deja, mais entre sa verification et l'insertion
il reste une fenetre : un index unique PARTIEL (LivreId WHERE DateRetour IS
NULL) la ferme, tout en laissant l'historique accumuler autant de prets clos
que necessaire sur le meme livre.
Les ebooks sont refuses cote API, pas seulement grises dans l'interface : une
fiche n'a pas d'exemplaire a confier.
Les prets sont COMMUNS au foyer, symetrique inverse du statut de lecture. Le
service ne recoit meme pas d'identite, pour qu'on ne puisse pas s'en servir
par inadvertance.
Interface, pensee mobile d'abord
- ecran « Prets en cours » avec bouton « Rendu » a meme la liste
- bloc pret sur la fiche d'un livre : etat courant, action, puis historique
- etiquette « Prete a X » dans le catalogue
- la barre d'actions passe sur deux lignes plutot que de comprimer ses
libelles maintenant qu'elle compte quatre entrees
Toutes les dates sont en UTC ; le client convertit la date locale du
<input type="date"> avant l'envoi, faute de quoi le pret se decalerait d'un
jour pour la moitie du globe.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
NettoyageIsbd.Auteur("Cormen, Thomas H. Auteur du texte") renvoyait
« Thomas H. Auteur du texte Cormen » : le point de l initiale etait
protege, donc plus aucun point ne servait de separateur de role.
Ce qui tranche : dans « Nom, Prenom », la seule chose qui puisse suivre
une initiale et faire encore partie du nom est une autre initiale
(« Tolkien, J. R. R. »). Tout le reste est le role. Le point est conserve
quand il clot une initiale, pour ne pas amputer le prenom.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Deux changements de modele en une seule migration, plus la recherche qui
en depend.
Le statut de lecture devient personnel. Il etait une colonne de Livre,
donc partage par tout le foyer, alors que deux membres lisent le meme
exemplaire a des rythmes differents. Il vit desormais dans une table
(LivreId, Utilisateur, Statut) avec unicite sur le couple. L'absence de
ligne vaut « non commence » : sur une bibliotheque de foyer la plupart
des couples n'ont aucun statut, et les materialiser tous multiplierait
les lignes par le nombre de comptes pour n'exprimer qu'un vide. Rien
n'est donc ecrit a la creation d'un livre.
Piege rencontre : un Dictionary<int, Statut> renvoyait la valeur 0 de
l'enumeration — « À lire » — pour un livre sans ligne, rendant « non
commence » indiscernable d'un choix explicite. Le dictionnaire est
desormais typé Statut?.
L'auteur devient une table. Deux formes normalisees y cohabitent, et ce
n'est pas une redondance : NomNormalise garde l'ordre de saisie pour la
recherche en sous-chaine, CleRegroupement trie les mots et porte l'index
unique, donc l'invariant « un auteur, une fiche ». Les initiales
echappent a la cle et sont traitees en memoire, sur une table qui compte
au plus quelques centaines de lignes.
Un livre peut avoir plusieurs auteurs — le lookup ISBN en renvoie quatre
pour Introduction to Algorithms — d'ou la table de liaison, avec une
position qui conserve l'ordre de la couverture.
Les rapprochements ambigus ne sont jamais appliques seuls : l'API les
liste, l'utilisateur accepte ou refuse, et les refus sont memorises pour
que la suggestion ne revienne pas. Le couple refuse est range par
identifiant croissant, donc un refus vaut dans les deux sens.
Reprise des donnees existantes. L'ancien statut, commun, est rattache a
AjoutePar — seule personne que la base associe au livre. Les statuts des
livres sans AjoutePar sont perdus : les attribuer serait une invention.
Les trois valeurs sont reprises telles quelles, « À lire » compris, parce
que c'est ce que l'ancienne interface affichait. L'ancien champ auteur
devient une fiche par valeur distincte.
La migration ne peut pas tout faire : lower() de SQLite ne retire pas les
accents, donc « Émile Zola » et « emile zola » y restent deux fiches.
ServiceRenormalisation finit le travail en C# au demarrage, reunit ces
variantes, applique aussi la regle des initiales — sans quoi une base
heritee resterait eclatee la ou une saisie neuve aurait ete reunie
d'emblee — et garde le nom d'affichage le plus presentable. Il est
idempotent, et sert de filet si les regles de normalisation changent.
L'ordre de la migration compte : les colonnes condamnees sont recopiees
dans une table de transit avant d'etre supprimees, parce que supprimer
une colonne sous SQLite reconstruit la table.
Verifie sur une base a l'ancien schema contenant 9 livres, 2 prets et
trois variantes de Zola : prets intacts, statuts rattaches, les trois
Zola reunis sous « Émile Zola », « P.F. Hamilton » absorbe par
« Peter F. Hamilton », « Hamilton » seul laisse en suggestion.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cas mesure, ISBN 9782749942636 :
dc:title = "Le plus grand defi ... (Nouvelle ed. augmentee) Aurélien Barrau"
dc:creator = "Barrau, Aurélien (1973-....). Auteur du texte"
Le nom est recolle en fin de titre sans le separateur ISBD « / », donc la
regle de nettoyage existante ne peut rien y faire : la notice est sale a
la source.
L'heuristique est volontairement stricte — comparaison en fin de chaine
uniquement, contre un auteur deja nettoye, et seulement si le nom
commence sur une frontiere de mot. « Lettre a Émile Zola » n'est pas
touche, et un titre qui EST le nom de l'auteur (biographie, recueil) est
laisse intact plutot que reduit a rien.
La comparaison passe par CompareOptions.IgnoreNonSpace plutot que par une
normalisation NFD : elle ignore les accents sans changer les longueurs,
donc les indices calcules restent valables sur la chaine d'origine.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
SQLite ne sait pas comparer sans tenir compte des accents : sa collation
NOCASE ne couvre que l'ASCII et il n'existe pas de unaccent() integre. La
seule facon d'obtenir « Emile » vers « Émile » sans charger tout le
catalogue en memoire est de stocker la forme mise a plat, et d'y chercher
un terme passe par exactement la meme fonction.
NormalisationTexte fait cette mise a plat. La ponctuation y devient une
espace au lieu de disparaitre : « P.F. Hamilton » doit donner trois mots,
sans quoi le rapprochement par initiales serait impossible.
RapprochementAuteurs porte la frontiere decidee dans CLAUDE.md. La cle de
regroupement — mots normalises tries — couvre casse, accents et ordre
inverse, et servira d'index unique. Les initiales abregees lui echappent :
elles demandent d'aligner les mots un a un, avec retour arriere, parce
qu'une correspondance gloutonne echouerait sur « P. Peter » face a
« Peter Paul ».
Le verdict est a trois niveaux, et l'asymetrie est deliberee : ne pas
fusionner laisse deux fiches a reunir d'un clic, alors qu'une fusion
erronee melange les livres de deux personnes et se defait mal. Un
patronyme seul face a un nom complet ressort donc « suggere », jamais
« certain ». Meme exigence sur l'alignement complet : « P. H. » et
« Peter Hamilton » s'alignent par les seules initiales, ce qui ne
concorde sur rien de verifiable — un mot entier commun est exige.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Le decodage EAN-13/EAN-8 est fait en C# par ZXing.Net (IsbnScanner), conformement
a la decision actee dans CLAUDE.md : le JavaScript ne fournit que les pixels de la
camera, aucune logique de decodage ne lui est confiee.
Le scan se greffe en amont du flux d'ajout par ISBN existant : des qu'un code est
lu et reconnu comme ISBN-13 valide, la recherche de notices est declenchee sans
que l'utilisateur ait a retaper quoi que ce soit. La saisie manuelle reste
accessible en permanence, y compris quand la camera echoue.
Points traites explicitement :
- camera arriere par defaut (facingMode "environment", en "ideal" pour ne pas
echouer sur un PC sans camera arriere) ;
- liberation de la camera a l'annulation, au succes et au demontage du composant ;
- cadence limitee a ~12 images/s et decodage restreint a la bande centrale
reduite a 640 px de large : inutile de decoder chaque frame en pleine taille ;
- messages distincts pour permission refusee, absence de camera, camera occupee
et contexte non securise (getUserMedia exige HTTPS ou localhost : une IP de
reseau local en http ne marchera jamais, c'est une source de confusion garantie).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
API
- CRUD complet sur /api/livres : liste filtrable (format, statut) et
cherchable (titre, auteur), consultation, creation, modification,
changement de statut isole, suppression.
- Aucune lecture ne filtre sur AjoutePar : la bibliotheque est commune au
foyer. Le critere n'existe meme pas dans CritereLivres, pour qu'on ne
puisse pas s'en servir par inadvertance. Deux tests verrouillent
l'invariant, dont un par reflexion sur le type des criteres.
- AjoutePar est renseigne par le serveur a la creation, jamais par le
client, et n'est pas reecrit a l'edition (pas plus que DateAjout).
- Identite lue dans les en-tetes SSOwat (YNH_USER, YNH_USER_EMAIL,
YNH_USER_FULLNAME), avec repli sur un utilisateur simule configure en
developpement uniquement. Sans en-tete ni simulation, l'identite reste
inconnue plutot qu'inventee. Expose par GET /api/moi.
- Migration appliquee au demarrage : pas d'etape manuelle a l'installation.
Interface Blazor, pensee mobile d'abord
- Catalogue unique (physiques et numeriques melanges, distingues par le
format) avec recherche, filtres et changement de statut directement dans
la liste.
- Ajout par ISBN : quand la cascade rend plusieurs notices, un ecran de
choix les presente en mettant en avant editeur et annee, seuls elements
qui les departagent. Le formulaire reste ensuite entierement modifiable.
- Ajout manuel, edition, suppression avec confirmation explicite.
- Couvertures : l'URL OpenLibrary n'est jamais validee par un appel
prealable (502 intermittents mesures en phase 2) ; un attribut onerror
masque l'image cassee et laisse apparaitre un substitut.
Decisions prises la ou CLAUDE.md etait muet
- Tri alphabetique par titre.
- Un ISBN saisi doit etre valide, mais reste facultatif.
- Ecran de choix affiche seulement a partir de deux candidats.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Recuperation des metadonnees d'un livre a partir de son ISBN, cote serveur
uniquement (aucune interface, aucune persistance).
Cascade : BnF en ISBN-13, puis BnF en ISBN-10 converti, puis OpenLibrary.
La conversion 13 -> 10 est indispensable et non optionnelle : la BnF indexe
l'ISBN tel qu'imprime, les ouvrages d'avant 2007 ne portent qu'un ISBN-10 et
sont introuvables par l'EAN-13 que lit le scanner.
Toutes les notices trouvees sont remontees (jusqu'a 5) : un meme ISBN peut
correspondre a plusieurs reeditions, le choix revient a l'utilisateur.
Le double appel /isbn puis /authors d'OpenLibrary est bien fait, avec repli
sur l'oeuvre quand l'edition ne porte aucun auteur : c'est le bug de BookLogr
(titre rempli, auteur vide) qu'il ne faut pas reproduire.
La couverture vient toujours d'OpenLibrary, avec ?default=false pour obtenir
un 404 plutot qu'une image placeholder.
87 tests xUnit, sur fixtures enregistrees : aucune dependance au reseau.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>