HormoneTrack-web/README.md
Siphonight a0245e72c2 Conteneurisation Docker + tests de release (container-test, mode --release)
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.
2026-09-08 21:07:23 +02:00

182 lines
8.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# HormoneTrack Web
> **La version navigateur d'HormoneTrack** — suivi de thérapie hormonale (THS)
> avec courbes estimées heure par heure, 100 % locale : **l'app tourne
> entièrement dans ton navigateur et tes données ne quittent jamais ta
> machine** (localStorage). Aucun compte, aucun serveur applicatif, aucune
> télémétrie.
>
> C'est le portage fidèle de l'**app Android** — les sauvegardes JSON sont
> **interchangeables dans les deux sens**. L'app Android vit dans son propre
> dépôt : [gitea.cloudyfy.fr/Siphonight/HormoneTrack](https://gitea.cloudyfy.fr/Siphonight/HormoneTrack)
> (README, docs et releases APK).
> La doc de développement : [docs/DEVELOPPEMENT.md](docs/DEVELOPPEMENT.md).
> **⚠️ Avertissement médical** : les courbes sont des **estimations
> pharmacocinétiques** à titre informatif — ce ne sont pas des mesures.
> Fie-toi toujours à tes analyses de sang et aux consignes de ton
> endocrinologue.
- **Statut** : web v1.4.10 — portage de l'Android v1.4.10 · **121 tests verts** ·
E2E navigateur ✅ · lint/i18n ✅
- **Journal des versions web** : [docs/CHANGELOG.md](docs/CHANGELOG.md)
- **Versionnage** : les versions web sont **alignées sur l'Android porté**
(web v1.4.10 = toutes les fonctionnalités de l'Android v1.4.10, sauf les
impossibilités structurelles du navigateur) — cf
[docs/DEVELOPPEMENT.md §3](docs/DEVELOPPEMENT.md).
---
## Démarrage rapide
### Avec le serveur de développement intégré
```bash
cd web
python3 scripts/serve.py # → http://127.0.0.1:8970/
```
Ouvre **http://127.0.0.1:8970/** dans ton navigateur. C'est tout.
- **Mode démo** (données de test, hook documenté) :
<http://127.0.0.1:8970/?demo=1> — charge 12 injections + 6 prises de sang
fictives si le stockage est vide, avec une bannière explicite.
- ⚠️ L'app doit être servie en HTTP : l'ouverture directe du `index.html`
en `file://` est bloquée par les navigateurs (modules ES).
### Avec n'importe quel serveur statique
Le dépôt est un site statique autonome — déploie-le tel quel
(nginx, caddy, Pages Gitea/GitLab, un NAS…) :
```bash
# nginx (extrait)
server {
root /var/www/hormonetrack; # = le contenu de ce dépôt
index index.html;
}
```
### Avec Docker (self-host NAS/VPS)
L'image ne fait **que servir les fichiers** : aucune donnée dedans, la vie
privée est identique (les données vivent dans les navigateurs). Build et
exécution :
```bash
docker compose up -d # build + run → http://<hôte>:8080/
docker compose up -d --build # rebuild après mise à jour du dépôt
```
- Image **non privilégiée** (`nginx-unprivileged`, utilisateur 101, port
8080), filesystem en **lecture seule** (composé prêt pour le NAS),
healthcheck intégré, gzip sur l'asset PK (550 Ko → ~150 Ko).
- Headers `no-cache` : une mise à jour d'image est prise en compte au
rechargement suivant, sans code périmé chez les clients.
- Validation en 3 commandes sur l'hôte qui a Docker (cf
[docs/DEVELOPPEMENT.md §10](docs/DEVELOPPEMENT.md)) : build, run, puis
`HRT_E2E_BASE=http://127.0.0.1:8080 node scripts/e2e.mjs` — le même test
E2E que le CI pilote le site SERVI PAR LE CONTENEUR.
Aucun build, aucune variable d'environnement, aucun composant serveur : les
données de chaque personne vivent **dans SON navigateur**, jamais sur la
machine qui sert les fichiers.
## Fonctionnalités (à l'image de l'app Android)
- **Courbes estimées heure par heure** : E2 (pg/mL) et T (ng/mL), vues
24 h / 7 j / 30 j, **zoom** (pinch, molette, boutons − / +, 6 h → 300 j,
échantillonnage adaptatif), **panoramique** (glisser droite = passé,
gauche = futur avec la prévision), **pics & creux** avec valeurs estimées.
- **Trois modèles PK superposables** : **Estrannaise** (tables du `.ods`),
**Transfem Science** (méta-analyse V3C, 7 esters), **WHSAH** (fit Mona,
6 esters) — toggles indépendants, calibrés séparément.
- **Modèle Bateman** paramétrable pour gel, patch et voie orale.
- **Simulation prévisionnelle** (Posologie) : projection des doses à venir,
extension de fenêtre sans saut, horizon jusqu'à 1 an, marqueurs de doses.
- **Log des doses** avec override d'ester par injection, édition, Δ jours
entre doses, temps sous THS.
- **Analyses de sang** E2 + T en une entrée, unités T multiples
(ng/mL, ng/dL, ng/L, nmol/L).
- **Calibration** par période d'ester et **par modèle affiché**
(auto-calibration optionnelle, désactivée par défaut + bouton manuel).
- **Seuils d'alerte** configurables (E2/T haut/bas) avec notification
navigateur et anti-spam.
- **Rappels** via les notifications du navigateur (actions « Loguer
maintenant » / « Reporter 1 h ») — tant que la page est ouverte, cf
[limites](#limites-vs-lapp-android).
- **Sauvegarde/Restauration JSON** compatible Android (import en mode
écrasement, réglages transportés).
- **Logs de diagnostic** exportables (Paramètres) pour le debug à distance.
- **FR + EN** (langue par app, indépendante du système), dialog
« Nouveautés » après mise à jour.
## Vie privée
- **100 % local** : les données (traitements, doses, analyses, réglages)
vivent dans le **localStorage de ton navigateur**, sur TA machine. Le
serveur qui sert l'app ne voit rien, ne stocke rien.
- Les seules requêtes réseau sont des **lectures** de fichiers statiques
(l'asset des profils PK, le changelog) — aucune donnée personnelle
n'est jamais envoyée.
- Sauvegarde = un fichier JSON que tu stockes où tu veux.
- `?demo=1` charge des données FICTIVES clairement banniérées, jamais
automatiquement.
## Compatibilité avec l'app Android
| Flux | Support |
|---|---|
| Export Android → import web | ✅ (backups v1 et v2 — testés sur de vrais exports) |
| Export web → import Android | ✅ (même schéma BackupData v2, champs Gson identiques) |
| Modèles PK / courbes | ✅ mêmes maths, mêmes paramètres, mêmes pins de tests |
| Calibrations (facteurs, k) | ✅ recalculées identiquement depuis les labs |
Le schéma de backup est verrouillé par des tests des deux côtés
(`tests/backup.test.js` ↔ `BackupGsonTest.kt` Android) — toute divergence
métadonnée↔code ferait échouer la CI.
## Limites vs l'app Android
| Fonctionnalité | Android | Web |
|---|---|---|
| Rappels app fermée | ✅ (AlarmManager) | ❌ page ouverte uniquement (pas de scheduler système) |
| Événements d'agenda récurrents | ✅ | ❌ impossible dans un navigateur (pas de CalendarProvider) |
| Montre (Gadgetbridge) | ✅ notifications miroir | ❌ |
| Verrou biométrique / widget | roadmap | ❌ |
Ces limites sont structurelles (plateforme navigateur), pas des choix de
design — elles sont listées dans le dialog « Nouveautés » et la doc de dev.
## FAQ
**Mes données sont-elles visibles par le serveur qui héberge l'app ?**
Non. Le serveur ne fait que distribuer des fichiers statiques (comme des
images). Tes données vivent dans ton navigateur et n'en sortent jamais.
**Je change de navigateur / d'ordinateur, je fais quoi ?**
Paramètres → « Exporter JSON » sur la première machine, « Importer JSON »
sur la seconde. Le backup transporte tout (données + réglages).
**Puis-je utiliser Android et web en parallèle ?**
Oui — exporte/importe entre les deux. Attention : l'import est un
écrasement (comme sur Android), pas une fusion.
**Pourquoi l'app me demande-t-elle la permission « Notifications » ?**
Pour les rappels de dose. Refuser n'empêche rien d'autre (la bannière
in-app s'affiche quand même à l'heure du rappel, page ouverte).
## Doc de développement
Architecture, portage fichier par fichier, format du backup, processus de
test (121 tests Node + E2E navigateur), processus de push et pièges connus :
**[docs/DEVELOPPEMENT.md](docs/DEVELOPPEMENT.md)**.
## Licence
**GPL-3.0** — voir [LICENSE](LICENSE), cohérente avec l'app Android
et l'écosystème Gadgetbridge. Les modèles PK appartiennent à leurs autrices
respectives ([Estrannaise](https://estrannaise.github.io/),
[Transfem Science](https://transfemscience.org),
[WHSAH Collective via Mona](https://github.com/mona-hrt/mona)).