trainlog/docs/design/app_shell_v1/proposal.md

887 lines
64 KiB
Markdown
Raw Permalink 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.

# APP_SHELL_V1 — proposition soumise au design gate
Date : 10 septembre 2026. Baseline inspectée : `7cb4996`, après
`TRAINING_KNOWLEDGE_V1=PASS` et `SESSION_GENERATOR_V1=PASS`.
Le dépôt était propre à l'ouverture. **Design seulement ; aucune autorisation
d'implémentation n'est déduite de ce document.** Ce dossier est une proposition,
pas une description de fonctionnalités déjà livrées ni un contrat FROZEN.
Les [maquettes Android](android_mockups.html) sont consultables hors ligne,
avec un [aperçu PNG](android_overview.png). Les [maquettes TUI en couleur](tui_mockups.html)
et leur [version texte](tui_mockups.txt) donnent les quatre grilles exactes et les
overlays compacts. Les données affichées sont fictives, uniquement dans ces
documents. Aucun catalogue ni aucune base utilisateur n'est modifié.
## 1. Décision proposée
Un même classement de l'information, deux interactions natives :
- Android : drawer modal de premier niveau, pages de section et navigation
vers les détails. Touches d'action de 48 dp minimum, contenu compact et défilant.
- TUI : shell persistant Notcurses ; navigation latérale quand la surface est
suffisante, panneau de navigation temporaire dans les petits terminaux.
- Un titre et un emplacement stables pour chaque fonction. La sélection de la
rubrique est dérivée de la destination ; elle n'est pas un second état.
- Mocha/Lavender, typographie système sur Android, hiérarchie par poids et
alignement dans le terminal. Les surfaces regroupent ; les cadres ne décorent pas.
L'ancien **Historique** devient **Séances → Séances effectuées**.
**Programmer une séance** héberge le générateur existant : préparer une séance
unique, modifier sa proposition puis l'accepter dans l'éditeur ordinaire.
Ce libellé n'introduit ni calendrier, ni modèles réutilisables, ni programme
multi-séances, ni seconde séance planifiée persistante.
## 2. Ce que l'inspection a établi
| Sujet | Implémentation constatée | Conséquence pour le shell |
|---|---|---|
| Android navigation | `TrainlogApp.kt:21` : enum de huit écrans ; `remember`, cible de retour particulière pour la création d'exercice, ID de séance sélectionné ; `BackHandler` revient souvent à Accueil | Introduire un propriétaire unique de routes et des retours hiérarchiques/caller-aware |
| Android cadre | `TrainlogComponents.kt:39` : colonne entièrement défilante, bannière comprise ; composants Foundation faits maison | Extraire un vrai shell fixe ; une seule zone défilante par écran |
| Android thème | `TrainlogTheme.kt` : accent Teal `#94e2d5`, muted Blue, toute la police monospace | Passage explicite à Lavender et à une hiérarchie sans-serif ; conserver les rôles métier |
| Android dépendances | BOM Compose `2026.08.00`, activity-compose `1.13.0`, Foundation/UI ; pas Material 3 ni navigation-compose ni bibliothèque d'icônes | L'ajout de Material 3 est un choix de la future implémentation, pas une dépendance supposée présente |
| TUI structure | `tui.c` contient environ 13 000 lignes, des boucles d'écran imbriquées et des états locaux | Extraction progressive des contrôleurs ; une seule boucle d'événements finale |
| TUI surfaces | `terminal.c:14` possède le contexte et le plan standard ; `TrainlogPanel` est une vue de coordonnées et son commit est vide | Les panneaux actuels ne peuvent pas servir de shell persistant sans changer l'adaptateur |
| TUI état terminal | `tui.c:63` utilise un pointeur file-static pendant `trainlog_tui_run()` | Remplacer cet accès implicite par un contexte d'application passé explicitement |
| Notcurses | `pkg-config notcurses-core` et `/usr/include/notcurses/version.h` : **3.0.17**, liaison `-lnotcurses-core` | Sélection d'API fondée sur ce header, sans mise à niveau nécessaire |
| Équipements | Android : choix/création dans la saisie ; TUI : catalogue autonome. Création/liste/résolution disponibles, modification de définition absente | Ajouter le point d'entrée Android en réutilisant les opérations existantes ; ne pas inventer un éditeur de définition |
| Persistance | Desktop et Android v11 dans le code et `docs/current_state.md` ; brouillon Android durable, brouillon TUI en mémoire | Le shell ne crée aucune migration ; ne pas promettre une reprise TUI après arrêt du processus |
Le texte desktop v9 de `AGENTS.md` §8 est un état ancien par rapport au code
et aux documents actuels v11. La proposition ne résout pas cet écart par une
modification de schéma. La formule « un plan standard » de `docs/tui.md`
décrit le backend existant ; les plans enfants proposés conservent un seul
contexte et un seul plan standard. Les contrats produit et de format restent
intacts. La nouvelle séquence demandée ici sera synchronisée dans la roadmap
lors de l'implémentation approuvée, sans annoncer le shell comme déjà livré.
## 3. Architecture de l'information finale
Légende : **V1** = exposé par la future implémentation APP_SHELL_V1 ;
**existant PC** = fonction déjà disponible seulement sur desktop ;
**futur** = emplacement réservé, absent des menus V1 tant que non implémenté.
```text
Accueil V1
Séances V1 : page de section
Séance en cours V1 : état/reprise
Programmer une séance V1 : générateur existant
Nouvelle séance manuelle V1 : action
Séances effectuées V1 : ancien Historique
Détail d'une séance
Exercices / occurrences / séries / activité / MAX / plan
Modifier selon capacité actuelle
Reprendre ce Test max Android, stable ID
Exercices V1 : ouvre Catalogue
Catalogue V1
Fiche exercice V1
Zones du corps / connaissances et sources V1
Équipements compatibles / utilisés selon données disponibles
Performances / MAX liens vers vues existantes
Modifier V1, action contextuelle
Créer V1, action de catalogue
Équipements V1 : ouvre Catalogue
Catalogue V1, autonome aussi Android
Fiche équipement V1
Fourni / Personnel / Référence inconnue V1, selon résolution réelle
Modifier la définition futur, API métier absente
Créer V1, capacités actuelles
Statistiques V1 : page de section utile
Vue d'ensemble futur STATS_V1
Fréquence futur STATS_V1
Progression futur STATS_V1
Par exercice existant PC ; enrichi STATS_V1
Zones du corps futur STATS_V1
Mensurations V1
Relevés / Ajouter / Détail
Modifier / Graphiques / Analyse corporelle existant PC
Vue sur 12 mois existant PC, déplacée d'Accueil
Capacités / MAX V1, vues actuelles seulement
Derniers MAX locaux Android
Mesures / historique / graphe / charges de travail existant PC
Synchronisation V1
État / Lancer / Résultat / Diagnostic capacités actuelles
Journal / Détail d'une synchronisation existant PC
Dossier d'échange / Récupération Android
Paramètres V1, contenu par plateforme
Profil d'estimation corporelle existant PC
Dossier d'échange Android, grant SAF existant
```
**Créer et Modifier sont des actions, pas trois catalogues parallèles.**
On ouvre Exercices directement sur son catalogue ; « Créer » est visible en
tête. « Modifier » concerne l'exercice sélectionné ou sa fiche. Même logique
pour Équipements lorsque l'opération existe. « Modifier l'équipement de cette
occurrence » reste une action de séance : ce n'est pas la modification d'une
définition du catalogue.
Les sous-rubriques futures de Statistiques ne sont ni des boutons grisés, ni
des pages « bientôt disponible ». Le hub V1 montre uniquement Mensurations,
Capacités / MAX et, sur PC, Par exercice. STATS_V1 ajoutera des destinations
en utilisant les mêmes routes de section et les mêmes composants. Les zones
actuelles restent pleinement visibles dans Exercices et Programmer ; aucune
analyse par zone n'est anticipée. Android conserve sa mission de capture et de
consultation locale ; réserver une route n'autorise pas une copie de toutes
les analyses desktop.
Sur Android, « Voir les derniers MAX » depuis une fiche exercice ouvre la
liste agrégée existante sous Statistiques → Capacités / MAX. Ce lien ne promet
pas une page MAX filtrée par exercice, qui reste du ressort de STATS_V1.
Sur TUI, le contexte d'exercice continue à ouvrir ses vues de performance/MAX
existantes ; depuis Statistiques, un sélecteur d'exercice fournit ce contexte.
Paramètres a une véritable première destination sur chaque plateforme.
Pas d'interrupteur de thème, notifications, unités ou police sans fonction
réelle. Le profil d'estimation et le grant SAF gardent leurs propriétaires
actuels ; les anciens accès contextuels pointent vers le même éditeur.
## 4. Inventaire exhaustif des capacités visibles et destination
Cette matrice distingue les plateformes : préserver n'exige pas d'inventer
sur Android une opération réservée aujourd'hui au PC.
| Capacité actuelle | Android | TUI | Destination / conservation |
|---|---|---|---|
| Reprise séance en cours | Brouillon durable, résumé, avertissements de récupération | Éditeur courant en mémoire | Accueil → Reprendre ; Séances → Séance en cours |
| Nouvelle séance, normal/Test max | Oui | Oui | Séances → Nouvelle séance manuelle → type |
| Ajouter/éditer/retirer une occurrence ; même exercice plusieurs fois | Oui | Oui | Séance en cours → occurrences distinctes par `entry_id` |
| Créer un exercice pendant la saisie | Oui, avec retour au formulaire | Oui, via sélecteur | Action « Créer un exercice » dans la sélection ; retour au même appelant |
| Séries réelles hétérogènes, charge par série, valeur absente/0, virgule décimale Android | Oui | Oui | Éditeur d'occurrence, valeurs réelles séparées du plan |
| SETS + DURATION ; CONTINUOUS + DURATION ; vitesse/distance si configurées | Oui | Oui | Même éditeur piloté par métadonnées, aucun set fictif |
| MAX explicite sans séries | Oui | Oui | Type Test max ; résultat d'occurrence |
| Abandon explicite / sauvegarde / erreurs de validation | Brouillon durable, abandon confirmé, finalisation atomique | Édition en mémoire, écriture finale transactionnelle | Actions contextuelles, diagnostic persistant et retour sans écrasement |
| Génération par zone, objectif, durée personnalisée/préréglée | Oui | Oui | Séances → Programmer une séance |
| Exposition récente, avertissement, couverture partielle/vide, raisons et provenance de charge | Oui | Oui | Paramètres/proposition/détail du générateur ; toutes les explications consultables |
| Proposition modifiable, supprimer/réordonner/régénérer/accepter/annuler | Oui ; édition dans l'aperçu | Aperçu puis édition ordinaire après acceptation | Même point d'entrée ; respecter les étapes réellement disponibles, pas de réécriture de politique |
| Liste des séances terminées, date/type, détail des actuals et du plan | Oui, historique local | Oui, historique canonique | Séances → Séances effectuées → Détail |
| Correction complète d'une séance persistée, retrait d'exercice, rollback | Non, pas d'éditeur général actuel | Oui | Détail → Modifier sur PC |
| Reprendre le même Test max terminé en gardant ID/date | Oui | Édition persistée existante | Détail du Test max ; respect du conflit de brouillon existant |
| Changer/retirer l'équipement d'une occurrence terminée | Oui | Oui via édition | Détail → occurrence → Équipement |
| Catalogue d'exercices, recherche préfixe normalisé | Oui | Oui | Exercices → Catalogue |
| Créer/éditer nom et zones d'exercice ; profil protégé si référencé | Oui | Nom/zones et création selon capacités actuelles | Créer / Fiche → Modifier ; IDs inchangés ; aucun renommage en masse |
| Zone primaire, secondaires, groupes descendants, Non renseignés | Oui | Oui | Catalogue : filtre et résumé ; Fiche : détail ; Éditeur : sélecteur existant |
| Connaissances exercice, confiance, références scientifiques, cas non résolu | Oui | Oui, vue défilante | Fiche → Connaissances ; affichage existant sans nouvelle inférence |
| Équipements compatibles et usages historiques d'un exercice | Connaissances / contexte existants | Relations du manifeste et usages historiques séparés | Fiche → Équipements ; ne pas présenter un usage passé comme une preuve de compatibilité |
| Meilleures performances ordinaires | Pas de page analytique dédiée | Oui | Statistiques → Par exercice ; lien `p` depuis fiche/catalogue |
| Derniers MAX explicites | Dans Historique | Vue MAX | Statistiques → Capacités / MAX ; lien dans Séances effectuées |
| MAX : historique, graphe, résultat récent/meilleur, arrondi 0,5/1/2,5/5 kg | Consultation locale simple | Oui | Capacités / MAX → exercice ; assistance inverse et absence de pourcentage conservées |
| Catalogue équipements fournis, recherche par nom/étiquette/alias, détail | Pendant saisie | Autonome | Équipements → Catalogue ; sélecteur partagé dans l'éditeur |
| Création équipement personnel | Nom simple, sémantique actuelle | Nom/étiquette/type/mode de charge | Catalogue → Créer et action dans sélecteur, sans élargir les contrats des formulaires |
| Équipement historique introuvable | Résolution selon données reçues | Référence inconnue explicite | Fiche/occurrence : « Référence inconnue » et identifiant lisible |
| Saisie de toutes les mensurations actuelles | Oui | Oui | Statistiques → Mensurations → Ajouter ; raccourci Accueil |
| Relevés corporels récents | Oui, résumés | Oui, sélection/détail/édition | Mensurations → Relevés |
| Correction relevé : identité/date/lien séance préservés | Non | Oui | Mensurations → Détail → Modifier |
| Tendances, normalisation multi-mesures, distinction gauche/droite | Non | Oui | Mensurations → Graphiques |
| Graphique mensuel 12 mois aujourd'hui sur Accueil | Non | Oui | Mensurations → Vue sur 12 mois, mêmes mois vides et même sélection du dernier relevé |
| Composition, tendances, proportions, symétrie et libellés d'estimation | Non | Oui | Mensurations → Analyse corporelle, deux pages actuelles |
| Taille et branche de formule du profil d'estimation | Non | Oui | Paramètres → Profil d'estimation ; raccourci `p` conservé dans l'analyse |
| USB/MTP appareil, stockage, rafraîchir | Côté PC | Oui | Synchronisation → État |
| Android→PC, PC→Android, bidirectionnel et confirmation exacte | Requête Android bidirectionnelle | Trois actions directes | Synchronisation ; aucun nouveau protocole ni menu de mode intermédiaire |
| Snapshot automatique, import catalogue automatique, effets après sauvegarde | Oui | Import/export par moteur | Mêmes services et mêmes déclencheurs ; pas de relance à chaque navigation |
| Autoriser/changer dossier SAF, relire catalogue PC | Oui | Sans objet | Paramètres → Dossier d'échange ; liens directs dans Synchronisation |
| Requête en attente, reçu, diagnostic de résultat | Oui | Moteur/daemon | Synchronisation ; badge global seulement s'il reflète un état connu |
| Journal structuré, direction, détail, erreurs/conflits | Reçu courant | Oui | Synchronisation → Journal → Détail |
| UTF-8, clavier, petits terminaux, resize, aide et raccourcis | Touch/TalkBack/clavier matériel | Oui | Contrats transversaux du shell |
L'audit d'implémentation devra parcourir cette matrice ligne par ligne. Les
anciens écrans n'ont pas tous la même profondeur de fonctions : la proposition
ne promet pas une parité métier qui n'existe pas.
## 5. Android : drawer, pages de section, retour
Retenir **ModalNavigationDrawer + ModalDrawerSheet + NavigationDrawerItem**,
avec `Scaffold` et barre supérieure Material 3. La dépendance `material3`
sera ajoutée en s'alignant sur le BOM déjà utilisé. Les composants officiels
prennent en charge le drawer modal ; le choix des hubs est une décision UX
de Trainlog. [Documentation Android du drawer](https://developer.android.com/develop/ui/compose/components/drawer).
Le drawer contient sept lignes : Accueil, Séances, Exercices, Équipements,
Statistiques, Synchronisation, Paramètres. Pas de sous-arbre déroulant : les
longs libellés et les rubriques futures allongeraient inutilement le parcours
sur téléphone. Chaque ligne a un pictogramme, un libellé et une zone tactile
complète ; la rubrique active porte un fond Surface 0, une marque latérale
Lavender et l'état sémantique sélectionné. Sous Séances, un petit texte peut
indiquer « Séance en cours » si elle existe ; ce texte n'est pas un second lien.
La sélection d'une rubrique ouvre sa racine et ferme le drawer. Réappuyer sur
la rubrique courante revient à sa racine, avec le même garde de formulaire si
nécessaire. Les listes mémorisent recherche, filtre et position. Pas de piles
de navigation indépendantes par item de drawer.
Aux racines : `☰ TRAINLOG` et un titre de page normal dans le contenu.
Dans un détail : flèche Retour et titre court dans la barre, contexte de
rubrique en sous-texte ; le menu secondaire contient une action « Navigation »
pour changer de rubrique sans empiler des retours. Le drawer demeure le même.
Pas de hamburger et flèche Retour concurrents dans le même emplacement.
Ordre de Retour : IME si ouvert → dialogue/feuille → drawer → route appelante
→ hub/racine → Accueil → comportement Android normal. L'éditeur de brouillon
Android conserve ses données via le repository ; Retour ne supprime rien.
Les formulaires non durables et la proposition non acceptée demandent de
confirmer une sortie qui perdrait les modifications. L'ouverture du drawer
seule ne quitte pas le formulaire.
La création inline transporte une intention de retour avec l'ID de l'occurrence
ou du formulaire appelant. Après création, l'exercice reste sélectionnable et
le même éditeur reprend ; aucun second brouillon et aucun retour forcé Accueil.
## 6. Android : cadre et composants
```text
TrainlogTheme (MaterialTheme + rôles complémentaires)
AndroidAppShell
ModalNavigationDrawer
Scaffold
TopAppBar fixe, contexte/navigation/actions courtes
ScreenHost une seule destination, insets appliqués une fois
SectionHeading titre 22 sp, contexte court
SearchAndFilters si liste
LazyColumn / Form contenu défilant
ContextActionBar seulement si formulaire/aperçu, au-dessus IME
SnackbarHost feedback ponctuel non critique
Dialog / ModalBottomSheet garde de sortie, confirmation, choix borné
```
Un écran fournit titre, filiation et liste d'actions sémantiques. Il ne
redessine ni bannière globale ni bouton Retour géant dans son contenu.
Un seul bouton principal rempli par contexte : Reprendre, Générer, Accepter
la proposition ou Terminer la séance. Les actions secondaires sont des
boutons texte ; les actions par ligne ont un menu accessible « Actions pour… ».
Les suppressions ne dominent pas Accueil : elles vivent dans l'éditeur/menu,
avec confirmation. Les avertissements importants restent dans le contenu et
près de l'action concernée ; un snackbar ne suffit pas pour une erreur de
persistance ou un avertissement du générateur.
Le formulaire de séance distingue clairement : **Exercice**, **Équipement**,
**Objectif prévu**, **Séries réalisées**. Une proposition acceptée affiche zéro
série réelle tant que l'utilisateur n'en a pas enregistré. Les nombres et les
unités sont alignés ; la cible n'est pas un placeholder qui pourrait être
enregistré comme une valeur réelle.
Les lignes du catalogue utilisent titre + contexte court, pas une grande
carte par exercice. Les connaissances détaillées sont une route lisible ou
une feuille défilante ; la confiance et les sources restent présentes.
## 7. Android : typographie, icônes, tailles
| Rôle | Proposition Android | Usage |
|---|---|---|
| Titre application | Sans-serif système 18 sp / 24, semibold | Barre TRAINLOG |
| Titre écran | 22 sp / 28, semibold | Séances, Exercices ; pas de titre géant |
| Titre section | 16 sp / 22, semibold | Dernière séance, Séries réalisées |
| Texte courant | 16 sp / 24 | Libellés, explications et champs |
| Texte secondaire | 14 sp / 20 | Équipement, date, contexte ; jamais erreur essentielle en 12 sp |
| Valeur importante | 24 sp / 30, medium | Un MAX isolé, durée estimée |
| Valeurs alignées | 16 sp / 24, chiffres tabulaires si pris en charge | kg, répétitions, durée, colonnes |
Utiliser la famille Android par défaut ; tenter `fontFeatureSettings="tnum"`
dans les valeurs, puis réserver `FontFamily.Monospace` aux cellules numériques
si la police système ne fournit pas l'alignement attendu. Pas de téléchargement
de police ni nouvelle dépendance typographique. Le texte suit `fontScale` ;
une maquette HTML système ne prétend pas reproduire exactement le moteur Compose.
Cibles tactiles **48 × 48 dp minimum**, ce qui dépasse la demande d'environ
44 dp et suit le minimum Android. Ne pas compacter les cibles pour faire entrer
une ligne ; empiler les actions. Vérifier TalkBack, ordre de lecture, intitulés
de suppression par série/occurrence, annonces d'erreur et absence de doubles
descriptions des icônes décoratives. [Accessibilité Compose](https://developer.android.com/develop/ui/compose/accessibility/api-defaults).
Il n'existe actuellement que les vecteurs du lanceur. Ajouter une petite
sélection de **Material Symbols en VectorDrawable XML**, rendus par `Icon` /
`painterResource`, même épaisseur et taille optique 24 dp. Pas de police
d'icônes ni de gros paquet `material-icons-extended`. Cette forme de ressource
est recommandée par la documentation Android actuelle.
[Icônes Compose](https://developer.android.com/develop/ui/compose/graphics/images/material).
## 8. Android : adaptation aux téléphones
| Largeur utile | Comportement proposé |
|---|---|
| 320 dp | Marge 16 dp ; toutes les actions principales sur une colonne ; valeurs d'une série sur deux lignes si nécessaire ; drawer `min(360 dp, largeur - 56 dp)` = 264 dp |
| 360 dp | Marge 16 dp ; lignes de catalogue sur deux lignes ; éditeur rep/charge compact avec actions de ligne séparées ; drawer 304 dp |
| 393412 dp | Même architecture, davantage de texte visible ; pas de nouvelle colonne de navigation ; drawer 337356 dp |
| Paysage / fenêtre réduite | Hauteur défilante, barre d'action au-dessus de l'IME ; pas de cartes à hauteur fixe |
| ≥600 dp | V1 garde le drawer modal ; formulaire centré avec largeur max utile, listes utilisant la surface. Sidebar/rail persistant possible plus tard, pas requis V1 |
Les largeurs sont celles de la fenêtre disponible, pas des modèles de téléphone.
À 200 % de police, le drawer peut réduire sa marge de fond visible à 24 dp
pour laisser davantage de place aux libellés. Titres/actions peuvent passer
sur plusieurs lignes avec césure lisible des mots longs ; on ne
réduit pas le texte. La barre d'action grandit avec son contenu et sa hauteur
est déduite du viewport. Un seul propriétaire applique les insets du système
et de l'IME, pour éviter leur double ajout.
## 9. Accueil et hub Séances
Accueil répond dans cet ordre :
1. Séance en cours : type, nombre d'occurrences, action **Reprendre**.
2. Sans séance : action principale **Programmer une séance**, puis Nouvelle
séance manuelle. Avec séance active, la reprise est prioritaire.
3. Dernière séance terminée : date, type, nombre d'exercices, accès au détail.
4. Dernier MAX explicite s'il existe et si la lecture actuelle le permet ;
libellé « Dernier MAX enregistré », avec exercice/date/équipement, sans flèche
de progression ni qualification de record inventée.
5. Accès rapide Mensurations et état de synchronisation connu.
Le compteur hebdomadaire et une synthèse de progression sont différés à STATS_V1 :
ils impliqueraient de figer de nouveaux calculs/calendriers sans nécessité
pour le shell. Le graphe corporel 12 mois actuel du PC reste accessible sous
Mensurations avec un lien depuis Accueil. Il ne disparaît pas du produit.
Le hub Séances présente quatre lignes/action groupées. Sans séance active,
« Aucune séance en cours » est du texte d'état, suivi de Programmer et Nouvelle
séance manuelle réellement actives. Avec une séance, « Nouvelle séance manuelle »
ouvre la reprise existante avec une explication ; aucune action n'écrase le
brouillon. Programmer peut ouvrir ses paramètres/aperçu ; l'acceptation
rencontre le conflit existant et propose Reprendre ou revenir à l'aperçu.
Un abandon du brouillon se fait explicitement dans son éditeur.
Pas de vignette « séance planifiée » fabriquée. La proposition non acceptée est
temporaire ; après acceptation, il s'agit de la séance en cours ordinaire.
## 10. TUI : architecture AppShell
```text
trainlog_tui_run(database empruntée)
TrainlogAppContext durée d'un run, pas global
NavigationState route + pile de retour bornée
SessionController brouillon actif en mémoire
ScreenController actif données / sélection / formulaire
FocusManager une cible logique
ActionModel actions disponibles du contexte
OverlayStack transactions UI temporaires
TrainlogTerminal seul propriétaire Notcurses
stdplane racine empruntée au contexte
header plan persistant
sidebar plan persistant si layout large
contentHost plan persistant
viewport(s) du contrôleur plans bornés aux rectangles alloués
footer plan persistant, deux lignes
overlayRoot(s) plans temporaires + widgets dédiés
```
Les plans persistants survivent aux changements d'écran. Un passage compact
peut désallouer la sidebar seule ; son état logique de navigation n'est pas
perdu. Le contentHost survit ; les vues filles sont démontées/remontées ou
redimensionnées. Un overlay ne détruit pas le contrôleur ou le contenu dessous.
Séparer les futurs fichiers par responsabilité : `app_shell.c`,
`navigation.c`, `focus.c`, `actions.c`, `layout.c`, `overlays.c`,
`components/list_view.c`, `components/search_field.c`, `components/form.c`,
`screens/{home,sessions,exercises,equipment,body,max,sync}.c`.
Ces noms sont une proposition interne, pas une nouvelle API publique.
`terminal.c` demeure la frontière Notcurses ; core/repository/sync ne voient
aucun plan, widget ni constante NCKEY.
Le contrôleur d'écran expose conceptuellement `enter`, `handle_action`,
`layout`, `render`, `leave`, `destroy` ; il retourne une intention sémantique
(`OpenRoute`, `OpenOverlay`, `Save`, `Back`), jamais un appel récursif à la
boucle d'un autre écran. Une seule boucle lit l'entrée et déclenche le rendu
composé. `render` ne fait pas de SQL et ne mute pas le domaine. Les lectures
sont effectuées par le contrôleur/data adapter avant rendu ; un résultat
immuable ou une erreur explicite est transmis à la vue.
## 11. API Notcurses sélectionnées et limites réelles
Source de signatures : `/usr/include/notcurses/notcurses.h` **3.0.17**.
Les pages web du projet sont complémentaires ; certaines synopsis HTML sont
mal formées, donc ne servent pas de déclaration C à recopier.
| Besoin | API disponible retenue | Discipline |
|---|---|---|
| Contexte | `notcurses_core_init`, `notcurses_stdplane`, `notcurses_render`, `notcurses_stop` | Un seul contexte, seul thread UI fait du rendu |
| Plans | `ncplane_create`, `ncplane_options` (`name`, `userptr`, `resizecb`), `ncplane_move_yx`, `ncplane_resize_simple` | Propriétaires explicites, rectangles contrôlés |
| Taille | `ncplane_dim_yx`, événement `NCKEY_RESIZE`, `notcurses_refresh` | Mesurer la géométrie courante, recalculer tout le layout avant rendu ; ne pas redimensionner stdplane manuellement |
| Resize callback | `ncplane_set_resizecb` | Marque le layout invalide seulement ; aucun SQL, mutation ou rendu récursif dans callback |
| Z-order | `ncplane_move_above/below`, `ncplane_move_family_top` | Empiler une famille d'overlay entière, footer au-dessus de la zone de contenu |
| Libération | `ncplane_destroy`, `ncplane_family_destroy` | Libérer les widgets avant leur famille restante ; aucune double destruction de leur plan |
| Couleurs/styles | `ncchannels_set_fg_rgb8`, `ncchannels_set_bg_rgb8`, `ncplane_set_channels`, `ncplane_set_styles`, `ncplane_set_base` | Rôles sémantiques centralisés ; bases opaques pour footer/overlay |
| Texte Unicode | Fonctions de sortie UTF-8 `ncplane_putstr_yx`, largeur en cellules via utilitaires existants/utf8proc | Couper/envelopper aux graphèmes, pas par octets ; unité de layout = cellule |
| Champ de recherche | `ncreader_create`, `ncreader_offer_input`, `ncreader_contents`, `ncreader_clear`, `ncreader_destroy` | Plan dédié, une seule ligne, contenu borné avant insertion ; contrat détaillé ci-dessous |
| Choix court | `ncselector_create`, `ncselector_offer_input`, `ncselector_selected`, `ncselector_destroy` | Type de séance/objectif/tri/actions courts ; `maxdisplay` calculé ; pas de catalogue complet dans le widget |
| Défilement | Viewport logique de liste + rendu des lignes visibles ; `ncplane_scrollup`/`ncplane_set_scrolling` seulement pour une zone contrôlée si utile | Le scrolling physique n'est pas la pagination des données |
| Souris | `notcurses_mice_enable(..., NCMICE_BUTTON_EVENT)`, `ncplane_translate_abs`, `notcurses_mice_disable` | Click/roue utiles, pas de mouvement continu nécessaire ; mêmes actions que clavier |
| Progression | `ncprogbar_create`, `ncprogbar_set_progress`, `ncprogbar_destroy` disponibles | Utiliser seulement si un vrai total/progrès est fourni ; sinon étape textuelle réelle, aucun pourcentage simulé |
`ncselector` est adapté aux petits choix, avec son titre et footer facultatifs
désactivés pour économiser des lignes. Il peut redimensionner son plan. Le
wrapper vérifie les longueurs/hauteurs autorisées ; si le choix ne tient pas,
le composant liste plat prend le relais. Le widget prend son plan en charge,
y compris en cas d'échec de création.
[Contrat officiel ncselector 3.0.17](https://notcurses.com/notcurses_selector.3.html).
`ncreader` n'est pas un éditeur de formulaire métier. Le wrapper intercepte
Entrée, Tab, Échap, F6 et F7 avant `offer_input`. Home/End, flèches, suppression
et caractères imprimables restent des entrées locales du reader focalisé.
Le `ncinput` brut reste privé à l'adaptateur. Le wrapper refuse les
contrôles/sauts de ligne et les insertions dépassant la capacité du champ,
assemble correctement les caractères composés, restitue la sélection/caret
et libère la copie allouée par `ncreader_contents`. La recherche catalogue
réutilise le plafond actuel de 200 octets UTF-8, avec message explicite au
dépassement. Défilement horizontal permis **seulement sous cette borne** ;
aucune croissance illimitée verticale. `NCREADER_OPTION_NOCMDKEYS` évite les
raccourcis implicites incompatibles. Le curseur n'est visible que tant que
le champ possède le focus. La saisie UTF-8 complexe fait l'objet d'un test
précoce ; si le reader ne tient pas ce contrat, le champ Trainlog existant
est conservé et rendu dans un plan dédié. Ce repli est défini, pas une promesse
d'API inexistante. [Limites documentées du reader](https://notcurses.com/notcurses_reader.3.html).
`ncmultiselector`, `ncmenu`, `ncreel`, `nctree` sont disponibles mais non retenus
pour le shell V1 : le modèle de zones primaire/secondaires existant ne doit pas
être remplacé par de simples cases indépendantes ; menu/reel/tree ajouteraient
une navigation ou des surfaces inutiles. Il n'existe pas de DataGrid métier
prêt à l'emploi dans les widgets inspectés. La table interactive Trainlog
compose de vrais viewports/headers avec sélection et événements, plutôt qu'un
texte statique ou un plan par ligne de toute la base.
**Le rattachement d'un plan enfant n'est pas un masque de clipping.** Chaque
plan de contenu doit rester dans son rectangle alloué ; les lignes sont
rendus dans le viewport visible, sans enfant déporté sous le footer. La hauteur
de liste ne devient jamais la hauteur totale du jeu de données. L'application
gère son OverlayStack : ce n'est pas une classe Notcurses inventée.
[Primitives de plans](https://notcurses.com/notcurses_plane.3.html).
## 12. Focus et dispatch TUI
Le focus est l'une des cibles : Navigation, Recherche, Liste/Table, Éditeur,
Actions du footer ou Overlay actif. Le header n'est pas un arrêt supplémentaire
sauf son contrôle Navigation en compact. Une seule cible reçoit une entrée.
Le focus visuel : `>` + libellé/ligne en Lavender et fond Surface 0. La
rubrique active porte aussi des crochets dans les maquettes texte et sa
sous-rubrique un point, distincts du `>` de focus. Une
sélection gardée dans une liste non focalisée conserve sa marque et un fond
neutre, mais pas le même accent que le composant actif. Un champ focalisé a
curseur et indication de champ ; une rubrique active du menu reste repérable
indépendamment du focus. Aucune couleur seule ne porte ces états.
Ordre du dispatch : resize/événements système → overlay supérieur → saisie
locale → actions du composant → raccourcis de route disponibles → action de
shell. PRESS/UNKNOWN et REPEAT volontaire restent une action logique ; RELEASE
est ignoré. Une entrée consommée n'est jamais réémise. Les lettres/chiffres
saisis dans un champ ne déclenchent pas de navigation.
Tab/Shift-Tab parcourt les composants visibles : navigation → recherche/filtre
→ contenu → actions du footer → navigation. En compact, le bouton Navigation
remplace la sidebar dans cette boucle. Dans un formulaire, Tab parcourt ses
champs puis ses actions et ressort ; aucun piège. Dans la table réelle existante,
Tab conserve sa fonction de cellule, et F6/F7 permettent de sortir directement.
Les cibles non disponibles sont absentes du parcours.
Après fermeture d'overlay, restaurer la cible logique et l'ID sélectionné,
pas un pointeur de plan détruit. Après resize, garder le même élément visible
et convertir le focus sidebar en contrôle Navigation si la sidebar disparaît.
## 13. Clavier et migration des raccourcis
Les anciens chiffres sont des **alias de destination**, pas les indices du
nouveau menu. Ils gardent donc leur sens même si Statistiques s'insère avant
Synchronisation. Les libellés du menu n'ont pas une numérotation trompeuse.
| Contexte / ancien raccourci | Nouveau comportement | Motif / compatibilité |
|---|---|---|
| `0` / Home : Accueil | Inchangé hors champ/overlay | Home dans un champ continue à déplacer le caret |
| `1` / F1 : nouvelle séance | Reprendre si active, sinon nouvelle manuelle | Évite de perdre le travail ; ne devient pas l'index de Séances |
| `2` / F2 : Historique | Séances → Séances effectuées | Même données, nouveau classement |
| `3` / F3 : Exercices | Catalogue Exercices | Inchangé |
| `4` / F4 : Équipements | Catalogue Équipements | Inchangé |
| `5` / F5 : Corps | Statistiques → Mensurations | Même données et actions |
| `6` : Sync | Synchronisation | Inchangé, F6 n'est pas un alias de 6 |
| `g` depuis Accueil | Programmer une séance | Même générateur ; ajouté au hub Séances seulement |
| Nouveau F6 | Navigation : focus sidebar ou ouvre panneau compact | Aucun ancien F6 ; aussi accessible comme contrôle textuel avec Tab |
| Nouveau F7 | Toutes les actions du contexte, aide comprise | Garantit accès aux raccourcis qui ne tiennent pas dans le footer |
| Nouveau `?` hors saisie | Aide contextuelle + alias globaux | Pas F1, déjà utilisé pour une séance |
| Tab/Shift-Tab : navbar/contenu | Cycle de composants, sens inverse conservé | Extension ; table de séries conserve son Tab-cellule |
| Flèches / PgUp / PgDown | Sélection, déplacement cellule, défilement/page du composant focalisé | Aucun changement implicite de rubrique en lisant une liste |
| Entrée liste exercice | Fiche exercice | Aligné sur `screen_exercise_detail`, malgré le raccourci descriptif ancien de docs/tui |
| `p` catalogue/fiche exercice | Performances de cet exercice | Vue existante réutilisée dans Statistiques |
| `m` catalogue/fiche exercice | MAX de cet exercice | Distinct de la performance ordinaire |
| `k` fiche/connaissances | Ouvrir/fermer connaissances | Inchangé |
| `e` fiche exercice | Modifier nom/zones | Stable ID ; pas EXERCISE_NAMING en masse |
| `a` catalogue exercice | Créer exercice | Inchangé |
| `n` catalogue équipement | Créer équipement | Conservé ; `n` n'est jamais raccourci Navigation global |
| `/` exercices/équipements | Recherche éditable, filtre en direct | Entrée rend le focus aux résultats ; aucun enregistrement |
| `/` séances effectuées | Même champ de recherche | Ajout ; filtre explicite de la liste, pas moteur de recherche global |
| `z` catalogue exercices | Cycle de zones actuel | Filtre interactif visuel ajouté sans changer descendants/Non renseignés |
| `x` catalogue exercices | Effacer recherche + filtre de zone | Inchangé ; ne pas généraliser à un contexte destructeur |
| `x` sélecteur équipement | Aucun équipement pour l'occurrence | Inchangé, ne devient pas effacement global de filtre |
| `e` / Entrée séance en cours | Éditer occurrence/séries | Inchangé |
| `r` séance en cours | Remplacer occurrence sélectionnée | Inchangé ; identité/ordre selon service existant |
| `a`, `d` séance | Ajouter / retirer occurrence | Retrait confirmé, catalogue intact |
| `f` séance | Enregistrer/terminer via garde actuelle | Inchangé ; action libellée dans F7 et le footer |
| `q` éditeur courant | Abandon explicite avec confirmation | Alias préservé ; ne quitte pas tout le processus |
| Échap éditeur global, auparavant abandon | Retour au hub en conservant l'édition en mémoire | Changement explicite requis pour une navigation sûre ; abandon via q/action |
| `a`, `d`/Delete, Entrée dans table de séries | Ajouter, supprimer, éditer cellule | Inchangé, jamais de valeurs cibles copiées |
| `f`/`b`, Échap dans table | Quitter la table vers l'occurrence ; Échap cellule annule sa saisie | Priorité locale conservée |
| Générateur : Entrée / `a` accepte l'aperçu ; `q`/Échap annule | Même actions et gardes ; détail via action dédiée | L'acceptation ne devient pas silencieusement « ouvrir détail » |
| Avertissement génération `c`/Entrée, `z`, `q` | Continuer, autre zone, annuler | Inchangé |
| Zones : `p`, Espace, `n`, Entrée, Échap | Primaire, secondaire, Non renseigné, valider, annuler | Modèle métier existant conservé |
| Détail séance : `e`, `i`, flèches/PgUp/PgDown | Éditer, fiche équipement, parcourir occurrences/lignes | Inchangé |
| Corps : `a`, `e`, `v`, `g` | Ajouter, modifier, analyse, superposition globale | Déplacé sous Mensurations ; `g` n'y génère pas une séance |
| Analyse corporelle : `p`, gauche/droite | Profil d'estimation, changer page | Même éditeur via Paramètres et même page analytique |
| Vue MAX : `r` | Cycle d'arrondi actuel | Pas de nouveau calcul |
| Sync : `a`, `p`, `b`, `r` | Android→PC, PC→Android, bidirectionnel, actualiser | Priorité locale ; `b` ne signifie pas Retour sur Sync |
| Sync : `s` anciennement retiré | Reste sans action | Ne pas réactiver un contrat obsolète |
| Sync confirmation : Entrée / Échap | Lancer une fois / annuler sans opération | Inchangé |
| Enfant : `b`/Échap Retour | Retour appelant, sauf conflits locaux ci-dessus | Aide affiche le sens exact du contexte |
| `q` racine application | Quitter, avec garde si état en mémoire | Aucun arrêt depuis un champ par lettre q |
| Confirmation historique `1`/`0` | Alias confirmer/annuler dans la confirmation de retrait concernée | Navigation globale suspendue ; focus initial sur Annuler |
Pas de Ctrl+S obligatoire : les terminaux peuvent l'interpréter comme contrôle
de flux. Les actions explicites et `f` restent la voie fiable. Les touches de
fonction ajoutées seront traduites dans `TrainlogKey`, sans fuite de NCKEY.
Garde de sortie du générateur : avant production d'un aperçu, Échap revient
au hub Séances, avec la garde de formulaire si une configuration modifiée
serait perdue. Une fois l'aperçu produit, `q`/Échap, Retour Android, navigation
par drawer et sortie de l'application passent par la même garde. Seule l'action
explicite « Abandonner la proposition » l'efface ; « Conserver et revenir »
la suspend dans le contrôleur en mémoire. Cela inclut ses modifications et
l'état d'acquittement de l'avertissement. Aucune restauration après mort du
processus n'est promise pour cet aperçu.
## 14. Listes, tables et recherche
Un modèle commun de composant transporte : ID stable de sélection, ordre,
requête, filtres, début de viewport, nombres connus, état chargement/erreur.
La liste ne prend pas l'index courant comme identité. Après édition, filtrage
ou sync, conserver l'ID si visible, sinon choisir le voisin déterministe et
annoncer le changement.
Les tables ont un en-tête fixe, des colonnes numériques alignées, une ligne
sélectionnée, le compteur de position (`3 / 24` si total connu), des marqueurs
`↑ autres` / `↓ autres`. Le mode compact replie les colonnes secondaires sous
la ligne sélectionnée ou dans le détail, sans masquer une valeur métier.
Les noms longs sont élidés en liste avec `…` ; la fiche révèle tout le texte.
Pas de défilement horizontal indispensable à 72 colonnes.
Recherche : `/` focalise un vrai champ de la liste, créé si nécessaire. Le
filtrage se met à jour à chaque modification validée du texte ; les lectures
sont coalescées et aucun résultat ancien ne remplace une requête plus récente.
Entrée revient à la liste en gardant le filtre. **Échap avec texte efface la
requête et reste dans le champ ; Échap à vide ferme le champ.** Un petit `×`
Android fait la même chose. Le filtre de zone ne disparaît pas sur Échap ;
`x` garde sa fonction complète dans le catalogue d'exercices.
Sémantique conservée : exercices = préfixe normalisé + filtre de zone ;
équipements = noms/étiquettes/alias via recherche existante. Pour les séances,
V1 filtre les libellés effectivement affichés (date et type) ; recherche par
exercice se fait depuis sa fiche/performances existantes. Ne pas promettre une
recherche plein texte historique sans définition. Les tris V1 restent ceux
des vues existantes ; ne pas ajouter une flèche de tri sans ordre réellement
implémenté. Un choix de tri futur aura toujours un tie-break par identité et
n'altérera pas les contrats chronologiques scientifiques.
Ressources : seuls les éléments visibles sont matérialisés graphiquement.
Les pages de lecture sont bornées, avec indicateur explicite quand le total
n'est pas connu (`lignes 112 · suite disponible`, pas un faux total). Certaines
API actuelles n'offrent que capacité + nombre et certaines vues capent leur
chargement (équipements 256, sync 64). La migration doit conserver un diagnostic
de résultat partiel ; elle ne doit pas annoncer une pagination exhaustive
automatique fournie par ces APIs. Ajouter au besoin des accesseurs de lecture
paginée **étroitement bornés**, avec le même ordre/filtres et sans schéma ni
écriture, fait partie du travail de support UI à spécifier avant codage.
L'absence de page suivante ne doit pas être confondue avec un plafond local.
Les données scientifiques ne reçoivent aucun plafond global nouveau.
## 15. Overlays et modalités
Confirmation courte : plan flottant centré, une surface et une bordure discrète,
texte explicite, boutons Annuler/Confirmer. Un seul chemin de validation évite
un double appel. Aide, détails longs, recherche contextuelle et récupération :
overlay défilant ou route de détail selon le besoin ; pas de succession de
boîtes minuscules pour éditer une séance entière.
L'OverlayStack possède : type, état temporaire, plan(s)/widget(s), action de
retour, cible de focus à restaurer. La profondeur est bornée par les parcours
autorisés (trois niveaux suffisent : formulaire → confirmation → aide), jamais
un empilement illimité. Un quatrième niveau est remplacé par une navigation
dans le panneau courant ou refusé avec diagnostic ; aucun état n'est perdu.
Tant qu'un overlay est ouvert, sa saisie est exclusive. Échap ferme le niveau
supérieur ou demande confirmation si sa fermeture perd une édition. Un clic
sur le fond ne valide ni ne déclenche l'écran du dessous. La navigation
globale est suspendue, notamment `1`/`0` dans une confirmation. Le footer
montre **les actions de cet overlay** ; il reste physiquement visible.
Le dimming est limité à la surface centrale, pas une couche opaque sur le
footer. À 72×20, l'overlay peut occuper tout le rectangle central 72×16 avec
une ligne de titre, contenu défilant et actions accessibles en footer ; ce
n'est pas un nouveau plein écran qui détruit le précédent.
Sync déjà confirmée : afficher la direction et l'étape réellement connue.
Ne pas ajouter un bouton Annuler si le moteur ne garantit pas l'annulation.
Conserver son exécution/exclusion actuelles ; pas de worker pool ni de nouveau
scheduler pour animer une barre. Les événements de navigation ne déclenchent
aucun second run ; à la fin, relire la taille et afficher le résultat.
## 16. Responsive TUI et rectangles exacts
Toutes les coordonnées ci-dessous sont zéro-based. Header : lignes `0..1`.
Footer : les deux dernières lignes. Le rectangle central est toujours
`(y=2, x=0, h=H-4, w=W)`. Les marges sont internes, pas des cadres externes.
| Terminal | Mode | Navigation | Contenu utile | Overlay |
|---|---|---|---|---|
| **120×35** | Étendu | Sidebar 22 colonnes, x0..21 ; séparateur x22 ; section active développée | x23..119, 97×31 ; tableau + détail inférieur ou deux colonnes si chaque bloc tient | Centré, largeur ≤80 et hauteur ≤29, dans rectangle central ; fond intact |
| **100×30** | Standard | Sidebar 22 colonnes, premier niveau seulement | x23..99, 77×26 ; sous-navigation dans hub/breadcrumb ; détail pleine largeur | Largeur ≤74, hauteur ≤24 ; champs sur une colonne si nécessaire |
| **80×24** | Compact | Aucun espace réservé à gauche ; `F6 Navigation` ouvre panneau de 32 colonnes | 80×20 ; listes sans panneau détail latéral | Confirmation ~64×10 ; formulaire jusqu'à 78×20 ; contenu défilant |
| **72×20** | Minimum | Aucun rail d'icônes ; Navigation devient overlay central complet | 72×16 ; titre/contexte compact et lignes de liste ; footer de 2 lignes intact | Jusqu'à 72×16, titre et viewport ; actions dans footer partagé |
Règle déterministe : sidebar si **W≥100 et H≥26** ; sous-section développée si
**W≥120 et H≥32**. Entre ces seuils, conserver le mode moins chargé. Un
terminal très large mais bas utilise donc le compact. Pas de mise à l'échelle
aveugle d'un rectangle unique. La sidebar étendue ne développe que la rubrique
active ; ses items futurs sont absents et son propre viewport peut défiler.
En compact, les sept rubriques sont toutes nommées dans Navigation. Séances
ouvre son hub avec les quatre actions ; pas besoin de deviner une icône.
Le header affiche « Séances / Effectuées » et « F6 Navigation » ; un chemin
très long est abrégé au parent + titre, le détail restant accessible.
Sous 72×20 : état petit terminal, dimensions actuelles/minimum lisibles,
action Quitter accessible et garde d'état en mémoire si nécessaire. Aucun
write, abandon ou changement de route sur resize. Les surfaces non adaptées
ne sont pas rendues ; le contrôleur reste vivant. Dès retour à une taille
valide, reconstituer la géométrie, le focus, le défilement et les overlays.
## 17. Footer contextuel : contrat anti-régression
Le footer appartient **uniquement au shell**, dans un plan opaque et réservé.
Ni écran, ni scrolling, ni modal ne peut peindre sur ses deux lignes. Il est
recomposé après chaque changement de focus/route/overlay et à chaque resize.
Le rendu de contenu reçoit un rectangle qui l'exclut. Éviter les chaînes de
raccourcis tronquées par `%.*s` comme méthode de layout.
Ligne 1 : déplacement/validation et actions les plus utiles au focus.
Ligne 2 : Navigation, Actions, Aide et Retour/Quitter quand disponibles. Les
alias redondants peuvent rester dans F7/Aide ; l'action n'est jamais supprimée
car la ligne est courte. Les intitulés viennent du même registre que le
dispatch ; toute action affichée possède un handler et toute action disponible
apparaît au moins dans F7. Un contexte vide expose Retour/Navigation/Aide.
Exemples tenant à 72 colonnes :
```text
↑↓ Choisir Entrée Détail / Rechercher e Modifier
F6 Navigation F7 Actions ? Aide Échap Retour
↑↓ Série ←→ Cellule Entrée Modifier a Ajouter d Supprimer
f Terminer F6 Navigation F7 Actions Échap Retour
a Android→PC p PC→Android b Bidirectionnel
r Actualiser F6 Navigation F7 Actions Échap Retour
Tab Choisir Entrée Confirmer Échap Annuler
Confirmation : Retirer cet exercice de la séance
```
Les avertissements ne remplacent pas le footer : une ligne/bannière défilante
au-dessus les porte, avec un marqueur permanent et un accès au texte complet.
Tester le footer sur liste vide, nom long, erreur, IME/reader, confirmation,
retour d'overlay, changement de focus, et resize aller-retour aux quatre tailles.
## 18. Tokens Catppuccin et stratégie d'icônes
**Recommandation : tokens plateforme synchronisés et documentés**, avec une
table canonique de rôles très courte. V1 n'ajoute pas un moteur de thème ni un
parseur JSON au démarrage, et n'emploie pas les catalogues scientifiques pour
les couleurs. Les adaptateurs C et Kotlin traduisent la même table. La revue
de changement de palette et une vérification légère de parité empêchent la
dérive observée aujourd'hui. Si plusieurs thèmes apparaissent plus tard,
la génération depuis un petit manifeste pourra devenir utile.
Palette de référence : [Catppuccin Mocha](https://catppuccin.com/palette/).
L'affectation sémantique suivante est la proposition Trainlog :
| Token | Couleur | Rôle |
|---|---|---|
| `background` | Base `#1e1e2e` | Zone principale |
| `chrome_surface` | Mantle `#181825` | Header, sidebar, footer |
| `backdrop` | Crust `#11111b` | Fond extérieur/atténuation d'overlay |
| `surface` | Surface 0 `#313244` | Groupe utile, ligne sélectionnée |
| `elevated_surface` | Surface 0 `#313244` | Dialogue, avec contour si séparation nécessaire |
| `separator` | Surface 1 `#45475a` | Séparation discrète, pas seule marque de focus |
| `text` | Text `#cdd6f4` | Contenu principal |
| `muted_text` | Subtext 1 `#bac2de` | Contexte, dates, équipement |
| `accent` / `focus` | Lavender `#b4befe` | Interaction, état actif |
| `on_accent` | Crust `#11111b` | Texte sur bouton rempli Lavender |
| `success` | Green `#a6e3a1` | Succès réel + libellé « Enregistré » |
| `notice` | Peach `#fab387` | Information demandant attention sans erreur |
| `warning` | Yellow `#f9e2af` | Avertissement explicite + texte |
| `error` | Red `#f38ba8` | Échec, erreur de saisie, suppression |
| `information` | Blue `#89b4fa` | Information utile + libellé, pas texte secondaire systématique |
Pas de nouvelle palette scientifique pour les graphes existants : conserver
leurs séries/semantiques, labels et différenciation, puis harmoniser leur
présentation dans UI_POLISH_V1 si nécessaire. Lavender ne signifie ni succès,
ni zone du corps, ni charge élevée.
Espacement Android : échelle 4/8/12/16/24 dp ; marge écran 16, séparations
internes 8/12, entre groupes 16/24. Rayon discret 812 dp sur une surface
groupée et sur les contrôles ; pas de capsule autour de chaque texte.
TUI : 1 cellule de séparation minimale, padding horizontal 12 cellules,
1 ligne entre groupes quand la hauteur le permet, sans conversion dp→cellule.
Les degrés d'emphase (normal/secondaire/actif/critique) sont communs, pas les
unités physiques ni les tailles de police.
| Concept | Android, vecteur suggéré | TUI, sens équivalent | Fallback terminal |
|---|---|---|---|
| Accueil | home | maison, si mode icônes choisi | Accueil |
| Séances | event_note | carnet/calendrier | Séances |
| Exercices | exercise | mouvement/exercice | Exercices |
| Équipements | fitness_center | haltère/matériel | Équipements |
| Statistiques | bar_chart | graphique | Statistiques |
| Synchronisation | sync | flèches de transfert | Synchronisation |
| Paramètres | settings | roue dentée | Paramètres |
| Connaissances | menu_book | livre | Connaissances |
TUI V1 fonctionne **par défaut avec les libellés et marqueurs Unicode simples**.
Les Nerd Font sont une amélioration opt-in, jamais détectées à tort à partir
de `TERM` ou de la seule largeur d'un glyphe : la présence réelle du dessin ne
se prouve pas ainsi. Si un mode Nerd est ajouté, il doit être réellement
actionnable dans les préférences de présentation ou une option locale explicite,
avec aperçu et retour immédiat au mode texte ; aucune modification de police
du terminal n'est tentée. En l'absence de cette option implémentée, aucun menu
ne la promet et le fallback textuel est la livraison V1.
Les glyphes ont une colonne fixe, mesurée en cellules ; s'ils ne tiennent pas,
le libellé est conservé et l'icône retirée. `>` marque la sélection sans
Nerd Font. Variante ASCII de flèches/traits si nécessaire ; accent/UTF-8 dans
les données reste conservé. Android n'emploie aucun glyphe Nerd.
## 19. Propriétaires d'état, durées de vie, reprise
| État | Propriétaire | Durée / restauration |
|---|---|---|
| Section active | Dérivée de la route canonique | Aucun booléen concurrent dans drawer/sidebar |
| Route, pile de retour, appelant inline | NavigationState plateforme | Pile bornée ; pas d'IDs d'objet dupliqués dans plusieurs états incohérents |
| Requête, filtre, sélection ID, scroll | État de destination | Conservé sur détail/retour et changement de section, cache borné |
| Android brouillon + raw partial fields | Repository/SQLite existants | Durable ; rechargé, jamais copié comme source de vérité dans le shell |
| TUI brouillon / correction de séance | SessionController d'un run | Conservé lors d'une navigation UI ; aucun engagement après sortie/crash |
| Proposition générée non acceptée | Contrôleur générateur | En mémoire ; aucune écriture à l'ouverture/render/resize/annulation |
| Correction non durable d'exercice/mensuration | Contrôleur formulaire | Sauvegarder explicitement ou confirmer perte ; erreur garde les valeurs |
| État de sync/reçu | Services actuels | Shell lit un résumé ; aucun deuxième moteur ni deuxième propriétaire d'opération |
| Focus et pile d'overlays | AppShell | Sauvegarde de cible logique, invalidation des handles de plan détruits |
| Plans/widgets | Adaptateur terminal / owner du composant | Libération explicite ; chaînes empruntées copiées avant destruction si nécessaires |
| Thème | Adaptateur plateforme, table de tokens | Données de présentation, hors DB métier et échange |
La route distingue la destination de son appelant : `sessions.completed.detail`
porte le session_id ; `stats.exercise.performance` porte l'exercise_id ; la
création inline reste une sous-route du workflow de séance et réutilise le
même composant de formulaire que la création standalone. Drawer/sidebar
dérivent leur sélection de cette route contextualisée ; le retour à l'appelant
n'exige pas un second état de rubrique. Un seul aperçu générateur et une seule
édition active sont retenus à la fois ; commencer un workflow qui remplacerait
un état non durable exige la garde de sortie.
Android : introduire un petit `AppRoute` typé (destination + IDs) et un
`AppState`/state holder à la racine. Une seule pile bornée suffit ; pas de
framework de navigation supplémentaire obligatoire pour V1. Des sauvegardes
Compose peuvent garder routes/requêtes/positions modestes, jamais sérialiser
une séance complète dans un Bundle. Les contrôleurs transitoires peuvent
survivre à une recréation d'Activity via state holder/ViewModel ciblé si utile ;
après mort du processus, l'Accueil propose explicitement la reprise du brouillon
durable, comme aujourd'hui. Une proposition non acceptée n'est pas restaurée
comme une séance. Ne pas ouvrir un écran de confirmation destructive à la
recréation sans revalider sa cible.
Les imports/exports racine et callbacks de sauvegarde actuels restent au niveau
de leur cycle de vie/service. Naviguer ne déclenche pas un `LaunchedEffect`
réinstallé par écran qui importerait plusieurs fois. Les résultats de génération
ou lectures asynchrones portent une identité de requête et ne mettent plus à
jour un écran détruit. Ne pas changer la réutilisation du repository ni partager
une connexion SQLite entre threads sans son contrat actuel.
TUI : une DB empruntée à `trainlog_tui_run(TrainlogDatabase *)`, fermée par son
propriétaire actuel. Aucun pointeur Notcurses process-global. Libérer : widgets
→ plans de contenu/overlays → plans du shell → contexte Notcurses. Sur échec
d'allocation, libérer uniquement ce qui a été créé et afficher une erreur
explicite ; ne pas perdre silencieusement un formulaire.
## 20. Plan d'implémentation après approbation explicite
1. **Figer le design approuvé et la matrice de parité.** Formaliser routes,
action IDs, propriété des états, géométrie et exceptions clavier. Confirmer
les requêtes paginées de support nécessaires sans élargir les règles métier.
2. **Socle de présentation.** Tokens C/Kotlin synchronisés ; Material 3 ajouté
au BOM, vecteurs locaux ; `AppRoute`/ActionModel. Contrôle C17 précoce pour
toute interface interne exposée via header, sans casser l'API/ABI publique.
Les commentaires WHY / CONTRACT / INVARIANT accompagnent chaque changement
de durée de vie, action, propriété, borne ou persistance dans le même patch.
3. **Shell TUI et banc de géométrie.** Contexte explicite, vrais plans, header,
footer, layout, focus, overlay, input adapter ; tests de footer à 72×20 avant
migration des longues listes. Aucun widget sur un plan possédé par le shell.
4. **Shell Android.** Extraire contenu de TrainlogScreen, drawer et scaffold,
retour hiérarchique, insets, actions standard ; aucun deuxième scroll root.
5. **Listes et pages en lecture.** Accueil, hubs, séances effectuées, catalogues,
connaissances, mensurations/MAX existants, sync journal. Déplacer le graphe
12 mois sans changer son calcul. Accès autonome équipements Android.
6. **Formulaires et flux sensibles.** SessionController TUI, éditeurs, MAX,
génération et acceptation, création inline, confirmations, SAF/sync. Migrer
les boucles TUI écran par écran vers le dispatch unique ; aucun ancien
`draw_shell` plein écran ne subsiste sur le parcours livré.
7. **Validation et revue.** Exécuter la matrice ci-dessous, revue ciblée puis
un audit final de tranche ; réparer les régressions et synchroniser les
documents canoniques après comportement stabilisé.
Les commits intermédiaires ne sont pas implicitement autorisés par ce plan.
Le présent design gate s'arrête avant l'étape 1 exécutable.
## 21. Validation prévue et risques à protéger
| Risque | Preuve exigée à l'implémentation |
|---|---|
| Footer effacé/coupé | Rectangles disjoints et capture de rendu aux quatre dimensions, après modals/focus/erreurs/resize ; F7 énumère toutes les actions |
| Contenu qui dépasse son parent | Test avec nom UTF-8 long, nombreux sets, overlay proche du bas ; aucune cellule peinte dans footer |
| Double destruction de widget/plan | Tests création/échec/destruction, ASan/UBSan sur allocation/resize et fermeture imbriquée |
| State perdu en naviguant | Brouillon Android brut `32,`, retour depuis création inline, rotation/background/force-stop ; TUI aller-retour de rubrique puis reprise |
| Faux engagement de durabilité TUI | Message explicite à la sortie avec séance en mémoire ; aucune base/brouillon sidecar ajouté |
| Générateur transforme cible en réel | Cas zéro actual, aperçu vide/partiel, avertissement non acquitté, accepter avec brouillon existant ; mêmes garde/rollback |
| Données/IDs perdus | Snapshot DB temporaire avant/après navigation seule ; aucune écriture, migration, renommage ; sauvegarde réelle contrôlée transactionnellement |
| Raccourcis cassés | Matrice ancien/nouveau ; collisions q/b/g/n/p/r/Home ; RELEASE ignoré ; widget ne redéclenche pas l'action |
| Dead navigation | Chaque destination visible et chaque action F7 mène à une capacité réelle, futures routes absentes |
| Analyse cachée/perdue | Parcours body, overlay, profil, MAX, pourcentages/arrondis et performance ordinaire ; aucun zéro artificiel ni changement de science |
| Sync répétée/altérée | Chaque direction conserve une confirmation et un appel moteur ; s inactif ; erreurs/reçus/SAF/manual recovery toujours accessibles |
| Unicode/search incomplets | Accents précomposés/décomposés, CJK, emojis/graphèmes, dépassement capacité, collage, noms longs ; aucune coupure d'octet |
| Liste incomplète présentée comme complète | Jeu au-delà des anciennes capacités ; indication/page suivante réelle, filtrage avant pagination |
| Petits téléphones/IME | 320/360/393/412 dp, portrait/paysage, police 100/130/200 %, TalkBack, cible 48 dp, boutons visibles au-dessus IME |
| Thème illisible | Contrastes mesurés, focus sans couleur, fallback sans Nerd Font, terminal sans true color testé |
Commandes futures : `meson compile -C build`, `meson test -C build
--print-errorlogs`, validateurs JSON/import, vérifications scientifiques
existantes si leurs consommateurs sont touchés, build Android Java 17
`assembleDebug`, tests UI/draft ciblés, C17 et ASan/UBSan au checkpoint C,
`git diff --check`, `git status --short`. Le hardware MTP et les captures
Android/TUI réelles demeurent des validations manuelles explicitement nommées.
Dans ce design pass, seule la cohérence des artefacts et l'inspection statique
sont validées ; aucun build de production ni test de dispositif n'est revendiqué.
## 22. Report explicite des tranches suivantes
| Tranche | Travail différé |
|---|---|
| EXERCISE_NAMING_V1 | Noms exercice distincts des noms de machine ; migration explicite de noms/affichage sans toucher exercise_id, entry_id, session_id, actuals, MAX, zones ou sync ; aucun exemple de maquette ne déclenche une migration |
| STATS_V1 | Vue d'ensemble, fréquence, progression, analyse par zones et enrichissement par exercice/capacités ; définition des calculs, périodes et périmètre Android ultérieure ; pas de graphiques fictifs pour remplir le menu |
| UI_POLISH_V1 | Ajustements visuels après usage réel : transitions, détails de densité, raffinements de graphes, icônes Nerd opt-in si non livrées, éventuelle navigation permanente grands écrans Android |
| Capacité distincte à définir | Modification des définitions d'équipement : règles d'identité, propriété fourni/personnel, synchronisation et mutation absentes aujourd'hui. Ni promise par APP_SHELL_V1 ni assimilée à EXERCISE_NAMING_V1 |
APP_SHELL_V1 livre l'organisation, le shell et la lisibilité fondamentales.
Le focus, le footer, l'accessibilité de base, la reprise et les fonctions
existantes ne sont pas différés sous prétexte de polish.
## 23. État du design gate
La proposition et ses maquettes sont destinées à la revue humaine. L'approbation
du design, lorsqu'elle sera donnée, précédera toute implémentation de production.
`APP_SHELL_V1_DESIGN=READY_FOR_HUMAN_REVIEW`
Aucune implémentation de production. Aucun renommage d'exercice. Aucun STATS_V1.
Aucun commit. Aucun push.