Appliquer la mise a jour au demarrage et fermer les fuites de cache

Sur l'application installee sur telephone, le bandeau restait sans effet et
le chargement se figeait a 100 %. La capture montrait le bandeau SANS son
CSS, alors que le script et sa regle sont arrivés dans le meme commit : ce
n'etait pas une mise a jour qui ne s'applique pas, mais une mise a jour
appliquee a moitie.

- MapFallbackToFile n'heritait pas des StaticFileOptions : « / », le
  start_url de la PWA, repartait sans Cache-Control (mesure), donc en cache
  heuristique. A1 avait couvert tous les fichiers statiques sauf celui-la.
- register('service-worker.js') se resolvait contre l'URL du document et non
  contre <base> : une ouverture sur une route profonde visait
  /souhaits/service-worker.js, qui repond 404 (verifie). Tout le hors-ligne
  tombait alors en silence.
- clients.claim() et un rechargement force borne a une fois par session
  donnent un filet a la chaine SKIP_WAITING -> controllerchange -> reload.
- Une version prete dans les 10 s suivant l'ouverture s'applique seule, sans
  bandeau ; au-dela on repasse par le clic, pour ne pas arracher une saisie.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mathieu
2026-08-20 20:23:31 +02:00
co-authored by Claude Opus 5
parent 347e1ce7fd
commit c256394c77
4 changed files with 178 additions and 20 deletions
+87
View File
@@ -2056,3 +2056,90 @@ accents et à la casse, cohérente avec le reste de l'application sans dupliquer
champ n'apparaît que s'il y a plus d'un prêt en cours — avec un seul prêt, filtrer n'apprendrait
rien, même raison que les lignes de filtre qui se cachent déjà ailleurs dans le catalogue quand
elles seraient inutiles.
## La mise à jour s'applique seule au démarrage — corrigé le 2026-08-20
Défaut remonté en usage sur l'**application installée sur téléphone** : bandeau « Une nouvelle
version est disponible », clic sans effet, chargement figé à **100 %**, puis un menu principal
d'une version antérieure au lot d'interface du 2026-08-18 (pas d'onglets en bas, filtres
dépliés, « Saisie manuelle » au catalogue).
### Ce que la capture d'écran prouve, et qu'aucune supposition ne remplaçait
Le bandeau s'affichait **sans son CSS** : texte brut en haut de l'écran, bouton par défaut, alors
que `#mb-maj` le pose en barre fixe **en bas**, fond bleu, bouton jaune. Or `js/mise-a-jour.js`
et la règle `#mb-maj` de `css/app.css` sont arrivés dans le **même commit** (`ebc5f95`). Le JS
était donc là, son CSS non — pendant que le cercle de chargement, issu du même `app.css`, était
bien stylé.
⚠️ **Le symptôme n'était donc pas « la mise à jour ne s'applique pas » mais « une mise à jour
s'est appliquée à moitié ».** Un blocage à 100 % est la signature exacte d'un assemblage
dépareillé : tout est téléchargé, le manifeste de démarrage et les `.wasm` ne concordent pas,
l'application ne démarre jamais. Chercher un défaut dans le bouton aurait manqué la cause.
### Le fallback n'hérite pas des `StaticFileOptions` — mesuré
Le lot A1 avait posé `Cache-Control: no-cache, must-revalidate` sur `UseStaticFiles`. Mesuré sur
un publish du code de l'époque :
| Requête | Cache-Control |
|---|---|
| `/index.html` | `no-cache, must-revalidate` ✅ |
| `/_framework/*` | `no-cache` (posé par `UseBlazorFrameworkFiles`) ✅ |
| **`/`** | **aucun** ❌ |
`MapFallbackToFile` a son **propre pipeline** et ne passe pas par les options du middleware
statique. Or `/` est le **`start_url` de la PWA**, la seule URL qu'ouvre l'application installée :
elle repartait en cache heuristique du navigateur (une fraction de l'âge du fichier), donc
potentiellement périmée pendant des jours. A1 avait couvert tous les fichiers statiques **sauf le
seul qui compte pour une PWA installée**. La coquille HTML décide de tout le reste : la servir
périmée suffit à mélanger deux versions sur l'appareil.
Correction : `app.MapFallbackToFile("index.html", optionsFichiersStatiques)`. Vérifié après
correction — `/`, `/livres/3` et `/souhaits` portent tous l'en-tête.
### L'enregistrement se résolvait contre l'URL du document, pas contre `<base>`
`register('service-worker.js')` se résout contre l'**URL du document**, et **non** contre
`<base href="/">`. Ouvrir l'application sur une route profonde visait donc
`/souhaits/service-worker.js` — vérifié : **404**, l'extension empêchant le fallback SPA de
répondre. L'enregistrement échouait alors silencieusement, **et avec lui tout le hors-ligne**.
Le chemin est désormais absolu, avec `scope: '/'` explicite.
### Trois maillons sans filet
`SKIP_WAITING``skipWaiting()``controllerchange``reload()`. Si un maillon manquait — un
worker en attente antérieur au gestionnaire de message, une reprise en main qui n'a pas lieu — le
bouton se grisait et **rien ne se passait, sans que rien ne le dise**. Deux ajouts :
- `self.clients.claim()` dans `onActivate` : `skipWaiting()` est censé reprendre les pages seul,
mais c'est cette reprise qui déclenche `controllerchange`, donc le rechargement ;
- un rechargement **forcé** 5 s après `SKIP_WAITING` si le contrôleur n'a pas changé.
⚠️ **Le rechargement forcé est borné à une fois par session** (`sessionStorage`), et **renonce**
si le stockage est refusé (navigation privée). Sans ce garde-fou, une version qui n'arrive pas à
s'activer transformerait une mise à jour ratée en **boucle de rechargement**, c'est-à-dire en
application inutilisable — bien pire que le défaut d'origine.
### ⚠️ Le clic n'est plus obligatoire — décision inversée, et pourquoi
CLAUDE.md actait « le message ne vient que de `js/mise-a-jour.js`, après un clic explicite :
jamais tout seul, pour ne pas mélanger deux versions au milieu d'une session ». Le raisonnement
reste juste — recharger sous une saisie en cours fait perdre le formulaire — **mais il ne vaut
que pour une session déjà entamée**.
D'où `FENETRE_DEMARRAGE` (10 s) : une version prête dans les premières secondes après l'ouverture
s'applique **toute seule, sans bandeau** ; au-delà, on repasse par le bandeau. Ouvrir
l'application depuis l'écran d'accueil du téléphone tombe toujours dans cette fenêtre — c'est
exactement le cas qui ne se mettait plus à jour, et c'est celui où l'interruption ne coûte rien.
### Ce qui n'est pas vérifiable ici, et qu'il faut confirmer sur le téléphone
Le navigateur d'automatisation **n'enregistre aucun service worker** (voir la section dédiée) :
la chaîne complète n'a donc pas pu être éprouvée en exécution. Ce qui **est** vérifié : les
en-têtes sur toutes les routes, le 404 du chemin relatif, le démarrage de l'application avec le
nouveau script, et les 441 tests.
⚠️ **Un appareil déjà dans l'état dépareillé ne se répare pas tout seul** : son cache mélangé
précède le correctif. Il faut vider les données du site (ou désinstaller puis réinstaller la
PWA) **une fois**. Le correctif empêche d'y retomber, il ne défait pas ce qui est déjà en place.