Files
mabibli/CLAUDE.md
T
Mathieu LimonierandClaude Opus 5 6a6d745af4 MaBibli 1.0.0
Gestion de bibliothèque personnelle auto-hébergée : catalogue, prêts,
scan de code-barres, consultation hors-ligne.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 22:36:16 +02:00

12 KiB

CLAUDE.md — contexte projet MaBibli

Il ne redit pas ce que la documentation dit déjà. Le pourquoi détaillé, avec ses mesures, est dans docs/architecture.md — à lire avant de toucher au modèle, au hors-ligne ou au déploiement.

Document Répond à
README.md Qu'est-ce que c'est, comment je le lance ?
docs/architecture.md Pourquoi le code est ainsi ?
docs/installer.md Comment je l'héberge ?
docs/publier-une-version.md Comment je sors une version ?
../mabibli_ynh/doc/ADMIN.md Où sont les données, qui y a accès ?

Objectif

Bibliothèque personnelle auto-hébergée, pour un foyer, consultable au téléphone comme au PC. Une seule collection commune, des lectures personnelles.

Deux solutions existantes ont été essayées avant de se lancer : uBiblio (dépend de Google Books, clé d'API obligatoire) et BookLogr (auteur non récupéré, faute du second appel /authors/{id}.json d'OpenLibrary). Aucune ne cochait prêts + pas de Google + autofill fiable.

Périmètre de la V1

Tout ce qui suit est implémenté et éprouvé. La V1 est un travail de finition, pas d'ajout : ce qui n'est pas dans ce tableau n'y entre pas.

Catalogue livres physiques et ebooks (fiches uniquement, aucun fichier hébergé), revues et numéros, séries et cycles, thèmes
Prêts à qui, depuis quand, historique complet ; un seul prêt ouvert par livre
Enrichissement scan EAN-13 (zbar en WebAssembly), douchette USB, saisie manuelle ; cascade BnF → OpenLibrary
Lecture statut personnel à lire / en cours / lu
Envies liste personnelle de livres et de revues, ordonnée à la main, exportable .txt et .csv
Bibliographie tout ce qu'un auteur a écrit (SRU BnF), possédé grisé, nouveautés
Hors-ligne PWA installable, consultation seule depuis neuf instantanés IndexedDB
Déploiement paquet YunoHost (voie de référence), image Docker (voie secondaire, sans authentification)

Hors périmètre, délibérément : hébergement de fichiers (ebooks, photos de couverture), écriture hors-ligne, authentification propre, cloisonnement par utilisateur, rôles ou lecture seule.


Décisions techniques actées

Sujet Décision Pourquoi
Backend C# / ASP.NET Core, .NET 10 choix de l'utilisateur, typage fort
Frontend Blazor WebAssembly, en PWA tout en C#, PWA quasi native
Base SQLite + EF Core fichier unique, pas de serveur DB séparé
Auth SSO YunoHost par en-têtes SSOwat (YNH_USER) OIDC écarté : YunoHost n'en documente aucun pour les apps
Portée des données collection commune, statuts et envies personnels une bibliothèque de foyer ; voir « les trois portées »
Sources ISBN BnF d'abord, OpenLibrary ensuite fonds francophone ; Google Books écarté (préférence explicite, clé d'API)
Scan zbar (LGPL-2.1) compilé en WASM, notre JS garde la caméra ZXing.Net ne lisait pas des codes que zbar lit sur le même livre
Hors-ligne instantanés JSON en IndexedDB, lecture seule un cache de réponses HTTP ne rendrait rien d'une recherche jamais tapée en ligne
Ebooks fiches uniquement inventaire, pas hébergement
Hébergement YunoHost, installation native (pas Docker) recommandation YunoHost, meilleures perfs sur petit matériel
Binaires dotnet publish --self-contained, compilation locale, une archive par architecture (linux-x64 et linux-arm64) rien à installer côté serveur, rien à compiler sur le serveur. ⚠️ Le RID désigne le serveur : la machine qui compile peut être de l'autre architecture, le publish est croisé
AOT WebAssembly désactivé, à réévaluer après mesure ne pas payer le coût de build avant d'avoir constaté un problème

