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>
This commit is contained in:
LIMONIER Mathieu
2026-08-20 13:03:14 +02:00
co-authored by Copilot
parent ce58294fd1
commit 5d45eb492e
16 changed files with 318 additions and 74 deletions
+82
View File
@@ -1684,6 +1684,88 @@ extérieur ou par Échap. Le calque est focalisé à l'ouverture (`FocusAsync`)
**Le substitut à initiale n'est jamais cliquable** : le bouton déclencheur n'est rendu que
lorsqu'une URL de couverture existe. Sans couverture, il n'y a rien à agrandir.
## Habillage — retours d'usage du 2026-08-19 traités (2026-08-20)
Lot A d'IDEES.md (4ᵉ série). Quatre items traités ; le cinquième (allègement des couvertures,
WebP) reste volontairement hors scope, voir plus bas.
### A1 : ce n'était pas un bug de style, mais l'absence de revalidation HTTP
Les six onglets s'affichaient parfois en liens bruts, sans aucune règle de
`MainLayout.razor.css`. Cause retenue : `UseStaticFiles()` ne posait **aucun** en-tête
`Cache-Control` — un navigateur peut donc garder `MaBibli.Client.styles.css` en cache
heuristique, **indépendamment du service worker**. Le service worker revalide bien via
`cache: 'no-cache'`, mais seulement quand il parvient à s'activer, ce qui n'est pas garanti
(voir « Le service worker ne s'enregistre pas… »).
**Correction** : `Program.cs` pose désormais `Cache-Control: no-cache, must-revalidate` sur tous
les fichiers statiques (`StaticFileOptions.OnPrepareResponse`). Les empreintes WASM étant
désactivées (noms de fichiers stables, voir plus haut), c'est cette revalidation forcée qui
empêche un navigateur de servir indéfiniment une version périmée — le service worker reste une
seconde ligne de défense, pas la seule.
⚠️ `no-cache` ne veut pas dire « ne jamais mettre en cache » : ETag et Last-Modified, déjà posés
par défaut par `StaticFileMiddleware`, permettent une revalidation conditionnelle (304) sans
retélécharger un fichier inchangé.
### A2 : le filtre reprend les pastilles de couleur des cartes
Le filtre du catalogue (statut de lecture, type de document) affichait des segments neutres,
alors que `ChoixStatut` (statut personnel sur la fiche) colorait déjà ses segments avec
`Libelles.ClasseStatut`. Les segments et l'étiquette de la carte reprennent maintenant la même
classe — le filtre statut réutilise `ClasseStatut`, et une nouvelle méthode
`Libelles.ClasseTypeDocument` (bleu pour Roman, magenta pour BD, aucune couleur pour
« Non précisé », qui n'est pas une valeur choisie) est appliquée aux deux endroits : le segment
du filtre et l'étiquette `etiquette-type` de la carte. Le formulaire de saisie reprend la même
classe, pour ne pas diverger une troisième fois.
### A3 : une identité visuelle, encore le logo par défaut de Blazor
`favicon.png` / `icon-192.png` / `icon-512.png` étaient encore le logo « @ » violet du template
`dotnet new blazorwasm`. Remplacés par un pictogramme de livre ouvert (deux pages en V, tranche
centrale blanche), dans le bleu d'accent existant (`#1b3a5c`) — généré par script (Pillow), pas
par un outil de génération d'images IA, pour rester un simple dessin géométrique. Un petit
`logo-bandeau.svg` (blanc, net à toute résolution) accompagne « MaBibli » dans le bandeau.
`mabibli_ynh/logo.png` (256×256, fond transparent — convention du catalogue d'applications
YunoHost) reprend le même dessin.
### A4 : une couverture cassée ne doit plus rien pouvoir agrandir
Le `onerror` masquait déjà l'image (`image-cassee`), mais le bouton `.couverture-declencheur`
qui l'enveloppe restait cliquable : cliquer une vignette cassée ouvrait quand même le calque
d'agrandissement, avec la même image cassée dedans. Le `onerror` désactive maintenant aussi ce
bouton (`this.closest('.couverture-declencheur').disabled = true`), et `:disabled` porte un
curseur neutre plutôt que `zoom-in`.
### A5 : les couvertures se mettent en cache pour la consultation hors-ligne
Nouveau magasin IndexedDB `couvertures` (clé = URL OpenLibrary, valeur = `Blob`), à côté du
magasin `instantanes` existant — `cache-hors-ligne.js` passe en version 2.
⚠️ **En ligne, l'affichage ne change pas** : `<img src>` pointe directement sur l'URL réseau,
aussi vite qu'avant. La mise en cache (`Cache.MettreEnCacheCouvertureAsync`) est un
fire-and-forget déclenché en parallèle, jamais attendu — elle ne doit rien à la rapidité
d'affichage. Le fetch qu'elle déclenche profite en général du cache HTTP du navigateur (même URL
que l'`<img>` déjà chargée), donc pas de second téléchargement réel dans le cas courant.
**Hors ligne**, `Couverture.razor` tente d'abord `Cache.LireCouvertureCacheeAsync` (URL `blob:`
via `URL.createObjectURL`) et ne retombe sur l'URL réseau — qui échouera simplement, comme avant
cette fonctionnalité — que si rien n'a été mis en cache.
⚠️ **Le jeton de résolution compare `(Url, EnLigne)`, pas seulement `Url`** : passer en ligne →
hors ligne sans que l'URL change (le cas le plus courant, puisqu'on ne change pas de livre en
perdant le réseau) est précisément le cas qui aurait été raté avec une mémoïsation sur la seule
URL — la couverture serait restée invisible hors-ligne malgré un cache rempli.
**Vérifié** : OpenLibrary envoie `Access-Control-Allow-Origin: *` (mesuré via `curl -I`), donc un
`fetch()` cross-origin ordinaire suffit pour lire les octets — pas besoin d'un contournement en
réponse opaque (`mode: 'no-cors'`).
**Ce qui n'est délibérément pas fait** : l'allègement (redimensionner à la taille réellement
affichée, convertir en WebP). IDEES.md posait la question à mesurer avant de s'y engager, même
règle que pour l'AOT WASM — non fait tant que la taille du cache image n'a pas montré de
problème réel en usage.
## Installation YunoHost — éprouvée en production le 2026-08-18
Le paquet vit dans le dépôt `mabibli_ynh` (voir « Deux dépôts distincts »). Installé,