- format.js : labNotesForDisplay (helper PUR, miroir du Kotlin) — notes
DISTINCTES d'une prise E2+T → une ligne par note préfixée du marqueur
(« E2 : … » / « T : … ») ; note identique (dialog commun) → dédupliquée ;
vides ignorées ; note seule → brute (v1.0 préservé). L'ancien code
(.find() première non vide) perdait la note T à l'affichage quand la
note E2 existait aussi (conservée en base, juste non montrée).
- labs.js : affiche TOUTES les lignes du helper (l'ancienne n'en rendait
qu'une).
- app.js (seed ?demo=1) : la dernière paire E2/T porte des notes
distinctes → fixture du scénario E2E.
- E2E : les deux notes préfixées s'affichent sur #labs.
- tests/lab-notes.test.js : 5 tests miroirs du Kotlin (145 verts au total).
- CHANGELOG [1.7.1] (l'oubli de cette entrée aurait laissé le dialog
« Nouveautés » vide — attrapé par l'E2E, leçon #46 miroir), §4 arbre
tests, README. WEB_VERSION 1.7.1.
Cause (divergence de portage, bug remonté « options reset sans changer de
menu ») : renderRoute() re-crée l'écran ENTIER à chaque tick minute (60 s),
mutation du store ou resize — et l'état des options était une variable
LOCALE de renderChart, retombant aux défauts à chaque re-création.
L'Android est immunisé (remember { mutableStateOf } survit aux
recompositions).
Fix (miroir Compose) :
- chart.js : état d'interaction au NIVEAU MODULE (chartUiState) ;
renderChart(container, ctx, { preserveState }) réutilise l'état quand
preserveState=true, le recrée aux défauts sinon ;
- app.js : preserveChartState = (route === 'chart' && lastRoute === 'chart')
comparé AVANT la mise à jour de lastRoute — tick/store/resize préservent
l'état, la navigation le réinitialise (miroir du reset d'un remember
Android quitté).
Épinglé par l'E2E : resize (même code path que le tick) → chip Tracé labs
+ légende restent en place ; navigation → retour aux défauts.
Docs : §4 « État d'interaction persistant », §8 scénario E2E, §11 leçon
#15 (tout état d'UI doit survivre au re-rendu global), CHANGELOG [1.6.1]
(fix web-only : parité fonctionnelle = Android 1.6.0), README.
- check.sh : NOUVELLE étape 3.bis — garde de PARITÉ du portage
Android↔web (si le dépôt Android voisin existe) : les symboles clés
des features partagées (moteur prolongation, chip, avertissement)
sont vérifiés des DEUX côtés — attrape un portage incomplet.
- Doc : arbre §4, suite lab-trajectory-model (§8), scénario E2E (§8),
leçons #12 étendue (LABX) et #14 (parité) (§11).
- CHANGELOG web : entrée [1.6.0] (séparé du CHANGELOG Android).
- README : statut web v1.6.0, 140 tests verts.
- chart.js : chip « Prolonger » (off par défaut, désactivé tant que
Tracé labs est off), split série "LAB" (ancrée) / "LABX" (prolongée,
rose atténué rgba(194,24,91,0.55)), légendes dédiées (clés LAB ET LABX
skippées de la boucle — miroir leçon #63), AVERTISSEMENT « simple
simulation, sans garantie de correspondre au réel, labs potentiellement
erronés » affiché sous la légende tant que la prolongation est visible
(condition factorisée : jamais de courbe prolongée sans avertissement).
- i18n : show_lab_track_extend, legend_lab_track_extend,
lab_track_extend_warning (FR/EN synchronisées).
- E2E : scénario v1.6.0 (chip désactivé → activation → légende +
avertissement → disparition à la désactivation) ; assertions version
1.6.0.
Miroir strict de pk/LabTrajectoryModel.kt (Android v1.5.0) :
- js/pk/lab-trajectory-model.js : courbe hybride M(t)×ρ(t) ancrée sur les
labs — passage EXACT garanti, ρ log-linéaire, garde #61 (lab hors
fenêtre d'action pas un ancrage), E2 seul, fenêtre [1er ; dernier lab],
< 2 labs → vide, indépendante de la calibration (sinon double
correction). JAMAIS dans levelAt/alertes.
- UI : chip 'Tracé labs' (off par défaut) dans la rangée modèles +
scroll horizontal CONFINÉ à la rangée (miroir du fix Android) ;
couleur #c2185b dashed ; leçon #63 Android-miroir — clé 'LAB' SKIPPÉE
de la boucle légende (sinon légende TFS dupliquée).
- 11 tests miroirs de LabTrajectoryModelTest.kt (132 verts total).
- WEB_VERSION = 1.5.0 + CHANGELOG [1.5.0] + docs (§4/§8, README).
Détail d'exécution épingle: les deux problèmes du portage ont été
attrapés par node --check/E2E avant release (imports ESM faux —
labIsSignificant vit dans pk-calibration.js, pas pk-engine.js).
Le premier déploiement NAS a échoué avec « exec /docker-entrypoint.sh:
exec format error » : image arm64 (buildée sur Mac Apple Silicon) exécutée
sur un hôte amd64. Le test réel l'a épiné : container-test.sh accepte
désormais un paramètre PLATFORM (build cross-arch SANS émulation — le
Dockerfile n'a aucun RUN — puis run émulé + E2E complet) :
bash scripts/container-test.sh <port> linux/amd64
→ validé vert sur linux/amd64 (healthcheck durci, E2E complet).
Docs §10 : leçon cross-arch (les 2 procédures de build — sur le serveur
cible ou multi-arch push via buildx), statut : les DEUX archs testées ;
note : l'« io_setup() failed » sous émulation Rosetta est inoffensif.
Conteneurisation (site 100 % statique — l'image ne détient AUCUNE donnée,
vie privée identique) :
- Dockerfile : nginxinc/nginx-unprivileged:alpine (uid 101, port 8080,
pas de root), healthcheck wget intégré, labels OCI
- nginx.conf : no-cache systématique (cohérence du jeu de fichiers à
chaque mise à jour d'image, coût nul : ~600 Ko + gzip sur l'asset PK),
gzip, en-têtes de sécurité, deny des dotfiles
- .dockerignore : runtime uniquement (docs/ inclus — dialog changelog) ;
local-test-data exclu en filet de sécurité
- docker-compose.yml : read_only + tmpfs (/var/cache/nginx, /run, /tmp —
les temporaires de nginx-unprivileged, constat au premier test réel)
+ no-new-privileges
Nouveaux tests avant release :
- scripts/container-test.sh : build + run DURCI (config compose) +
healthcheck healthy + endpoints 200 + headers (no-cache/nosniff/DENY/
no-referrer) + gzip réel + MIME strict des modules ES + fichiers cachés
non servis + image propre (pas de node_modules/npm) + docs/ embarqué +
E2E playwright complet CONTRE LE CONTENEUR (HRT_E2E_BASE)
- scripts/e2e.mjs : mode externe HRT_E2E_BASE (pilote un site déjà déployé
— conteneur inclus, serveur local non démarré)
- scripts/check.sh --release : supplée les 9 vérifications (version ↔
changelog ↔ tag ↔ arbre propre + test conteneur si daemon)
Runtime Docker local installé pour le CI-like (colima 2 CPU / 2 Go) :
premier build + run réel = 2 problèmes testés et corrigés (tmpfs /tmp,
key case header_json curl). CONTAINER TEST OK en configuration durcie.