Commit d0510d6a authored by Jordan Grossemy's avatar Jordan Grossemy
Browse files

Deux écarts trouvés en appliquant le cahier au produit

La campagne s'est arrêtée à la connexion, faute de session — mais les écrans du
tunnel d'entrée, eux, se testent sans compte. Deux écarts sur quatre écrans.

── Un écran de partage sans titre, dans trois de ses quatre états

Le lien de partage s'adresse à quelqu'un qui n'a pas de compte : c'est parfois
le seul écran de Kastell qu'une personne verra. Trois de ses états — lien
incomplet, lien expiré ou révoqué, ouverture en cours — n'avaient aucun titre de
page. Un lecteur d'écran y arrivait sans savoir où il était.

Et la garantie que j'avais écrite au lot 4 ne l'a pas vu, pour deux raisons qui
méritent d'être dites. « Partage.tsx » manquait à sa liste de six écrans. Mais
même présent, il aurait passé : le contrôle cherche « <h1 » dans le fichier, et
le fichier en portait un — dans l'état de succès. Un titre caché derrière une
condition échappe à un contrôle qui lit le source. C'est précisément ce que ce
cahier de test existe pour attraper, et il l'a attrapé au premier essai.

── Un bandeau d'avertissement illisible en thème sombre

« Instance de test — ne pas utiliser pour de la production » portait
« color: #fff » en dur. En thème clair, blanc sur le rouge critique donne
6,55:1. En thème sombre, « --crit » est un saumon clair fait pour porter du
texte, non pour en recevoir : 2,71:1, sous le seuil AA. Un avertissement
permanent qu'on ne lit pas est un avertissement perdu.

La teinte du texte suit désormais le fond de page — 5,52:1 en clair, 7,01:1 en
sombre. Et la paire entre dans les quinze que la mesure de contraste contrôle à
chaque version : elle n'y était pas, ce qui explique qu'une couleur figée en dur
ait pu tenir.

── Ce que la campagne a validé, sans écart

Connexion et inscription : titre de page, champs tous étiquetés, quatre sur
quatre à l'inscription, aucun débordement à 1440 ni 596 px, cibles à
quarante-quatre points, texte à 15,2:1.

Clavier seul sur la connexion : sept éléments atteignables, tous avec un contour
de focus visible, ordre de tabulation logique.

Bilinguisme du tunnel : « lang="en" », aucun texte français résiduel, et les
refus du serveur traduits — « Session required. » contre « Session requise. »
selon l'en-tête, seuil de validation conservé.

Atterrissage sur un jeton inconnu : le refus est dit, sans divulguer si le lien
a existé.

Co-Authored-By: Claude (RCA)
parent d504ee1b
Loading
Loading
Loading
Loading
Loading
+12 −1
Changes for apps/api/src/verification/accessibilite.ts: 12 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -60,6 +60,10 @@ const PAIRES: [string, string, string, number][] = [
  ["pastille succès", "ok", "ok-doux", 4.5],
  ["pastille accent", "accent", "accent-doux", 4.5],
  ["bordure de champ", "ligne-forte", "surface", 3],
  // Trouvé par la campagne de test, non par ce contrôle : le bandeau
  // d'instance portait « color: #fff » en dur, ce qui tenait en thème clair
  // et tombait à 2,7:1 en sombre. La paire est désormais mesurée.
  ["bandeau d'environnement", "fond", "crit", 4.5],
];

export async function verifierAccessibilite(r: Rapport): Promise<void> {
@@ -98,8 +102,15 @@ export async function verifierAccessibilite(r: Rapport): Promise<void> {
  // la page de connexion, un lecteur d'écran annonçait un formulaire sans dire
  // de quelle page il s'agissait. On contrôle le tunnel, parce que c'est le
  // seul endroit qu'on atteint sans être passé par ailleurs.
  // « Partage.tsx » manquait à cette liste, et la campagne de test l'a trouvé :
  // l'écran d'un lien de partage invalide n'avait aucun titre de page. À noter
  // pour qui lira ce contrôle : il porte sur le fichier, non sur chaque état
  // rendu. Un écran dont le titre se cache derrière une condition y échappe —
  // c'est exactement ce qui s'est produit ici, le titre n'existant que dans
  // l'état de succès. Le reste se voit à l'écran, pas au grep.
  const tunnel = ["ecrans/Connexion.tsx", "ecrans/Inscription.tsx",
    "ecrans/Amorce.tsx", "ecrans/SecondFacteur.tsx", "ecrans/Atterrissage.tsx"];
    "ecrans/Amorce.tsx", "ecrans/SecondFacteur.tsx", "ecrans/Atterrissage.tsx",
    "ecrans/Partage.tsx"];
  const sansTitre: string[] = [];
  for (const f of tunnel) {
    const source = await readFile(`${web}${f}`, "utf8");
+10 −3
Changes for apps/web/src/ecrans/Partage.tsx: 10 added lines, 3 removed lines.
Original line number Diff line number Diff line
@@ -48,7 +48,7 @@ export function Partage() {
    return (
      <div className="centre">
        <div className="carte alerte" style={{ maxWidth: 460 }}>
          <strong>{t("Lien incomplet.")}</strong>
          <h1 className="titre-plat">{t("Lien incomplet.")}</h1>
          <p style={{ marginTop: 6, fontSize: 13.5 }}>
            {t("Vérifiez que vous "
                                                                     + "avez copié l’adresse "
@@ -60,14 +60,21 @@ export function Partage() {
  }

  if (isLoading) {
    return <div className="centre"><p className="discret">{t("Ouverture du partage…")}</p></div>;
    return (
      <div className="centre">
        {/* Un titre dans chaque état, y compris celui-ci : sans lui, un
            lecteur d'écran arrive sur une page sans nom. */}
        <h1 className="annonce">{t("Document partagé")}</h1>
        <p className="discret">{t("Ouverture du partage…")}</p>
      </div>
    );
  }

  if (error || !data) {
    return (
      <div className="centre">
        <div className="carte alerte" style={{ maxWidth: 460 }}>
          <strong>{t("Ce lien ne donne plus accès au document.")}</strong>
          <h1 className="titre-plat">{t("Ce lien ne donne plus accès au document.")}</h1>
          <p style={{ marginTop: 6, fontSize: 13.5 }}>
            {error instanceof ErreurApi
              ? error.message
+5 −1
Changes for apps/web/src/styles.css: 5 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -287,8 +287,12 @@ label { display: block; font-size: 12px; font-weight: 600; color: var(--encre-2)
/* Volontairement distinct du bandeau d'exercice : celui-ci dit « cette crise
   n'est pas réelle », celui-là dit « cette instance ne l'est pas ». Les
   confondre ferait lire l'un pour l'autre. */
/* La couleur du texte suit le fond de page, elle n'est pas figée en blanc.
   « #fff » tenait en thème clair et tombait à 2,7:1 en thème sombre, où
   « --crit » est un saumon fait pour porter du texte, non pour en recevoir.
   Un bandeau d'avertissement illisible est un avertissement perdu. */
.bandeau-environnement {
  background: var(--crit); color: #fff; text-align: center;
  background: var(--crit); color: var(--fond); text-align: center;
  font-family: Archivo, sans-serif; font-size: 11px; font-weight: 700;
  letter-spacing: 0.16em; text-transform: uppercase; padding: 4px;
  flex: 0 0 auto; z-index: 32;