feature/reprise-accessiblite #178

Merged
Nora merged 12 commits from feature/reprise-accessiblite into develop 2026-09-07 21:54:10 +00:00
Owner
No description provided.
Le jeton `--faint` valait #56565e, soit 2,72:1 sur `--ink-900` — très en
dessous des 4,5:1 que WCAG 1.4.3 AA demande pour du texte normal, et ses
21 usages sont tous entre .62rem et .74rem : jamais du « texte large ».

Il portait des choses qui se lisent : les libellés des deux formulaires
visiteurs, la légende du choix de format dans le tunnel d'achat, les
termes de la fiche d'identité, le pied de page entier, le statut
« Épuisée » du musée.

#7c7c85 donne 4,79:1. La bascule exacte est à #797982 (4,50:1) ; deux
crans de plus évitent de vivre sur l'arrondi des calculateurs. Un seul
jeton bougé, pas de `--faint-deco` de compensation pour les deux usages
décoratifs — YAGNI.

Le test lit les valeurs *dans* la feuille de style et couvre aussi
`--muted` et `--accent-dim`, qui passaient déjà : le filet garde
désormais le fichier entier.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SNZ5grqNECq3g4YKERBYWV
Le curseur de l'accueil battait en `infinite`. WCAG 2.2.2 laisse cinq
secondes à un contenu qui clignote, au-delà il faut pouvoir l'arrêter, et
aucun contrôle n'était offert — sur la page la plus vue du site.

`infinite` → `4`. Quatre cycles à 1,05 s font 4,2 s : la machine donne son
signe de vie au chargement, puis le curseur se fige, toujours visible.
Le critère est satisfait par la déclaration, pas par un bouton pause qu'il
aurait fallu construire et rendre accessible à son tour.

Par ailleurs la feuille portait une trentaine de `transition` et zéro
`prefers-reduced-motion` (la galerie 3D, elle, la posait déjà). Un bloc de
garde en fin de fichier les couvre toutes sans les énumérer.

Le test lit la feuille de style : plus aucune déclaration d'animation ne
peut repartir en `infinite` sans le faire rougir.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SNZ5grqNECq3g4YKERBYWV
Trois manquements sur un seul élément, et le contexte les aggravait tous :
un écran plein cadre, aucune autre commande, un visiteur debout qui vient
de scanner un QR code.

- `controls` : la boucle infinie devient arrêtable (WCAG 2.2.2). Rien
  n'est enlevé au kiosque, la lecture démarre toujours seule.
- `aria-label` : le lecteur avait un nom vide (WCAG 4.1.2).
- Alternative au contenu vidéo (WCAG 1.2.3) : « comment c'est fait » est
  déjà cette description, écrite, en six étapes. On la désigne par un lien
  plutôt que d'en rédiger une seconde. En haut à droite, pas en bas : la
  barre de commandes native occupe maintenant le bas du cadre.

Écart assumé, qui partira nommé dans la déclaration d'accessibilité : la
piste musicale du reel n'est pas sous-titrée. Elle n'a aucune parole, et
trois pages d'exposition à durée de vie de quatre mois ne paient pas un
sous-titrage de fond instrumental.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SNZ5grqNECq3g4YKERBYWV
Quatre endroits posaient un `target="_blank"` techniquement correct mais
muet : un lecteur d'écran lit l'`aria-label` et rien d'autre, il annonçait
« Partager sur LinkedIn » sans jamais dire que le contexte allait changer
(RGAA 13.1 / WCAG 3.2.5).

- `share.on` devient « Partager sur %network% (nouvelle fenêtre) » dans
  les 6 locales : une clé, six fichiers, trois boutons réparés d'un coup.
- Crédits musique : le nom de l'auteur seul ne disait ni où l'on va ni
  qu'on en sort. Nouvelle clé `music.author_link` en 6 locales, et `rel`
  gagne `noreferrer` — c'est une cible tierce.
- Back-office (FR only, libellés en dur) : médiathèque et aperçu d'œuvre
  gagnent la mention et `noreferrer`.

