- FIX#70 : generateForecastDoses garde !isActive — un injectable archivé
avec Posologie et de l'historique projetait encore ses injections
(graphique Prévision, extension de fenêtre, horizon LabTiming) ; le chip
Prévision s'activait sur des Posologies inactives. FIX : garde moteur +
horizon/chip sur les ACTIFS. §6.bis inchangé : l'historique reste simulé,
seul le FUTUR s'arrête (comme les rappels v1.4.0). Miroir web. 3 tests
ForecastDosesGuardTest + 3 miroirs web.
- Icônes de forme de prise dans Doses : routeIcon pur (pilule/seringue
IM+SC teinte tertiary pour la SC/goutte/sparadrap/compte-gouttes ;
⚠️ MedicationLiquid absent d'icons-extended 1.7.8 → Colorize).
- Marqueurs « prise de sang le même jour » : labMarkersForDose pur (jour
calendaire LOCAL, lab à l'heure exacte = APRÈS) → encart 🧪↑/🧪↓ en bout
de ligne. 7 tests DosesExtrasTest + 10 miroirs web.
274 tests JVM + 14 UI + lint verts ; web 194 tests + E2E verts (check.sh).
Validé émulateur sur APK RELEASE avec données réelles seedées + scénario du
bug (2 inactifs à Posologie 7 j) : Prévision on/off 0 crash, glyphes et
marqueurs en place.
- Demande : « les traitements inactifs sont peu discernables — les placer
à la fin (tout en bas), et peut-être les rendre plus distincts ».
- Helper pur treatmentsForDisplay (tri STABLE : actifs d'abord, inactifs
après, ordre relatif conservé dans chaque groupe).
- LazyColumn en 3 sections : actifs / en-tête inactive_section (seulement
si des inactifs existent) / inactifs — cartes alpha 0.55 en plus du
badge, lisibles et tappables (l'édition d'un archivé reste possible).
- Miroir web : treatmentsForDisplay dans js/data/models.js + rendu
sections + opacity 0.55 + i18n inactive_section FR/EN (web v1.11.0 sync).
- 5 tests JVM TreatmentsDisplayTest + 4 tests web (miroir).
264 tests JVM + 14 UI + lint verts ; web 184 tests + E2E verts (check.sh).
Validé émulateur sur APK RELEASE avec données réelles seedées (5 actifs en
haut, en-tête + EV-old/EEn-old tout en bas, 0 crash).
- 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.
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é