@ -79,7 +79,6 @@ Fonctionnalités v1 :
| 6 sept. 2026 (session v1.3.1) | En-tête « temps sous THS » sur Doses ; **logs de diagnostic** (AppLog : buffer 500 lignes, persisté, exportable de Paramètres) ; **fix toggle agenda** (callback async) ; **87 tests verts** , APK v1.3.1 + releases. |
| 6 sept. 2026 (session v1.3.2) | 3 bugs remontés : toggle agenda (callback async fix + ContextCompat au save + Posologie requise), export logs plantait (pattern JSON réutilisé + AppLog), dialog changelog récurrent (version vue mémorisée avant affichage + titre BuildConfig documenté) ; export réel mis à jour (3 traitements : EV inactif + EEn actif + CPA oral) ; **87 tests verts** , APK v1.3.2 + releases. |
| 6 sept. 2026 (session farewell) | Repo créé côté farewell → **push SSH (alias `farewell` : giteassh:2222) + 12 releases publiées avec APK vérifiés par téléchargement** ; piège lexicographique `v1.2.10 < v1.2.5` en comparaison de chaînes épinglé (§14 #38 ) ; les deux instances Gitea sont synchrones. |
| 6 sept. 2026 (session v1.3.3) | **Audit de reprise de maintenance** (nouvelle session IA) : 3 bugs racines trouvés — permissions agenda ABSENTES du manifest (jamais déclarées, §14 #44 ), export logs plantait TOUJOURS (le fix v1.3.2 réimplémentait l'IO au lieu de réutiliser BackupManager, §14 #45 ), bump de version JAMAIS commité (tags v1.3.0– 1.3.2 tous `versionCode 14 / "1.3.0"` , §14 #46 ). Corrigés + doc rafraîchie (DB v3, targetSdk 36, régression 3 = export v1.3.1, §19/§20 staleness) ; **téléphone de test documenté : Google Pixel 9 /e/OS** (AOSP ; SAF DocumentsUI standard). |
| 6 sept. 2026 (session v1.3.0) | Dialog « Nouveautés » après mise à jour (CHANGELOG embarqué en asset, tâche `copyChangelog` , version vue en DataStore) ; **événements d'agenda récurrents** (calendrier local HormoneTrack, RRULE posologie, permission runtime, Room v3 `calendarEventId` ) ; Paramètres : version + lien releases ; **78 tests verts** , APK v1.3.0 + releases. |
| 5 sept. 2026 (session v1.2.10) | Sens des boutons de zoom inversé (+ = zoom avant, convention carte — retour utilisateur) ; 67 tests verts, APK v1.2.10 + releases. |
| 5 sept. 2026 (session v1.2.9) | **Zoom du graphique** (pinch + boutons, 6 h → 300 j, focal stable, échantillonnage adaptatif `stepForRange` , labels X 1 h/3 h) ; **README : disclaimer IA-assisté** en en-tête ; 3 tests ; **67 tests verts** , APK v1.2.9 + releases. |
@ -99,14 +98,14 @@ et en testant** — aucun build n'avait été lancé avant la session 3.
| Kotlin | 2.3.21 (plugin compose 2.3.21) | idem |
| KSP | 2.3.11 (versionnage indépendant depuis KSP2) | idem |
| Compose BOM | **2026.08.00** (Compose 1.12 ; material3 pinné par le BOM) | `app/build.gradle.kts` |
| Room | 2.8.4 (KSP) — **DB v 3 + MIGRATION_1_2 (forecastIntervalDays) + MIGRATION_2_3 (calendarEventId) **, `fallbackToDestructiveMigration` retiré | idem + `AppDatabase.kt` |
| Room | 2.8.4 (KSP) — **DB v 2 + MIGRATION_1_2 **, `fallbackToDestructiveMigration` retiré | idem + `AppDatabase.kt` |
| Navigation Compose | 2.10.0 | idem |
| AppCompat | 1.8.0 (langue par app) | idem |
| DataStore Preferences | 1.2.1 | idem |
| Gson | 2.14.0 | idem |
| JUnit | 4.13.2 (testImplementation) | idem |
| WorkManager | 2.11.2 (déclaré, non utilisé — supprimable) | idem |
| compileSdk / targetSdk | **37** / **36** ; minSdk 26 ; Java target 17 | app |
| compileSdk / targetSdk | **37** / 37 ; minSdk 26 ; Java target 17 | app |
| buildFeatures | compose + **buildConfig** (VERSION_NAME pour l'app) | app |
Notes importantes (v1.2.0) :
@ -130,12 +129,6 @@ Notes importantes (v1.2.0) :
## 4. Environnement de build (cette machine)
- **macOS (Apple Silicon), brew présent, Java 21 (Microsoft OpenJDK) sur `/usr/bin/java`** ✓
- **Téléphone de test** : **Google Pixel 9 sous /e/OS** (ROM dé-Googlée, base AOSP) —
SAF/DocumentsUI standard (l'export JSON via `CreateDocument` y fonctionne → c'est LE
pattern d'IO de référence pour tout export de fichier) ; pas de Play Store, sideload
par `adb install` ou APK direct. Les retours utilisateur (bugs, exports JSON) viennent
de ce téléphone. (Historique : la montre est une Huawei Watch GT 3 via Gadgetbridge,
cf §17 — ce n'est PAS le téléphone.)
- **SDK Android** : installé via `brew install --cask android-commandlinetools`
→ `/opt/homebrew/share/android-commandlinetools` (524 MB)
- licences acceptées : `yes | sdkmanager --licenses`
@ -173,12 +166,9 @@ Points clés :
## 6. Modèle de données (Room)
DB `hormonetrack.db` , **version 3 **, migrations explicites (⚠️ plus de
DB `hormonetrack.db` , **version 2 **, migrations explicites (⚠️ plus de
`fallbackToDestructiveMigration` — retiré en v1.2.0 car l'utilisatrice a des données
réelles ; toute évolution de schéma = `Migration(x, y)` + ALTER TABLE) :
- **v1 → v2** : `forecastIntervalDays REAL` (prévision, v1.2.0)
- **v2 → v3** : `calendarEventId INTEGER` (agenda récurrent, v1.3.0)
réelles ; toute évolution de schéma = `Migration(x, y)` + ALTER TABLE).
### `Treatment` (treatments)
- base : `id` , `name` , `type` (ESTRADIOL/ANTI_ANDROGEN/PROGESTOGEN/OTHER), `route`
@ -399,14 +389,12 @@ Emplacement : `app/src/test/java/com/hormonetrack/`. Répertoire de travail d'ex
non calibré ≈ 367 = les « 375 » rapportés), facteur unique EEN plausible, lab T en
unité aberrante neutralisé, prévision 6 j exacte, auto-cal cohérente.
**Tout nouvel export utilisateur = un nouveau test de régression.**
- ** `RegressionUserCase3Test` ** (6) : 3ᵉ régression (export **v1.3.1** — mis à
jour à la v1.3.2) — le scénario **transition** : traitement **EV inactif**
(29 doses 2– 8 mg, janvier→juillet) + traitement **EEn actif** (9 doses) +
**CPA oral** (anti-androgène), soit 3 traitements, 51 doses, 26 labs.
Vérifie : parsing, **l'inactif reste simulé** (bug v1.2.4 — le moteur reçoit
TOUS les traitements), calibration par période couvrant EV **et** EEN,
k T par ester, continuité de la courbe pendant la transition, niveau
actuel ~EEn équilibre.
- ** `RegressionUserCase3Test` ** (6) : 3ᵉ régression (export v1.2.3) — le scénario
**transition** : traitement **EV inactif** (29 doses 2– 8 mg, janvier→juillet) +
traitement **EEn actif** (9 doses), 22 labs sur les deux périodes. Vérifie :
parsing, **l'inactif reste simulé** (bug v1.2.4 — le moteur reçoit TOUS les
traitements), calibration par période couvrant EV **et** EEN, k T par ester,
continuité de la courbe pendant la transition, niveau actuel ~EEn équilibre.
### 8.bis Données de test réelles : HORS dépôt (`local-test-data/` )
@ -415,11 +403,8 @@ du moteur sur les **exports réels** de l'utilisatrice. Ce sont des **données d
personnelles** : elles ne sont **pas versionnées** , pour ne rien divulguer dans le
dépôt (ni maintenant, ni si le repo devient public un jour).
- emplacement : `local-test-data/backup-v1.0.0.json` , `backup-v1.2.0.json` ,
`backup-v1.2.3.json` (ancien export, plus consommé par un test) et
`backup-v1.3.1.json` (copiés tels quels depuis l'export JSON de l'app) ;
`RegressionUserCaseTest` lit v1.0.0, `RegressionUserCase2Test` lit v1.2.0,
`RegressionUserCase3Test` lit **v1.3.1** ;
- emplacement : `local-test-data/backup-v1.0.0.json` et `backup-v1.2.0.json`
(copiés tels quels depuis l'export JSON de l'app) ;
- `.gitignore` contient `local-test-data/` → jamais commités ;
- les tests font `Assume.assumeTrue(file.exists())` dans le `@Before` : **sans le
fichier, la classe est IGNORÉE** (skipped, pas failed) — un clone neuf ou une CI
@ -433,20 +418,8 @@ dépôt (ni maintenant, ni si le repo devient public un jour).
- **l'historique a été nettoyé avant le premier push** : les premiers commits
embarquaient les valeurs (tests + doc) → `git filter-branch --tree-filter` avec
un script d'anonymisation (timestamps décalés de +30 j, valeurs perturbées dans
la prose des docs), tags réécrits, refs purgeées.
⚠️ **Lint de dette connue (constat v1.3.3)** : le scanner travaillait sur une liste
de motifs figée ; depuis que `local-test-data/` s'enrichit de NOUVEAUX exports
(ex. backup-v1.3.1.json ajouté à la v1.3.2), la ré-extraction des valeurs fait
que le scan `--history` détecte des fragments JSON de labs **encore présents
dans l'historique des révisions v1.1.0 → v1.2.3** (tests `RegressionUserCase{,2}`
data-driven AVANT la migration fichiers gitignorés ; fragments de la forme
« value » + timestamp). Amplitude : quelques valeurs numériques de labs — les
mêmes ordres de grandeur figurent déjà en PROSE dans les docs publiés (§14 #23 ,
CHANGELOG 1.1.0). Le working tree est propre. **Décision utilisateur (6 sept.
2026)** : purge complète de l'historique NON relancée (les révisions sont déjà
répliquées sur les deux Gitea, aucune fuite nouvelle par push) ; la dette
(réécriture d'historique + re-tags + force-push des 2 remotes + re-publish des
releases) reste ouverte et doit être re-décisionnée si le dépôt passe PUBLIC.
la prose des docs), tags réécrits, refs purgeées. `git grep` sur **toutes** les
révisions ne trouve aucune donnée réelle.
- ** `LabsGroupingTest` ** (4) : v1.2.2 — regroupement de l'écran Analyses (paire E2+T
même timestamp ; timestamps différents séparés ; tri E2 avant T avant autres ;
ordre chronologique décroissant).
@ -501,12 +474,7 @@ l'entier PAIR !), début = prochaine occurrence à l'heure de rappel (ou 12:00).
L'id est stocké sur le traitement (`calendarEventId` , Room v3) ; au save :
supprimer + recréer (fiable) si changé, supprimer si désactivé. Permissions
WRITE_CALENDAR + READ_CALENDAR demandées à l'activation du switch.
Manifest : `POST_NOTIFICATIONS` , `SCHEDULE_EXACT_ALARM` , `RECEIVE_BOOT_COMPLETED` ,
`VIBRATE` , `WRITE_CALENDAR` , `READ_CALENDAR` .
⚠️ **Permissions agenda (v1.3.3)** : WRITE_CALENDAR + READ_CALENDAR ont été
ajoutées au manifest seulement en v1.3.3 — elles étaient documentées ici
depuis v1.3.0 mais **jamais déclarées dans le XML** (bug #44 , §14) : tout
jeton « le manifest contient X » doit être VÉRIFIÉ dans le fichier réel.
Manifest : `POST_NOTIFICATIONS` , `SCHEDULE_EXACT_ALARM` , `RECEIVE_BOOT_COMPLETED` , `VIBRATE` , `WRITE_CALENDAR` , `READ_CALENDAR` .
Sur la montre : remontée par Gadgetbridge **ou** Huawei Health (cf §17).
## 10. UI & navigation
@ -767,18 +735,12 @@ Historique complet — **à ne pas réintroduire** (utile pour diff/revert) :
(openOutputStream "wt") + journalisation AppLog à chaque étape.
Leçon : quand un pattern d'IO marche (export JSON), le réutiliser
tel quel plutôt que d'en écrire un nouveau.
38. **Piège lexicographique de tags/versions** (v1.3.0, publication
41. **Comparaison lexicographique de tags/versions** (v1.3.0, publication
farewell) : `tag >= "v1.2.5"` en comparaison de CHAÎNES fait `v1.2.10 <
v1.2.5` ("1" < "5") → la boucle de publication a sauté v1.2.10. →
toujours comparer les versions STRUCTURÉEMENT (tuple numérique, cf
ChangelogHelper.isVersionNewer) ; même famille que le bug #22 (casse
EEn) : identifiants « presque pareils ». (Numéro corrigé en v1.3.3 :
l'entrée était dupliquée sous « #41 » alors que §2/§16 référencent #38 .)
43. **Course entre uploads rapprochés** (v1.2.6→v1.3.2, confirmé 4 fois) :
publier les 2 APK par DEUX INVOCATIONS rapprochées de gitea-release.py
→ le second upload écrase le premier (APK release disparu de la
release). → `scripts/publish-release.py` : UNE invocation fait purge +
upload des 2 APK + vérification PAR TÉLÉCHARGEMENT de chacun (§16.bis).
EEn) : identifiants « presque pareils ».
36. **Sens des boutons de zoom inversé** (v1.2.10, retour utilisateur) :
le « + » dézoomait (fenêtre plus longue) et le « − » zoomait — contre
la convention carte. → « + » = zoom avant (fenêtre courte), « − » =
@ -802,41 +764,6 @@ Historique complet — **à ne pas réintroduire** (utile pour diff/revert) :
(chips, dropdown création, rappels) ; simulation et calibration reçoivent
TOUS les traitements. Épinglé par la régression n°3.
**Session v1.3.3 (audit de reprise de maintenance — session IA démarrant sans
aucun contexte, dans l'esprit de cette doc) :**
44. **Permissions agenda absentes du manifest** (v1.3.0 → v1.3.2) : la
fonctionnalité « événement d'agenda récurrent » ne pouvait JAMAIS
fonctionner — `WRITE_CALENDAR` /`READ_CALENDAR` n'étaient **pas déclarées
dans AndroidManifest.xml** (la demande limitée à l'exécution est refusée
d'office sans déclaration, et `CalendarEvents.ensureCalendar/upsert`
lèvent SecurityException). La doc §9 les listait pourtant depuis v1.3.0
→ leçon : « documenté » ≠ « implémenté » ; toute affirmation « le
manifest contient X » doit être vérifiée dans le XML réel (30 s de
`grep uses-permission` ). Les fixes v1.3.1/v1.3.2 du « toggle agenda »
réparaient la logique UI mais pas la cause racine. FIX : déclaration +
commentaire dans le manifest.
45. **Export des logs plantait TOUJOURS l'app** (le « fix » v1.3.2, #41
plus haut, était insuffisant) : l'IO restait réimplémentée inline
(leçon du #41 : réutiliser BackupManager — PAS appliquée) et 3 appels
`AppLog.log` du callback étaient HORS try/catch — or une exception non
interceptée dans une coroutine à scope racine (`CoroutineScope
(Dispatchers.IO)`) remonte au handler de la thread = crash du process.
FIX : (a) l'export réutilise `BackupManager.writeBackup` TEL QUEL —
le même code path que l'export JSON qui marche sur le Pixel 9 /e/OS ;
(b) tout le callback est gardé ; (c) `AppLog.log` est fail-safe
(l'IO fichier est avalée dans le wrapper — une erreur disque ne doit
jamais tuer l'app pour une ligne de journal) ; (d) feedback visible
succès/échec + `logLineCount` rafraîchi (sur Main).
46. **Bump de version jamais commité** (v1.3.0 → v1.3.2) : les tags
v1.3.0/v1.3.1/v1.3.2 contenaient TOUS `versionCode = 14`
/ `versionName = "1.3.0"` → `BuildConfig.VERSION_NAME` était faux dans
les APK publiés (Paramètres, titre du dialog « Nouveautés »,
comparaison `isVersionNewer` ). La checklist §16 étape 1 existait mais
n'a jamais été appliquée. FIX : versionCode 17 / « 1.3.3 » commité
AVANT le tag ; le bump fait partie du commit de release (jamais un
état local non commité au moment du build).
**Leçons** : (a) ne jamais croire un build « probablement bon » sans l'avoir lancé ;
(b) les tests sémantiques attrapent ce que la compilation ne voit pas ; (c) se méfier des
constantes stdlib « de mémoire » (`ln2` ), des mélanges Float/Double, et des APIs M3
@ -846,16 +773,13 @@ synthétiques ratent — mais garde ces données **hors du dépôt** (§8.bis) ;
(e) attention aux identifiants « presque pareils » entre sources (constantes app
vs clés d'asset) ; (f) après un échec de COMPILATION, jeter les résultats de tests
de la même passe (XML périmés) ; (g) tout script de réécriture d'historique doit
être idempotent ; (h) dans un KDoc, `/**` imbrique ; (i) quand un pattern d'IO
marche (export JSON), le réutiliser TEL QUEL — une réimplémentation « équivalente »
perd les garde-fous acquis à l'usage (cf #45 ) ; (j) vérifier les POSTULATS dans le
code réel, pas dans la doc (« le manifest contient WRITE_CALENDAR », cf #44 ) ;
(k) un bump de version non commité = métadonnées fausses dans les APK publiés
(cf #46 ) — le bump fait partie du commit de release ; (l) le scan de
confidentialité s'ADAPTE aux nouveaux exports (`local-test-data/` ) : se régénère
donc à CHECK chaque push — un historique « propre hier » peut devenir hit dès
qu'un nouvel export introduit des motifs qui collent ; juger les hits avec
l'utilisatrice (cf dette documentée §8.bis).
être idempotent ; (h) dans un KDoc, `/**` imbrique. (a) ne jamais croire un build « probablement bon » sans l'avoir lancé ;
(b) les tests sémantiques attrapent ce que la compilation ne voit pas ; (c) se méfier des
constantes stdlib « de mémoire » (`ln2` ), des mélanges Float/Double, et des APIs M3
expérimentales sans `@OptIn` ; (d) **un test de régression sur les VRAIES données
utilisateur** (`RegressionUserCaseTest` = export JSON réel) attrape les bugs de
convention (casse, unités) que les tests synthétiques ratent ; (e) attention aux
identifiants « presque pareils » entre sources (constantes app vs clés d'asset).
## 15. Comment régénérer l'asset pk_profiles.json
@ -1051,6 +975,7 @@ Volontaires (v1) :
- Pas de ViewModel/DI (couplage UI↔repo via CompositionLocal)
- Modèle T empirique (non publié) — étiqueté estimation partout
- Import JSON = **écrasement** depuis v1.2.6 (mode fusion non implémenté)
- `fallbackToDestructiveMigration()` — à retirer à la migration v2 du schéma
- WorkManager déclaré non utilisé
- Profils par **tables** (pas par formule) : les D/k1– k3 de l'ODS ne sont pas consommés —
rétro-ingénierie des fits non tentée ; les tables sont exactes
@ -1070,17 +995,16 @@ Volontaires (v1) :
3. Mode « planifier les injections » (schedule récurrent → pré-remplir le log)
4. Import JSON : détection de doublons / mode fusion optionnel (l'écrasement est fait, v1.2.6)
5. Verrou biométrique (BiometricPrompt), widget, export CSV
6. Charts : tooltip au toucher (le pan est fait v1.2.0, **le zoom v1.2.9** ) ;
6. Charts : **zoom** + tooltip au toucher (le pan est fait, v1.2.0) ;
MaterialExpressiveTheme quand l'API passera publique (cf §3)
7. **Vrai keystore de distribution** (signature dédiée ≠ clé debug → nécessite
une migration : backup → désinstallation → installation signée → réimport)
Fait (à ne pas refaire) : pan du chart (v1.2.0), **zoom du chart (v1.2.9)** ,
pics/creux (v1.2.3), prévision par fréquence (v1.2.0), calibration par période
d'ester E2 **et** T (v1.2.1/v1.2.3), édition doses (v1.1.0) et labs (v1.2.2),
E2+T en une entrée (v1.2.2), migration Room v1→v2 sans fallback destructif
(v1.2.0), rooms v2→v3 (calendarEventId, v1.3.0), dépôt Gitea + releases APK
(push session).
Fait (à ne pas refaire) : pan du chart (v1.2.0), pics/creux (v1.2.3),
prévision par fréquence (v1.2.0), calibration par période d'ester E2 **et** T
(v1.2.1/v1.2.3), édition doses (v1.1.0) et labs (v1.2.2), E2+T en une entrée
(v1.2.2), migration Room v1→v2 sans fallback destructif (v1.2.0), dépôt Gitea
+ releases APK (push session).
8. Phase 2 montre : watchface `.hwt` custom, puis mini-app Lite Wearable (cf §17)
9. Retirer WorkManager ou l'utiliser (reschedule de sécurité quotidien)
@ -1121,25 +1045,6 @@ Sur le téléphone de test (à compléter par l'utilisatrice) :
- [ ] **v1.2.4** : passer un traitement à inactif → retiré de « Log rapide » et
du dropdown des nouvelles doses, rappel annulé, **mais sa simulation reste
sur le graphique** et la calibration couvre toujours ses périodes
- [ ] **v1.3.3** : **export des logs** → le gestionnaire de fichiers s'ouvre,
le .txt est écrit, un message « Logs de diagnostic exportés » s'affiche ;
cas d'échec (annulation du chooser = silencieux, échec d'écriture =
message d'erreur, l'app ne doit JAMAIS planter — bug #45 , Pixel 9 /e/OS)
- [ ] **v1.3.3** : **version affichée** « 1.3.3 » dans Paramètres et en titre du
dialog « Nouveautés » (bug #46 : les APK v1.3.0– 1.3.2 affichaient 1.3.0) ;
au premier lancement de v1.3.3, le dialog liste 1.3.1→1.3.3 (dernière vue
mémorisée = « 1.3.0 » sur les anciens APK)
- [ ] **v1.3.3** : **événement d'agenda** : le switch demande la permission
agenda (cette fois le système doit vraiment montrer le dialogue de
permission — elle est désormais dans le manifest, bug #44 ) ; après
accord, l'événement récurrent apparaît dans l'app d'agenda du Pixel 9 ;
le smoke-test R8 de la checklist §16 doit aussi couvrir export/import
de backup + graphiques (réflexion Gson sous R8)
---
*Doc mise à jour le 6 sept. 2026 (v1.3.3) — build OK, 87/87 tests verts (44 sans les
données locales), dépôts Gitea (cloudyfy + farewell) avec releases APK, aucune donnée
de santé dans le dépôt ni l'historique. v1.3.3 = session de reprise de maintenance :
3 bugs racines corrigés (permissions agenda, export logs, bump de version) ; doc
raccourcie (DB v3, targetSdk 36, régression 3 = export v1.3.1, numéros #38/#43 rétablis,
staleness §19/§20 nettoyé).*
*Doc mise à jour le 6 sept. 2026 (v1.3.2) — build OK, 87/87 tests verts (44 sans les données locales), dépôts Gitea (cloudyfy + farewell) avec releases APK, aucune donnée de santé dans le dépôt ni l'historique.*