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.

Contrat de l'éditeur

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.

1. Périmètre

1.1 Les cinq surfaces

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.

1.2 Les deux modes de citation

Le rendu des citations est choisi une fois à l'ouverture d'une surface (observeQuoteCardsEnabled().first()), jamais recâblé en cours de session :

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.

1.3 Hors périmètre de cette phase

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).

2. Glossaire

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.

3. Géométrie

3.1 Colonne

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 ».

3.2 Budget cible (360 × 740 dp, clavier ouvert, fenêtre utile 330 dp)

É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).

4. Règles par zone

4.1 App bar d'éditeur

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.

4.2 Champ

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).

4.3 Tiroir de format

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). BbcodeTextField expose son ScrollState mais 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).

4.4 Barre d'action

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.

4.5 Bande de contexte

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

4.6 Panneau citations (mode cartes)

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.

4.7 Aperçu en partage

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).

4.8 Feuilles smileys et options

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.

5. Transitions et collisions

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.

6. Accessibilité

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é.

7. Contrats par surface

7.1 Éditeur de post (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.

7.2 Formulaire de sujet (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.

7.3 Éditeurs MP

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.

7.4 Feuille de réponse rapide (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.

8. Erreurs

8.1 Contrat

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.

8.2 Résumés

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.

9. Critères d'acceptation

Repris de #1417, vérifiés sur appareil et filmés (S10e à 360 dp et Android 10).

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.

10. Traçabilité

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

11. Lots

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

12. Questions ouvertes

  1. Disposition du tiroir (ED-24) : rangées fixes ou défilement avec indice — à trancher par mesure en R0a.
Décision associée

ADR-019 — Refonte de la vue Éditeur (#1417) : chrome fixe, champ élastique, bande de contexte unique

Statut

Proposé — 2026-09-23

Contexte

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.

Décision

La norme détaillée vit dans la spec de l'éditeur. Cet ADR en fixe les choix structurants.

1. Chrome à hauteur fixe, champ élastique

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.

2. Barre d'action en icônes, tiroir de format superposé

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.

3. Une bande de contexte unique, de hauteur constante

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.

3 bis. Les en-têtes quittent la colonne

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).

4. Les deux modes de citation sont préservés

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.

5. Aperçu en partage, cran physique, pas de réglage

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.

6. Un seul contrat d'erreur

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.

7. Découpage

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.

Conséquences

Alternatives considérées