Le test des locales interroge `getCatalogue($locale)->defines()` et le
message de la locale elle-même, jamais `trans()` : avec `fallbacks: [fr]`
une clé allemande manquante répondrait en français et le filet passerait
au vert pendant que /de/ reste muet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SNZ5grqNECq3g4YKERBYWV
`data-action="change->filter#submit"` sur les trois <select>, et le
contrôleur Stimulus masquait le bouton Filtrer dès que JS tournait. WCAG
3.2.2 tolère qu'un réglage change le contexte, mais seulement si le
visiteur en a été averti. Il ne l'était pas.

Le cas qui fait mal : sous Firefox/Windows, parcourir un <select> natif
aux flèches émet un `change` à chaque touche. Chaque flèche rechargeait la
page, et l'option visée restait hors d'atteinte — sans bouton visible pour
s'en sortir, puisqu'il venait d'être masqué.

Deux gestes :
- le bouton reste. Le repli sans JS redevient un vrai repli, pour tout le
  monde, et le `data-filter-target` devenu inutile disparaît avec lui ;
- une ligne au-dessus du groupe (`museum.filter.auto_hint`, 6 locales)
  reliée au <form> par `aria-describedby` : c'est précisément
  l'avertissement préalable que le critère demande.

L'auto-soumission est gardée : elle porte le ressenti « instrument de
contrôle » voulu, et annoncée elle est conforme.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SNZ5grqNECq3g4YKERBYWV
Deux manquements qui n'en faisaient qu'un. Aucun lien d'évitement nulle
part, et les 48 `<main>` du dépôt vivaient dans les gabarits enfants sans
le moindre `id` : même en ajoutant le lien, il n'aurait visé personne.

Turbo Drive est désactivé sur ce site. Chaque navigation est un document
neuf, donc au clavier il fallait retraverser la marque, quatre liens de
nav, l'horloge et six langues avant le contenu — à chaque page.

Le repère remonte dans les layouts, une fois pour toutes. C'est le choix
qui coûte le moins de churn et qui répare en passant les quatre pages qui
n'avaient aucun repère (crédits musique, expo/how, error, error404). Les
40 `<main class="…">` enfants redescendent en `<div>` : les classes ne
bougent pas, aucune règle CSS n'est touchée, et le document redevient
valide avec un seul `<main>`.

`tabindex="-1"` n'est pas cosmétique : sans lui, Safari et les anciens
Chrome déplacent le défilement mais pas le focus, et le lecteur d'écran
reste dans l'en-tête.

Le lien est autonome, sans `.visually-hidden-focusable` : Bootstrap n'est
chargé que par les pages qui passent par base.html.twig, et les huit pages
autonomes du back-office portent le même lien. La règle est donc dans
app.css et dans admin.css, chacune à sa palette.