Les invariants à ne jamais casser

Chacun a été payé au moins une fois. Le détail est dans docs/architecture.md ; ce qui suit est la liste dure.

  1. Les trois portées. Commune (catalogue, auteurs, séries, revues, thèmes, prêts) — on ne filtre jamais dessus. Personnelle (statut de lecture, envies, bibliographies masquées) — on filtre toujours sur l'appelant, et l'objet d'autrui est introuvable (404), jamais refusé (403). Trace (AjoutePar) — une information, pas une frontière.
  2. Cinq tables refusent de fusionner : Livre, Revue, Serie, LivreSouhaite, RevueSouhaitee. Les fondre obligerait à écrire « et qui n'est pas une revue » à chaque lecture — un invariant qu'on réécrit partout finit par être oublié quelque part. L'ergonomie peut être regroupée ; le schéma, non.
  3. Le service n'écoute que sur 127.0.0.1. C'est une frontière de sécurité : YNH_USER n'est digne de confiance que parce que SSOwat l'écrase à chaque requête. Ne jamais élargir ASPNETCORE_URLS.
  4. Toute vue de l'API reçoit son instantané hors-ligne (CacheHorsLigne, noms de magasins : catalogue, auteurs, prets-en-cours, utilisateur, souhaits, series, revues, souhaits-revues, version). Sans le sien, l'écran est mort sans réseau — et le symptôme (un 404 en plein écran) ne désigne pas la cause.
  5. Toute dimension de filtre s'ajoute aux deux implémentations et au test qui les confronte : MaBibli.Shared/Catalogue/FiltreLivres.cs (serveur, sur IQueryable) et MaBibli.Client/Services/FiltreLivresLocal.cs (navigateur, sur des DTO).
  6. Toute écriture recalcule les colonnes normalisées (RecalculerFormes(), porté par les entités de MaBibli.Shared/Entites/). Sept colonnes en dépendent, dont trois portent une unicité — et ServiceRenormalisation les rattrape au démarrage.
  7. Toute route nouvelle s'inscrit dans RemonteeRoutes. Le repli existe pour qu'une route oubliée reste utilisable, pas pour dispenser de la ligne.
  8. Ne pas réactiver les empreintes WASM (WasmFingerprintAssets=false). Publier l'API réécrivait mal index.html et l'application restait blanche.
  9. On grise, on ne masque jamais ; hors-ligne on désactive avec son motif, on ne fait pas disparaître. Un bouton absent est indiscernable d'une fonction supprimée.
  10. On n'affiche que ce qui a été choisi — le format seulement pour les ebooks, le type de document seulement s'il n'est pas NonPrecise, les rôles seulement à partir de deux auteurs.
  11. Deux NULL sont distincts pour SQLite. Toute clé d'unicité personnelle (AuteurNormalise, NumeroNormalise) est NOT NULL avec un défaut vide.
  12. Le mode est dans l'URL, jamais dans un booléen interne (/livres/3 consulte, /livres/3/edition modifie) — et le composant s'abonne à LocationChanged, les deux routes partageant le même paramètre.
  13. amd64.url dans manifest.toml ne se change JAMAIS à la main, et depot_code en tête de publier.sh porte la vraie URL du dépôt. La documentation, elle, est neutralisée en forge.example.org — anonymiser ce qui sert casserait le déploiement, et l'échec surviendrait bien plus loin, à l'installation.

Deux pièges Blazor qui reviennent

  • Une règle CSS scopée n'atteint pas ce que rend un composant enfant (NavLink et tout composant du projet). Soit ::deep, soit la feuille globale — et les règles du menu restent globales, la navigation étant ce qui doit le moins pouvoir tomber.
  • Un composant qui écrit dans un objet prêté par son parent doit le prévenir (EventCallback), sinon tout ce que le parent calcule à partir de cet objet reste figé sur l'état d'avant.

