# 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 | | 393–412 dp | Même architecture, davantage de texte visible ; pas de nouvelle colonne de navigation ; drawer 337–356 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 1–12 · 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 8–12 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 1–2 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.