Deux filets : un test fonctionnel par famille de page (premier focusable =
le lien, un seul `<main>`, et il porte l'id), et un garde sur l'arbre des
gabarits — un `<main>` qui n'est pas le repère nommé n'existe plus ici.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SNZ5grqNECq3g4YKERBYWV
Aucun n'est sur le parcours d'achat, tous coûtaient une ligne.

- Sauvegardes : le champ d'import n'avait ni `<label>` ni `aria-label`. Un
  lecteur d'écran annonçait « fichier, bouton » et rien d'autre, sur la
  seule page admin qui porte un vrai formulaire.
- Sauvegardes : les trois colonnes d'action finissaient sur des `<th>`
  vides, donc chaque bouton de ligne était annoncé sous un en-tête sans
  nom. Libellé hors écran — et `.visually-hidden` défini dans admin.css,
  puisque ces pages ne chargent pas Bootstrap.
- Tableau de bord : `role="alert"` sur une bannière rendue avec la page.
  `alert` est fait pour l'injection dynamique, il coupe la parole au
  démarrage. `role="status"` dit la même chose sans interrompre.
- Repli 2D de la galerie 3D : c'est ce que voient les robots et les
  lecteurs d'écran sans WebGL, il n'avait aucun repère de contenu. Un
  `<main>`, pas de lien d'évitement : il n'y a pas d'en-tête à sauter.
- Schéma de la spirale : le `<canvas role="img">` portait un nom figé
  pendant que deux curseurs le redessinaient. L'`aria-label` se met
  maintenant à jour au même endroit que le texte des `<output>`.

Go-live : le JS de `gallery3d` est cuit au build, comme celui du renderer
— rebuild de l'image puis `--force-recreate`. Rien d'autre.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SNZ5grqNECq3g4YKERBYWV
C'est le seul manquement de l'audit qui est une obligation en soi. L'EAA
impose la publication d'une déclaration, que le site soit conforme ou non :
un site parfaitement accessible sans déclaration est en infraction, un site
imparfait avec une déclaration honnête ne l'est pas. Il n'y en avait
aucune, et `grep -ri accessibilit` sur les gabarits, les traductions et la
config ne rendait rien.

Une page statutaire de plus, exactement comme les trois autres : même
module, même contrôleur (6 → 7 méthodes, sous le plafond mago), même
dispatcher par locale, mêmes bannières de traduction de travail sur les
cinq versions non FR. Lien dans la colonne « Légal » du pied de page.

Elle se joue en dernier exprès : écrite avant les correctifs, elle aurait
annoncé « non conforme » et vingt manquements, pour être réécrite une
semaine plus tard. Écrite après A→F, elle annonce « partiellement
conforme » et une liste courte d'écarts assumés, et elle est vraie le jour
où elle sort.

Les cinq points qu'EN 301 549 §12 exige y sont : niveau de conformité,
liste nommée de ce qui ne l'est pas et pourquoi, mécanisme de retour (le
module Feedback existait déjà, on le désigne), voie de recours (Défenseur
des droits), date et méthode d'évaluation.

Écarts déclarés : le reel de l'expo non sous-titré, la galerie 3D WebGL
dont le repli 2D est l'alternative, l'horloge d'en-tête, les cibles
tactiles de la nav (critère AAA en WCAG 2.1, hors norme), le back-office
hors périmètre.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SNZ5grqNECq3g4YKERBYWV
docs: la mise en conformité EAA rejoint les go-live à jouer
Some checks failed
CI / Renderer - couverture JS (pull_request) Has been skipped
CI / Renderer - audit des dépendances JS (pull_request) Successful in 1m21s
CI / Renderer - tests + lint JS (pull_request) Successful in 5m37s
CI / PHP - tests + static + lint (pull_request) Failing after 2m42s
c555478357
Tranches A→H livrées. La seule contrainte d'ordre restante tient en une
ligne, et elle ne concerne que le repli 2D : le JS de `gallery3d` est cuit
au build, comme celui du renderer. Rebuild puis `--force-recreate`.

Les écarts assumés sont recopiés ici pour qu'on ne les redécouvre pas dans
six mois en croyant à des bugs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SNZ5grqNECq3g4YKERBYWV
update ci
Some checks failed
CI / Renderer - couverture JS (pull_request) Has been skipped
CI / PHP - tests + static + lint (pull_request) Failing after 42s
CI / Renderer - audit des dépendances JS (pull_request) Successful in 47s
CI / Renderer - tests + lint JS (pull_request) Successful in 4m49s
395a627918
rollback CI
All checks were successful
CI / Renderer - couverture JS (pull_request) Has been skipped
CI / Renderer - audit des dépendances JS (pull_request) Successful in 1m0s
CI / PHP - tests + static + lint (pull_request) Successful in 7m26s
CI / Renderer - tests + lint JS (pull_request) Successful in 6m6s
fb74f3e0f7
Nora merged commit 7d7b834677 into develop 2026-09-07 21:54:10 +00:00
Nora deleted branch feature/reprise-accessiblite 2026-09-07 21:54:11 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Nora/ArtAutomatic!178
No description provided.