e46fa99e3bb5be8560c5dd9522fc171fb362905a
11
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
94f0ef15c0 |
Compte les pages d'un livre, et fais des envies une liste à deux moitiés
Lot U — le nombre de pages `Livre.NombrePages` est nullable, sans valeur par défaut : « 0 page » se lirait comme une donnée là où l'on veut dire « on ne sait pas ». Même raison que pour `TypeDocument.NonPrecise` — un défaut qui ne prétend rien n'a rien à reprendre, d'où une migration réduite à un `AddColumn`. Le service refuse un zéro plutôt que de l'écrire ; effacer le champ reste la façon de revenir à « inconnu ». Le préremplissage vient de `dc:format`, que rien ne lisait jusqu'ici. ⚠️ Ce champ n'est pas un nombre mais une phrase décrivant le support, et les notices déjà enregistrées sous Fixtures/ le montrent : « 1 vol. (113 p.) : ill., couv. ill. en coul. ; 18 cm », « 503 p. : couv. ill. ; 17 cm ». Les règles sont donc étroites — un nombre suivi de « p. » ou de « page(s) », rien d'autre — et tout le reste rend `null`. L'erreur n'est pas symétrique : un champ vide se remplit à la main en trois secondes, un chiffre faux s'enregistre sans que personne ne le voie. « 1 vol. » ne vaut pas 1, « 30 cm » ne vaut pas 30, et « (p. 45-90) », qui est une pagination de contribution, ne vaut rien. La valeur reste proposée dans un champ modifiable, et rien n'est déduit pour un ebook. Lot X — éditer une envie, et souhaiter une revue `PUT /api/souhaits/{id}` recalcule la clé d'œuvre et l'auteur normalisé : sans ce recalcul, le rapprochement « déjà au catalogue » continuerait de se faire sur l'ancienne forme, et le signalement mentirait sans le dire. Le filtre sur l'appelant fait partie de la clé de recherche, pas d'une vérification ultérieure — l'envie d'un autre est introuvable (404), jamais refusée (403). ⚠️ Une édition peut heurter l'unicité (utilisateur, œuvre, auteur), ce qu'un ajout ne peut pas : renommer une envie en une autre déjà présente répond par un message lisible, jamais par « UNIQUE constraint failed ». 400 et non 409, contrairement au doublon du catalogue : là-bas posséder deux exemplaires est légitime et l'appel se reconfirme, ici l'index l'interdit et il n'y a rien à confirmer. Le rang n'est pas touché — l'ordre a son propre point d'entrée. ⚠️ La couverture n'est écrite que si la charge utile en porte une. Aucun écran n'offre de champ « URL de couverture » pour une envie (décision actée), donc un remplacement inconditionnel l'aurait effacée à la première faute de frappe corrigée. `RevueSouhaitee` est une table sœur, et non des colonnes de plus sur `LivreSouhaite` : un numéro n'a pas d'auteur et se distingue par son numéro, deux choses que la clé d'unicité des envies de livres ne sait pas exprimer sans devenir fausse pour tout le monde. `NumeroNormalise` est NOT NULL avec un défaut vide — SQLite tient deux NULL pour distincts, et « Médor, sans numéro » s'ajouterait autant de fois qu'on cliquerait. L'ISSN est canonisé avec son tiret, seul code du projet rangé ainsi. Le coût de la table sœur est payé partout où il devait l'être : affichage, `.txt`, `.csv` et instantané hors-ligne `souhaits-revues`. ⚠️ Les revues forment une SECTION à part plutôt que des lignes entrelacées : chaque table numérote son rang indépendamment, et mélanger deux suites sans rapport produirait un ordre que personne n'a choisi. Le `.txt`, groupé par auteur, ne pouvait de toute façon pas les accueillir — elles n'en ont pas, et « Auteur non précisé » désigne des livres dont l'auteur est inconnu. Le CSV gagne une colonne « Type » : sans elle, un tri par titre rendrait revues et livres indiscernables, et la colonne des codes mêlerait ISBN et ISSN en silence. `ServiceRenormalisation` connaît la nouvelle table, avec la règle de collision déjà en place. ⚠️ L'ISSN y est canonisé à part : `Renormaliser` n'applique rien quand la clé ne bouge pas, un ISSN mal formé sur une ligne au titre inchangé y échapperait. `RevueSouhaitee` ne porte PAS de `CoverUrl` : rien à ajouter au garde de `GET /api/couvertures`. Vérifié en exécution : ISSN « 24666718 » rangé « 2466-6718 », édition de l'envie d'un autre en 404, et les deux exports portant bien les deux moitiés. 602 tests au vert (552 au départ), aucun avertissement de compilation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
1b47828c11 |
Remonte le type de document, colle le menu, et fais du retour une remontée
Cinq retours d'usage du 2026-08-21 qui ne touchent que l'interface (lots Q, R, S, T et V d'IDEES.md). Aucune migration, aucune entité modifiée. Q — L'ordre des champs du formulaire livre est une décision, pas une mise en page : type de document, thèmes, auteurs, rôles, puis le reste. Le type commande la suite de la saisie — les rôles ne se posent que là — et il fallait descendre tout le formulaire pour dire « c'est une BD », c'est-à-dire après avoir saisi ce qui en dépend. Rien n'est présélectionné : « non précisé » ne prétend toujours rien, et les rôles gardent leur règle actée (deux auteurs au moins, BD ou non). Des tests verrouillent la préservation des rôles à chaque frappe, que ce réordonnancement ne doit pas entamer. R — Bandeau et menu passent dans un même conteneur collant : sur PC la rangée des six destinations défilait avec la page et devenait inatteignable au bas d'une longue liste. Un seul conteneur, et non deux éléments collants superposés, qui auraient obligé à écrire en dur la hauteur d'un bandeau qui varie avec la pastille hors-ligne et le nom d'utilisateur. Sur téléphone le menu déployé occupe l'écran entier : sous le bandeau, il partageait l'écran avec la liste qu'on quittait. Trois portes de sortie s'ajoutent à la fermeture déjà en place sur LocationChanged — croix, Échap (le calque prend le focus à l'ouverture, comme celui d'agrandissement des couvertures) et clic hors des liens. Le plein écran est explicitement annulé au-delà de 40 rem, sans quoi un menu ouvert au doigt puis une fenêtre agrandie laisseraient un calque sans bascule pour le refermer. Toutes les règles du menu restent en feuille globale. S — ⚠️ Le retour du bandeau devient une remontée hiérarchique d'un cran de route, ce qui RENVERSE la décision actée le 2026-08-20 (« le retour passe par l'historique du navigateur, jamais par une destination calculée »). Le motif d'alors reste vrai — un même écran s'atteint par plusieurs chemins — mais l'historique remonte aussi les allers-retours (filtre, ordre, édition) et l'on cliquait cinq fois sans quitter le même écran. La parenté est une table explicite et testable, pas un découpage naïf d'URL : toutes les routes n'ont pas la forme d'une arborescence, /auteurs/{id}/bibliographie remontant à la liste des auteurs, dont il n'existe pas de fiche. Le garde-fou « ne jamais sortir de l'application » y vit désormais aussi : la fonction rend toujours un chemin interne, là où history.length ne disait pas ce qu'on croyait dans une PWA standalone. js/navigation.js n'a plus d'utilisateur et disparaît. T — Sur la fiche d'une série, « Modifier » et « Changer l'ordre » rejoignent la ligne du titre et de l'avancement, groupés à droite comme la bibliographie et la liste des auteurs le font déjà. Reléguées au bas de l'écran, les actions d'une saga de vingt tomes ne se découvraient qu'après avoir déroulé la liste. « Changer l'ordre » n'apparaît toujours qu'à partir de deux tomes ou deux sous-séries. V — Dans la liste des revues, toute la ligne ouvre la fiche, comme la carte entière le fait au catalogue. Le titre reste un vrai lien — adresse, clavier, clic-milieu — et son clic ne remonte pas jusqu'à la ligne, qui naviguerait une seconde fois ; toute action posée un jour sur cette ligne devra faire de même. ⚠️ Aucune vérification en navigateur : le rendu de ces écrans repose sur la compilation et la relecture. 552 tests au vert (506 avant ce lot). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
ad00fd5116 |
Aligne les thèmes sur le point-virgule des auteurs
Le champ des auteurs se saisit au point-virgule depuis toujours ; celui des thèmes se saisissait à la virgule. Deux champs voisins du même formulaire ne doivent pas se saisir de deux façons. La virgule reste acceptée en repli : c'était l'habitude, et un thème n'en contient jamais — contrairement à un titre d'article à la une, qui arrive au lot suivant et pour lequel elle couperait le titre en deux. D'où le paramètre de `ListeSaisie` plutôt qu'une règle unique. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
c2d408afc6 |
Ajoute les thèmes communs sur les fiches de livres
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> |
||
|
|
5d45eb492e |
Traiter le lot A d'IDEES.md : CSS onglets, pastilles, identité visuelle, couverture cassée, cache couvertures
- A1 : forcer la revalidation HTTP (Cache-Control: no-cache) sur les fichiers statiques, pour empêcher un navigateur de garder indéfiniment un CSS/JS périmé quand le service worker ne s'active pas. - A2 : le filtre du catalogue (statut, type de document) reprend les mêmes pastilles colorées que les cartes (Libelles.ClasseStatut / nouvelle Libelles.ClasseTypeDocument). - A3 : remplacement du logo par défaut de Blazor par un pictogramme de livre ouvert (favicon, icônes PWA, logo du bandeau, logo mabibli_ynh). - A4 : une couverture cassée désactive aussi son bouton d'agrandissement. - A5 : cache IndexedDB des couvertures pour la consultation hors-ligne, sans ralentir l'affichage en ligne (mise en cache en tâche de fond). L'allègement (redimensionnement, WebP) reste hors scope, à mesurer avant de s'y engager. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> |
||
|
|
07cb48b2be |
Distinguer scénario, dessin et couleurs sur chaque livre
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> |
||
|
|
49eb8e1c98 |
Distinguer romans et BD par un type de document
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> |
||
|
|
ebc5f95d49 |
Consulter la bibliotheque hors-ligne depuis un cache IndexedDB
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> |
||
|
|
6b783bafa0 |
Appliquer les retours d'interface : fiche en lecture seule, liste allegee
Cinq retours d'IDEES.md, tous pensés pour le mobile d'abord.
Fiche du livre en deux modes, portés par deux routes : « /livres/{id} »
consulte, « /livres/{id}/edition » modifie. Ouvrir une fiche depuis le
catalogue ne place plus un formulaire vivant sous le pouce. Mettre le mode
dans l'adresse fait du retour navigateur une sortie d'édition naturelle.
Piège rencontré : les deux routes visent le même composant avec le même Id,
donc aucun paramètre ne change et le routeur ne redessine pas — l'URL passait
à « /edition » sans que l'écran bascule, alors qu'un chargement direct de
cette URL marchait, ce qui masque le défaut. Le composant s'abonne donc à
LocationChanged.
Restent hors « Éditer », parce que ce ne sont pas des modifications de la
fiche : le statut de lecture (action la plus fréquente, et personnelle) et
les prêts (gestes du quotidien, donnée du foyer). La suppression, elle, est
réservée au mode édition.
Dans la liste, le statut devient une pastille en lecture seule sur la ligne
des étiquettes ; les trois boutons actionnables par carte partaient trop
facilement en faisant défiler. Le format ne s'affiche plus que pour les
ebooks, le physique étant le cas par défaut — le formulaire le
présélectionne déjà, l'énumération valant 0.
Couverture agrandissable au clic, fermeture au clic extérieur ou par Échap
(le calque est focalisé à l'ouverture, sans quoi Échap resterait sans effet).
Le substitut à initiale n'est jamais cliquable : le déclencheur n'existe que
s'il y a une image.
Le regroupement par auteur existait déjà (table Auteur, page /auteurs, filtre
« ?auteur= ») ; y manquaient les liens depuis la fiche et les styles de la
page auteurs, jamais écrits — la liste sortait en puces brutes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
b594a4b2c1 |
Sortir le statut de lecture et l'auteur de la table Livre
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> |
||
|
|
fd6438b912 |
Phase 3 : CRUD des livres, statuts de lecture et identite SSOwat
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> |