Le mensonge du mode texte : pourquoi les TUI modernes sont un cauchemar pour l’accessibilité
(xogium.me)- L’hypothèse selon laquelle les applications de terminal sont intrinsèquement accessibles parce qu’elles sont basées sur du texte ne tient plus avec les TUI modernes ; des frameworks comme Ink, Bubble Tea et tcell peuvent créer un environnement plus hostile pour les utilisateurs de lecteurs d’écran
- Un CLI empile sa sortie comme un flux linéaire via
stdin/stdout, dans l’ordre chronologique, alors qu’une TUI traite le terminal comme une grille 2D de cellules de caractères, ce qui rend le suivi du flux plus difficile pour les lecteurs d’écran gemini-clipeut, via Ink, redessiner l’arbre de composants React pour l’adapter à la grille du terminal, en déplaçant le curseur entre spinner, minuterie et historique de conversation, ce qui peut provoquer des lectures répétées, des plantages et des retards de saisie avec Speakup et NVDA- Des outils plus anciens comme
nano,vim,menuconfiget Irssi réduisent le bruit des mises à jour de coordonnées et limitent les interférences avec la ligne de saisie grâce au masquage du curseur, à un focus en colonne unique et à l’usage des zones de défilement VT100 - Pour créer des outils de terminal accessibles, il faut éviter les frameworks d’interface déclaratifs qui traitent le terminal comme un canevas et les redessinages agressifs, et garantir un comportement plus proche d’un flux CLI simple et linéaire
L’idée fausse selon laquelle « c’est accessible parce que c’est du texte »
- L’hypothèse selon laquelle une application exécutée dans un terminal est intrinsèquement accessible ne correspond pas aux conditions réelles d’utilisation
- L’idée qu’un lecteur d’écran puisse facilement interpréter du texte ASCII brut parce qu’il n’y a ni graphismes, ni DOM complexe, ni canevas WebGL s’effondre face aux TUI modernes
- Des frameworks d’interface terminal comme Ink (JS/React), Bubble Tea (Go) et tcell cherchent à améliorer l’expérience développeur (DX), mais peuvent créer un environnement plus hostile pour les utilisateurs aveugles ou malvoyants
- Dans bien des cas, les TUI modernes sont pires en matière d’accessibilité qu’une interface graphique mal implémentée
Différence structurelle entre CLI et TUI
-
CLI : un flux linéaire
- Un CLI fonctionne sur la base de
stdin/stdout; on saisit une commande, le résultat s’ajoute en dessous, puis le curseur descend - Comme la sortie est linéaire et s’accumule dans l’ordre chronologique, cela convient bien à des lecteurs d’écran au niveau noyau comme Speakup
- Un CLI fonctionne sur la base de
-
TUI : une grille 2D
- Une TUI traite la fenêtre du terminal non comme un flux de texte, mais comme une grille 2D où chaque cellule de caractère est utilisée comme un pixel
- En abandonnant le flux temporel au profit d’une mise en page spatiale, on obtient une structure difficile à suivre pour les lecteurs d’écran
Les problèmes mis en évidence par gemini-cli
gemini-cliest un outil écrit avec Node.js et le framework Ink ; en apparence, il ressemble à une simple interface de chat- En interne, Ink essaie d’ajuster l’arbre de composants React à la grille du terminal
- Lorsqu’on l’utilise avec Speakup (Linux) ou NVDA (Windows), l’application ne se contente pas d’échouer simplement : elle inonde continuellement le lecteur d’écran de texte à lire
-
Un écran qui se comporte comme un canevas réactif
- Comme le framework traite l’écran comme un canevas réactif, chaque mise à jour déclenche un redessinage
- Quand l’IA est en train de « réfléchir », il déplace le curseur matériel jusqu’à la position de la minuterie pour mettre à jour un compteur ou un spinner, écrit la nouvelle valeur, puis revient à la position d’origine
- Pour un utilisateur voyant, cette opération passe instantanément ; pour un utilisateur de lecteur d’écran, cela se transforme en répétitions du type « Responding... Time elapsed 1s... Responding... Time elapsed 2s... »
- Comme le curseur se déplace fugitivement entre l’indicateur d’état, le spinner et l’historique de conversation, Speakup tente de lire ce qui se trouve sous le curseur à chaque instant
- Résultat : les mises à jour de la minuterie et des fragments de conversation se mélangent, ce qui rend difficile la concentration sur ce qu’on est réellement en train de saisir
-
Instabilité avec NVDA et lors du collage
- Sous Windows, si l’on ouvre un terminal avec NVDA, qu’on se connecte à une machine Linux en SSH, puis qu’on rejoint une session
screenpour y coller du texte, NVDA peut planter immédiatement ou le système devenir fortement instable - À chaque caractère saisi ou texte collé, l’état de l’application change, et le framework estime qu’il faut rerendre l’interface
- Si l’historique de conversation fait partie de l’état, il peut tenter de redessiner ou de recalculer instantanément la mise en page de milliers de lignes de texte
- Plus il y a de messages dans la conversation, plus le problème survient souvent
- Même la combinaison
Insert+5, utilisée pour éviter les notifications de contenu dynamique, ne permet pas d’échapper au problème
- Sous Windows, si l’on ouvre un terminal avec NVDA, qu’on se connecte à une machine Linux en SSH, puis qu’on rejoint une session
-
La boucle de latence à la saisie
- Quand un framework comme Ink tourne dans un environnement mono-thread comme Node.js, les dégradations de performance augmentent à mesure que l’historique grossit
- Si l’on colle un gros bloc de texte, il faut calculer les différences sur des milliers de lignes
- Le système passe son temps à calculer comment redessiner l’écran, ce qui retarde le traitement de la saisie
- Il peut alors falloir attendre jusqu’à 10 secondes avant qu’un caractère réapparaisse après avoir appuyé sur une touche
Pourquoi les anciens outils fonctionnent
- Si des outils comme
nano,vimetmenuconfigrestent utilisables, ce n’est pas parce qu’ils sont toujours parfaits en matière d’accessibilité - L’essentiel, c’est qu’ils peuvent masquer complètement le curseur ou réduire le bruit produit par le suivi de sa position
-
nanoetvim: masquer le curseur- L’utilisation de
nanoavec une option affichant la position du curseur, comme--constantshow, ou devimsans réglages particuliers, peut dégrader fortement l’usage - Lorsque le curseur est visible et que son suivi est actif, Speakup donne la priorité aux mises à jour de position du curseur plutôt qu’à l’écho des caractères
- Si l’utilisateur saisit « a », il entend « Column 2 » au lieu de « a » ; s’il saisit « b », il entend ensuite « Column 3 »
- Ces anciens outils peuvent être configurés pour supprimer les mises à jour du curseur visuel ou de la barre d’état, ce qui permet au lecteur d’écran de s’appuyer sur le flux de saisie des caractères plutôt que sur les mises à jour de coordonnées
- Les frameworks modernes ne proposent généralement ni mode « no-cursor » ni mode « headless » et partent du principe qu’un curseur visuel est indispensable
- L’utilisation de
-
menuconfig: un focus en colonne unique- Le
menuconfigdu noyau Linux fonctionne parce qu’il maintient strictement un focus en colonne unique - Même avec des bordures et des titres, la zone active reste une liste verticale, et le curseur y reste fixé
- Le curseur ne part pas en bas à droite pour mettre à jour une horloge avant de revenir en haut à gauche pour rafraîchir un titre
- La complexité spatiale reste faible, ce qui évite aux lecteurs d’écran de se perdre
- Le
-
Irssi : l’usage des zones de défilement
- Irssi n’est pas accessible par hasard : c’est un outil de chat qui exploite depuis plus de 20 ans les zones de défilement VT100 via son propre moteur de rendu
- Lorsqu’un nouveau message arrive, il demande au pilote du terminal de « définir les lignes 1 à 23 comme zone de défilement »
- Il envoie ensuite une commande de défilement vers le haut ; le terminal déplace alors le contenu, puis écrit le nouveau texte en bas de cette zone
- Cette approche minimise les interférences avec la ligne de saisie
- Au lieu de réécrire manuellement tous les caractères à l’écran, elle s’appuie sur les capacités matérielles du terminal
- Les frameworks modernes ignorent ces capacités matérielles et choisissent de recalculer les différences d’état de l’écran puis de réécrire les caractères, une méthode plus coûteuse en calcul et hostile à l’accessibilité
Le problème du traitement des issues dans gemini-cli
- Google et les mainteneurs de
gemini-clidonnent l’impression de se soucier de l’accessibilité, mais dans le dépôt, des régressions importantes en la matière restent à l’abandon - Des régressions d’accessibilité comme l’Issue #3435 et l’Issue #11305 ne font l’objet ni de discussions, ni d’une feuille de route, ni de correctifs
- L’Issue #1553 avait été ouverte pour suivre ce type d’échec d’accessibilité, mais elle n’a pas été résolue et a été fermée automatiquement par un bot
- Le bot a clôturé l’issue avec une formule générique expliquant qu’il n’y avait pas eu d’activité depuis longtemps et qu’il fallait gérer le backlog
- Fermer des signalements d’accessibilité au motif que les mainteneurs n’y ont pas touché pendant des mois n’est pas du tri : c’est dissimuler les preuves
- Cela envoie le signal que, si l’on ignore suffisamment longtemps un bug, il finit par ne plus exister, alors que le logiciel reste en pratique inutilisable pour les utilisateurs aveugles ou malvoyants
- Les indicateurs de « Closed Issues » du projet peuvent s’améliorer, mais les problèmes d’accessibilité, eux, ne sont pas résolus
Conclusion : comment créer des outils de terminal accessibles
- Si l’accessibilité compte dans une application pour terminal, il faut cesser d’utiliser des frameworks d’interface déclaratifs qui traitent le terminal comme un canevas
- Les piles TUI dites « modernes » sont optimisées pour permettre aux développeurs d’écrire du code comme avec React, au prix de la capacité de la machine à rendre efficacement du texte
- Si une application ne permet pas à l’utilisateur de masquer le curseur, ou si elle dépend de redessinages agressifs pour afficher des spinners et des minuteries, alors elle devient un outil inaccessible
- Pour les utilisateurs aveugles ou malvoyants, un flux CLI simple et linéaire est bien préférable à une TUI « intelligente » qui introduit de la latence, déverse en continu du texte à lire et disperse le curseur sur tout l’écran
1 commentaires
Avis sur Lobste.rs
Cet article, comme d'autres billets de blog du même genre, dégage fortement une odeur de rédaction assistée par IA
Les LLM adorent ce type de titres : « The Architectural Flaw », « The Lag Loop », « Why The ‘Old Guard’ Works », « The Lost Art of Scrolling Regions », « The ‘Stale Bot’ excuse: A Case Study in Neglect »
Ça aurait pu être un excellent billet de blog, mais si l'auteur a juste balancé un plan dans ChatGPT et s'est arrêté là, tout le monde y perd, autant les lecteurs que l'auteur
C'est pareil quand on qualifie de problème « classique » un souci très spécifique et ponctuel
C'est vraiment déprimant. En résumé, il existe des TUI accessibles comme Irssi, mais les frameworks TUI modernes ignorent ces précédents et reposent sur des diffs de grille et des déplacements de curseur
Les lecteurs d'écran lisent le contenu à l'endroit où le curseur se déplace, donc le résultat devient incohérent ou produit un énorme spam vocal
Je me demande si l'explication technique donnée ici est totalement exacte
En particulier, Ink n'a pendant longtemps pas du tout pris en charge le rendu incrémental, et la plupart des applications qui utilisent Ink ne l'activent toujours pas. Même ce rendu incrémental est basé sur les lignes, donc il ne déplace pas réellement le curseur jusqu'à la position du timer
Pour Gemini CLI, activer le rendu incrémental nécessite l'utilisation du buffer alternatif, et cela est désactivé quand le mode intégré compatible avec les lecteurs d'écran est activé. La documentation de l'option concernée est ici
À noter aussi que rich/textual en Python est souvent bien plus rapide qu'Ink, même sur un langage plus lent et majoritairement mono-thread. Calculer des diffs sur des milliers de lignes n'est pas forcément si lent, et certainement pas au point de prendre 10 secondes
Je ne doute pas que l'expérience utilisateur soit frustrante et cassée, mais la cause précise avancée semble pouvoir venir d'une hallucination de LLM ou d'informations incomplètes. Le rendu incrémental d'Ink, même activé, ne fonctionne pas comme décrit
En réalité, il est très probable que ce soit le redessin complet de l'écran qui perturbe les lecteurs d'écran, et que le redessin ligne par ligne soit mauvais autrement, en faisant relire au hasard des fragments de texte disjoints sans rapport avec le changement
Accuser uniquement les TUI n'est pas juste
Le vrai problème, c'est que la prise en charge de l'accessibilité est médiocre dans presque toute la pile
D'abord, la plupart des émulateurs de terminal à rendu GPU n'utilisent pas du tout les API d'accessibilité fournies par le système. Quand le texte est rendu par le GPU, les outils d'accessibilité ne peuvent pas le « lire » et n'y voient qu'une image. Kitty, Alacritty et WezTerm sont dans ce cas. Mon terminal Ghostty, lui, peut être lu via les API d'accessibilité sur macOS, tout comme iTerm2 et Terminal.app
Ensuite, il n'existe absolument aucune séquence de terminal ni mouvement standard permettant à une TUI de transmettre des informations d'accessibilité à l'émulateur de terminal. Il faudrait l'équivalent de commentaires de type ARIA pour les cellules de terminal, les zones d'exécution et les régions, mais il n'y a aucune tentative en ce sens. Même si une TUI gère bien le curseur, ça posera problème dans beaucoup de cas d'usage
Par exemple, sur Ghostty, nous avons travaillé à intégrer OSC133 avec les API d'accessibilité afin d'exposer chaque prompt de shell, saisie et commande comme des éléments structurellement significatifs, et non comme de simples blocs de texte. Cela montre bien que la spécification des terminaux, les TUI et les émulateurs de terminal doivent fonctionner ensemble
Toute la pile est pourrie, et il y a très peu de gens qui veulent vraiment la corriger. Moi aussi j'ai un temps limité et je fais de mon mieux, mais c'est un sujet immense qui demande même de la politique d'écosystème, donc c'est difficile à porter
En bonus, la réalité à la fois fascinante et terrible, c'est que l'IA aide ici à améliorer l'accessibilité. Beaucoup d'outils d'IA utilisent, ou abusent de, l'API d'accessibilité pour lire la liste des fenêtres et effectuer des saisies. Du coup, davantage d'applications commencent à prendre l'intégration de l'accessibilité beaucoup plus au sérieux à cause des cas d'usage liés à l'IA
Ça m'énerve tous les jours que Claude Code et gemini-cli ne soient pas basés sur readline
Ils ont ajouté quelques frappes similaires, mais il manque toute la longue traîne des raccourcis readline familiers
Anthropic pourrait reconnaître que vouloir « faire comme du développement web » était une erreur et repartir sur readline
L'idée selon laquelle l'expérience de développement familière pour les développeurs qui créent ces outils serait plus importante que l'expérience utilisateur familière pour ceux qui les utilisent est fausse
Il n'existe pratiquement aucune solution tierce connue et bien maintenue. Si on a besoin d'une zone de saisie souple, il faut la construire soi-même dès le départ
À comparer avec l'excellent widget Input de Textual ou, dans l'écosystème JS, la bibliothèque OpenTUI
Je n'aime pas les LLM, donc le fait que l'UI soit mauvaise est personnellement un avantage, mais il y a peut-être une raison pour laquelle ils n'utilisent pas readline
J'ai l'impression que des éditeurs de terminal comme kakoune ou helix auraient du mal à passer les critères d'accessibilité, à moins d'utiliser l'astuce consistant à « masquer le curseur »
Ils risquent malgré tout d'être moins accessibles que VS Code
À part VS Code, quels IDE ou IDE légers multiplateformes sont accessibles ? L'attitude de VS Code, de plus en plus hostile, ne me plaît pas. Peut-être les IDE de JetBrains
L'inconvénient, c'est que même si Emacs lui-même est multiplateforme, emacspeak a peut-être une dépendance assez forte à Linux à cause de la synthèse vocale. Ou peut-être pas. Je ne l'ai jamais essayé sous Windows
Si c'est pour les personnes aveugles, il faut emacspeak ou les outils d'accessibilité de la plateforme pour les déficients visuels
L'accessibilité est un spectre, pas une case à cocher
Links a un mode terminal braille distinct, qui remplace les faux éléments d'interface graphique par un menu plein écran plus simple et transforme la navigation aux flèches en navigation par ligne
Un autre cas intéressant est edbrowse. C'est un navigateur en mode texte créé par Karl Dahlke, qui est aveugle, mais contrairement aux navigateurs web en mode texte plus populaires, il n'utilise pas de TUI et emploie une interface en ligne de commande de style ed
Si c'est bien le framework Ink, ça expliquerait probablement pourquoi certaines CLI utilisent 100 % du CPU et restent figées à redessiner indéfiniment de longs historiques de conversation. C'est dommage