- launchMode="singleTop" sur MainActivity : un tap notification app-ouverte
délivre onNewIntent SANS recréer l'activité (l'ancien standard+CLEAR_TOP
recréait tout et rendait le chemin v1.10.0 inopérant — code mort).
- EXTRA_PLANNED_AT : l'instant planifié voyage dans l'alarme (scheduleFor),
le snooze le forward, le garde « créneau déjà honoré » évalue le jour du
CRÉNEAU et non celui du feu (alarme inexacte/Doze glissant après minuit).
- BootReceiver : goAsync + coroutine (dette §20.bis #2), runBlocking supprimés.
- Audit doc : comptes résiduels 223 → 259 (§14 #64, §16.ter) ; §9/§20.bis à jour.
259 tests JVM + 14 UI + lint verts ; validations émulateur sur APK RELEASE
avec données réelles seedées (singleTop 0 recreation, planned_at A/B, 0 crash).
Web non concerné (aucune logique miroir touchée).
- #68 : le « creux » était le minimum de la fenêtre entière — pour un ester à
montée lente (EEn, pic ~J+5 ≈ intervalle 7 j) il tombait juste après
l'injection PRÉCÉDENTE : date antérieure à la stabilisation affichée sur la
même carte, « juste avant ton injection du … » faux de plusieurs jours
(réel : creux 5 oct 02:32 < stab 7 oct 04:03). FIX : creux = niveau
PRÉ-INJECTION du créneau (principe v1.8.0 restauré ; EV inchangé). Miroir
web appliqué. Régression n°6 data-driven (backup-v1.9.8.json, balayage de
now ±60 j) ; les 17 LabTimingTest passent inchangés.
- #69 : tap notification → dialog ; le fermer + changer d'onglet + revenir sur
l'accueil le rouvrait à chaque fois (demande au niveau activité,
consommation dans les remember de Home). FIX : MutableState<Long?> dans
MainActivity consommé UNE fois + extras retirés de l'intent ; bonus : tap
notification app-ouverte ouvre aussi le dialog. Test UI LogDoseRequestTest.
- Rappel sauté si dose déjà loggée (demande) : garde au déclenchement —
hasDoseLoggedOnDay (pur, jour calendaire LOCAL) dans ReminderReceiver,
re-programmation du créneau suivant dans tous les cas. 6 tests JVM +
vérification A/B émulateur (EEn dose-du-jour → 0 notif, Fluoxetine → 1).
259 tests JVM + 14 UI + lint verts ; web 180 tests + E2E verts (check.sh).
Validations émulateur §16.ter sur APK RELEASE avec données réelles seedées
(reco : creux 11 oct 20:08 ≥ stab 7 oct 06:03 ; dialog 1× puis plus jamais ;
changelog 1.10.0 affiché ; 0 crash).
Remontée : « la suggestion change tout le temps à chaque injection si
l'injection n'est pas faite pile à la même heure » — la règle v1.8.1
comparait l'écart inter-doses EXACTEMENT à l'écart précédent : un log
30 min plus tard cassait le régime et repoussait la stabilisation de
5 × t½ à chaque injection (creux recommandé fuyant).
Fix : chaque écart doit rester dans l'INTERVALLE DE POSOLOGIE ± 24 h
(GAP_TOLERANCE_MS — « je m'injecte le même jour, à l'heure près »). La
fenêtre se réfère à l'intervalle THÉORIQUE : des logs à 6,8 j puis 7,2 j
ne se déstabilisent plus en cascade ; un vrai changement de créneau
(2 j au lieu de 7 j) reste hors fenêtre. Le créneau affiché suit
toujours la dernière dose réelle (voulu).
Tests : « interval change » ré-épinglé (NOW+21 j) + 2 nouveaux (flou
d'heure ± 23 h → pas de reset ; écart 25 h → reset). 249 verts + lint.
versionCode 44 / 1.9.7 + CHANGELOG/§2/§7.11/README.
Remontée : le rappel quotidien (CPA) masquait le rappel hebdomadaire
(EEn) — « je ne le vois quasiment pas ».
- NextDoseCard : UNE ligne par traitement, TRIÉES par prochaine prise ;
1ʳᵉ ligne (la plus proche) en avant, suivantes en style secondaire ;
titre pluriel « Prochaines doses » dès 2 lignes (next_doses FR/EN)
- Helper PUR nextDoseLine (locale/zone paramétrables, template jours
localisé next_dose_days) + 3 tests NextDoseLineTest
- Notifications inchangées (une alarme par traitement)
- versionCode 43 / 1.9.6 + CHANGELOG/§2/README/GUIDE
Bug remonté : activer Estrannaise dans le graphique puis le nuage
n'affichait rien tant qu'aucun traitement n'était STOCKÉ avec le modèle
ESE — « la seule méthode trouvée était de mettre un traitement en cours
sur le modèle ESE, mais ça ne devrait pas être un prérequis ».
Cause : le filtre du nuage testait le pkModel STOCKÉ du traitement, alors
que la courbe ESE affichée redessine toutes les doses E2 avec
modelOverride = "ESE" (peu importe le modèle stocké) — courbe et nuage
n'utilisaient pas la même définition de « quelles doses sont tracées en
ESE ».
Fix : EstrannaiseCloud.compute couvre toutes les doses E2 à profil
injectable dont l'ESTER EFFECTIF (override compris) est couvert par le
fit Estrannaise — indépendamment du modèle stocké. L'exclusivité ESE
reste portée par le chip (activable seulement si ESE est affiché).
L'oral Bateman reste hors nuage (pas d'ester échantillonnable). Les
traitements inactifs sont inclus (§6.bis : la courbe ESE les trace aussi).
Tests réécrits (237 verts / 207 sans données locales) : « les doses d'un
traitement TFS sont couvertes quand ESE est affiché » (épinglé Android +
web) ; oral seul → vide (conservé). Validé émulateur §16.ter sur le
profil réel de l'utilisatrice (EEn stocké TFS, ESE affiché, nuage visible
autour de la courbe, 0 crash). Web miroir v1.9.2. versionCode 40 /
versionName 1.9.2.
- EstrannaiseCloud.compute : paramètre scalePerEster — le nuage reçoit la
MÊME calibration que la courbe ESE (autoByModel ESE). Avant : courbe ESE
calibrée mais nuage BRUT → les deux flottaient à des échelles différentes
(remontée : « le nuage ne s'active que autour du tracé, pas autour du
modèle Estrannaise »). ChartScreen passe autoM?.esterScales.
- +1 test : nuage calibré ×2 = nuage brut ×2 (le nuage suit la calibration).
- Validé émulateur §16.ter (release, seed réel + traitement EV ESE actif
de test) : nuage rendu autour de la courbe ESE calibrée, 0 crash.
- CHANGELOG 1.9.1 : explication documentée du +4 pg/mL constaté depuis
v1.8.2 (l'arrondi de la médiane de calibration — plus juste que la
troncature — a déplacé une échelle auto-calibrée d'un cran ; le niveau
actuel est le CORRIGÉ). §2 session, §7.12 nuage calibré, README.
- DIAG du test médiane nettoyé (l'épinglage de l'arrondi reste, en
assertion propre ±0,005).
- versionCode 39 / versionName 1.9.1. Web sync v1.9.1.
- ChartScreen : one-liner sur la clé « labResults entière » (le pourquoi
du fix#66, là où on lit le code).
- §20.bis : les 4 pistes d'optimisation écartées/différées de l'audit
(importJson batch, BootReceiver goAsync, fusion generateForecastDoses,
cache terminalDecayParameters) — avec la raison de chaque report, pour
qu'une future session ne redécouvre pas et ne re-pose pas les questions.
Critique v1.8.0 : « changé d'ester, de dosage ET de posologie, et l'app
me disait stabilisée depuis février » — le proxy « 1ʳᵉ dose du traitement »
était aveugle aux changements récents.
- LabTiming.regimeStartMs (nouveau, internal) : le régime courant = la
séquence terminale de doses à (ester effectif, dose mg, écart
inter-doses) CONSTANTS — l'écart est comparé exactement (7 j ± 1 h casse
le régime) : tout changement récent réinitialise la stabilisation et la
reco saute au premier creux post-stabilisation. KDoc complet (règle +
justification conservatrice des intervalles irréguliers).
- LabRecommendation.ester = l'ESTER EFFECTIF de la dernière dose (override
compris) — l'encart/carte racontent le bon ester.
- HomeScreen : carte compacte « Prochaine prise de sang (suggestion) »
(creux daté + créneau associé + mention de stabilisation SEULEMENT si le
régime n'était pas déjà stable) — même calcul que la page Analyses
(demande v1.8.1 : suggestion aussi sur l'Accueil).
- 3 nouveaux tests (223 verts / 193 sans données locales) + lint :
changement de dose / d'intervalle / d'ester → réinitialisation épinglée.
- Validé émulateur §16.ter (release, seed réel v1.7.0) : la carte passe de
« stabilisé depuis le 06/02 » à « EEN pas stabilisé avant le 29/09 —
premier creux fiable après ton dernier changement », creux 27/09 22:43,
0 crash. versionCode 36 / versionName 1.8.1. Web sync v1.8.1.
- LabsScreen : labNotesForDisplay (helper PUR, testé) — notes DISTINCTES
d'une prise E2+T → DEUX lignes préfixées du marqueur (« E2 : … » /
« T : … ») ; note identique (dialog commun) → une seule ligne
(dédupliquée) ; vides ignorées ; note seule → brute (v1.0 préservé).
L'ancien code ne prenait que la première note non vide (hypothèse
« toutes identiques » cassée par l'édition unitaire) : l'autre note
était conservée mais perdue à l'affichage.
- RegressionUserCase5Test (7 tests) : NOUVEL export réel v1.7.0
(local-test-data/backup-v1.7.0.json, gitigné) — 3 traitements dont CPA
oral, 64 doses, 28 labs ; la dernière prise porte EXACTEMENT le cas du
bug (deux notes distinctes E2/T) : les deux affichées, aucune perdue ;
plausibilité moteur (CPA oral + EEn actif + EV inactif simulé),
prévision 7 j, notes de doses préservées. Aucune valeur de santé en dur
(data-driven, garde §8.bis).
- LabsGroupingTest : +5 tests sur labNotesForDisplay. 211 verts
(181 sans données locales) + lint vert.
- Validé émulateur §16.ter : APK release seedé avec l'export v1.7.0 →
écran Analyses affiche « E2 : … » ET « T : … », 0 crash.
- versionCode 34 / versionName 1.7.1. Web sync en miroir (web v1.7.1).
- proguard-rules.pro : -keep class LabTrajectoryModel { *; } — le mapping
R8 montrait R8$$REMOVED$$CLASS (inlining du calcul + du synthétique
$default dans le producer) : courbe vide en release, debug et 191 tests
JVM aveugles. Validé émulateur §16.ter sur l'APK release re-buildé
(données réelles seedées) : ancrée + prolongée + avertissement + 0 crash.
- Checklist §16 : NOUVELLE étape 3.bis — validation émulateur sur l'APK
RELEASE obligatoire AVANT le tag (leçon #64 : jamais oublier le
simulateur) + procédure de re-tag si fix avant publication.
- §14 : bug #64 complet (symptôme, diagnostic mapping, fix) ; §2 et
CHANGELOG mis à jour.
- LabTrajectoryModel : extendBeyondLastLab (ρ_last constant au-delà du
dernier lab significatif), lastAnchorMs, extensionHorizonEndMs
(horizon = dernière dose E2 + cutoff du traitement — cutoffHours rendu
public, une seule source de vérité) ; boucle de calcul inchangée.
- ChartScreen : chip « Prolonger » (5ᵉ, off par défaut, désactivé tant
que Tracé labs est off), split série "LAB" (ancrée) / "LABX"
(prolongée, rose atténué α 0,55), légendes dédiées (branche explicite
avant le else — leçon #63), AVERTISSEMENT « simple simulation, sans
garantie de correspondre au réel, labs potentiellement erronés » sous
la légende quand la prolongation est visible.
- 8 nouveaux tests (191 verts / 168 sans données locales) + lint vert.
- Bump versionCode 32 / versionName 1.6.0.
Constats de l'audit systématique (leçon #44 : toute affirmation doit être
vérifiée dans le fichier réel) :
- effectifs de régression RÉELS : 4 classes = 6/6/6/5 = 23 tests (doc
disait 4 pour la classe 4) → « 160 sans les données locales » (183−23)
corrigé dans §8, §8.bis, §16, footer et README ; les vieux chiffres
(44/87 v1.3.x, 150/172 v1.4.x) épinglés comme historiques
- §14 : entrée #63 ajoutée à l'historique des bugs (elle était citée en
§7.10 mais absente de la liste « à ne pas réintroduire »)
Le portage navigateur (ex-web/) vit désormais dans ~/projects/HormoneTrack-web
— dépôt séparé sur les mêmes instances Gitea, historique 100 % propre (la
dette de confidentialité §8.bis reste confinée à ce dépôt), versions
alignées (web v1.4.10 = portage de l'Android v1.4.10), changelogs
totalement séparés.
- §16 : étape 4.bis de la checklist de release = sync web (checklist §10
du dépôt web) ; publication Gitea web gelée tant que la parité qualité
n'est pas validée (commit + tag seulement)
- README §Git : note sur le dépôt web apparié
- FIX toggle agenda : Posologie requise (refus + message si absente),
permission vérifiée RÉELLEMENT via ContextCompat au save (le flag
remembered pouvait être faux après recomposition), AppLog à chaque étape
- FIX export logs : réutilise le pattern éprouvé de l'export JSON
(openOutputStream wt), chaque étape journalisée dans AppLog
- FIX dialog changelog : version vue mémorisée AVANT l'affichage (sinon
réapparaissait à chaque réouverture), titre = BuildConfig.VERSION_NAME
(documenté : la version de l'APP, pas du contenu)
- RegressionUserCase3Test mis à jour (3 traitements : EV inactif + EEn
actif + CPA oral avec posologie 1 j, 51 doses, 26 labs)
- versionCode 16, versionName 1.3.2