# CHANGELOG — HormoneTrack Web > Journal des versions de l'**app web** (séparé du CHANGELOG Android — > `docs/CHANGELOG.md` à la racine du dépôt). Convention Keep a Changelog : > section la plus récente EN PREMIER ; ce fichier est lu par le dialog > « Nouveautés » de l'app web (fetch `docs/CHANGELOG.md`). ## [1.9.1] — 2026-09-19 ### Corrigé — le nuage d'incertitude entoure maintenant la courbe Estrannaise calibrée - Miroir du fix Android v1.9.1 : la courbe ESE affichée est calibrée (scalePerEster des labs) mais le nuage était tracé brut → échelles différentes. Le nuage reçoit maintenant le MÊME scalePerEster que la courbe ESE. - Harmonisation de l'arrondi de la médiane de calibration (le web avait la troncature historique) : les niveaux estimés web et Android convergent. Le niveau correct = l'ARRONDI (plus juste que la troncature, cf CHANGELOG Android 1.9.1). ## [1.9.0] — 2026-09-18 ### Modèle Estrannaise ANALYTIQUE + nuage d'incertitude (portage Android v1.9.0, versions sync) - **Le modèle ESE n'est plus les tables horaires ODS** : il est désormais la **forme close 3C publiée par estrannaise.js** (les sources originelles dont l'ODS n'était que l'échantillonnage horaire) — fidélité épinglée : RMS 0,00 vs les tables sur 0→200 j, écart de pic ≤ 0,1 %. Les tables ne sont plus fetchées au démarrage (−550 Ko, démarrage plus rapide) ; `pk_profiles.json` reste uniquement comme référence des tests. - **6 esters injectables couverts** (EV, EU, EEn, EC, EB + **EUCS**, undécylate suspension cristalline, exclusif à ce fit) — l'ancien ESE n'en couvrait que 3. - **NUAGE D'INCERTITUDE (exclusif ESE)** : Estrannaise publie le posterior MCMC de ses paramètres (313 échantillons/ester) — chip `Nuage` (off par défaut, activable à volonté, actif SEULEMENT si ESE est affiché) → nuage diffus de courbes montrant la plage d'imprécision du modèle, comme sur le site estrannaise. TFS/WHSAH ne publient pas de posterior → pas de nuage pour eux. - t½ terminale ESE désormais **analytique** (ln2/min(k1,k2,k3)) — la recommandation de prise de sang n'estime plus la t½ par pente de table. - **Tests** : fidélité (RMS, pics), cas dégénérés, MCMC (313 × 6), nuage (32 courbes, dispersion, exclusivité ESE), t½ analytique. ## [1.8.1] — 2026-09-17 ### Corrigé — la stabilisation ignorait les changements récents (dose/ester/intervalle) - **Bug remonté** : la recommandation disait « stabilisée depuis février » alors que l'ester, le dosage ET la posologie venaient de changer — l'ancien calcul partait de la **1ʳᵉ dose du traitement** (proxy aveugle aux changements). - **Fix** : le régime courant = la **séquence terminale de doses** à (ester effectif, dose mg, **écart inter-doses**) constants — tout changement récent réinitialise la stabilisation : la recommandation saute au premier creux **après** la stabilisation du nouveau régime (règle des ~5 demi-vies conservée). L'encart porte l'ester **effectif** de la dernière dose (override compris). - **Nouveau : encart sur l'Accueil** (demande) — la suggestion de prochaine prise de sang est désormais aussi visible directement sur la page d'accueil (creux daté + créneau associé, mention de stabilisation si le régime n'était pas déjà stable). - 3 nouveaux tests miroirs (155 au total) : changement de dose / de posologie (intervalle) / d'ester → réinitialisation épinglée. ## [1.8.0] — 2026-09-17 ### Prochaine prise de sang recommandée — portage de l'Android v1.8.0 (versions sync) - **Nouvel encart sur la page Analyses** : « Prochaine prise de sang (suggestion) » — quand un traitement E2 **injectable actif a une Posologie**, l'app déduit de ses courbes **le creux estimé juste avant l'injection suivante** (heure exacte + créneau associé + mention « ou simplement la veille »), **au premier creux où l'ester est stabilisé** (~5 demi-vies terminales après le dernier changement — on saute les creux « de transition »). - `js/pk/lab-timing.js` : miroir fidèle du Kotlin (créneaux generateForecastDoses, courbe E2 fine sans calibration, filtres maintenant/+6 h/dernière prise/stabilisation). Le modèle Estrannaise lit sa demi-vie dans les tables (`pkProfileStore.terminalHalfLifeDays`, refactor miroir), TFS/WHSAH analytiquement. - **Sans Posologie** sur l'injectable actif : encart remplacé par l'invite « renseigne une Posologie… » (demande v1.8.0) — le lien est découvrable. - 7 tests miroirs (152 au total) + **scénario E2E** (encart rendu sur #labs avec la démo EEn 7 j : creux daté, créneau, stabilisation). ## [1.7.1] — 2026-09-16 ### Corrigé — une seule note sur deux s'affichait dans les Analyses - **Bug remonté** (identique Android) : dans une prise de sang E2+T, quand les deux entrées portent des notes **distinctes** (ex. une note clinique sur l'E2, une note « DHT » sur la T), une seule s'affichait — l'autre était bien conservée mais perdue à l'affichage. - **Cause** : l'ancien code ne prenait que la première note non vide du groupe (hypothèse historique « toutes identiques » — vraie à l'époque du dialog de création, fausse depuis l'édition unitaire). - **Fix** : `labNotesForDisplay` (helper PUR dans `format.js`, miroir du Kotlin) — notes distinctes → une ligne par note préfixée du marqueur (« E2 : … » / « T : … ») ; note identique → une seule ligne ; vides ignorées. - Épinglé par : 5 tests unitaires (miroir du Kotlin) + **scénario E2E** (la dernière paire de démo porte des notes distinctes, les deux s'affichent sur #labs) + régression data-driven Android sur l'export réel. ## [1.7.0] — 2026-09-13 ### Unités des axes du graphique — portage de l'Android v1.7.0 (versions sync) - **pg/mL** (E2, axe gauche) et **ng/mL** (T, axe droit) affichées au sommet des colonnes de labels du graphique de simulation — miroir exact du fix Android (padTop élargi 12 → 26 px, texte gris dans la couleur des labels). - Universelles, PAS des clés i18n (déjà en dur dans les légendes). > Note de parité : la **sauvegarde automatique quotidienne** de l'Android > v1.7.0 n'est PAS portée — le navigateur ne peut pas écrire périodiquement > dans un dossier arbitraire sans intervention (voir §12 Limites) ; > l'export/import manuel du backup couvre la même donnée. ## [1.6.1] — 2026-09-13 ### Corrigé — les options du graphique se réinitialisaient « toutes seules » - **Bug remonté** : sur l'écran Graphiques, les options sélectionnées (modèles affichés, Tracé labs, Prolonger, zoom, panoramique…) revenaient d'elles-mêmes aux défauts « alors qu'on n'a pas changé de menu ». - **Cause** (divergence de portage) : le shell web re-crée l'écran ENTIER à chaque **tick minute** (60 s), **mutation du store** ou **resize** — et l'état des options vivait dans une variable LOCALE de la fonction de rendu, donc retombait aux défauts à chaque re-crétion. Côté Android, l'équivalent vit dans des `remember { mutableStateOf }` qui survivent à toutes les recompositions : le bug est structurellement impossible côté APK — c'était un piège du portage, pas de la logique. - **Fix** : l'état d'interaction du graphique vit au niveau MODULE et le shell signale les re-rendus du **même** écran (`preserveState: true`) → options préservées pendant qu'on regarde le graphique ; à l'entrée depuis la navigation (quitter puis revenir), retour aux défauts — comportement identique à l'Android. - **Épinglé par l'E2E** : re-render déclenché (resize — même code path que le tick) → chip et légende toujours en place ; navigation → reset. > Note de parité : fix **web-only** (pas de changement Android) — les > fonctionnalités portées restent celles de l'Android v1.6.0. ## [1.6.0] — 2026-09-12 ### « Tracé labs » prolongé au-delà du dernier lab — portage de l'Android v1.6.0 - **Nouveau chip `Prolonger`** (à côté de `Tracé labs`, **off par défaut**, désactivé tant que Tracé labs est off) : la courbe ancrée sur tes prises de sang se PROLONGE au-delà du dernier lab — la limite v1.5.0 rendait invisible justement la période la plus récente. - **Recette** : au-delà du dernier lab significatif, `courbe(t) = M(t) × ρ_dernier lab` — le modèle PK (dosages, esters injectés depuis, pics et descentes classiques) × le ratio mesuré au dernier lab, supposé constant. Les doses loguées APRÈS le dernier lab font repartir la courbe en pic (override d'ester compris). - **Horizon honnête** : la prolongation s'arrête quand le MODÈLE lui-même s'éteint (dernière dose + cutoff moteur : 10 t½ terminales V3C/WHS, fin de table ODS, 30 t½ Bateman) — jamais de ligne à zéro inventée. - **Lisibilité** : partie prolongée dessinée SÉPARÉMENT (même rose, atténué) avec sa légende dédiée + **AVERTISSEMENT visible** sous la légende : simple simulation sans garantie de correspondre au réel, extrapolée à partir de tes résultats de laboratoire — qui peuvent eux-mêmes être erronés. Fie-toi à ta prochaine prise de sang. - Garde anti-ratés inchangée (#61) : un lab hors fenêtre d'action ne prolonge pas la courbe et n'est jamais traversé. - Portage fidèle : `extensionHorizonEndMs`/`lastAnchorMs` (miroir du Kotlin), 8 nouveaux tests unitaires miroirs + **scénario E2E navigateur** (activation des chips, légende et avertissement rendus). ## [1.5.0] — 2026-09-11 ### « Tracé labs » — portage de la version Android v1.5.0 (versions synchronisées) - **Nouvelle courbe superposable** (chip `Tracé labs`, **off par défaut**) : `courbe(t) = M(t) × ρ(t)` — le modèle PK garde la forme biologique (Tmax, queues, accumulation), l'amplitude est recalée sur CHAQUE prise de sang — **passage EXACT garanti** sur chaque lab, transition log-linéaire d'un lab au suivant (jamais de saut vertical). - Garde anti-ratés (#61) : un lab hors fenêtre d'action n'est pas un ancrage ; E2 uniquement ; fenêtre bornée [1er ; dernier lab] — la courbe s'arrête au dernier lab (pas d'extrapolation). - Indépendante de la calibration (sa propre référence) ; hors pics/creux ; n'entre JAMAIS dans niveau accueil/alertes. - La rangée de chips du graphique défile horizontalement (4ᵉ chip — miroir du fix Android v1.5.0). - 11 tests miroirs de `LabTrajectoryModelTest.kt` (Android). ## [1.4.10] — 2026-09-08 Version initiale de la version web — portage navigateur de l'app Android v1.4.10, 100 % locale (données dans le localStorage, aucun serveur applicatif, aucune télémétrie). ### Moteur PK (fidèle à l'Android, testé) - **Trois modèles superposables** : Estrannaise (tables ODS de `pk_profiles.json`, même asset que l'app Android), Transfem Science (forme close V3C de la méta-analyse, 7 esters), WHSAH (fit license-free de Mona, 6 esters — PEP non couvert) + Bateman paramétrable (gel/patch/oral). - Pics de l'article TFS épinglés (±2 %) ; équilibre EV 5 mg/7 j = Figure 11 (Cmax 384 / Cmin 142 / Cavg 269) ; fidélité Mona WHSAH (EV 367,5 @ 1,69 j, EEn 187,9 @ 4,99 j, t½ 7,34 j). - Invariants Android préservés : bisection `computeKa` dans le bon sens (#19), modèle strict + extrapolation terminale ≥ 1 % du pic (#20/#21), lookup insensible à la casse (#22), conversion des labs T (#23/#26), garde de significativité de calibration (#61), créneaux de prévision passés sautés (#35), rappels sur la grille Posologie (#52). - Calibration **par période d'ester** et **par modèle affiché** (#60) : auto-calibration optionnelle (désactivée par défaut) + calibration manuelle par traitement ; k T par période d'ester. ### Fonctionnalités - Accueil : niveau actuel estimé (E2 pg/mL + T ng/mL) avec delta « vs il y a 6 h » réel (fix #53), cartes d'alerte de seuil, carte prochaine dose (jours au-delà de 24 h, fix v1.4.1), log rapide, mini-graphique 24 h. - Graphiques : 24 h/7 j/30 j, zoom 6 h → 300 j (pinch, molette, boutons, focal du pinch conservée), panoramique avec **cumul fractionnaire** des deltas (fix #62), prévision avec extension de fenêtre sans saut (fix #54) et horizon borné 30 j–1 an, toggles 3 modèles alignés sur les traitements (fix #58), pics/creux avec valeurs, marqueurs de doses réelles et projetées (fix #55), labels X à minuit local dans le fuseau choisi (v1.4.5), labs T convertis en ng/mL au rendu. - Doses : historique groupé par jour, Δ jours depuis la dose précédente du même traitement, édition (override d'ester par injection selon le modèle), en-tête « temps sous THS » (3 placeholders, bug #47 épinglé). - Analyses : prise de sang E2 + T en une entrée, groupement par timestamp, suppression par prise entière, unités T multiples (ng/mL, ng/dL, ng/L, nmol/L). - Traitements : 22 presets, éditeur complet (ester/modèle/Bateman, calibration, Posologie, rappel), traitement inactif = drapeau administratif (la simulation reste, bug v1.2.4). - Rappels web : notifications navigateur tant que la page est ouverte (bannière in-app + actions « Loguer maintenant » / « Reporter 1 h »). Limite documentée : pas de notification onglet fermé. - Seuils d'alerte : évaluation du niveau estimé, notification web, anti-spam par état persisté (même codec que l'app Android). - Sauvegarde JSON **compatible Android dans les deux sens** (BackupData v2, rétrocompat v1, import en mode écrasement avec IDs conservés) ; paramètres utilisateur transportés (langue, auto-cal, seuils). - Logs de diagnostic exportables (buffer 500 lignes, même format que l'app Android). - FR + EN (langue par app, indépendante du système), dialog « Nouveautés » après mise à jour. ### Processus - 121 tests Node (`node --test`) : profils ODS (pins ODS identiques à l'Android), fidélité TFS/WHSAH, moteur, calibration, backup (dont import des exports Android réels de `local-test-data/` si présents), helpers du graphique, alertes, rappels, changelog, store. - `scripts/check.sh` : syntaxe de tous les modules + cohérence i18n FR/EN + tests + smoke HTTP optionnel. - **Dépôt GIT SÉPARÉ** de l'app Android (HormoneTrack-web) — historique 100 % propre (aucune dette de confidentialité), versions alignées sur l'Android, changelogs totalement indépendants. ### Non porté (impossible ou hors périmètre navigateur) - Événements d'agenda récurrents (pas de CalendarProvider dans un navigateur) — l'éditeur affiche une note explicative. - Notifications quand l'onglet est fermé (aucun scheduler système côté web). - Verrou biométrique, widget — non implémentés non plus côté Android v1.