Aller au contenu
Mocky/Docs v0.2
Fonctionnalités

SEO et accessibilité

9 min de lecture

La passe qualité, à côté, demande si un écran ressemble à du remplissage écrit par une machine. Celle-ci pose une autre question, plus difficile à trancher honnêtement : le balisage de l'écran est-il correct — un moteur de recherche peut-il le lire, et une personne au lecteur d'écran peut-elle s'en servir.

C'est un rapport séparé, une note séparée et une boucle de correction séparée. Le reste de cette page explique surtout pourquoi.


Où ça se trouve#

Ouvrez un projet, puis le bouton Audit de la barre d'outils. Le panneau s'ouvre à droite avec chaque écran du projet en vignette. Choisissez-en un, cliquez sur Évaluer.

Rien ne se déclenche automatiquement. Rien ne tourne à la génération. Ouvrir le panneau coûte une lecture de localStorage.

Partir abandonne l'analyse. Fermer le panneau, ou sélectionner un autre écran — depuis les vignettes ou depuis le canevas — annule une évaluation en cours. Une analyse approfondie est un appel modèle de plusieurs secondes, et elle n'avait aucun AbortController : elle continuait d'être payée après la fermeture du panneau, et le spinner qu'elle laissait derrière elle se dessinait par-dessus le rapport de l'écran que l'utilisateur venait justement de demander. Le panneau montre le rapport d'un seul écran : une analyse qui tourne pour un autre ne peut que cacher celui qu'on voulait voir.


Les deux moitiés#

Déterministe, dans le navigateur#

Toutes les questions de structure sont tranchées localement, à partir d'un AST, sans le moindre appel réseau : src/lib/audit/inspect.ts parcourt l'écran une fois et remet à src/lib/audit/rules.ts un résumé à tester.

C'est l'arrangement inverse de la passe qualité, qui détecte côté serveur — et c'est délibéré. Celle-ci enveloppe impeccable, un module Node qui lit node:fs à l'import et ne peut pas être empaqueté pour un navigateur. Ces règles-ci sont celles de Mocky, et les exécuter ici rend l'évaluation instantanée, gratuite, et opérante backend éteint. C'est ce qui permet de l'offrir par défaut et de mettre le modèle derrière un second bouton.

Les règles s'appliquent à un AST et jamais au texte du source (invariant I1). se trouve dans une chaîne, dans un bloc que le modèle a laissé en commentaire, et dans un exemple de code que l'écran affiche lui-même. Un rapport affirmant que trois images n'ont pas de alt alors que ces trois images n'existent pas est pire que pas de rapport : il envoie quelqu'un chercher pour rien, et il décrédibilise les points qui sont réels.

Jugée, sur le serveur#

POST /api/muse/audit pose à un modèle les questions qu'une règle ne peut pas trancher : le texte alternatif décrit-il ce que l'image montre, un titre décrit-il la section qui le suit, ce libellé de lien voudrait-il encore dire quelque chose lu hors contexte. Le catalogue est server/muse/quality/audit-questions.js.

C'est derrière la case Analyse approfondie parce que cela consomme des jetons. L'écran voyage comme une donnée, dans le tour user, sous un en-tête qui le dit (invariant Q5), et un verdict citant une question jamais posée est écarté plutôt qu'affiché.

Si la passe ne peut pas tourner — pas de modèle, un délai dépassé, une réponse illisible — le rapport déterministe est renvoyé avec une notice. Perdre un rapport complet et gratuit parce qu'un appel réseau a échoué serait absurde (invariant Q1).


Ce que la note ne veut pas dire#

Chaque dimension est notée sur 100 et chacune déclare confidence: 'partial'. Ce n'est pas de la modestie, c'est de l'arithmétique.

Une analyse du source seul connaît le balisage et rien du rendu. Elle ne peut pas mesurer :

  • le contraste des couleurs tel qu'il est peint,
  • la visibilité du focus,
  • la taille des cibles tactiles,
  • l'ordre de tabulation réel,
  • le comportement à d'autres largeurs.

C'est la moitié de WCAG. Donc rien ici ne revendique un niveau de conformité, et le panneau imprime la réserve à côté du nombre plutôt que dans une infobulle — un nombre dont personne ne lit la réserve est un nombre qui sera cité sans elle.

Deux de ces points ont depuis reçu une réponse partielle, et le rapport prend soin de dire à quel point elle l'est. Le contraste est calculé là où un élément pose lui-même sa couleur de texte et son fond, et la hauteur d'une cible tactile est déduite des classes qui la déclarent. Ni l'un ni l'autre n'est une mesure sur une page rendue : c'est pourquoi la famille « Couleur » annonce une confiance moyenne et la famille « Cibles tactiles » une confiance basse, et pourquoi aucune des deux ne dit quoi que ce soit des éléments qu'elle n'a pas pu lire.

