diff --git a/CLAUDE.md b/CLAUDE.md
index 0523cff..52ba41b 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -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** : `` 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'`
` 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é,
diff --git a/IDEES.md b/IDEES.md
index 94ec3c5..7de93fa 100644
--- a/IDEES.md
+++ b/IDEES.md
@@ -116,69 +116,17 @@ sans rapport.
## Lot A — L'habillage : la navigation ne ressemble à rien
-Le lot qui se voit à chaque écran, et le seul dont le défaut ressemble à une régression.
+**A1 à A4 traités le 2026-08-20** (voir `CLAUDE.md`, section « Habillage — retours d'usage du
+2026-08-19 traités »). Reste ouvert :
-### A1. Les six onglets s'affichent en liens bruts (anomalie, pas une demande)
+### A5. Alléger les couvertures (redimensionnement / WebP)
-Constaté sur la capture du 2026-08-19 : `CatalogueAuteursSériesRevuesPrêtsEnvies` s'affiche en
-bas de page, en bleu souligné, collés les uns aux autres — c'est-à-dire **sans aucune des règles
-de `MainLayout.razor.css`** : ni `position: fixed`, ni `display: flex`, ni les libellés espacés.
+Le cache hors-ligne des couvertures est fait (voir `CLAUDE.md`), mais chaque affichage en ligne
+retélécharge toujours l'image OpenLibrary à sa taille d'origine — rien n'est redimensionné à la
+taille réellement affichée, ni converti en WebP.
-⚠️ **Ce n'est probablement pas un défaut de mise en forme à refaire, mais une feuille de style
-périmée.** Sur la même capture, le bandeau supérieur **est** bleu foncé — or `.bandeau` et
-`.onglets` vivent dans le **même fichier** scopé. Une moitié appliquée et l'autre pas ne
-s'explique pas par le CSS lui-même : elle s'explique par un `MaBibli.Client.styles.css` **d'avant
-l'ajout des onglets**, servi à côté d'un WASM à jour (le balisage, lui, montre bien les six
-onglets).
-
-Hypothèse à vérifier **avant** de toucher au style, sous peine de « corriger » du code sain :
-
-1. le nom `MaBibli.Client.styles.css` est **stable** (les empreintes sont désactivées, voir
- `CLAUDE.md`) — rien d'autre que le service worker ne le fait expirer ;
-2. or le service worker **ne s'enregistre pas** dans certains contextes, et un onglet resté
- ouvert diffère l'activation d'un nouveau worker.
-
-Contrôles qui trancheraient : ouvrir `/MaBibli.Client.styles.css` sur le serveur et y chercher
-`.onglets` ; comparer avec le fichier du dernier publish ; recharger en vidant le cache.
-Si la règle **est** présente et pourtant sans effet, alors seulement chercher du côté de
-l'attribut de portée (`b-xxxxx`) que `NavLink` doit relayer via ses attributs additionnels.
-
-### A2. Les filtres reprennent le vocabulaire visuel des cartes
-
-Les états *À lire / En cours / Lu* et les types *Roman / BD* s'affichent en pastilles colorées
-sur les cartes du catalogue, mais en segments neutres dans le panneau « Filtrer ». Demander la
-**même pastille** dans le filtre, pour qu'on reconnaisse ce qu'on filtre.
-
-### A3. Une identité visuelle : trois images, trois emplacements distincts
-
-À ne pas confondre, les trois ne vivent pas au même endroit :
-
-| Où | Quoi | Porté par |
-|---|---|---|
-| Catalogue d'applications YunoHost | logo de l'app | `mabibli_ynh` (`doc/`, manifeste) |
-| Bandeau de l'application | petit logo à côté de « MaBibli » | `MaBibli.Client/wwwroot` |
-| Installation PWA (téléphone, PC) | icône d'accueil et écran de démarrage | `manifest.webmanifest` + icônes 192/512 |
-
-⚠️ La troisième est la seule qui a des contraintes dures (tailles, `purpose: maskable`), et
-c'est aussi la seule que le service worker met en cache — donc celle dont un changement se voit
-le plus tard.
-
-### A4. Couvertures : ne pas agrandir ce qui n'existe pas
-
-Cliquer une vignette absente ne doit rien ouvrir. `CLAUDE.md` affirme que le bouton n'est rendu
-que lorsqu'une `CoverUrl` existe — **le cas remonté est donc probablement l'image *cassée*** :
-URL présente, mais 502 ou 404 d'OpenLibrary (intermittence documentée). Le `onerror` remplace
-l'image ; il devrait aussi **retirer le déclencheur d'agrandissement**.
-
-### A5. Garder les couvertures en cache, et les alléger
-
-Aujourd'hui chaque affichage retélécharge l'image chez OpenLibrary — donc rien hors-ligne, et
-des vignettes manquantes au gré des 502. Piste : stocker l'image (IndexedDB, à côté des
-instantanés) et la réduire à la taille réellement affichée.
-
-⚠️ À peser : c'est le premier contenu **binaire et volumineux** du cache hors-ligne, dont
-`CLAUDE.md` note qu'il pèse aujourd'hui ~2,4 Ko pour 10 livres. Une conversion (WebP) n'a de sens
-que si la mesure montre un gain — même règle que pour l'AOT.
+⚠️ À peser avant de s'y engager, même règle que pour l'AOT : une conversion n'a de sens que si la
+mesure montre un gain réel. Pas encore mesuré à l'échelle d'un vrai fonds.
---
diff --git a/MaBibli.Api/Program.cs b/MaBibli.Api/Program.cs
index 38357ea..12220c7 100644
--- a/MaBibli.Api/Program.cs
+++ b/MaBibli.Api/Program.cs
@@ -73,8 +73,21 @@ if (app.Environment.IsDevelopment())
// Le client Blazor WebAssembly est servi comme fichiers statiques par cette API :
// un seul processus tourne sur le serveur.
+//
+// ⚠️ Les empreintes WASM sont désactivées (voir CLAUDE.md) : tous ces fichiers portent des noms
+// stables. Sans en-tête Cache-Control, un navigateur peut garder une version périmée en cache
+// heuristique (par exemple MaBibli.Client.styles.css) même si le service worker, lui, revalide
+// déjà à l'installation — mais seulement quand il parvient à s'activer. `no-cache` force une
+// revalidation conditionnelle (ETag/Last-Modified, déjà posés par défaut) à chaque requête :
+// pas de re-téléchargement si le fichier n'a pas changé, mais jamais de contenu périmé servi
+// sans repasser par le serveur.
+var optionsFichiersStatiques = new StaticFileOptions
+{
+ OnPrepareResponse = ctx =>
+ ctx.Context.Response.Headers.CacheControl = "no-cache, must-revalidate",
+};
app.UseBlazorFrameworkFiles();
-app.UseStaticFiles();
+app.UseStaticFiles(optionsFichiersStatiques);
app.MapIsbnEndpoints();
app.MapLivresEndpoints();
diff --git a/MaBibli.Client/Composants/Couverture.razor b/MaBibli.Client/Composants/Couverture.razor
index fff65ec..8914692 100644
--- a/MaBibli.Client/Composants/Couverture.razor
+++ b/MaBibli.Client/Composants/Couverture.razor
@@ -1,3 +1,6 @@
+@using MaBibli.Client.Services
+@inject CacheHorsLigne Cache
+@inject EtatReseau Reseau
@*
Couverture d'un livre, avec substitut.
@@ -10,7 +13,19 @@
le onerror a marqué comme cassé.
Agrandissement (IDEES.md) : seulement quand il y a une URL. Sans couverture, il n'y a rien
- à agrandir — le substitut à initiale reste un simple décor, jamais un bouton.
+ à agrandir - le substitut à initiale reste un simple décor, jamais un bouton.
+
+ ⚠️ Image cassée après coup (404/502 intermittent d'OpenLibrary) : le onerror désactive aussi
+ le bouton déclencheur (this.closest('.couverture-declencheur').disabled = true), pas
+ seulement l'image. Sans ça, cliquer une vignette cassée agrandissait quand même une image
+ cassée (A4, IDEES.md).
+
+ Cache hors-ligne (A5, IDEES.md) : EN LIGNE, l'affichage utilise l'URL réseau telle quelle —
+ rien ne doit ralentir la voie rapide — et une mise en cache démarre en tâche de fond,
+ sans attendre son résultat, pour que la même couverture reste visible hors-ligne. HORS
+ LIGNE, on tente d'abord le cache : l'URL réseau échouerait de toute façon.
+ ⚠️ L'allègement (redimensionner, convertir en WebP) n'est PAS fait ici : à mesurer avant
+ de s'y engager, comme pour l'AOT — voir IDEES.md.
*@