Proposé · non publié dans la doc du projet
Rendu de docs/specs/editor.md et docs/adr/019-refonte-vue-editeur.md, prêts à entrer dans le dépôt. Les liens internes vers la doc du projet pointent vers des pages qui n'existent pas encore.
Le contrat normatif de la vue Éditeur refondue : géométrie, zones, règles de comportement, erreurs, accessibilité et découpage en lots. Refonte #1417, décisions structurantes dans l'ADR-019. Version v0.3 (intègre deux relectures contre le code et une relecture de cohérence).
Statut : Proposé (23/09/2026). Rien de ce contrat n'est implémenté. Relevé de l'existant sur
devà08d5b41c(bêta 0.62.0). Les hauteurs en dp sont des cibles calculées, non mesurées : elles ne valent critère qu'après confirmation filmée sur appareil. Maquettes non contractuelles : Vue Éditeur #1417.
Conventions. Une règle normative porte un identifiant ED-nn ; elle est opposable en revue. Les critères d'acceptation C1–C10 sont ceux de #1417, repris sans modification en § 9. Les lots R0a–R4 sont définis en § 11. « Doit » = obligatoire ; « peut » = permis. Les signes ❝ ↺ ✕ ⤢ des tableaux désignent une icône (drawable selon l'ADR-015), pas le glyphe rendu.
| Surface | Écran | ViewModel | Implémentation |
|---|---|---|---|
| Répondre / modifier son message | PostEditorScreen |
PostEditorViewModel |
composants :core:ui/editor |
| Nouveau sujet / modifier le premier message | TopicFormScreen |
TopicFormViewModel |
composants :core:ui/editor |
| Nouveau MP | PrivateMessageComposeScreen |
PrivateMessageComposeViewModel |
composants :core:ui/editor |
| Réponse dans une conversation MP | PrivateMessageReplyScreen (MessageEditorComponents) |
PrivateMessageReplyViewModel |
composants :core:ui/editor |
| Réponse rapide (vue Topic) | QuickReplySheet |
QuickReplyViewModel |
propre : champ BasicTextField 1, fenêtre Dialog |
Les quatre premières sont appelées éditeurs plein écran ; la cinquième, la feuille. L'éditeur plein écran est le préréglage de surface par défaut (WritingSurfacePreset.FULL_EDITOR, #951) ; la feuille est opt-in.
Le rendu des citations est choisi une fois à l'ouverture d'une surface (observeQuoteCardsEnabled().first()), jamais recâblé en cours de session :
[quotemsg] sous les gardes anti-écrasement existantes. Le BBCode est éditable, l'interleaving est manuel, l'envoi prend le contenu du champ tel quel.[quotemsg] à l'envoi seulement.ED-01 — La refonte préserve les deux modes. Aucun changement de mode ne convertit un brouillon existant. Les règles propres au mode cartes (pastille, panneau) ne s'appliquent pas au mode inline.
Citer la portion sélectionnée d'un post lu (#381) ; Rechercher sur internet (#1431) ; sections Favoris / Récents du sélecteur de smileys (#1154) ; loupe à l'appui long dans le sélecteur (#1022) ; validateur d'équilibre des balises (#1444) ; annulation d'un upload en cours (n'existe pas aujourd'hui, n'est pas ajoutée).
| Terme | Définition |
|---|---|
| App bar d'éditeur | Barre du haut, 48 dp : retour, titre d'écran, contexte en seconde ligne, action aperçu. Rien de ce qu'elle porte ne dépend de la frappe. |
| Champ | La zone de saisie BbcodeTextField (BasicTextField 2), avec son label et son cadre. Le texte défile dedans. |
| Tiroir de format | Les chips BBCode, dépliées par l'action format, superposées au bas du champ. |
| Bande de contexte (la bande) | Ligne de 32 dp au-dessus de la barre d'action : pastilles d'état à gauche, un événement à droite. |
| Pastille | Élément d'état durable de la bande (brouillon retrouvé, citations en cartes, destinataires, sondage préservé). Tap = raccourci vers la surface qui la gouverne. |
| Événement | Élément transitoire de la bande (upload, erreur, fin d'insertion de citation). Un seul à la fois. |
| Barre d'action | Barre du bas, 56 dp, épinglée au-dessus du clavier : format, smileys, image, options, envoyer. |
| Bouton armé | « Envoyer » qui, au premier tap, devient « Confirmer » pendant 4 s (ArmedSubmitButton, #312 v2). |
| Panneau | Surface plein écran qui remplace temporairement le champ, avec sa propre app bar et sa propre barre du bas (« Retour au message ») : le panneau citations (ED-50) et le panneau d'en-tête (ED-101, ED-113). |
| Feuille | Surface superposée, qui laisse voir l'écran derrière (smileys, options, réponse rapide). Une ModalBottomSheet est une fenêtre Dialog distincte, avec ses propres insets. |
| Partage | Aperçu ouvert à côté du source, séparés par un séparateur déplaçable. |
| Cran | Butée du séparateur quand le champ atteint 160 dp ; au-delà, glissement élastique. |
| Fenêtre utile | Hauteur de fenêtre disponible une fois retirés la barre d'état, le clavier et la barre de navigation. |
| Plancher | Hauteur minimale du champ : 160 dp (EditorLayoutBudget), cédant seulement dans une fenêtre trop courte. |
| Clavier (IME) | Clavier logiciel du système (input method editor). |
| BTF2 | BasicTextField 2 (TextFieldState), sur lequel repose BbcodeTextField depuis 0.59.8. |
| Size class | Classes de largeur de fenêtre Material (Compact < 600 dp, Medium, Expanded). |
| Spike | Prototype jetable d'exploration, mesuré sur appareil, qui tranche une faisabilité avant un lot. |
| Hauteur compacte | Fenêtre de moins de 400 dp de hauteur, mesurée hors clavier (paysage téléphone). Un portrait clavier ouvert n'est pas en hauteur compacte. |
flowchart TB SB["Barre d'état (inset)"] --> AB["App bar d'éditeur — 48 dp fixe<br/>(absente en hauteur compacte)"] AB --> F["Champ — élastique, plancher 160 dp<br/>tiroir de format superposé à sa base"] F --> CB["Bande de contexte — 0 ou 32 dp<br/>(jamais modifiée par une frappe)"] CB --> ACT["Barre d'action — 56 dp fixe"] ACT --> IME["Clavier / barre de navigation<br/>inset unique navigationBars ∪ ime"]
ED-02 — Seuls l'app bar (48 dp), la bande de contexte (0 ou 32 dp) et la barre d'action (56 dp) ont une hauteur propre ; le champ prend tout le reste, sans descendre sous le plancher de 160 dp tant que la fenêtre utile le permet. Le tiroir de format n'a pas de hauteur dans la colonne (ED-21).
ED-03 — Aucun élément ne s'intercale entre l'app bar et le champ. Les en-têtes (titre et sous-catégorie d'un sujet, destinataires et sujet d'un MP) vivent dans un panneau d'en-tête (ED-101, ED-113). Seule dérogation : les cartes de citation en R1a (ED-140).
ED-04 — Insets : l'inset bas est unique, WindowInsets.navigationBars.union(WindowInsets.ime) appliqué une seule fois à la barre d'action (#624). Le mode de fenêtre reste adjustNothing sur API 30+ et adjustResize sous API 30 (#1404). La refonte ne modifie pas cette mécanique.
ED-05 — En hauteur compacte (fenêtre hors clavier < 400 dp), l'app bar s'efface avec son titre ; l'action aperçu passe dans la barre d'action en dernière icône avant « Envoyer » ; le retour passe par le geste système. Le plancher de 160 dp ne s'applique pas (environ 100 dp restent clavier ouvert). Pour les surfaces à panneau d'en-tête (sujet, nouveau MP), l'entrée du panneau passe aussi dans la barre d'action, en icône « En-tête » placée avant l'aperçu ; la barre compte alors au plus six icônes et « Envoyer ».
| Élément | 0.60.0 (avant #1432) | Refonte |
|---|---|---|
| App bar | — | 48 dp |
Titre d'écran (titleLarge, 28 sp de ligne) |
~28 dp à fontScale 1 | 0 |
| Barre d'outils BBCode | 46 dp | 0 (tiroir) |
| Bouton d'aperçu | 36 dp | 0 (action) |
| Espacements de colonne | 36 dp | 12 dp |
| Barre d'action | 56 dp | 56 dp |
| Bande de contexte | — | 0 ou 32 dp |
| Champ | 128 dp | 182–214 dp |
Le critère à tenir est 182 dp (cas avec bande). Le gain net est de +54 à +86 dp face à 0.60.0, de +22 à +54 dp face à 0.62.0 où le plancher de 160 dp existe déjà ; l'app bar est du chrome neuf. Budget établi à fontScale 1, à remesurer à 1,3 (les hauteurs de texte sont en sp).
ED-10 — Ligne 1 : titre d'écran court (« Répondre », « Modifier », « Nouveau sujet », « Nouveau MP »), en titleSmall (20 sp de ligne) ; ligne 2 : contexte, en labelMedium (16 sp) — titre du sujet ; pour le formulaire de sujet, le résumé d'ED-101 ; pour le nouveau MP, le résumé d'ED-113 ; pour la réponse MP, le titre de la conversation. Ellipse sur une ligne chacune. À fontScale 1,3 les 36 sp tiennent dans 48 dp sans marge verticale ; l'app bar ne grandit jamais.
ED-11 — Actions : aperçu seulement. Pas de menu de débordement.
ED-12 — Le label du champ devient « Message » dans les quatre éditeurs plein écran. Libellés actuels : « Contenu BBCode » (editor_field_label, éditeur de post et formulaire de sujet, tronqué à fontScale 1,3, #872) et « Votre réponse » (messages_reply_field_label, éditeurs MP). Le placeholder devient « Saisissez votre message… » dans les quatre éditeurs (aujourd'hui editor_field_placeholder pour le post et le sujet, « Écrivez votre message… » messages_reply_field_placeholder pour les MP) ; la feuille garde « Votre message… » jusqu'à R4.
ED-13 — Autofocus, capitalisation en début de phrase, neutralisation du TextClassifier (#1427), poignées et autoscroll (#447), verrouillage en lecture seule pendant un upload par lot : inchangés. L'autofocus reste propre à chaque surface (aujourd'hui : éditeur de post et feuille oui ; formulaire de sujet et éditeurs MP non).
ED-20 — L'action format déplie un tiroir contenant les actions BBCode : Gras, Italique, Souligné, Barré, Citation, Code, Bloc fixé, Spoiler, Lien, Couleur (Rouge, Bleu, Vert, Orange, Gris). L'action Image quitte le tiroir pour le menu image (ED-31).
ED-21 — Le tiroir est superposé au bas du champ : la hauteur du champ ne change ni à l'ouverture ni au repli, et la sélection en cours est préservée (C6). Le seul mouvement permis est celui d'ED-22.
ED-22 — Pendant que le tiroir est ouvert, le champ réserve sa hauteur comme marge basse de défilement : le caret et la ligne courante restent visibles au-dessus du tiroir, y compris après une insertion en dernière ligne (C4, C5). Si le caret se trouve sous le tiroir à son ouverture, un défilement interne minimal, en une seule animation, le ramène au-dessus ; aucun autre élément ne bouge.
ED-23 — Le tiroir se replie à la frappe suivante, au lancement d'un sélecteur (images, smileys, options) et à l'ouverture du panneau citations ; il n'est pas rouvert au retour.
ED-24 — Disposition : rangées fixes, sans défilement horizontal, si les libellés tiennent à 360 dp et fontScale 1,3 ; sinon défilement horizontal avec un dégradé de bord signalant le débordement. Premier rendu Roborazzi : dix chips en trois rangées à 360 dp et fontScale 1. À trancher par mesure (R0a).
Précondition bloquante (R0a).
BbcodeTextFieldexpose sonScrollStatemais aucune marge basse de contenu pilotée de l'extérieur ; un padding extérieur réduirait le champ et ne prouverait ni C5 ni C6. ED-21 et ED-22 ne sont engageables qu'après un spike device concluant (caret en dernière ligne, sélection, fontScale 1,3, clavier physique, retour du sélecteur d'images).
ED-30 — La barre porte, de gauche à droite : format, smileys, image, options, [en hauteur compacte : en-tête puis aperçu], espace, « Envoyer ». Les actions secondaires sont des icônes seules ; « Envoyer » garde son libellé.
ED-31 — L'action image ouvre un menu qui porte toutes les actions image existantes de la surface : « Uploader une image » (quatre éditeurs plein écran), « Insérer par URL » (éditeur de post, formulaire de sujet), « Baliser la sélection en [img] » (éditeurs MP, action BbcodeAction.Image actuelle). Aucune action existante n'est perdue. Ces trois libellés sont nouveaux (actuels : « Uploader » bbcode_action_image_upload, dialogue « Insérer une image » editor_image_url_title).
ED-32 — Chaque icône a un nom accessible, repris par son info-bulle à l'appui long (TooltipBox + PlainTooltip) :
| Action | Nom accessible | Origine |
|---|---|---|
| Format | « Mise en forme » | nouveau |
| Smileys | « Smileys » | editor_smiley_open |
| Image | « Image » (ouvre le menu d'ED-31) | nouveau |
| Options | « Options » | editor_actions_options |
| Aperçu | « Afficher l'aperçu » / « Masquer l'aperçu » | editor_preview_show / editor_preview_hide |
| En-tête (hauteur compacte) | « Titre et sous-catégorie » (sujet) / « Destinataires et sujet » (MP) | nouveau |
ED-33 — Icônes : drawables vectoriels locaux via RedfaceVectorIcon (ADR-015). Existent : ic_arrow_back, ic_ms_visibility / ic_ms_visibility_off, ic_image_upload, ic_ms_add_photo_alternate. À créer : ic_ms_text_format (format), ic_ms_mood (smileys), ic_ms_more_horiz (options, ellipse horizontale — ic_ms_menu est un menu hamburger, il ne convient pas), ic_ms_title (en-tête).
ED-34 — Bouton armé : comportement actuel conservé. Premier tap → « Confirmer » (editor_submit_confirm) en couleurs tertiaires, compte à rebours de 4 s, second tap = envoi. Pendant l'armement, les actions secondaires sont masquées (comportement actuel des quatre éditeurs : PostEditorScreen et TopicFormScreen via EditorSubmitBar, MessageEditorComponents) pour garantir que « Confirmer » ne passe pas à la ligne ; le bouton reste aligné à droite et ne grossit pas sous le doigt.
ED-40 — Hauteur 32 dp exactement, une ligne, jamais deux. Absente (0 dp) quand elle n'a ni pastille ni événement.
ED-41 — Aucune transition de la bande n'est causée par une modification du texte ou de la sélection du champ. Ses transitions sont causées par : l'ouverture de la surface ; un geste (retour d'un sélecteur ou d'un panneau, « Masquer », Restaurer / Ignorer, retrait d'une carte) ; l'aboutissement, en succès ou en échec, d'une opération lancée par un geste (envoi, upload, récupération d'une citation). Une telle transition peut survenir pendant la frappe ; elle ne modifie ni la hauteur du champ ni la position du caret (ED-02, ED-40). L'autosave des brouillons est silencieuse : aucune pastille, aucun événement.
ED-42 — Pastilles (gauche), dans cet ordre quand elles coexistent :
| Pastille | Quand | Tap |
|---|---|---|
| ❝ N citations | mode cartes, N ≥ 1 | ouvre le panneau citations |
| ↺ Brouillon retrouvé | offre de restauration en attente (#1415) | propose Restaurer / Ignorer |
| N destinataires | réponse MP seulement | « Gérer » pour l'owner d'un DT (#618), sinon liste des participants |
| Sondage préservé | modifier le premier message d'un sujet à sondage | détail : non éditable |
ED-43 — Événement (droite) : un seul à la fois, priorité erreur > upload > insertion de citation. Quand un événement est affiché, les pastilles se compactent à leur icône et compteur nus (icône seule pour les pastilles sans compteur : brouillon, sondage) ; elles restent visibles dans la limite d'ED-47. Le retour « Déjà citée » d'ED-52 a la priorité de l'insertion de citation.
ED-44 — Un événement ne se vide pas de lui-même. Fin d'upload : « N images insérées » + « Masquer ». Fin d'insertion de citation (feuille) : acquittable de même. Une erreur persiste jusqu'à « Masquer », le prochain envoi ou la fermeture de la surface. Toute nouvelle action de même nature (nouvel envoi, nouvel upload) remplace l'événement obsolète. Un lot d'upload arrêté par un échec indique le nombre d'images déjà insérées, qui sont conservées.
ED-45 — Un seul emplacement d'action par événement : « Détails » quand un texte intégral existe, « Masquer » sinon ; quand le remède est une navigation (Se connecter, Réglages, Actualiser), l'emplacement porte le remède, et le détail reste accessible par un appui sur le résumé. La surface de détail porte elle-même « Masquer ».
ED-46 — Pendant un upload, une barre de progression de 2 dp est dessinée dans les 32 dp de la bande (marge négative), sans en changer la hauteur.
ED-47 — Saturation à 360 dp : la zone d'événement réserve au moins 168 dp au résumé et au moins 64 dp à l'action. Sans événement, au plus trois pastilles avec libellé ; avec un événement, au plus deux pastilles compactées. Au-delà, une pastille « +N » ouvre la liste des états restants. Aucune cible tactile ne recouvre sa voisine.
ED-48 — Retirer la dernière pastille par un geste (dernière carte retirée, brouillon ignoré) fait disparaître la bande : changement voulu, provoqué par un geste, annoncé dans les notes de build.
stateDiagram-v2 [*] --> Absente Absente --> Etat : ouverture avec pastille\nou geste (retour panneau, Restaurer) Absente --> Evenement : échec d'envoi / début d'upload\n/ insertion de citation Etat --> EtatEvenement : upload, erreur, insertion de citation Evenement --> EtatEvenement : pastille ajoutée par un geste EtatEvenement --> Etat : Masquer / envoi suivant Evenement --> Absente : Masquer / envoi suivant Etat --> Absente : dernière pastille retirée par un geste note right of Etat : Aucune transition causée\npar le texte ou la sélection
ED-50 — La pastille ❝ N ouvre un panneau plein écran qui remplace le champ : cartes auteur — extrait, monter / descendre / retirer par carte, « Tout vider » en action d'app bar à partir de deux cartes (#436), annonces TalkBack et focus après retrait conservés.
ED-51 — Le panneau propose l'ordre d'insertion à l'envoi : « Toutes en tête » (actuel) ou « Une par une, au curseur » (interleaving, #805), et le réglage de longueur de « Citer le début » (#1254).
ED-52 — Les cartes restent dédupliquées par numreponse. Re-citer un post déjà cité conserve une seule carte et le signale par l'événement « Déjà citée », acquittable par « Masquer », au lieu de l'ignorer sans rien dire (#878).
ED-53 — Le retour au message restaure le focus et la sélection du champ.
ED-60 — L'action aperçu ouvre le rendu à côté du source : dessous en portrait, à droite quand la largeur dépasse Compact ou en hauteur compacte. Une seule mise en page, deux directions.
ED-61 — Ouvrir ou fermer l'aperçu ne touche pas au clavier.
ED-62 — Le séparateur s'arrête quand le champ atteint 160 dp (le cran). Au-delà, le glissement est élastique et une puce « Relâcher pour fermer le clavier » apparaît pendant le sur-glissement seulement ; relâcher ferme le clavier, une seule fois. Aucun réglage.
ED-63 — La position du séparateur est mémorisée en dp, par size class, dans les préférences utilisateur (DataStore), sans réglage exposé.
ED-64 — La barre d'action reste identique en lecture : on peut envoyer sans fermer l'aperçu.
ED-65 — Le calcul de l'aperçu sort de _state.update {}, s'exécute hors du thread principal, est annulable et ne traite que le dernier texte, dans les quatre ViewModels plein écran. Précondition de R2b.
ED-66 — Smileys dans l'aperçu : le parseur reconnaît déjà les builtin :code: et les tokens perso ; restent les émoticônes de ponctuation ambiguës et l'URL des smileys perso (#873).
ED-70 — La feuille smileys est conservée : onglets Standard (58 builtin) et Wiki (recherche perso dès 3 caractères, états Idle / Recherche / Résultats / Vide / Erreur), grille à 62 % de la hauteur avec plancher 320 dp (#900), décoration de cellule en réglage (#989), insertion au token puis fermeture.
ED-71 — La feuille options est conservée : trois bascules HFR (signature, smileys désactivés, notification par e-mail), valeurs par défaut lues dans le formulaire. Le ton du message (16 icônes, #340) n'est plus replié par défaut. Les éditeurs MP gardent leurs options sans ton.
ED-80 — Retour d'un sélecteur (images, smileys, options) : le tiroir reste replié (ED-23), le focus et la sélection du champ sont restaurés, sans double animation (C6).
ED-81 — Rotation pendant un upload (ViewModel conservé) : l'upload se poursuit, compteur compris. Mort du process : aucun relancement automatique ; les images déjà insérées sont conservées par le brouillon, la progression repart de zéro (C8).
ED-82 — Tiroir ouvert + événement d'erreur + clavier ouvert : les trois coexistent sans changement de hauteur ; le tiroir recouvre le bas du champ, la bande reste visible au-dessus de la barre.
ED-83 — Changement de compte pendant la saisie : les jobs en cours sont annulés (comportement actuel), sans erreur fabriquée ; le brouillon reste attaché à sa clé d'éditeur.
ED-84 — Escalade feuille → plein écran (⤢) : le texte et les citations sont transmis, pas la sélection.
ED-90 — Ordre de focus de la bande : pastilles → résumé d'événement → action d'événement → champ.
ED-91 — Le texte intégral d'un événement et son action restent accessibles à TalkBack, même quand le résumé visuel est tronqué.
ED-92 — Les cibles tactiles des pastilles et de l'action d'événement font au moins 48 dp, au-delà des 32 dp visuels de la bande, sans recouvrir une cible voisine.
ED-93 — Clavier physique : Maj+flèches suit les mêmes règles que les poignées (C10) ; l'insertion depuis le tiroir au clavier respecte ED-22.
ED-94 — Le champ reste un seul nœud défilable pour TalkBack ; nom accessible conservé.
PostEditorScreen)Règles des § 3 à 6 sans exception. Hydratation du formulaire, échec et nouvel essai, sauvegarde attendue avant retour et garde contre l'écrasement d'une saisie par un GET tardif : inchangés.
TopicFormScreen)ED-100 — Le titre du sujet (capitalisation de phrase) et la sous-catégorie quittent la colonne. Le sélecteur de sous-catégorie reste absent pour les catégories qui n'en ont pas (#213). Aujourd'hui ils occupent, avec la barre d'outils, une zone défilante plafonnée (EditorLayoutBudget).
ED-101 — Aucun repli n'est déclenché par le focus, le caret ou l'IME. Dès l'ouverture du formulaire, la seconde ligne de l'app bar résume le titre (« Ajouter un titre » s'il est vide) et la sous-catégorie ; elle reste visible pendant l'édition du corps. Un appui sur la seconde ligne de l'app bar ouvre un panneau d'en-tête contenant le champ de titre et, si la catégorie le permet, le sélecteur de sous-catégorie. À la fermeture, le corps retrouve focus et sélection. La hauteur du corps ne change ni à la frappe ni au déplacement du caret (C6, C7).
ED-102 — Le formulaire de sujet n'affiche pas de pastille de sous-catégorie : l'app bar la porte déjà (ED-101). La pastille « sondage préservé » reste dans la bande.
ED-110 — Nouveau MP, dans le panneau d'en-tête : destinataires en pastilles saisies au clavier ; la virgule seule valide une pastille (les pseudos HFR contiennent des espaces) ; le POST reste la CSV actuelle (#606). Le sujet garde son compteur de caractères.
ED-111 — Réponse MP : « Destinataires : N · Gérer » pour l'owner d'un DT (#618).
ED-112 — Les erreurs d'envoi MP sont déjà conservées à la frappe (withDraftPreview) ; seul l'avertissement « votre message a peut-être été envoyé », propre aux MP et rédigé différemment pour la réponse et le nouveau MP, est un état sans équivalent ailleurs : ses deux détails sont conservés.
ED-113 — Nouveau MP : destinataires et sujet vivent dans un panneau d'en-tête ouvert par un appui sur la seconde ligne de l'app bar, selon le mécanisme d'ED-101. À l'ouverture d'un nouveau MP sans destinataire, le panneau est ouvert d'office, focus sur le champ destinataires ; « Retour au message » le ferme et donne le focus au corps. La seconde ligne résume « N destinataires · sujet » (« Ajouter des destinataires » si vide). La bande n'affiche pas de pastille de destinataires pour le nouveau MP.
QuickReplySheet)ED-120 — La feuille devient l'éditeur en mode feuille : même champ (BbcodeTextField), même barre d'action, même bande, format et smileys, même bouton armé au lieu du dialogue « Publier la réponse ? ».
ED-121 — Brouillon : bannière de restauration comme les autres surfaces au lieu de l'application d'office ; le contexte de page et de compte est conservé ; la page courante reste rafraîchie à chaque ouverture (updateCurrentPage, comportement actuel). L'issue #1014 est à requalifier : le code semble déjà la corriger.
ED-122 — Cartes de citation plafonnées (QUICK_REPLY_MAX_CARDS_HEIGHT = 112 dp, #808) tant que la feuille garde des cartes.
Préconditions (R4). Dans la feuille, titre, cartes, champ, bande, barre et tiroir demandent ~420 dp sur ~330 disponibles : soit le tiroir y est superposé, soit la feuille devient plein écran. La fenêtre Dialog a ses propres insets ; aucune preuve JVM ne vaut pour elle.
ED-130 — Toute erreur d'envoi, d'upload ou de citation affichée dans la bande est une valeur d'un type unique porté par :core:ui : cause, résumé (≤ 30 caractères cible, une ligne, ellipse), détail optionnel (texte intégral), action (Détails, Masquer ou remède). Un seul jeu de chaînes par cause, partagé par les cinq surfaces (R0b). Les autres erreurs (chargement d'une conversation MP, validation d'URL d'image, recherche de smileys) restent à leurs surfaces respectives et hors de la bande.
ED-131 — La persistance d'une erreur à la frappe est fixée par cause et par surface ; la règle cible est la persistance (ED-44), et aucune surface ne doit effacer une erreur sur un changement de texte.
Budget : une bande d'une ligne laisse environ 204 dp au résumé sans pastille, au moins 168 dp avec deux pastilles compactées (ED-47), soit ≈ 34 caractères à fontScale 1 et ≈ 22 au pire cas (1,3 système × 1,15 du préréglage de lecture L de l'app). Une troncature automatique couperait le remède : les textes sont réécrits.
| Cause | Résumé | Détail |
|---|---|---|
| Message vide | Message vide | — |
| Hash invalide | Formulaire rechargé, réessayez | oui |
| Anti-flood | Anti-flood HFR, patientez | oui — 3 réponses par 10 min |
| Sujet fermé | Sujet fermé, envoi impossible | — |
| Connexion requise | Connexion requise | oui — remède : Se connecter |
| Réponse inattendue | Réponse inattendue, réessayez | oui |
| Réseau | Erreur réseau, réessayez | — |
| Session expirée | Session expirée, reconnexion | oui — remède : Se connecter |
| Sous-catégorie absente | Sujet à actualiser avant envoi | oui — remède : Actualiser |
| MP peut-être envoyé | Envoi incertain, vérifiez | oui |
| Aucune image reçue | Aucune image reçue | oui — explorateur, redémarrage |
| Image trop lourde | Image trop volumineuse | oui |
| Type refusé | Format d'image refusé | oui |
| Refus serveur | Refus de l'hébergeur (HTTP %1$d) | oui — nom de l'hébergeur |
| Réponse illisible | Réponse illisible de l'hébergeur | oui — nom de l'hébergeur |
| Hébergeur non configuré | Hébergeur non configuré | oui — remède : Réglages |
| Citation non récupérée | Citation non récupérée | oui |
Relevé actuel, borné aux erreurs d'envoi, d'upload et de citation : 33 chaînes — 7 d'upload (:core:ui), 9 d'envoi (:feature:editor), 9 de la feuille dont 8 copies mot pour mot (:feature:topic), 8 des MP (:feature:messages : 7 de réponse + 1 formulation propre au nouveau MP pour l'envoi incertain).
Toutes ces erreurs sont persistantes au sens d'ED-44 (aucune ne s'efface à la frappe) ; l'action est « Détails » quand la colonne Détail est renseignée, le remède quand il est indiqué, « Masquer » sinon.
Repris de #1417, vérifiés sur appareil et filmés (S10e à 360 dp et Android 10).
[img] après upload, citation : le texte inséré et le caret résultant sont visibles sans geste supplémentaire.C9 est dérogé entre les lots et se clôt en R4. C7 est satisfait par construction pour le sujet (ED-101) et le nouveau MP (ED-113) : aucun en-tête ne reste dans la colonne. Roborazzi (ADR-016) sert à l'inspection des états visuels ; il ne prouve ni les insets IME, ni le redimensionnement système, ni la fenêtre Dialog.
Chaque fonction existante, sa place dans la refonte, la règle qui la porte, le lot et la preuve.
| Fonction en 0.62.0 | Dans la refonte | Règle | Lot | Preuve |
|---|---|---|---|---|
| Champ borné, plancher 160 dp | Champ élastique, plancher conservé | ED-02 | R1a | device |
| Labels « Contenu BBCode » / « Votre réponse » | « Message » | ED-12 | R1a, R1b | Roborazzi + device 1,3 |
Titre d'écran (titleLarge) |
Ligne 1 de l'app bar | ED-10 | R1a | Roborazzi |
| 11 chips BBCode (10 + Couleur) | Tiroir, 10 chips ; Image → menu image | ED-20, ED-31 | R1a | Roborazzi + device |
| Uploader épinglé, spinner | Menu image, événement de bande | ED-31, ED-43 | R1a | device |
| Dialogue « Insérer une image » (URL) | Menu image | ED-31 | R1a | Roborazzi |
Action [img] des éditeurs MP |
Menu image, « Baliser la sélection » | ED-31 | R1b-2 | device |
| Compteur « Envoi n/N… » | Événement + barre 2 dp | ED-43, ED-46 | R1a | Roborazzi |
| 7 erreurs d'upload | Contrat unique, résumé + détail | ED-130 | R0b | tests JVM |
| 9 erreurs d'envoi | Contrat unique, persistance | ED-130, ED-131 | R0b, R1a | tests JVM |
| 8 + 1 erreurs de la feuille | Contrat unique | ED-130 | R0b | tests JVM |
| 8 erreurs MP + « peut-être envoyé » | Contrat unique, état conservé | ED-112, ED-130 | R0b | tests JVM |
| Bandeau brouillon Restaurer / Ignorer | Pastille ↺ | ED-42 | R1a | device (C8) |
| Autosave, purge, rétention | Inchangés, silencieux | ED-41 | — | — |
| Citations inline (défaut) | Inchangées | ED-01 | — | device |
| Cartes de citation ↑ ↓ ✕ | Conservées en R1a (ED-140), puis pastille + panneau | ED-42, ED-50 | R2a | Roborazzi + device |
| « Tout vider » (≥ 2 cartes) | Action d'app bar du panneau | ED-50 | R2a | Roborazzi |
| Choix du rendu des citations (réglage) | Inchangé, lu à l'ouverture | ED-01 | — | — |
| Aperçu Afficher / Masquer | Bascule en R1a (ED-140), puis partage source / rendu | ED-60 | R2b | device |
| Smileys Standard / Wiki | Feuille conservée | ED-70 | R1a | Roborazzi |
| Options HFR (3 bascules) | Feuille conservée | ED-71 | R1a | Roborazzi |
| Ton du message (16 icônes) | Grille dépliée | ED-71 | R1a | Roborazzi |
| Options MP sans ton | Conservées | ED-71 | R1b-2 | Roborazzi |
| Bouton armé, secondaires masqués | Conservé | ED-34 | R1a | device |
| Cibles tactiles de la bande | ≥ 48 dp | ED-92 | R1a | device, fontScale 1,3 |
| Titre du sujet + sous-catégorie | Résumé en app bar + panneau d'en-tête | ED-100, ED-101 | R1b-1 | Roborazzi + device (C7) |
| Note « sondage préservé » | Pastille | ED-42 | R1b-1 | Roborazzi |
| Destinataires CSV | Pastilles, virgule seule, dans le panneau d'en-tête | ED-110, ED-113 | R1b-2 | device |
| Destinataires + sujet du nouveau MP (dans la colonne) | Résumé en app bar + panneau d'en-tête | ED-113 | R1b-2 | Roborazzi + device (C3, C7) |
| Compteur de sujet MP | Conservé | ED-110 | R1b-2 | Roborazzi |
| « Destinataires : N · Gérer » | Conservé | ED-111 | R1b-2 | device |
| Hydratation, échec / nouvel essai (post) | Inchangés | § 7.1 | R1a | tests JVM |
| Chargement du formulaire, échec / nouvel essai (sujet) | Inchangés | § 7.2 | R1b-1 | tests JVM |
| Chargement de la conversation, échec / nouvel essai (MP) | Inchangés, hors bande | ED-130 | R1b-2 | tests JVM |
| Sauvegarde attendue avant retour | Inchangée | § 7.1 | — | tests JVM |
| Garde anti-écrasement (GET tardif) | Inchangée | § 7.1 | — | tests JVM |
| Changement de compte (jobs annulés) | Inchangé, sans erreur fabriquée | ED-83 | R1a | tests JVM |
| Poignées, autoscroll, TextClassifier | Inchangés | ED-13 | — | device (C1, C2) |
| Autofocus par surface, capitalisation | Inchangés | ED-13 | — | device |
| Feuille de réponse rapide | L'éditeur en mode feuille | ED-120 | R4 | device |
| Bouton plein écran de la feuille | ⤢, texte et citations | ED-84 | R4 | device |
| « Insertion de la citation… » | Événement acquittable | ED-44 | R4 | device |
| « Publier la réponse ? » | Bouton armé | ED-120 | R4 | device |
| Contexte page de la feuille | Conservé, rafraîchi à chaque ouverture | ED-121 | R4 | tests JVM |
Un lot = une PR = une build dev = un retour testeurs. Promotion bêta à la fin de la phase complète.
ED-140 — Règle transitoire : aucun lot ne retire un parcours avant le lot qui le remplace. En R1a, les cartes de citation (mode cartes) restent affichées et utilisables en tête du champ, par dérogation à ED-03, jusqu'à R2a ; l'aperçu reste une bascule plein champ depuis l'app bar jusqu'à R2b.
Critères de sortie de R0a (vidéo S10e + Android 10, fontScale 1 et 1,3) : caret en dernière ligne visible au-dessus du tiroir ouvert, avant et après insertion d'un chip ; sélection étendue conservée à l'ouverture et au repli ; aucun changement de hauteur du champ ; retour du sélecteur d'images sans double animation ; Maj+flèches cohérent. Échec d'un critère = la règle du tiroir superposé est révisée avant R1a.
flowchart LR R0a["R0a — spike tiroir / BTF2<br/>(device)"] --> R1a R0b["R0b — contrat d'erreur<br/>33 chaînes"] --> R1a R1a["R1a — PostEditor"] --> R1b1["R1b-1 — TopicForm"] R1a --> R1b2["R1b-2 — MP"] R1a --> R2a["R2a — panneau citations"] P["calcul d'aperçu<br/>hors _state.update"] --> R2b["R2b — aperçu en partage"] R1a --> R2b R1b1 --> R4["R4 — feuille unifiée<br/>clôture C9"] R1b2 --> R4 D["géométrie Dialog tranchée"] --> R4
| Lot | Issue | Contenu | Précondition | Preuve |
|---|---|---|---|---|
| R0a | issue à créer | Spike : marge basse de défilement pilotée par le tiroir sur BbcodeTextField, dix libellés sur 360 dp à fontScale 1,3 |
— | vidéo S10e + Android 10 |
| R0b | issue à créer | Type d'erreur unique dans :core:ui, migration des 33 chaînes, feuille comprise |
— | tests JVM |
| R1a | #1450 | App bar, barre d'action, tiroir, bande — PostEditorScreen |
R0a, R0b | C3–C6, C10 |
| R1b-1 | #1451 | TopicFormScreen |
R1a validé sur device | C3–C7, C10 |
| R1b-2 | #1451 | Nouveau MP, réponse MP | R1a validé sur device | C3–C7, C10 |
| R2a | #1452 | Panneau citations (mode cartes) | R1a | C5, C6 |
| R2b | #1452 | Aperçu en partage | ED-65 livré | C6, C8 |
| R4 | #1454 | Feuille unifiée | géométrie Dialog | C6, C8, C9 |
Proposé — 2026-09-23
La vue Éditeur regroupe cinq surfaces d'écriture : répondre ou modifier un message (PostEditorScreen), créer un sujet ou modifier son premier message (TopicFormScreen), écrire un nouveau MP (PrivateMessageComposeScreen), répondre dans une conversation MP (PrivateMessageReplyScreen / MessageEditorComponents), et la feuille de réponse rapide de la vue Topic (QuickReplySheet). Les quatre premières partagent les composants de :core:ui/editor (BbcodeTextField, BbcodeToolbar, SmileyPickerSheet, EditorOptionsSheet, QuoteCards, ArmedSubmitButton, EditorLayoutBudget, EditorUpload) ; la feuille a sa propre implémentation.
L'objectif produit, posé par un testeur sur le topic DEV et repris par l'issue parapluie #1417, est que les bords haut et bas du champ restent visibles et que le texte défile dedans.
Le problème mesuré : sur un téléphone de 360 × 740 dp clavier ouvert, il reste environ 330 dp de fenêtre utile. En 0.60.0 le chrome en consomme 202 (titre d'écran, barre d'outils BBCode, bouton d'aperçu, bandeaux empilés, espacements, barre d'envoi) et laisse environ 128 dp au champ — trois lignes. Le correctif de densité #1432 a garanti un plancher de 160 dp en rangeant le chrome haut dans une zone bornée défilante, au prix d'une barre d'outils rognée au milieu de ses chips et d'un bandeau de brouillon tronqué. Trois testeurs distincts demandent la même chose (densité, #872).
La migration du champ vers BasicTextField 2, initialement prévue en dernier lot, a été livrée en avance (0.59.8) sous la pression d'un bug de sélection ; elle n'est plus un préalable.
Le plan a été cadré en quatre passes croisées (Claude, Sol, Claude Fable, puis Sol contre le code en lecture seule, et une relecture de cohérence par Claude Fable), arbitré par le mainteneur, et maquetté (maquettes, vocabulaire). La relecture contre le code a notamment établi que les cartes de citation sont désactivées par défaut : dans le mode par défaut, la citation est insérée en BBCode éditable dans le champ.
La norme détaillée vit dans la spec de l'éditeur. Cet ADR en fixe les choix structurants.
Une app bar d'éditeur de 48 dp en haut (retour, titre d'écran, contexte en seconde ligne, aperçu), une barre d'action de 56 dp en bas. Entre les deux, le champ prend toute la hauteur restante, avec le plancher de 160 dp existant. Le titre d'écran (titleLarge, 28 sp de hauteur de ligne), la barre d'outils dans le flux et le bouton « Afficher l'aperçu » disparaissent de la colonne.
Sous 400 dp de hauteur de fenêtre mesurée hors clavier (paysage téléphone), l'app bar s'efface avec son titre : l'aperçu passe dans la barre d'action, le retour passe par le geste système. Sinon le chrome fixe vaudrait 136 dp sur les ~150 disponibles.
La barre d'action porte les actions permanentes en icônes seules — format, smileys, image, options — et « Envoyer » en toutes lettres. Chaque icône a un nom accessible et une info-bulle à l'appui long (TooltipBox). Les icônes suivent l'ADR-015.
Les chips BBCode vivent dans un tiroir déplié par l'icône de format. Le tiroir est superposé au bas du champ, jamais inséré dans la colonne, pour que son ouverture et son repli ne déplacent pas le texte (C6). Le champ réserve la hauteur du tiroir comme marge basse de défilement, pour que le caret ne passe jamais dessous (C5). La faisabilité de cette marge sur BbcodeTextField n'est pas acquise : elle fait l'objet d'un spike device bloquant avant tout lot de mise en page.
Tout ce qui était un bandeau empilé — brouillon retrouvé, citations en cartes, compteur d'upload, erreur d'envoi ou d'upload — vit dans une bande de 32 dp ancrée au-dessus de la barre d'action. L'état durable à gauche en pastilles, l'événement du moment à droite en résumé d'une ligne, avec un seul emplacement d'action (« Détails », « Masquer » ou le remède). La bande est absente quand elle n'a rien à dire, ne change jamais de hauteur, et n'apparaît ni ne disparaît sur une frappe.
Conséquence sur le code : dans l'éditeur de post, une erreur cesse de s'effacer au premier caractère tapé ; elle attend « Masquer », le prochain envoi ou la fermeture. Les éditeurs MP conservent déjà leurs erreurs d'envoi : la règle se fixe par cause et par surface, pas globalement.
Le titre et la sous-catégorie d'un nouveau sujet, les destinataires et le sujet d'un nouveau MP quittent la colonne : la seconde ligne de l'app bar les résume, un appui ouvre un panneau d'en-tête. Aucun repli déclenché par le focus ou le clavier (ce serait une seconde animation, C6) ; le corps garde toute sa hauteur (C7 satisfait par construction).
Le mode par défaut (citation en BBCode dans le champ, favorable à l'interleaving, parité web) reste inchangé : aucune pastille, rien à matérialiser à l'envoi. Le mode cartes (opt-in) remplace ses cartes dépliées par une pastille dans la bande et un panneau citations plein écran. Aucune conversion silencieuse d'un mode à l'autre.
L'aperçu cesse d'être une bascule : il s'ouvre à côté du source — dessous en portrait, à droite en paysage et sur tablette — pour que les deux restent comparables. Ouvrir l'aperçu ne touche pas au clavier. Le séparateur s'arrête quand le champ atteint 160 dp ; au-delà, le glissement devient élastique et relâcher ferme le clavier, une seule fois. La position est mémorisée par size class. Un réglage utilisateur a été écarté : un intitulé obscur, et une matrice de test doublée sur un comportement qui ne se prouve que sur appareil.
Les erreurs d'envoi, d'upload et de citation — 33 chaînes réparties en quatre jeux divergents (7 d'upload dans :core:ui, 9 d'envoi dans :feature:editor, 9 dans :feature:topic, 8 dans :feature:messages) — convergent vers un type unique (cause, résumé, détail, action) avant la bande, dans une PR de fondation qui inclut la feuille de réponse rapide.
Un lot = une PR = une build dev = un retour testeurs. Aucun lot ne retire un parcours avant le lot qui le remplace : jusqu'à R2a et R2b, les cartes de citation et la bascule d'aperçu restent accessibles. Promotion en bêta à la fin de la phase complète, pas lot par lot ; entre deux lots, le critère C9 (comportement identique) est explicitement dérogé et annoncé.
| Lot | Contenu | Précondition |
|---|---|---|
| R0a | Spike device : tiroir superposé et marge basse sur BbcodeTextField |
— |
| R0b | Fondation : contrat d'erreur unique, 33 chaînes migrées | — |
| R1a | App bar, barre d'action, tiroir, bande — PostEditorScreen seul |
R0a concluant, R0b |
| R1b-1 | TopicFormScreen |
R1a validé sur device |
| R1b-2 | Nouveau MP et réponse MP | R1a validé sur device |
| R2a | Panneau citations (mode cartes), interleaving | R1a |
| R2b | Aperçu en partage | calcul de l'aperçu hors de _state.update sur les quatre ViewModels |
| R4 | Réponse rapide unifiée : l'éditeur en mode feuille ; clôture de C9 | géométrie du tiroir en fenêtre Dialog tranchée |
Le « Citer la portion sélectionnée » (#381) sort de la phase : la sélection concernée est celle du post lu (SelectionContainer de PostRenderer), pas celle du champ.
TooltipBox, API @ExperimentalMaterial3Api (déjà opt-in dans 32 fichiers du projet).