trainlog/docs/design/app_shell_v1/proposal.md

64 KiB
Raw Blame History

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 sont consultables hors ligne, avec un aperçu PNG. Les maquettes TUI en couleur et leur version texte 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é.

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.

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

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.

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.

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

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.

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.

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.

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.

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 :

↑↓ 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. 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.