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

La gravité s'affichait encore en code à trois endroits, dont la pastille

J'annonçais un seul site en fin de lot précédent. Il y en avait trois, et je les
ai trouvés l'un après l'autre en vérifiant à l'écran plutôt qu'au grep.

── Le sélecteur du déclenchement

Il bouclait sur un tableau littéral de codes et rendait la variable de boucle —
« vigilance », « mineure », « majeure », « critique » en minuscules, sur l'écran
qu'on ouvre le jour J. Il boucle désormais sur « GRAVITES », la liste du paquet
partagé, et rend « LIBELLES_GRAVITE[g] » traduit.

── Le sélecteur du pilotage, trouvé en cherchant le premier

Même forme, avec sa propre liste locale. Cette liste-là ne change pas : elle
omet « vigilance » à dessein — c'est le niveau d'avant la crise, et on n'y
redescend pas, on sort de crise. Je l'ai documentée plutôt que remplacée. Seul
l'affichage passe par les libellés.

── La pastille de la coque, trouvée en vérifiant le pilotage

« MAJEURE » en haut à gauche d'une interface réglée en anglais, à côté d'un
« Severity Major » correct. C'est le troisième site, et le plus visible : il est
à l'écran en permanence pendant toute la crise.

── Deux trous dans mes propres garanties, plus intéressants que les trois écarts

Le contrôle des énumérations brutes cherche « {gravite} » et « {statut} ». Les
deux sélecteurs nomment leur variable de boucle « g » : il ne pouvait pas les
voir. Une garantie neuve cherche donc la forme et non le nom — boucler sur la
liste des gravités, puis rendre la variable en nœud de texte. Éprouvée contre
les deux états du source, avant et après : elle signale les deux sites d'avant
et se tait sur ceux d'après.

La pastille, elle, était vue et écartée. L'exception du contrôle portait sur la
ligne entière — « className » présent, on passe — et cette ligne porte les deux
à la fois : la classe, puis le code en nœud de texte juste après. L'exception
porte maintenant sur l'occurrence, en regardant ce qui la précède immédiatement :
« $ » pour une interpolation de gabarit, « nom= » pour une valeur d'attribut.
Passée sur toute l'interface, la règle resserrée ne signale que la pastille et
rien d'autre — aucun faux positif à excepter.

1178 garanties au vert. Vérifié à l'écran dans les deux langues : « Vigilance
Mineure Majeure Critique » et « Watch Minor Major Critical » au déclenchement,
« Minor Major Critical » au pilotage, la pastille à « MAJOR » en anglais et
« MAJEURE » en français.

── Ce qui reste, et que je n'ai pas touché

Sept blocs nomment un groupe de commandes par une « span.etiq » plutôt que par
un « label » : ils échappent au balayage du lot précédent, qui portait sur les
« label ». Tous ne sont pas des groupes de commandes — deux sont des listes,
un est une mise en page — et il faut trancher au cas par cas. Le groupe de
gravité du pilotage en fait partie.

