diff --git a/CLAUDE.md b/CLAUDE.md
index dd73ab9..54d1d3c 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -107,7 +107,7 @@ de la fonctionnalité.
- `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). Corollaire vérifié : tester le scan depuis un téléphone en pointant l'IP locale du PC (`http://192.168.x.x`) **échouera toujours** — ce n'est pas un contexte sécurisé. Le composant détecte ce cas et le dit explicitement.
- **Une `DOMException` perd son `name` en traversant le pont JS→C#** : `getUserMedia` refusé remonte en C# sous la forme « Permission denied undefined », sans `NotAllowedError`. Or c'est ce nom qui distingue « permission refusée » de « pas de caméra » de « caméra occupée ». Le JS doit donc **attraper l'erreur et renvoyer un code de statut** ; parser le message côté C# ne marche pas.
-- **L'import du module JS doit porter une chaîne de requête** (`./js/scanner-camera.js?m=1`). En développement, l'import map généré par Blazor réécrit le chemin vers un nom empreinté que l'API hôte ne sert pas (elle utilise `UseStaticFiles`, qui ignore les points d'entrée empreintés) : l'import part en 404 et le scan ne démarre jamais. La chaîne de requête empêche cette réécriture ; en publication rien n'est réécrit pour ce fichier, donc le comportement est identique.
+- **L'import du module JS se fait sur un chemin nu** (`./js/scanner-camera.js`). Il a longtemps porté une chaîne de requête `?m=1` : l'import map généré par Blazor réécrivait le chemin vers un nom empreinté que l'API hôte ne servait pas (elle utilise `UseStaticFiles`, qui ignore les points d'entrée empreintés), et l'import partait en 404. Depuis que les empreintes WASM sont désactivées (voir « Empreintes WASM désactivées »), **il n'y a plus d'import map du tout** et le contournement a été retiré. Vérifié en développement et sur le publish self-contained : le module se charge dans les deux cas.
### Squelette validé
@@ -259,29 +259,35 @@ dotnet publish MaBibli.Api -c Release -r linux-x64 --self-contained
Le self-contained embarque le runtime .NET dans le binaire : **aucun `dotnet-runtime` à installer** côté serveur, pas de conflit de versions. C'est le modèle de `radarr_ynh`.
-### ⚠️ BLOQUANT CONNU : le publish de l'API produit un `index.html` cassé
+### Empreintes WASM désactivées — pourquoi (ex-bloquant du publish, résolu le 2026-08-18)
-**Constaté le 2026-08-18, non résolu. À corriger impérativement avant tout packaging YunoHost.**
+**Ne pas réactiver `OverrideHtmlAssetPlaceholders` ni les empreintes WASM sans relire ce qui suit.**
-`dotnet publish MaBibli.Api -c Release` produit un `wwwroot/index.html` dont les placeholders ne sont **pas** substitués :
+Le `index.html` de `MaBibli.Client` ne contient **ni import map, ni placeholder `#[.{fingerprint}]`** : il référence directement `_framework/blazor.webassembly.js`. C'est délibéré, et `MaBibli.Client.csproj` porte `false` pour que les fichiers servis portent bien ces noms stables.
-```html
-
-` vide, `src="_framework/blazor.webassembly#[.{fingerprint}].js"` littéral), alors que seuls les noms empreintés existaient sur disque. La racine répondait **200**, le script de démarrage **404**, l'application publiée restait **blanche**. `dotnet publish MaBibli.Client` seul, lui, produisait un `index.html` correct.
-`dotnet publish MaBibli.Client` seul produit au contraire un `index.html` correct : le défaut vient de l'hébergement du client par l'API, qui embarque le `wwwroot` de *build* du client plutôt que son `wwwroot` *publié*.
+#### La cause réelle (SDK .NET 10.0.300)
-**Piège de diagnostic** : vérifier que `/` renvoie 200 ne prouve rien — c'est ce qui a fait passer le défaut inaperçu à la phase 1. Il faut vérifier les scripts que `index.html` référence réellement, ou charger la page dans un navigateur.
+Elle est dans `Microsoft.NET.Sdk.StaticWebAssets.HtmlAssetPlaceholders.targets`. La cible de **publication** `GenerateHtmlAssetPlaceholdersPublishStaticWebAssets` reçoit la liste des fichiers HTML à réécrire via `HtmlFiles="@(_HtmlStaticWebAssets)"` — or cet item n'est **jamais alimenté par le chemin de publication** : il l'est uniquement par la cible de **build** `ResolveHtmlAssetPlaceholdersBuildConfiguration`.
-Pistes déjà essayées **sans succès** (inutile de les retenter à l'identique) :
-- `true` sur `MaBibli.Api` ;
-- `false` sur le client — donne bien un `dotnet.js` stable, mais `blazor.webassembly.js` reste empreinté car il provient des assets du package framework ;
-- `false` sur les deux projets.
+Quand on publie le client seul, le build a tourné dans la même instance MSBuild, l'item est rempli, la réécriture a lieu. Quand c'est l'**API** qui publie, elle demande au projet client ses assets de publication dans une instance où la cible de build n'a **pas** tourné : `@(_HtmlStaticWebAssets)` est vide, la tâche de réécriture ne produit **aucun fichier**, aucun asset HTML calculé n'entre dans `staticwebassets.publish.json` — et c'est alors le fichier source `MaBibli.Client/wwwroot/index.html` qui est recopié tel quel par `ComputeResolvedFilesToPublishList`, placeholders compris. Vérifié : le manifeste de publication de l'API ne contient **aucun** asset `.html`.
-Le développement n'est pas affecté : `dotnet run --project MaBibli.Api` fonctionne normalement.
+C'est pour cela que `true` sur l'API ne changeait rien : la cible s'exécutait bien, mais sur une liste vide.
+
+#### La correction retenue
+
+Supprimer le besoin de réécriture plutôt que réparer la réécriture : sans import map ni placeholder, il n'y a plus rien à substituer, et le fichier source recopié tel quel est déjà le bon.
+
+- `false` sur le client ;
+- **pas** de `OverrideHtmlAssetPlaceholders` (c'est lui qui, à `true`, force `BlazorFingerprintBlazorJs` et empreinte `blazor.webassembly.js` — la tentative précédente n'avait échoué que parce que les deux propriétés étaient combinées) ;
+- `index.html` : `
@@ -31,7 +29,7 @@
Reload
🗙
-
+
diff --git a/MaBibli.Tests/NettoyageIsbdTests.cs b/MaBibli.Tests/NettoyageIsbdTests.cs
index 493af12..7c11296 100644
--- a/MaBibli.Tests/NettoyageIsbdTests.cs
+++ b/MaBibli.Tests/NettoyageIsbdTests.cs
@@ -85,12 +85,17 @@ public class NettoyageIsbdTests
public void Auteur_laisse_passer_ce_qui_est_deja_propre(string brut, string attendu)
=> Assert.Equal(attendu, NettoyageIsbd.Auteur(brut));
- [Fact]
- public void Auteur_ne_coupe_pas_sur_une_initiale()
- {
- // « H. » est une initiale, pas un séparateur de rôle : la couper amputerait le prénom.
- Assert.Equal("Thomas H. Cormen", NettoyageIsbd.Auteur("Cormen, Thomas H."));
- }
+ [Theory]
+ // Contre-exemples : le nom se termine légitimement par une initiale, il n'y a pas de rôle
+ // à retirer — couper au point amputerait le prénom.
+ [InlineData("Cormen, Thomas H.", "Thomas H. Cormen")]
+ [InlineData("Tolkien, J. R. R.", "J. R. R. Tolkien")]
+ // Cas réel : le rôle suit une initiale. Protéger le point de l'initiale ne doit pas
+ // protéger le rôle — sinon on obtient « Thomas H. Auteur du texte Cormen ».
+ [InlineData("Cormen, Thomas H. Auteur du texte", "Thomas H. Cormen")]
+ [InlineData("Tolkien, J. R. R. (1892-1973). Auteur du texte", "J. R. R. Tolkien")]
+ public void Auteur_distingue_une_initiale_d_un_role(string brut, string attendu)
+ => Assert.Equal(attendu, NettoyageIsbd.Auteur(brut));
[Theory]
[InlineData("le Livre de poche (Paris)", "le Livre de poche")]