Le rôle vit sur le LIEN livre-auteur, jamais sur l'auteur : le même auteur
scénarise un album et dessine le suivant, et il ne doit pas exister en deux
fiches pour autant — ce que l'index unique sur la clé de regroupement
interdit de toute façon.
Les rôles se retrouvent par clé d'auteur résolue et non par position :
ResoudreAsync écarte les noms vides et fusionne les variantes, donc sa
liste peut être plus courte que la saisie.
Piège traité : le champ des noms reconstruit la liste à chaque frappe.
Decouper reprend les rôles connus en rapprochant sur la forme normalisée,
sans quoi corriger une faute de frappe les effacerait tous.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Clôt la décision du premier lot, qui écartait les périodiques faute de
modèle. Douze numéros d'un magazine ne font plus douze fiches identiques.
Table à part, pour la troisième fois et pour le même motif que la liste
d'envies et les séries : logée dans Livres, une revue entrerait dans le
catalogue, ses compteurs, les doublons et les bibliographies, et il
faudrait écrire « et qui n'est pas une revue » à chaque lecture.
Un numéro est recensé, rien de plus — ni prêt ni statut, tous deux liés à
Livre par clé étrangère. Réversible.
« Créer ou retrouver » et non « créer » : scanner le numéro suivant du même
magazine retombe forcément sur la même revue. L'unicité de l'ISSN est un
index partiel, sans quoi les revues sans ISSN se bloqueraient entre elles.
Le flux 977 mène désormais à la fiche de la revue, et ne pré-remplit plus
le formulaire d'un livre.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Première relation entre livres du projet. Une série contient des séries
(un cycle EST une série de séries) et des PLACES dans l'ordre de lecture —
pas des livres : une place sans livre est le trou qu'on vient voir.
L'ordre est stocké, jamais déduit d'une année : une préquelle se lit avant
le livre paru dix ans plus tôt. Les tomes non possédés se saisissent à la
main, aucune source ne donnant l'ordre de lecture d'une saga.
Le titre de la place survit à la suppression du livre (SetNull, pas de
cascade), sans quoi perdre un exemplaire effacerait le tome 3 de la série.
Les séries sont communes au foyer. Seule exception : la mise en envies d'un
tome manquant, qui écrit dans une liste personnelle — c'est le seul point
où les deux portées se rencontrent.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Format disait physique ou numérique, jamais roman ou BD. Trois valeurs
seulement : une valeur s'ajoute sans migration, la renommer une fois posée
sur des centaines de fiches, non.
Le défaut est « non précisé », pas « roman » : les fiches existantes n'ont
pas été saisies avec cette question en tête, et un AddColumn suffit
justement parce que la valeur par défaut ne prétend rien. L'interface
n'affiche donc que ce qui a été choisi, comme pour le format.
Filtre ajouté aux DEUX implémentations, serveur et hors-ligne, avec quatre
jeux de critères de plus dans le test qui les confronte.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
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>
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>
Mise en place de la structure de base, sans aucune fonctionnalité métier :
- MaBibli.sln à la racine, avec trois projets ciblant net10.0
- MaBibli.Client : Blazor WebAssembly PWA (dotnet new blazorwasm --pwa),
pages d'exemple du modèle (Counter, Weather) retirées
- MaBibli.Api : ASP.NET Core, sert aussi les fichiers statiques du client
(UseBlazorFrameworkFiles + MapFallbackToFile), via le package
Microsoft.AspNetCore.Components.WebAssembly.Server
- MaBibli.Shared : entités et enums partagés (Livre, Pret, Format, Statut),
conformes au modèle de données de CLAUDE.md — AjoutePar est une simple
traçabilité, Pret est une table séparée pour garder l'historique complet
- EF Core + SQLite dans l'API : MaBibliDbContext, chaîne de connexion,
et migration initiale InitialCreate vérifiée par un database update
- dotnet-tools.json : dotnet-ef épinglé en outil local
Vérifié : dotnet build sans avertissement, et
dotnet publish MaBibli.Api -c Release -r linux-x64 --self-contained
produit un dossier unique dont le wwwroot contient bien _framework/.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>