Fusionner la reparation du publish et le correctif NettoyageIsbd
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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 `<WasmFingerprintAssets>false</WasmFingerprintAssets>` pour que les fichiers servis portent bien ces noms stables.
|
||||
|
||||
```html
|
||||
<script type="importmap"></script> <!-- vide -->
|
||||
<script src="_framework/blazor.webassembly#[.{fingerprint}].js"> <!-- littéral -->
|
||||
```
|
||||
#### Le symptôme d'origine
|
||||
|
||||
Or seuls les noms **empreintés** existent sur disque (`blazor.webassembly.958z1vx7fr.js`). Résultat vérifié en exécutant le binaire publié : la racine répond **200**, mais le script de démarrage et `_framework/dotnet.js` renvoient **404**. L'application publiée affiche une **page blanche** — elle ne démarre pas.
|
||||
`dotnet publish MaBibli.Api -c Release` produisait un `wwwroot/index.html` aux placeholders **non substitués** (`<script type="importmap"></script>` 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) :
|
||||
- `<OverrideHtmlAssetPlaceholders>true</OverrideHtmlAssetPlaceholders>` sur `MaBibli.Api` ;
|
||||
- `<WasmFingerprintAssets>false</WasmFingerprintAssets>` sur le client — donne bien un `dotnet.js` stable, mais `blazor.webassembly.js` reste empreinté car il provient des assets du package framework ;
|
||||
- `<StaticWebAssetsFingerprintContent>false</StaticWebAssetsFingerprintContent>` 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 `<OverrideHtmlAssetPlaceholders>true</OverrideHtmlAssetPlaceholders>` 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.
|
||||
|
||||
- `<WasmFingerprintAssets>false</WasmFingerprintAssets>` 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` : `<script src="_framework/blazor.webassembly.js">`, sans `<script type="importmap">` ni `<link rel="preload" id="webassembly">` (deux balises que seule la machinerie de placeholders sait remplir).
|
||||
|
||||
Le cache-busting reste assuré par le service worker, qui compare les empreintes de `service-worker-assets.js`.
|
||||
|
||||
**Piège de diagnostic à conserver** : 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.
|
||||
|
||||
### Deux dépôts distincts
|
||||
|
||||
|
||||
@@ -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.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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 <b>H.</b> ») n'en est pas un — le couper amputerait le prénom.
|
||||
/// <para>
|
||||
/// 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 <b>une autre initiale</b> (« Tolkien, J. R. R. »). Tout le
|
||||
/// reste est le rôle.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Le point en <paramref name="i"/> clôt-il une initiale (majuscule isolée) ?
|
||||
/// </summary>
|
||||
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] == '\'');
|
||||
|
||||
/// <summary>
|
||||
/// Le texte à partir de <paramref name="debut"/> commence-t-il par une initiale
|
||||
/// (une majuscule suivie d'un point ou de la fin de chaîne) ?
|
||||
/// </summary>
|
||||
private static bool CommenceParUneInitiale(string s, int debut)
|
||||
=> debut < s.Length
|
||||
&& char.IsUpper(s[debut])
|
||||
&& (debut + 1 == s.Length || s[debut + 1] == '.');
|
||||
}
|
||||
|
||||
@@ -67,13 +67,11 @@
|
||||
|
||||
private enum Etat { Demarrage, Actif, Erreur }
|
||||
|
||||
// Le « ?m=1 » n'est pas cosmétique. En développement, l'import map généré par Blazor
|
||||
// fait pointer « ./js/scanner-camera.js » 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
|
||||
// échoue en 404 et le scan ne démarre jamais. Une chaîne de requête empêche l'import map
|
||||
// de réécrire le chemin ; en publication, aucune réécriture n'est générée pour ce fichier,
|
||||
// donc le comportement est identique. Vérifié dans les deux modes.
|
||||
private const string CheminModule = "./js/scanner-camera.js?m=1";
|
||||
// Chemin nu : il n'y a plus d'import map depuis que les empreintes WASM sont désactivées
|
||||
// (voir MaBibli.Client.csproj), donc plus de réécriture vers un nom empreinté que l'hôte
|
||||
// ne servirait pas. Le contournement « ?m=1 » qui neutralisait cette réécriture n'a plus
|
||||
// lieu d'être. Vérifié en développement et en publication.
|
||||
private const string CheminModule = "./js/scanner-camera.js";
|
||||
|
||||
// ~12 images/s : bien assez pour scanner, et deux fois moins de travail que 25 fps.
|
||||
private const int IntervalleMs = 80;
|
||||
|
||||
@@ -4,7 +4,14 @@
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<OverrideHtmlAssetPlaceholders>true</OverrideHtmlAssetPlaceholders>
|
||||
<!--
|
||||
Empreintes WASM désactivées, et donc pas d'import map ni de placeholders dans index.html.
|
||||
Raison : quand c'est MaBibli.Api qui publie ce projet, le SDK .NET 10 n'exécute jamais la
|
||||
réécriture des placeholders et recopie index.html brut (voir CLAUDE.md). Des noms de
|
||||
fichiers stables suppriment le problème à la racine. Le cache-busting reste assuré par le
|
||||
service worker, qui compare les empreintes de service-worker-assets.js.
|
||||
-->
|
||||
<WasmFingerprintAssets>false</WasmFingerprintAssets>
|
||||
<ServiceWorkerAssetsManifest>service-worker-assets.js</ServiceWorkerAssetsManifest>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>MaBibli</title>
|
||||
<base href="/" />
|
||||
<link rel="preload" id="webassembly" />
|
||||
<link rel="stylesheet" href="lib/bootstrap/dist/css/bootstrap.min.css" />
|
||||
<link rel="stylesheet" href="css/app.css" />
|
||||
<link rel="icon" type="image/png" href="favicon.png" />
|
||||
@@ -14,7 +13,6 @@
|
||||
<link href="manifest.webmanifest" rel="manifest" />
|
||||
<link rel="apple-touch-icon" sizes="512x512" href="icon-512.png" />
|
||||
<link rel="apple-touch-icon" sizes="192x192" href="icon-192.png" />
|
||||
<script type="importmap"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@@ -31,7 +29,7 @@
|
||||
<a href="." class="reload">Reload</a>
|
||||
<span class="dismiss">🗙</span>
|
||||
</div>
|
||||
<script src="_framework/blazor.webassembly#[.{fingerprint}].js"></script>
|
||||
<script src="_framework/blazor.webassembly.js"></script>
|
||||
<script>navigator.serviceWorker.register('service-worker.js', { updateViaCache: 'none' });</script>
|
||||
</body>
|
||||
|
||||
|
||||
@@ -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")]
|
||||
|
||||
Reference in New Issue
Block a user