Une note de 100 signifie « rien à signaler dans ce que ceci peut voir ». Elle ne signifie pas accessible.

La notation compte des règles distinctes, jamais des éléments. Vingt images sans alt, c'est une seule chose à corriger ; la compter vingt fois noierait tous les autres points et rendrait le nombre inutile. Le panneau liste tout de même chaque élément fautif.


Le détail par famille#

Sous les deux notes, le panneau liste huit familles — titres, images, liens et contrôles, formulaires, couleur, typographie, cibles tactiles, structure — avec chacune sa note. Ce sont les mêmes points, découpés une seconde fois : seo et a11y répondent à comment se comporte cet écran, les familles répondent à où aller corriger. Les deux ne se recouvrent pas. Un bouton sans nom et une image sans alt sont deux erreurs d'accessibilité qui n'ont rien à voir l'une avec l'autre, tandis que img-alt et img-alt-redundant sont un après-midi dans un seul fichier.

L'ordre est figé dans RULE_FAMILIES et volontairement pas dérivé du rapport. Une liste triée par note se réorganise à chaque réévaluation, et la ligne qu'on était en train de lire se déplace sous le curseur entre deux clics sur Évaluer.

Chaque ligne porte une confiance, et celle-ci décrit la méthode, pas le passage :

FamillesConfiancePourquoi
titres, images, liens, formulaires, structureélevéedu balisage — un alt, un niveau de titre, une paire for/id sont dans le source ou n'y sont pas
couleurmoyenneseuls les éléments qui posent eux-mêmes leurs deux couleurs ont pu être comparés
typographie, cibles tactilesbassepy-2 plus une interligne est une affirmation sur une boîte que personne n'a rendue

Une ligne peut ne porter aucun nombre, et c'est la raison d'être de la section. Une famille qui n'a rien à examiner affiche sans objet ; une famille dont le source ne décrit pas assez les sujets affiche non mesuré. Aucune des deux n'affiche 100. Un écran sans formulaire et un écran dont les formulaires sont irréprochables produisent la même liste vide, et un seul des deux a mérité la note pleine — invariant Q4, appliqué un cran sous la réserve du panneau lui-même. Les deux mots restent distincts parce qu'ils énoncent deux faits différents : « il n'y avait rien à vérifier ici », et « il y avait quelque chose, et le code n'en dit pas assez pour trancher ».

Les points jugés par le modèle sont rangés eux aussi. Leurs identifiants n'existent que dans server/muse/quality/audit-questions.js : chaque question y déclare donc sa famille, et le navigateur écarte tout nom qu'il ne sait pas placer — la même discipline que celle qui écarte un verdict citant une question jamais posée. Sans cela, un constat du modèle sur les titres retirerait des points au SEO pendant que la ligne « Titres » afficherait encore 100, et le détail contredirait le nombre juste au-dessus de lui.


Le document exporté#

