From 9cb3ca535d49c6908258de17313b99dd0fda610b Mon Sep 17 00:00:00 2001 From: mathieu Date: Tue, 18 Aug 2026 02:42:58 +0200 Subject: [PATCH 1/2] =?UTF-8?q?Reparer=20la=20publication=20de=20l=20API?= =?UTF-8?q?=20(index.html=20cass=C3=A9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le publish de MaBibli.Api recopiait index.html tel quel, placeholders compris, et l application publiee restait blanche. Cause : dans le SDK .NET 10, la cible de publication GenerateHtmlAssetPlaceholdersPublishStaticWebAssets lit la liste des fichiers HTML dans @(_HtmlStaticWebAssets), item alimente uniquement par la cible de *build*. Quand c est l API qui demande au client ses assets de publication, la cible de build n a pas tourne : la liste est vide, aucun asset HTML calcule n entre dans le manifeste, et c est le fichier source qui est recopie par ComputeResolvedFilesToPublishList. Correction : supprimer le besoin de reecriture plutot que la reparer. WasmFingerprintAssets=false et pas de OverrideHtmlAssetPlaceholders (c est lui qui, a true, forcait l empreinte de blazor.webassembly.js) ; index.html reference des noms stables, sans import map ni preload placeholder. Le contournement « ?m=1 » de l import du module scanner devient inutile. Verifie : publish self-contained lance, scripts references en 200, application chargee dans un navigateur (catalogue affiche, module JS du scanner charge, aucun 404). Co-Authored-By: Claude Opus 5 --- CLAUDE.md | 38 +++++++++++-------- .../Composants/ScannerCodeBarres.razor | 12 +++--- MaBibli.Client/MaBibli.Client.csproj | 9 ++++- MaBibli.Client/wwwroot/index.html | 4 +- 4 files changed, 36 insertions(+), 27 deletions(-) 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 🗙 - + From 0fde244d0bea1e6231366e3cecbfe5517ac79a50 Mon Sep 17 00:00:00 2001 From: mathieu Date: Tue, 18 Aug 2026 02:43:07 +0200 Subject: [PATCH 2/2] Couper le role BnF meme quand le prenom finit par une initiale MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NettoyageIsbd.Auteur("Cormen, Thomas H. Auteur du texte") renvoyait « Thomas H. Auteur du texte Cormen » : le point de l initiale etait protege, donc plus aucun point ne servait de separateur de role. Ce qui tranche : dans « Nom, Prenom », la seule chose qui puisse suivre une initiale et faire encore partie du nom est une autre initiale (« Tolkien, J. R. R. »). Tout le reste est le role. Le point est conserve quand il clot une initiale, pour ne pas amputer le prenom. Co-Authored-By: Claude Opus 5 --- MaBibli.Api/Services/Isbn/NettoyageIsbd.cs | 50 +++++++++++++++------- MaBibli.Tests/NettoyageIsbdTests.cs | 17 +++++--- 2 files changed, 45 insertions(+), 22 deletions(-) diff --git a/MaBibli.Api/Services/Isbn/NettoyageIsbd.cs b/MaBibli.Api/Services/Isbn/NettoyageIsbd.cs index fa6deab..563b85e 100644 --- a/MaBibli.Api/Services/Isbn/NettoyageIsbd.cs +++ b/MaBibli.Api/Services/Isbn/NettoyageIsbd.cs @@ -128,7 +128,9 @@ public static partial class NettoyageIsbd var pointRole = IndexDuPointDeRole(s); if (pointRole >= 0) { - s = s[..pointRole]; + // Quand le rôle suit une initiale (« Cormen, Thomas H. Auteur du texte »), le point + // qui sert de séparateur appartient aussi au nom : on le conserve. + s = EstPointDInitiale(s, pointRole) ? s[..(pointRole + 1)] : s[..pointRole]; } s = RetirerPonctuationFinale(s); @@ -174,12 +176,7 @@ public static partial class NettoyageIsbd while (s.EndsWith('.')) { - var i = s.Length - 1; - var estInitiale = i >= 1 - && char.IsUpper(s[i - 1]) - && (i == 1 || s[i - 2] == ' ' || s[i - 2] == '-' || s[i - 2] == '\''); - - if (estInitiale) + if (EstPointDInitiale(s, s.Length - 1)) { break; } @@ -194,9 +191,15 @@ public static partial class NettoyageIsbd /// Position du point qui introduit le rôle, ou -1. /// /// - /// Un point suivi d'une espace sépare le rôle du nom. On ignore les points d'initiales - /// (« Cormen, Thomas H. Auteur ») : un point précédé d'une majuscule isolée n'est pas - /// un séparateur de rôle. + /// Un point suivi d'une espace sépare le rôle du nom. Le point d'une initiale + /// (« Cormen, Thomas H. ») n'en est pas un — le couper amputerait le prénom. + /// + /// Mais protéger l'initiale ne doit pas protéger le rôle qui la suit : « Cormen, Thomas H. + /// Auteur du texte » renvoyait auparavant « Thomas H. Auteur du texte Cormen ». Ce qui + /// tranche : dans « Nom, Prénom », la seule chose qui puisse suivre une initiale et faire + /// encore partie du nom est une autre initiale (« Tolkien, J. R. R. »). Tout le + /// reste est le rôle. + /// /// private static int IndexDuPointDeRole(string s) { @@ -207,16 +210,31 @@ public static partial class NettoyageIsbd continue; } - var estInitiale = i >= 1 - && char.IsUpper(s[i - 1]) - && (i == 1 || s[i - 2] == ' ' || s[i - 2] == '-' || s[i - 2] == '\''); - - if (!estInitiale) + if (EstPointDInitiale(s, i) && CommenceParUneInitiale(s, i + 2)) { - return i; + continue; } + + return i; } return -1; } + + /// + /// Le point en clôt-il une initiale (majuscule isolée) ? + /// + private static bool EstPointDInitiale(string s, int i) + => i >= 1 + && char.IsUpper(s[i - 1]) + && (i == 1 || s[i - 2] == ' ' || s[i - 2] == '-' || s[i - 2] == '\''); + + /// + /// Le texte à partir de commence-t-il par une initiale + /// (une majuscule suivie d'un point ou de la fin de chaîne) ? + /// + private static bool CommenceParUneInitiale(string s, int debut) + => debut < s.Length + && char.IsUpper(s[debut]) + && (debut + 1 == s.Length || s[debut + 1] == '.'); } 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")]