Move the interface sources to web/ (Vue 3 single-file components, a reactive store, framework-free modules) and assemble them with Vite into fixed asset names under the gitignored src/Compagnon/Web/, embedded in the single plugin DLL. Templates are precompiled, no router or Pinia is added and no inline script is emitted, so the script-src 'self' policy still holds. Extract the delicate logic - position estimation with its freshness bounds and seeded window, history selection, help overlap, subtitle validation and ASS cleanup, saved connection parsing - into modules tested without a browser by node:test, injecting the clocks. Build the interface in scripts/package.sh before the C# compile, bump the plugin to 0.7.0, switch npm run typecheck to vue-tsc and document the Vite dev proxy flow in the README. Prove parity with the untouched end-to-end suite: 34 tests on the disposable server with and without the /jellyfin prefix, stable gap within 330 ms and seek recovery within 789 ms against the burnt-in timecode. Record the decision in docs/adr/0003 and the evidence in docs/validation/issue-14.md.
3.4 KiB
Validation de la réécriture de l'interface — Vue 3 + Vite
Protocole
Paquet 0.7.0, Jellyfin Server 12.0.0 (image figée dans tests/run.sh),
Jellyfin Web de cette image, Chromium 149.0.7827.55, Playwright 1.61.1,
Node.js 24.18.0, Vue 3.5.24, Vite 7.3.6, TypeScript 5.9.3.
Compagnon de 375 × 812 pixels, hébergement sans préfixe puis suite complète
sous /jellyfin.
npm run typecheck
npm run test:unit
bash tests/run.sh
Aucun test de la suite existante n'a été modifié : history, bilingual,
quick-connect, recovery et installed sont ceux du dépôt, seule la
référence à l'archive dans tests/run.sh suit la version 0.7.0.
Architecture livrée
L'interface vit dans web/ : composants monofichiers Vue assemblés par Vite,
écrits en TypeScript, sans router ni Pinia. La logique délicate est extraite
dans web/src/lib/ — estimation de position, fraîcheur des observations,
sélection de l'historique, chevauchement de l'aide, validation et nettoyage des
pistes, lecture de la connexion conservée — et testée sans navigateur par
node:test avec des horloges injectées. Le magasin réactif (web/src/store.ts)
reste une transposition des flux de l'ancien app.js : mêmes gardes de
génération, mêmes bornes, mêmes messages.
La construction produit des noms fixes (index.html, app.js, style.css)
dans src/Compagnon/Web/, dossier généré et ignoré par Git, embarqué tel quel
dans le DLL. scripts/package.sh construit l'interface puis compile C#.
L'index généré ne contient aucun script en ligne, compatible avec la CSP
script-src 'self' du contrôleur. Le bundle pèse 75,85 kB (29,65 kB compressés).
Résultats
Exécution complète du 18 septembre 2026 :
| Vérification | Résultat |
|---|---|
npm run typecheck (vue-tsc) |
0 erreur |
npm run test:unit |
21 tests sur 21 |
npm audit |
0 vulnérabilité |
| Compilation C# Release | 0 erreur, 0 avertissement |
bash tests/run.sh |
34 tests sur 34, 131,4 s |
Les contrôles installés passent avec et sans préfixe /jellyfin. La parité DOM
est vérifiée par la suite existante : identifiants et classes (#status,
#details, #history .main-block, .help-block, #sessions, #tracks,
#help-tracks), libellés accessibles, focus après approbation, à l'expiration
d'un code et à la déconnexion, options de pistes identiques entre les deux
sélecteurs.
Les mesures de synchronisation contre le timecode incrusté restent dans les
objectifs : écart maximal 330 ms en lecture stable à 1× (seuil 500 ms) et
recalage après déplacement de 789 ms (seuil deux secondes), soit
stablePass et seekPass vrais. Les extractions SRT, WebVTT et ASS restent du
même ordre que le lot #5.
Limites
- Aucun changement visuel : la refonte demandée reste un lot séparé (#15). Vite minifie la feuille servie (748 octets au lieu de 907), règles identiques.
- La suite de tests de navigateur sert les fichiers générés depuis
src/Compagnon/Web/: exécuter un test sansnpm run buildpréalable sert un dossier vide. - Le bundle d'interface est plus lourd que le script écrit à la main (75,85 kB contre 26,9 kB) ; il est mis en cache par le navigateur et la page reste publique, sans donnée supplémentaire exposée.
- Les navigateurs antérieurs à
AbortSignal.anyrestent hors périmètre, comme avant la réécriture.