Un écran Mocky est un composant React autonome. Il n'a ni , ni </code>, ni URL, ni routage — le prompt de génération l'interdit. La moitié « document » du SEO ne peut donc pas être notée sur un écran, puisqu'elle n'y existe pas. Elle existe dans ce que l'export écrit, et elle est vérifiée à part, en bas du panneau, une fois pour le projet au lieu d'être répétée sur chaque écran.</p> <p>Écrire cette vérification a mis au jour trois vrais défauts dans <code>indexHtml</code>, tous invisibles parce que personne ne lit le <code><head></code> de son propre export :</p> <div class="lm-table"><table><thead><tr><th>Avant</th><th>Maintenant</th></tr></thead><tbody><tr><td><code><title></code> valait le <strong>nom de paquet slugifié</strong> (<code>my-shop</code>)</td><td>le vrai nom du projet</td></tr><tr><td><code>lang="en"</code> en dur, y compris sur les projets français</td><td>suit la langue de l'interface</td></tr><tr><td>aucune <code><meta name="description"></code></td><td>écrite quand le projet nomme un produit</td></tr></tbody></table></div> <p><code>lang</code> n'était pas un défaut cosmétique. C'est ce qui indique au lecteur d'écran quelle langue prononcer : tous les exports français étaient lus à voix haute en anglais.</p> <hr> <h2 id="corriger">Corriger<a class="lm-anchor" href="#corriger" aria-label="Lien vers cette section">#</a></h2> <p>Chaque point exigible porte un bouton <strong>Corriger</strong>, et il y a un <strong>Tout corriger</strong>. Les deux passent par <code>runPolishLoop</code> — les mêmes quatre conditions d'arrêt que la passe qualité, parce que « cette passe a-t-elle vraiment abouti » mérite d'être résolu une seule fois — avec <code>AUDIT_FIX_PROMPT</code> et l'audit comme vérification.</p> <p>Ce prompt est un troisième frère de <code>FIX_PROMPT</code> et <code>POLISH_PROMPT</code>, et fusionner deux d'entre eux casse les deux. L'instruction centrale de chacun est fausse pour les autres :</p> <div class="lm-table"><table><thead><tr><th></th><th>Dit</th><th>Parce que</th></tr></thead><tbody><tr><td>Réparation</td><td>corrige seulement l'erreur, ne restyle pas</td><td>un plantage n'est pas un problème de design</td></tr><tr><td>Peaufinage</td><td>corrige ces points, un changement visuel est attendu</td><td>un point de « slop » <strong>est</strong> un problème de style</td></tr><tr><td>Correction d'audit</td><td>corrige le balisage, l'écran doit rester identique</td><td>une passe de sémantique qui redessine a échoué même si tous les points ont disparu</td></tr></tbody></table></div> <p><strong>« Corriger » est indisponible pendant une génération.</strong> Une correction est une mutation d'écran, et toutes les mutations d'écran de Mocky partagent un seul <code>AbortController</code>, un seul indicateur d'occupation et une seule surcouche de progression — une deuxième ne tourne donc pas à côté de la première, elle l'écrase. C'était la seule des cinq à ne pas le vérifier, et lancée pendant une régénération elle prenait le bouton « Stop » en otage : « Stop » annulait la correction, la régénération devenait inarrêtable, et la surcouche de progression passait sur l'écran visé par le panneau d'audit. Les boutons sont maintenant désactivés tant qu'autre chose tourne, et ils disent pourquoi.</p> <p>Les points indicatifs ne sont jamais corrigés automatiquement. Certaines règles contredisent légitimement un parti pris : une page d'accueil peut vraiment être une seule <code><section></code> sans <code><nav></code>, et y forcer des repères serait du balisage ajouté pour faire plaisir au rapport. Même raisonnement que <code>policy.js</code> dans la passe qualité (invariant Q2).</p> <p>Après une correction, le rapport de l'écran est jeté plutôt que conservé. Il décrit un source qui n'existe plus, et une note périmée affichée à côté d'un code modifié est pire que pas de note du tout.</p> <p><code>Screen.quality</code> n'est <strong>pas</strong> écrit par cette passe. Ce champ enregistre l'audit de design sur 20, et y mettre une note d'accessibilité ferait partager un seul nombre à deux mesures différentes.</p> <hr> <h2 id="fichiers">Fichiers<a class="lm-anchor" href="#fichiers" aria-label="Lien vers cette section">#</a></h2> <div class="lm-table"><table><thead><tr><th>Chemin</th><th>Rôle</th></tr></thead><tbody><tr><td><code>src/lib/audit/inspect.ts</code></td><td>parcours AST → faits sur le balisage</td></tr><tr><td><code>src/lib/audit/rules.ts</code></td><td>le catalogue de règles et la notation</td></tr><tr><td><code>src/lib/audit/index.ts</code></td><td><code>auditScreen</code>, <code>auditExport</code>, le client de la passe approfondie</td></tr><tr><td><code>src/components/AuditPanel.tsx</code></td><td>le panneau, le choix par vignettes, les boutons de correction</td></tr><tr><td><code>server/muse/quality/audit-questions.js</code></td><td>les questions jugées</td></tr><tr><td><code>server/muse/quality/audit-judge.js</code></td><td>un appel modèle, verdicts filtrés</td></tr><tr><td><code>src/i18n/parts/audit.ts</code></td><td>tous les noms et descriptions de règles, dans les deux langues</td></tr></tbody></table></div> </div> <div class="lm-end"><div class="lm-fb" data-endpoint=""><span class="lm-fb-q">Cette page vous a-t-elle aidé ?</span><div class="lm-fb-btns"><button class="lm-btn lm-btn-sm" type="button" data-lm="fb" data-value="yes"><svg class="lm-i" aria-hidden="true" focusable="false"><use href="#lm-i-thumbUp"/></svg>Oui</button><button class="lm-btn lm-btn-sm" type="button" data-lm="fb" data-value="no"><svg class="lm-i" aria-hidden="true" focusable="false"><use href="#lm-i-thumbDown"/></svg>Non</button></div><form class="lm-fb-form" hidden><label for="lm-fb-text">Qu’est-ce qui vous a manqué ?</label><textarea id="lm-fb-text" rows="3"></textarea><button class="lm-btn lm-btn-sm lm-btn-primary" type="submit">Envoyer</button></form><p class="lm-fb-thanks" hidden>Merci pour votre retour !</p></div><div class="lm-edit"><a href="https://github.com/PetitOursManu/Mocky/edit/main/docs/fr/seo-accessibility.md" target="_blank" rel="noopener"><svg class="lm-i" aria-hidden="true" focusable="false"><use href="#lm-i-pencil"/></svg>Modifier cette page</a><a href="https://github.com/PetitOursManu/Mocky/issues" target="_blank" rel="noopener"><svg class="lm-i" aria-hidden="true" focusable="false"><use href="#lm-i-flag"/></svg>Signaler un problème</a></div><nav class="lm-pager" aria-label="Précédent / Suivant"><a class="lm-pager-a" href="/fr/video-export/" rel="prev"><small><svg class="lm-i" aria-hidden="true" focusable="false"><use href="#lm-i-left"/></svg>Précédent</small><strong>Motion Ultra — films</strong></a><a class="lm-pager-a lm-pager-next" href="/fr/deployment/" rel="next"><small>Suivant<svg class="lm-i" aria-hidden="true" focusable="false"><use href="#lm-i-right"/></svg></small><strong>Déploiement</strong></a></nav></div> </article> </div> <footer class="lm-foot"> <span class="lm-foot-mark"><svg class="lm-mark" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="4.5" fill="var(--lm-primary)"/><circle cx="12" cy="12" r="9" fill="none" stroke="var(--lm-primary)" stroke-opacity=".35" stroke-width="1.6"/></svg>Documentation propulsée par <strong>Lumy</strong></span> <span class="lm-grow"></span> <a href="/llms.txt">llms.txt</a> <button type="button" data-lm="ai-tools">MCP</button> </footer> </main> <aside class="lm-toc" aria-label="Sur cette page"> <p class="lm-toc-t"><svg class="lm-i" aria-hidden="true" focusable="false"><use href="#lm-i-list"/></svg>Sur cette page</p><div class="lm-toc-track"><span class="lm-toc-mark" aria-hidden="true"></span><ul><li><a href="#ou-ca-se-trouve" data-id="ou-ca-se-trouve">Où ça se trouve</a></li><li><a href="#les-deux-moities" data-id="les-deux-moities">Les deux moitiés</a></li><li class="lm-toc-sub"><a href="#deterministe-dans-le-navigateur" data-id="deterministe-dans-le-navigateur">Déterministe, dans le navigateur</a></li><li class="lm-toc-sub"><a href="#jugee-sur-le-serveur" data-id="jugee-sur-le-serveur">Jugée, sur le serveur</a></li><li><a href="#ce-que-la-note-ne-veut-pas-dire" data-id="ce-que-la-note-ne-veut-pas-dire">Ce que la note ne veut pas dire</a></li><li><a href="#le-detail-par-famille" data-id="le-detail-par-famille">Le détail par famille</a></li><li><a href="#le-document-exporte" data-id="le-document-exporte">Le document exporté</a></li><li><a href="#corriger" data-id="corriger">Corriger</a></li><li><a href="#fichiers" data-id="fichiers">Fichiers</a></li></ul></div> <div class="lm-toc-extra"> <button type="button" data-lm="ask"><svg class="lm-i" aria-hidden="true" focusable="false"><use href="#lm-i-sparkles"/></svg>Poser une question sur cette page</button> <button type="button" data-lm="source"><svg class="lm-i" aria-hidden="true" focusable="false"><use href="#lm-i-file"/></svg>Voir la source</button> <a href="https://github.com/PetitOursManu/Mocky/edit/main/docs/fr/seo-accessibility.md" target="_blank" rel="noopener"><svg class="lm-i" aria-hidden="true" focusable="false"><use href="#lm-i-pencil"/></svg>Modifier cette page</a> </div> </aside> </div> <div class="lm-overlay lm-search" id="lm-search" hidden><div class="lm-search-card" role="dialog" aria-modal="true" aria-label="Rechercher"><div class="lm-search-in"><svg class="lm-i" aria-hidden="true" focusable="false"><use href="#lm-i-search"/></svg><input id="lm-search-input" type="search" autocomplete="off" spellcheck="false" placeholder="Rechercher dans la doc…" aria-controls="lm-search-res"><kbd>Esc</kbd></div><div class="lm-search-res" id="lm-search-res" role="listbox"></div><div class="lm-search-foot"><span><kbd>↑</kbd><kbd>↓</kbd>naviguer</span><span><kbd>↵</kbd>ouvrir</span><span class="lm-grow"></span><span>Tolère les fautes de frappe</span></div></div></div><div class="lm-overlay lm-sheet-wrap" id="lm-ai" hidden><aside class="lm-sheet" role="dialog" aria-modal="true" aria-labelledby="lm-ai-title"><header class="lm-sheet-head"><svg class="lm-i" aria-hidden="true" focusable="false"><use href="#lm-i-sparkles"/></svg><h2 id="lm-ai-title">Assistant de la doc</h2><span class="lm-grow"></span><button class="lm-icon-btn" type="button" data-lm="close" aria-label="Fermer"><svg class="lm-i" aria-hidden="true" focusable="false"><use href="#lm-i-x"/></svg></button></header><div class="lm-ai-msgs" id="lm-ai-msgs"><p class="lm-ai-welcome">Posez n’importe quelle question sur cette documentation. Les réponses viennent de ces pages, avec des liens vers elles.</p></div><form class="lm-ai-form" id="lm-ai-form"><input id="lm-ai-input" autocomplete="off" placeholder="Posez votre question…" aria-label="Posez votre question…"><button class="lm-btn lm-btn-primary" type="submit" aria-label="Envoyer"><svg class="lm-i" aria-hidden="true" focusable="false"><use href="#lm-i-send"/></svg></button></form><p class="lm-ai-note">Répond uniquement à partir de cette doc. Pas besoin de compte.</p></aside></div> <div class="lm-overlay" id="lm-dialog" hidden><div class="lm-dialog" role="dialog" aria-modal="true" aria-labelledby="lm-dialog-title"><div class="lm-dialog-head"><h2 id="lm-dialog-title"></h2><button class="lm-icon-btn" type="button" data-lm="close" aria-label="Fermer"><svg class="lm-i" aria-hidden="true" focusable="false"><use href="#lm-i-x"/></svg></button></div><div class="lm-dialog-body" id="lm-dialog-body"></div></div></div> <div class="lm-lightbox" id="lm-lightbox" hidden role="dialog" aria-modal="true" aria-label="Agrandir"><div class="lm-lb-bar"><span>Pincez ou faites défiler pour explorer l’image.</span><button class="lm-icon-btn" type="button" data-lm="close" aria-label="Fermer"><svg class="lm-i" aria-hidden="true" focusable="false"><use href="#lm-i-x"/></svg></button></div><div class="lm-lb-scroll"><img id="lm-lb-img" alt=""></div></div> <div class="lm-gloss" id="lm-gloss" role="tooltip" hidden></div> <div class="lm-toast" id="lm-toast" role="status" aria-live="polite"></div> <script type="application/json" id="lm-data">{"lang":"fr","base":"/","slug":"seo-accessibility","title":"SEO et accessibilité","search":"/_lumy/search-fr.json?v=94fef442","source":"/fr/seo-accessibility/index.md","strings":{"copy":"Copier","copied":"Copié","pageCopied":"Page copiée en Markdown","linkCopied":"Lien de la section copié","noResults":"Aucun résultat pour","suggestions":"Suggestions","resultPages":"Pages","resultSections":"Sections","resultGlossary":"Glossaire","glossary":"Glossaire","glossaryMore":"Ouvrir le glossaire","sourceTitle":"Source de la page","sourceIntro":"C’est tout ce que l’auteur écrit : du Markdown, plus quelques blocs :::.","aiToolsTitle":"Utiliser cette doc avec un assistant IA","aiToolsIntro":"Donnez à un assistant toute la documentation en texte brut, ou connectez-le par MCP.","llmsTxt":"Index de toutes les pages, pour les LLM","llmsFull":"Toute la documentation dans un seul fichier","mcpRemote":"Connectez un client MCP à ce site :","sources":"Sources","assistantError":"L’assistant n’a pas pu répondre. Réessayez dans un instant.","feedbackThanks":"Merci pour votre retour !","stepToggle":"Marquer l’étape {n} comme faite","signIn":"Se connecter","signOut":"Se déconnecter","dashboard":"Tableau de bord","account":"Compte"},"glossary":{},"glossaryUrl":"/fr/glossary/","assistant":{"endpoint":"/_lumy/api/ask"},"feedback":{"endpoint":"/_lumy/api/feedback"},"llms":{"index":"/llms.txt","full":"/llms-full.txt"},"mcp":{"url":"https://mocky-docs.emanuelvigreux.fr/_lumy/mcp"},"server":{"registration":false,"private":false},"dev":false}</script> </body> </html>