Co-Authored-By: Claude (RCA)
parent c87a1392
Loading
Loading
Loading
Loading
Loading
+37 −1
Changes for apps/api/src/verification/interface.ts: 37 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -386,13 +386,49 @@ export async function verifierInterface(r: Rapport): Promise<void> {
      const ligne = texte.slice(debut, texte.indexOf("\n", m.index!));
      // « className={`gravite ${c.gravite}`} » est une classe, « value={a.statut} »
      // la valeur d'un sélecteur : ni l'une ni l'autre ne s'affiche comme texte.
      if (/className|queryKey|key=|value=/.test(ligne)) continue;
      //
      // L'exception portait d'abord sur la ligne entière, et c'est ce qui a
      // laissé passer la pastille de la coque de crise : sa ligne porte les
      // deux à la fois — la classe, puis le code en nœud de texte juste après.
      // Elle porte maintenant sur l'occurrence : ce qui la précède
      // immédiatement suffit à trancher, « $ » pour une interpolation de
      // gabarit, « nom= » pour une valeur d'attribut.
      const tete = texte.slice(0, m.index!);
      if (tete.endsWith("$") || /[\w-]+=$/.test(tete.slice(-24))) continue;
      brutes.push(`${f.split("/").pop()} : ${ligne.trim().slice(0, 46)}`);
    }
  }
  r.verifie("EX-21", "aucune énumération n'est affichée sans passer par ses libellés",
    brutes.length === 0, brutes.slice(0, 3).join(" ; "));

  // Le contrôle ci-dessus cherche une variable nommée « gravite » ou
  // « statut ». Deux sélecteurs y échappaient, et la campagne suivante les a
  // trouvés à l'écran : ils bouclent sur la liste des codes et rendent la
  // variable de boucle, appelée « g ». Le déclenchement redéclarait la liste en
  // littéral, le pilotage en gardait une locale — et tous deux affichaient
  // « majeure », minuscule comprise, à côté d'écrans qui disent « Majeure ».
  //
  // On cherche donc la forme, pas le nom : boucler sur la liste des gravités,
  // puis rendre la variable telle quelle en nœud de texte. Ce que ce contrôle
  // ne voit pas, et qu'il faut savoir : une liste passée par une variable
  // intermédiaire. C'est le prix d'une vérification par le source.
  const SOURCE = /(GRAVITES|\[[^\]\n]*\])\s*\.map\(\s*\(?\s*(\w+)/g;
  const CODES = /"(?:vigilance|mineure|majeure|critique)"/;
  const codesNus: string[] = [];
  for (const f of fichiers) {
    if (f.includes("/langues/")) continue;
    const texte = await readFile(f, "utf8");
    for (const m of texte.matchAll(SOURCE)) {
      if (m[1] !== "GRAVITES" && !CODES.test(m[1]!)) continue;
      const bloc = texte.slice(m.index!, m.index! + 500);
      if (new RegExp(`>\\s*\\{\\s*${m[2]!}\\s*\\}|\\{\\s*${m[2]!}\\s*\\}\\s*<`).test(bloc)) {
        codesNus.push(`${f.slice(web.length)}:${texte.slice(0, m.index!).split("\n").length}`);
      }
    }
  }
  r.verifie("EX-21", "aucun sélecteur de gravité n'affiche le code au lieu du libellé",
    codesNus.length === 0, codesNus.join(", "));

  // ── Une étiquette nomme un champ, ou elle ne nomme rien (EX-19) ──────────
  //
  // Treize « label » coiffaient un jeu de boutons, un tableau, ou un champ posé
+3 −1
Changes for apps/web/src/coque/Coque.tsx: 3 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ import { Directs } from "../ecrans/Directs.js";
import { CanauxCrise } from "../ecrans/CanauxCrise.js";
import { Recensement } from "../ecrans/Recensement.js";
import { useT } from "../langues/index.js";
import { LIBELLES_GRAVITE, type Gravite } from "@kastell/shared";

interface SalleListee {
  id: string; libelle: string; cellule_id: string | null; genre: string;
@@ -337,7 +338,8 @@ export function Coque({ criseId }: { criseId: string }) {
          </span>
        ) : crise && (
          <>
            <span className={`gravite ${crise.gravite}`}>{crise.gravite}</span>
            <span className={`gravite ${crise.gravite}`}>
              {t(LIBELLES_GRAVITE[crise.gravite as Gravite] ?? crise.gravite)}</span>
            <span className="chrono">{crise.chrono}</span>
          </>
        )}
+3 −2
Changes for apps/web/src/ecrans/Declenchement.tsx: 3 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -4,6 +4,7 @@ import { api, ErreurApi } from "../api.js";
import { useEtat } from "../etat.js";
import { useRoute } from "../routeur.js";
import { useT } from "../langues/index.js";
import { GRAVITES, LIBELLES_GRAVITE } from "@kastell/shared";

/** Pour un champ « datetime-local », qui n'accepte pas l'ISO avec fuseau. */
const pourChamp = (d: Date) => {
@@ -127,13 +128,13 @@ export function Declenchement() {
          <div className="champ">
            <span className="nom-groupe">{t("Gravité")}</span>
            <div role="group" aria-label={t("Gravité")} className="ligne" style={{ flexWrap: "wrap" }}>
              {["vigilance", "mineure", "majeure", "critique"].map((g) => (
              {GRAVITES.map((g) => (
                <button
                  key={g} type="button"
                  className={gravite === g ? "accent" : ""}
                  onClick={() => definirGravite(g)}
                >
                  {g}
                  {t(LIBELLES_GRAVITE[g])}
                </button>
              ))}
            </div>
+5 −1
Changes for apps/web/src/ecrans/PilotageCrise.tsx: 5 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -4,6 +4,10 @@ import { api, ErreurApi, type Cockpit } from "../api.js";
import { useT } from "../langues/index.js";
import { LIBELLES_GRAVITE, type Gravite } from "@kastell/shared";

/**
 * Ce qu'une crise ouverte peut devenir. « vigilance » n'y est pas : c'est le
 * niveau d'avant la crise, et on n'y redescend pas — on sort de crise.
 */
const GRAVITES = ["mineure", "majeure", "critique"] as const;

/**
@@ -85,7 +89,7 @@ export function PilotageCrise({ criseId, crise }: { criseId: string; crise: Cock
        <div className="ligne" style={{ gap: 8, marginTop: 8, flexWrap: "wrap" }}>
          {GRAVITES.map((g) => (
            <button key={g} className={gravite === g ? "accent petit" : "petit"}
              onClick={() => definirGravite(g)}>{g}</button>
              onClick={() => definirGravite(g)}>{t(LIBELLES_GRAVITE[g])}</button>
          ))}
        </div>
        <input style={{ marginTop: 8, maxWidth: 460 }} value={motif}