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>
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.
- 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. - 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. - Le service n'écoute que sur
127.0.0.1. C'est une frontière de sécurité :YNH_USERn'est digne de confiance que parce que SSOwat l'écrase à chaque requête. Ne jamais élargirASPNETCORE_URLS. - 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. - Toute dimension de filtre s'ajoute aux deux implémentations et au test qui les
confronte :
MaBibli.Shared/Catalogue/FiltreLivres.cs(serveur, surIQueryable) etMaBibli.Client/Services/FiltreLivresLocal.cs(navigateur, sur des DTO). - Toute écriture recalcule les colonnes normalisées (
RecalculerFormes(), porté par les entités deMaBibli.Shared/Entites/). Sept colonnes en dépendent, dont trois portent une unicité — etServiceRenormalisationles rattrape au démarrage. - Toute route nouvelle s'inscrit dans
RemonteeRoutes. Le repli existe pour qu'une route oubliée reste utilisable, pas pour dispenser de la ligne. - Ne pas réactiver les empreintes WASM (
WasmFingerprintAssets=false). Publier l'API réécrivait malindex.htmlet l'application restait blanche. - 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.
- 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. - Deux
NULLsont distincts pour SQLite. Toute clé d'unicité personnelle (AuteurNormalise,NumeroNormalise) estNOT NULLavec un défaut vide. - Le mode est dans l'URL, jamais dans un booléen interne (
/livres/3consulte,/livres/3/editionmodifie) — et le composant s'abonne àLocationChanged, les deux routes partageant le même paramètre. amd64.urldansmanifest.tomlne se change JAMAIS à la main, etdepot_codeen tête depublier.shporte la vraie URL du dépôt. La documentation, elle, est neutralisée enforge.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 (
NavLinket 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.SaisieListeporte 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 parAria.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 efn'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_codeen tête du script commande toutes les URL dérivées. Marche à suivre complète dansdocs/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.mdetmabibli_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 :
NonPreciseplutôt queRoman,NULLplutôt que0page, 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
Rangmais 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 parCleOeuvre. - 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 |