Files
mabibli/CLAUDE.md
T
mathieuandClaude Opus 5 d56061db6b Sortir les sources du tarball et acter ZXing.Net pour le scan ISBN
Le dépôt ne versionnait qu'une archive `mabibli-init.tar.gz`, ce qui
empêchait git de suivre le contenu des fichiers. Les trois fichiers sont
désormais à plat et l'archive est supprimée.

Scan ISBN : remplacement de `html5-qrcode` par ZXing.Net (C#, Apache 2.0).
Validé concrètement en .NET 10 — round-trip EAN-13 OK, publish Blazor WASM
OK, 0,53 ms/frame dans le pire cas (échec sur frame 640x480 bruitée),
+192 Ko brotli sur le payload. Motif principal : le décodage reste
réutilisable hors navigateur si le projet évolue en scanner de
bibliothèque.

Clarification du besoin hors-ligne : consultation de la bibliothèque
existante uniquement. Signale au passage que SQLite vit côté serveur et
qu'un cache client sera nécessaire — point d'architecture non résolu.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 21:01:46 +02:00

11 KiB
Raw Blame History

CLAUDE.md — Contexte projet MaBibli

Ce fichier donne le contexte complet du projet à Claude Code. Lis-le en entier avant de commencer à coder.

Objectif du projet

Application self-hosted de gestion de bibliothèque personnelle, à héberger sur YunoHost, accessible depuis smartphone et PC.

Fonctionnalités attendues (v1)

  1. Catalogue de livres physiques — liste, ajout, édition, suppression
  2. Catalogue de livres numériques (ebooks) — même chose, avec un champ format distinct du physique
  3. Gestion de prêts
    • Prêter un livre à une personne (nom, date de prêt)
    • Marquer comme récupéré (date de retour)
    • Historique des prêts passés par livre (pas juste l'état courant)
  4. Récupération automatique des infos via ISBN
    • Scan caméra du code-barres (EAN-13 / ISBN)
    • Saisie manuelle de l'ISBN
    • Dans les deux cas : appel à une ou plusieurs API pour pré-remplir titre, auteur, éditeur, couverture
  5. Statuts de lecture — à lire / en cours / lu (au minimum), assignable à chaque livre

Décisions techniques actées

Sujet Décision Pourquoi
Langage backend C# / ASP.NET Core Choix de l'utilisateur, typage fort
Frontend Blazor WebAssembly Support PWA quasi natif (dotnet new blazorwasm --pwa), tout en C#, offline partiel
Base de données SQLite + Entity Framework Core Fichier unique, pas de serveur DB séparé, adapté à un usage perso/familial
Auth / multi-utilisateur Déléguée au SSO de YunoHost Pas de système de login custom à maintenir
Hébergement YunoHost, installation native (pas Docker) YunoHost déconseille Docker pour ses apps (moins fiable, plus lourd) ; installation native = meilleures perfs sur petit matériel
Packaging YunoHost S'inspirer de radarr_ynh Radarr est aussi en .NET, packagé sans Docker sur YunoHost. Leur manifest.toml montre un déploiement self-contained (dotnet publish -r linux-x64 --self-contained), donc pas besoin d'installer dotnet-runtime via apt côté serveur — le binaire embarque son propre runtime
Scan ISBN ZXing.Net (C#, Apache 2.0) exécuté dans le WASM ; le JS ne fournit que les pixels caméra Décodage en C#, réutilisable hors navigateur si le projet évolue en scanner de bibliothèque. Voir la section dédiée ci-dessous
Consultation hors-ligne Cache local des données côté client (mécanisme à trancher) Le besoin est de consulter la bibliothèque existante sans réseau, pas d'enrichir de nouveaux livres. Voir « Stratégie hors-ligne »

Scan du code-barres — ZXing.Net (décision actée)

Le décodage EAN-13 se fait en C# avec ZXing.Net (micjahn, Apache 2.0), et non avec une bibliothèque JS type html5-qrcode.

Pourquoi

  • Réutilisable hors navigateur. Si le projet évolue vers un scanner de bibliothèque (app native, scan en masse, décodage d'une photo côté serveur), le code de décodage se transpose tel quel. Une bibliothèque JS serait à réécrire intégralement.
  • Un seul langage, cohérent avec le reste de la stack.
  • L'argument « offline » n'entre pas en compte ici : html5-qrcode fonctionne aussi hors-ligne (fichier JS servi par la PWA, aucun appel réseau). Ce n'est pas un critère de départage.

Mesures réelles (validées sur .NET 10, publish Blazor WASM OK)

Mesure Résultat
Décodage EAN-13 propre (380×160) 0,04 ms/frame
Pire cas : frame 640×480 bruitée sans code-barres (échec) 0,53 ms/frame
Surcoût du payload PWA +192 Ko (brotli)

Le pire cas est le chiffre qui gouverne le framerate : la majorité des frames caméra ne contiennent pas de code-barres lisible, et c'est l'échec de décodage qui coûte le plus cher.

⚠️ Ces chiffres sont mesurés en JIT x64 natif. En Blazor WASM le code est interprété par défaut : compter un facteur ~10-20×, soit ~5-10 ms/frame — largement suffisant pour scanner à 10-15 fps. Activer <RunAOTCompilation>true</RunAOTCompilation> ramène ça à 1-2 ms, au prix d'un build nettement plus lent.

Pièges à connaître

  • Le JS interop ne disparaît pas. getUserMedia et <canvas>/getImageData sont des API web sans équivalent C#. Prévoir ~30 lignes de JS maison dont le seul rôle est de pousser un byte[] vers C#. Toute la logique de décodage reste en C#.
  • Ne pas perdre de temps à essayer de réduire la taille via un reader ciblé. Remplacer MultiFormatReader par EAN13Reader pour aider le trimmer ne change rien : mesuré à 192 495 octets à l'octet près dans les deux cas. ZXing.Net n'est pas trim-friendly.
  • RGBLuminanceSource accepte directement le buffer RGBA du canvas (BitmapFormat.RGBA32) — aucune bibliothèque d'image nécessaire (pas de SkiaSharp ni ImageSharp).
  • Le scan caméra exige HTTPS (garanti par YunoHost en prod ; en dev, localhost est considéré comme sûr).

Squelette validé

using ZXing;
using ZXing.Common;

public static class IsbnScanner
{
    static readonly MultiFormatReader Reader = new()
    {
        Hints = new Dictionary<DecodeHintType, object>
        {
            [DecodeHintType.POSSIBLE_FORMATS] = new List<BarcodeFormat>
            {
                BarcodeFormat.EAN_13, BarcodeFormat.EAN_8,
            },
            [DecodeHintType.TRY_HARDER] = true,
        },
    };

    /// rgba : buffer brut issu de ctx.getImageData(...).data
    public static string? TryDecode(byte[] rgba, int width, int height)
    {
        var source = new RGBLuminanceSource(
            rgba, width, height, RGBLuminanceSource.BitmapFormat.RGBA32);
        return Reader.decode(new BinaryBitmap(new HybridBinarizer(source)))?.Text;
    }
}

Stratégie hors-ligne — à trancher avant de coder

Besoin réel : consulter la bibliothèque déjà enregistrée sans réseau (liste des livres, statuts, prêts en cours). Il ne s'agit pas d'enrichir de nouveaux livres hors-ligne — le lookup ISBN exige de toute façon un accès à OpenLibrary.

⚠️ Point d'architecture non résolu : « les données sont déjà en local » n'est vrai qu'au sens serveur. En Blazor WebAssembly, le code tourne dans le navigateur, alors que SQLite vit côté serveur. Le service worker de la PWA met en cache les assets (HTML/CSS/WASM), mais pas les réponses de l'API. Sans travail supplémentaire, l'app se lancera hors-ligne mais affichera une liste vide.

Options à évaluer :

  1. Cache client des réponses API (IndexedDB, ou cache du service worker sur les routes GET) — le plus simple, lecture seule, suffisant pour le besoin exprimé.
  2. SQLite compilé en WASM côté client, avec persistance IndexedDB/OPFS, synchronisé avec le serveur — bien plus lourd, ne se justifie que si l'écriture hors-ligne devient nécessaire.

Par défaut, partir sur l'option 1 tant que le besoin reste la consultation.

Sources de données ISBN — point d'attention important

Ne pas dépendre d'une seule source, et éviter Google Books si possible (préférence explicite de l'utilisateur : pas de dépendance à Google).

Stratégie : interroger plusieurs sources libres et gratuites, sans clé API obligatoire, en cascade (si la première ne répond pas ou renvoie des données incomplètes, essayer la suivante) :

  1. OpenLibrary (https://openlibrary.org/isbn/{isbn}.json) — source principale, gratuite, sans clé
    • ⚠️ Bug connu identifié lors des tests avec BookLogr : l'endpoint /isbn/{isbn}.json renvoie souvent le titre mais l'auteur est juste une référence (/authors/OL...A), pas le nom directement. Il faut faire un second appel vers /authors/{id}.json pour récupérer le nom. Un projet qui oublie ce second appel se retrouve avec titre rempli mais auteur vide (symptôme exact observé et diagnostiqué chez BookLogr) — ne pas reproduire ce bug.
    • La couverture est un service séparé : https://covers.openlibrary.org/b/isbn/{isbn}-L.jpg (peut exister même si la fiche bibliographique est incomplète)
  2. Fallback envisageable : autres sources ouvertes (à évaluer — éviter Google Books sauf si l'utilisateur change d'avis explicitement)

Modèle de données (base de départ, à affiner)

Livre
├── Id
├── Isbn
├── Titre
├── Auteur
├── Editeur
├── Format : Physique | Numerique
├── Statut : ALire | EnCours | Lu
├── CoverUrl
├── DateAjout
└── UtilisateurId (propriétaire, via SSO YunoHost)

Pret
├── Id
├── LivreId (FK vers Livre)
├── Emprunteur (nom, texte libre)
├── DatePret
└── DateRetour (nullable — NULL tant que non rendu)

Garder Pret comme table séparée (pas un champ sur Livre) pour conserver l'historique complet des prêts passés, pas juste l'état actuel.

Historique du projet (pourquoi ces choix)

L'utilisateur a testé deux solutions existantes avant de se lancer dans un projet custom :

  • uBiblio (Docker, Python) — gère prêts + scan ISBN, mais dépend de Google Books (clé API requise) pour l'autofill, ce qui ne convient pas à l'utilisateur qui veut éviter cette dépendance
  • BookLogr (Docker, Python) — utilise OpenLibrary nativement mais a le bug décrit ci-dessus (auteur non récupéré)

Conclusion : aucune des deux solutions existantes ne coche toutes les cases (prêts + pas de dépendance Google + autofill fiable) → développement d'une solution sur mesure.

Prochaines étapes suggérées

  1. Scaffolder le projet Blazor WebAssembly PWA (dotnet new blazorwasm --pwa) — commande vérifiée valide en .NET 10, l'option --pwa existe toujours
  2. Ajouter le projet API ASP.NET Core : le client WASM tourne dans le navigateur, SQLite vit côté serveur — une API est indispensable, elle n'était pas explicitée dans la version initiale de ce document
  3. Mettre en place le modèle EF Core + SQLite + migrations
  4. Implémenter le service de lookup ISBN (OpenLibrary avec le double-appel titre+auteur)
  5. CRUD livres (physique/numérique, statuts de lecture)
  6. Gestion des prêts
  7. Intégration scan caméra (ZXing.Net + interop caméra minimal)
  8. Cache hors-ligne pour la consultation (voir « Stratégie hors-ligne »)
  9. Packaging YunoHost (manifest.toml, conf/systemd.service, conf/nginx.conf, scripts/install) en s'inspirant de radarr_ynh

Questions ouvertes

  • Mécanisme exact du SSO YunoHost : le principe est acté (pas d'auth custom), mais l'implémentation reste à préciser — vraisemblablement la lecture d'en-têtes injectés par SSOwat derrière nginx (Remote-User). Cela conditionne le remplissage de UtilisateurId.
  • Sources ISBN de fallback : toujours « à évaluer ». Candidats sans clé API et hors Google : OpenLibrary Search API, BnF (SRU — pertinent pour le fonds francophone), Wikidata.
  • Choix du cache hors-ligne : voir la section dédiée.