Le validateur des thèmes
Un thème n’est distribué par le portail que s’il passe le validateur des thèmes : il doit implémenter tout le langage Core v1, être sûr et ne rien charger de l’extérieur. Cette page explique ce qu’il vérifie, et pourquoi.
Pourquoi un validateur
Un navigateur ne signale jamais une erreur CSS : une règle mal écrite est ignorée en silence, une classe oubliée ne fait simplement rien. Une personne qui écrit <!-- _class: content gallery-bento --> avec un thème qui a oublié gallery-bento obtient une slide cassée, sans aucun message.
Le validateur garantit donc qu’un thème distribué implémente tout le langage, et qu’il est sûr et autonome : il sera envoyé sur tous les postes et inclus dans chaque rendu.
Où il s’exécute
Un seul module, packages/core/src/theme-rules, appliqué à l’identique partout :
| Endroit | Quand | Étapes | Rôle |
|---|---|---|---|
| Éditeur du portail (navigateur) | Pendant la saisie, après une courte pause | 1 à 6 | Retour immédiat, aperçu, mesure de la géométrie |
| Serveur du portail | Enregistrement d’un brouillon, publication | 1 à 5, plus le rapport de l’étape 6 | Autorité : refuse un thème non conforme |
| Extension VS Code | Installation et utilisation de tout thème, du portail ou local | 1 à 4 | Signale un thème non conforme dans Problèmes et la barre d’état |
Chaque résultat porte la version des règles (theme-rules@2), enregistrée avec la version publiée. Une règle ajoutée plus tard ne rend pas invalides les versions déjà publiées : elles gardent leur rapport d’origine.
Erreur ou avertissement
Chaque constat a un identifiant stable (contract.class-without-effect…), une gravité, une position dans le CSS quand elle a un sens, et un message traduit avec une piste de correction.
- Erreur : la version ne peut pas être publiée, donc jamais distribuée.
- Avertissement : affiché, n’empêche pas la publication.
Un brouillon peut être enregistré avec des erreurs de contrat ou de rendu (travail en cours), mais pas avec des erreurs de lecture, d’identité ou de sécurité (étapes 1 à 3). La publication exige zéro erreur.
Les six étapes
Le CSS passe par six étapes, dans l’ordre. Les étapes 1 à 5 sont statiques : elles s’exécutent partout, y compris sur le serveur, sans navigateur. L’étape 6 demande un vrai moteur de rendu.
Étape 1 — Lecture
| Règle | Vérification | Pourquoi |
|---|---|---|
format.encoding | Pas de caractère nul | Fichier corrompu ou binaire |
format.syntax | Le CSS est analysé par un vrai analyseur CSS (css-tree) ; chaque erreur de syntaxe est signalée avec sa ligne | Le navigateur ignorerait la règle fautive sans prévenir |
Ensuite, le validateur travaille sur un arbre syntaxique, pas sur le texte : un commentaire, une chaîne ou un échappement ne peuvent pas tromper les contrôles suivants.
Étape 2 — Identité
| Règle | Vérification | Pourquoi |
|---|---|---|
identity.theme-missing, identity.theme-multiple | Exactement un /* @theme Nom */ | C’est le nom écrit dans theme: par les auteurs |
identity.theme-name | Une lettre, puis lettres, chiffres, - ou _ ; 64 caractères au plus | Nom utilisable partout sans ambiguïté |
identity.name-stable | Une nouvelle version garde le nom du thème | Sinon les présentations existantes perdraient leur thème |
identity.language-missing, identity.language-unknown | /* @dwa-language core-v1 */ présent ; seul core-v1 existe | Déclare le contrat implémenté |
identity.slide-size-unit, identity.slide-size | Si le thème fixe la taille de section, elle est en pixels et au format 16:9 | L’export PowerPoint et la mesure des débordements supposent du 16:9 |
Sans taille déclarée, Marp utilise 1280 × 720 : rien n’est signalé.
Étape 3 — Sécurité et autonomie
Une ressource distante permettrait de suivre les ouvertures de présentations, casserait le travail hors ligne et pourrait changer après la publication : l’empreinte du thème ne garantirait plus rien.
| Règle | Vérification |
|---|---|
security.url | Chaque url(…) (et les chaînes de src(), image-set(), cross-fade()) est une URI data:. https:, chemins relatifs et autres schémas sont refusés |
security.data-encoding | La ressource incorporée est encodée en base64 |
security.mime | Type accepté : PNG, JPEG, GIF, WebP, WOFF, WOFF2, TTF, OTF |
security.payload | Les premiers octets décodés correspondent au type annoncé (signature PNG, JPEG, WOFF…) |
security.svg | SVG refusé, même incorporé : il peut contenir des scripts et des références externes |
security.import | @import refusé, sauf les thèmes intégrés à Marp (default, gaia, uncover) |
security.at-rule | Règles @ acceptées : @font-face, @media, @supports, @keyframes, @layer, @container, @charset, @import (cas ci-dessus) |
security.style-break | Aucune séquence </style, même en commentaire : Marp insère le thème dans une balise <style> |
security.legacy | expression(), behavior, -moz-binding, javascript: refusés |
Les échappements CSS (\2014) restent autorisés : l’analyseur les décode et les contrôles portent sur les valeurs décodées. Un u\72l( est vu comme url( et contrôlé.
Étape 4 — Contrat Core v1 : chaque classe doit avoir un effet
C’est le cœur du validateur. Pour chaque classe de la référence, la question est : « si un auteur l’écrit, est-ce que quelque chose change ? » La liste des classes est générée depuis le catalogue partagé, le même que l’autocomplétion de l’extension.
Le test différentiel :
- Pour chaque classe, le validateur construit une slide de contrôle : le HTML que Marp produit pour le Markdown d’exemple de la classe (celui que l’extension insère).
- Il calcule, sans navigateur, les valeurs déclarées de chaque élément de la slide et de ses pseudo-éléments : quelles règles s’appliquent, et laquelle gagne selon la cascade (
!important, spécificité, ordre). Les sélecteurs sont évalués sur un DOM virtuel, y compris:is(),:not(),:has()et les sélecteurs d’attribut comme[class*='image-text']. - Il refait le calcul sur la même slide sans la classe testée.
- Si aucune valeur ne change, la classe n’est pas implémentée.
Exemple avec theme/DWA.css, qui contient section.cols-3 { --columns: 3; … } :
Classe testée : cols-3 Slide : <section class="content cols-3"> + 3 groupes
Sans cols-3 → section : --columns non défini
Avec cols-3 → section : --columns = 3
⇒ effet constaté ✓Pour un thème qui a oublié gallery-bento, les valeurs sont identiques avec et sans la classe : erreur « La classe « gallery-bento » n’a aucun effet ».
Pourquoi des valeurs plutôt que « une règle mentionne la classe » : une règle peut citer la classe mais être toujours écrasée par une autre ; inversement, DWA.css utilise des sélecteurs par fragment ([class*='image-text']) qui ne citent jamais image-text-70 en entier. Le test juge l’effet, quelle que soit l’écriture.
| Règle | Vérification | Gravité |
|---|---|---|
contract.class-without-effect | La classe change au moins une valeur déclarée. Les classes par défaut (color-1, fit) peuvent ne rien changer | Erreur |
contract.distinct-values | Deux membres d’une même famille ne donnent pas le même résultat : cols-3 ≠ cols-4, image-text-30 ≠ image-text-70, color-2 ≠ color-5… Sinon un auteur choisirait une option qui n’existe pas | Erreur |
contract.callout-size | callout-size-N donne au titre du callout une taille de N px : l’extension et l’export PowerPoint s’appuient sur ce nombre | Erreur |
contract.text-effect | Le texte reste du texte à l’export PowerPoint : sont refusés le texte peint par un fond (background-clip: text), -webkit-text-fill-color, le contour des glyphes (text-stroke) et l’écriture verticale (writing-mode). Dégradés, pseudo-éléments et transformations du décor restent libres | Erreur |
contract.selector-skipped | Sélecteur que le validateur n’a pas pu évaluer : son effet n’est pas pris en compte | Avertissement |
Les classes supplémentaires propres au thème sont autorisées : le contrat fixe ce qui doit exister, pas ce qui est interdit.
Étape 5 — Rendu Marp
Le CSS est enregistré dans Marp Core et toutes les slides de contrôle sont rendues. Un échec est une erreur render.marp : le thème doit être accepté par l’outil qui l’utilisera.
Étape 6 — Contrôles géométriques
Les slides de contrôle sont rendues dans un vrai navigateur, à la taille de slide du thème, et la géométrie est mesurée (getBoundingClientRect, tolérance de 2 px).
| Règle | Vérification | Gravité |
|---|---|---|
visual.inside | Aucun élément ne sort de la slide (sauf l’image volontairement recadrée de image-full) | Erreur |
visual.columns | cols-N : N groupes sur une même ligne, de gauche à droite, de même largeur | Erreur |
visual.split | image-text-N : image à gauche, part de l’image ≈ N % (± 3 points) ; text-image-N : à droite | Erreur |
visual.gallery | gallery cols-N : N images par ligne | Erreur |
visual.bento | gallery-bento : l’image 1 occupe les deux rangées, l’image 2 deux colonnes | Erreur |
visual.timeline | timeline : étapes alignées horizontalement ; timeline-vertical : empilées | Erreur |
visual.title | with-title : titre au-dessus du contenu, sur toute la largeur | Erreur |
visual.legend | with-legend : légende sous tout le reste | Erreur |
visual.chart-wide | chart-wide : deux commentaires côte à côte au-dessus du visuel | Erreur |
visual.contrast | Pour chaque palette, contraste du texte sur la couleur de fond ≥ 4,5:1 | Avertissement |
Dans l’éditeur du portail, la mesure se fait dans une iframe isolée, sans scripts ni réseau. Le serveur n’a pas de navigateur : à la publication, l’éditeur envoie son rapport de l’étape 6, et le serveur l’exige sans erreur, calculé sur la même empreinte SHA-256 du CSS et la même version des règles. Il refait lui-même les étapes 1 à 5.
Taille du thème
Il n’y a pas de règle de taille : les images et polices incorporées en base64 font naturellement grossir le CSS. Seule subsiste une limite technique de transport, la taille maximale d’une requête envoyée au portail.
Limites connues
- L’étape 4 prouve que chaque classe fait quelque chose, pas qu’elle le fait bien : c’est le rôle de l’étape 6, qui ne couvre que les compositions mesurables listées ci-dessus.
- La cascade virtuelle n’est pas un moteur de rendu : les conditions
@mediaet@supportsne sont pas évaluées, et un sélecteur non évalué est seulement signalé par un avertissement. - Le contraste n’analyse pas les images de fond ; vérifiez la lisibilité dans l’aperçu.
- Les slides de contrôle ont des textes courts : elles ne garantissent pas qu’une vraie présentation ne déborde pas. La mesure des débordements de l’extension s’en charge.
- L’étape 6 est mesurée par le navigateur de l’éditeur : une personne qui forgerait son rapport contournerait les contrôles géométriques, mais ne publierait que dans sa propre organisation. La sécurité (étapes 1 à 3) est toujours vérifiée par le serveur.
- L’extension applique les étapes 1 à 4 ; elle ne refait ni le rendu Marp ni les contrôles géométriques.