Deux pièges de saisie

  • Un champ relié sur oninput à une propriété qui découpe puis recompose est intapable : son propre séparateur disparaît sous les doigts. SaisieListe porte la saisie un-par-un pour les auteurs, les thèmes et les articles à la une.
  • aria-expanded="@Booleen" ne marche pas : Blazor le traite comme un attribut de présence. Passer par Aria.Etat(...).

Travailler dans ce dépôt

dotnet build && dotnet test
dotnet run --project MaBibli.Api

L'API sert aussi le client compilé : une seule commande suffit.

  • Une seule migration, InitialCreate, appliquée seule au premier démarrage.
  • dotnet ef n'a pas besoin de démarrer l'application (FabriqueDbContextConception). Sa chaîne de connexion ne sert qu'à la génération de code.
  • Publier : mabibli_ynh/build/publier.sh, seul script, seule source des trois valeurs qui doivent rester cohérentes (version, URL, sha256). depot_code en tête du script commande toutes les URL dérivées. Marche à suivre complète dans docs/publier-une-version.md.
  • Toute la documentation vit dans ce dépôt. Ne pas recréer de guide dans mabibli_ynh — deux jeux de documentation pour une seule chaîne finissaient par diverger. Seules exceptions : mabibli_ynh/doc/DESCRIPTION.md et mabibli_ynh/doc/ADMIN.md, que YunoHost lit lui-même.
  • Les écrans se vérifient sur la géométrie réelle (getBoundingClientRect, scrollWidth) et les styles calculés, aux trois largeurs 320, 375 et 1280 px — pas à l'œil. Un débordement de 8 px ne se voit sur aucune capture d'écran.
  • La base de développement doit rester garnie (un cycle, des tomes, une revue à plusieurs numéros, des envies), sans quoi la moitié des écrans s'affiche vide et ne prouve rien.

Comment ce projet tranche

  • Mesurer avant de tailler, et toujours comparer deux publish : le coût d'une bibliothèque n'est pas le poids de son assembly, c'est la traîne qu'elle impose au trimmer.
  • La vitesse ne dit rien du taux de réussite. Un décodeur a été blanchi sur des mesures de vitesse alors qu'il ratait des codes.
  • Une source intermittente produit exactement les symptômes d'une source lacunaire.
  • Ce qui ne prétend rien vaut mieux que ce qui prétend faux : NonPrecise plutôt que Roman, NULL plutôt que 0 page, une tranche d'ISBN en moins plutôt qu'une fausse.

Ce qui reste ouvert

Rien ici n'est acté.

Après la V1, si l'usage le demande

  • Réordonner les envies de revues. Elles ont un Rang mais aucun écran pour les déplacer ; la section est courte par nature.
  • Filtrer le catalogue par thème. Demanderait une dimension de plus dans les deux implémentations de filtrage, le contrat de cache et le test de confrontation.
  • OpenLibrary en second rideau de la bibliographie par auteur. ⚠️ À ne pas confondre avec la cascade ISBN, où OpenLibrary sert déjà de seconde source : la bibliographie, elle, n'interroge que le SRU de la BnF, donc rien des auteurs étrangers non traduits. Deux difficultés : résoudre un nom vers un identifiant OpenLibrary (/search/authors.json), sensible aux homonymes ; et des œuvres remontées en langue originale, donc non rapprochables du catalogue par CleOeuvre.
  • Wikidata en troisième source ISBN, seulement si la cascade BnF → OpenLibrary montre ses limites en usage.

Écarté, et à ne pas rouvrir sans nouvelle demande

Item Raison
Photographier une couverture premier stockage de fichiers du projet, ce qu'« ebooks : fiches uniquement » écarte. Une URL se colle à la main
Traducteur, préfacier, photographe dans les rôles une valeur s'ajoute sans migration ; la renommer une fois posée sur des centaines de liens, non
Thèmes déduits d'un lookup le vocabulaire Rameau de la BnF est une indexation professionnelle, pas les étiquettes attendues
Réécrire le reboot Bootstrap dans app.css il porte les variables --bs-* dont ses propres règles dépendent, et un comportement vérifié sur 22 routes, pour 3 Ko