[ADD] tests: guards for layers, clock, sources, colours, watch patterns
Guards scan the tree file by file and line by line, fail on an empty scan,
and are each shown red on a temporary tree. They cover the interface import
graph, the Electron bridge, one clock, refused dependencies, how tests are
written, hard-coded strings, translation keys, forbidden words, colours and
the conflict hue, markup, pruned selectors and watch patterns. Coverage
keeps the engine integration test on a small machine. Fixes: delays in
tests, the conflict hatch class; « la rend évitable » is a named exception
(§ 5.6). Checked: 2587 node, 465 browser tests, from the index alone.
--- FR ---
[ADD] épreuves : gardes des couches, horloge, sources, couleurs, motifs
Les gardes relèvent par fichier et par ligne, échouent sur un balayage vide
et sont chacune montrées rouges sur un arbre temporaire. Elles couvrent le
graphe d'imports de l'interface, le pont d'Electron, l'horloge unique, les
dépendances refusées, l'écriture des épreuves, les chaînes en dur, les clés
de traduction, les mots interdits, les couleurs et la teinte de conflit, le
balisage, les sélecteurs élagués et les motifs de surveillance. Corrigés :
délais d'épreuves, classe de la trame de conflit ; « la rend évitable » est
une exception nommée (§ 5.6). Vérifié : 2587 node, 465 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 13:21:21 -04:00
// © 2026 TechnoLibre (http://www.technolibre.ca)
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
// Gardes des sources de l'interface, lues dans l'arbre d'analyse et non dans
// le texte : chaque composant passe par parse de svelte/compiler, chaque
// module JavaScript par le même analyseur, enveloppé dans un <script module>,
// chaque feuille .css par parseCss. Un commentaire n'est donc jamais relevé :
// la règle porte sur ce que le code fait, et un commentaire qui nomme un
// interdit le documente.
//
2026-10-09 04:51:45 -04:00
// Neuf gardes : les chaînes visibles en dur (§ 14.6), les clés littérales de
[ADD] tests: guards for layers, clock, sources, colours, watch patterns
Guards scan the tree file by file and line by line, fail on an empty scan,
and are each shown red on a temporary tree. They cover the interface import
graph, the Electron bridge, one clock, refused dependencies, how tests are
written, hard-coded strings, translation keys, forbidden words, colours and
the conflict hue, markup, pruned selectors and watch patterns. Coverage
keeps the engine integration test on a small machine. Fixes: delays in
tests, the conflict hatch class; « la rend évitable » is a named exception
(§ 5.6). Checked: 2587 node, 465 browser tests, from the index alone.
--- FR ---
[ADD] épreuves : gardes des couches, horloge, sources, couleurs, motifs
Les gardes relèvent par fichier et par ligne, échouent sur un balayage vide
et sont chacune montrées rouges sur un arbre temporaire. Elles couvrent le
graphe d'imports de l'interface, le pont d'Electron, l'horloge unique, les
dépendances refusées, l'écriture des épreuves, les chaînes en dur, les clés
de traduction, les mots interdits, les couleurs et la teinte de conflit, le
balisage, les sélecteurs élagués et les motifs de surveillance. Corrigés :
délais d'épreuves, classe de la trame de conflit ; « la rend évitable » est
une exception nommée (§ 5.6). Vérifié : 2587 node, 465 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 13:21:21 -04:00
// t qui n'existent pas, le contenu des tables de traduction (§ 5.5, § 7.6,
// § 7.7, § 12.8, § 12.10.8), les couleurs (§ 12.7, § 13.3, § 13.5), le
// balisage et les fenêtres du navigateur (§ 13.1, § 2.1), le texte comprimé et
// les animations (§ 7.4, § 19.2), le câblage des composants (§ 12.9, § 13.3),
2026-10-09 04:51:45 -04:00
// les sélecteurs que le compilateur élague (§ 13.3), les touches nommées hors
// de la table des raccourcis (§ 7.3, § 19.8). Chaque garde rend un
[ADD] tests: guards for layers, clock, sources, colours, watch patterns
Guards scan the tree file by file and line by line, fail on an empty scan,
and are each shown red on a temporary tree. They cover the interface import
graph, the Electron bridge, one clock, refused dependencies, how tests are
written, hard-coded strings, translation keys, forbidden words, colours and
the conflict hue, markup, pruned selectors and watch patterns. Coverage
keeps the engine integration test on a small machine. Fixes: delays in
tests, the conflict hatch class; « la rend évitable » is a named exception
(§ 5.6). Checked: 2587 node, 465 browser tests, from the index alone.
--- FR ---
[ADD] épreuves : gardes des couches, horloge, sources, couleurs, motifs
Les gardes relèvent par fichier et par ligne, échouent sur un balayage vide
et sont chacune montrées rouges sur un arbre temporaire. Elles couvrent le
graphe d'imports de l'interface, le pont d'Electron, l'horloge unique, les
dépendances refusées, l'écriture des épreuves, les chaînes en dur, les clés
de traduction, les mots interdits, les couleurs et la teinte de conflit, le
balisage, les sélecteurs élagués et les motifs de surveillance. Corrigés :
délais d'épreuves, classe de la trame de conflit ; « la rend évitable » est
une exception nommée (§ 5.6). Vérifié : 2587 node, 465 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 13:21:21 -04:00
// relevé « chemin:ligne motif « extrait » », trié, qui nomme d'un coup chaque
// fichier et chaque ligne fautifs ; elle lève sur un balayage vide (§ 14.2).
//
// Le périmètre est le code livré : les épreuves — *.test.js, dont
// *.navigateur.test.js — et test/ portent exprès ce que les gardes refusent,
// et n'en font pas partie. D'autres épreuves font tourner chaque garde sur un
// arbre temporaire qui porte, à plus d'un niveau, les formes refusées et des
// formes voisines admises.
import assert from 'node:assert/strict' ;
import { mkdirSync , mkdtempSync , readdirSync , readFileSync , rmSync , writeFileSync } from 'node:fs' ;
import { tmpdir } from 'node:os' ;
import { dirname , join , relative , sep } from 'node:path' ;
import { fileURLToPath } from 'node:url' ;
import { compile , parse , parseCss } from 'svelte/compiler' ;
import { TABLES _APPLICATION } from '../src/application/libelles.js' ;
import { creerTraduction } from '../src/application/traduction.js' ;
import { TABLES _INTERFACE } from '../src/interface/libelles.js' ;
[FIX] interface: ghost labels, keyboard, valid HTML, closed data-* list
The drag ghost names what releasing at the point changes, floor included.
Escape on a seat returns to its table, a refused Tab stays put, the legend
toggle exposes aria-expanded, and the header markup is valid HTML. Every
data-* attribute the interface sets belongs to one closed list that a guard
reads. The tree guards follow helpers a test imports, and the reporter says
the whole series stays watched under coverage on a small machine.
Checked: every new test red first; 3001 node, 635 browser and 63 long tests
green from the index alone.
--- FR ---
[FIX] interface : fantôme nommé, clavier, HTML valide, data-* fermés
Le fantôme du glisser nomme ce que relâcher au point modifie, sol compris.
Échap sur un siège remonte à sa table, un Tab refusé reste en place, la
bascule de la légende expose aria-expanded, et la tête est du HTML valide.
Chaque attribut data-* que l'interface pose appartient à une liste fermée
qu'une garde lit. Les gardes de l'arbre suivent les aides qu'une épreuve
importe, et le rapporteur dit la série entière surveillée sous couverture.
Vérifié : chaque épreuve rouge d'abord ; 3001 node, 635 navigateur et 63
longues vertes depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 04:01:57 -04:00
import { JETONS _CONFLIT } from '../src/interface/marques.js' ;
2026-10-09 04:51:45 -04:00
import { CONTEXTES _RACCOURCIS , RACCOURCIS } from '../src/interface/raccourcis.js' ;
[FIX] interface: ghost labels, keyboard, valid HTML, closed data-* list
The drag ghost names what releasing at the point changes, floor included.
Escape on a seat returns to its table, a refused Tab stays put, the legend
toggle exposes aria-expanded, and the header markup is valid HTML. Every
data-* attribute the interface sets belongs to one closed list that a guard
reads. The tree guards follow helpers a test imports, and the reporter says
the whole series stays watched under coverage on a small machine.
Checked: every new test red first; 3001 node, 635 browser and 63 long tests
green from the index alone.
--- FR ---
[FIX] interface : fantôme nommé, clavier, HTML valide, data-* fermés
Le fantôme du glisser nomme ce que relâcher au point modifie, sol compris.
Échap sur un siège remonte à sa table, un Tab refusé reste en place, la
bascule de la légende expose aria-expanded, et la tête est du HTML valide.
Chaque attribut data-* que l'interface pose appartient à une liste fermée
qu'une garde lit. Les gardes de l'arbre suivent les aides qu'une épreuve
importe, et le rapporteur dit la série entière surveillée sous couverture.
Vérifié : chaque épreuve rouge d'abord ; 3001 node, 635 navigateur et 63
longues vertes depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 04:01:57 -04:00
import { DESIGNATIONS } from './navigateur/designations.js' ;
[ADD] tests: guards for layers, clock, sources, colours, watch patterns
Guards scan the tree file by file and line by line, fail on an empty scan,
and are each shown red on a temporary tree. They cover the interface import
graph, the Electron bridge, one clock, refused dependencies, how tests are
written, hard-coded strings, translation keys, forbidden words, colours and
the conflict hue, markup, pruned selectors and watch patterns. Coverage
keeps the engine integration test on a small machine. Fixes: delays in
tests, the conflict hatch class; « la rend évitable » is a named exception
(§ 5.6). Checked: 2587 node, 465 browser tests, from the index alone.
--- FR ---
[ADD] épreuves : gardes des couches, horloge, sources, couleurs, motifs
Les gardes relèvent par fichier et par ligne, échouent sur un balayage vide
et sont chacune montrées rouges sur un arbre temporaire. Elles couvrent le
graphe d'imports de l'interface, le pont d'Electron, l'horloge unique, les
dépendances refusées, l'écriture des épreuves, les chaînes en dur, les clés
de traduction, les mots interdits, les couleurs et la teinte de conflit, le
balisage, les sélecteurs élagués et les motifs de surveillance. Corrigés :
délais d'épreuves, classe de la trame de conflit ; « la rend évitable » est
une exception nommée (§ 5.6). Vérifié : 2587 node, 465 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 13:21:21 -04:00
import { describe , test } from './lanceur.js' ;
const RACINE = fileURLToPath ( new URL ( '..' , import . meta . url ) ) ;
// --- Lecture des sources ------------------------------------------------------
// Une épreuve, node ou navigateur : hors du périmètre de chaque garde.
const EPREUVE = /\.test\.[cm]?js$/ ;
// Chemins relatifs, en « / », des fichiers des arbres donnés dont le nom finit
// par l'une des extensions, épreuves exclues, triés. Un arbre absent n'en
// fournit aucun ; un répertoire node_modules n'est jamais retenu.
function sources ( racine , arbres , extensions ) {
const chemins = [ ] ;
for ( const arbre of arbres ) {
let entrees ;
try {
entrees = readdirSync ( join ( racine , arbre ) , { recursive : true , withFileTypes : true } ) ;
} catch ( erreur ) {
if ( erreur . code === 'ENOENT' ) continue ;
throw erreur ;
}
for ( const entree of entrees ) {
if ( ! entree . isFile ( ) ) continue ;
const chemin = relative ( racine , join ( entree . parentPath , entree . name ) ) . split ( sep ) . join ( '/' ) ;
if ( chemin . split ( '/' ) . includes ( 'node_modules' ) || EPREUVE . test ( chemin ) ) continue ;
if ( extensions . some ( ( extension ) => chemin . endsWith ( extension ) ) ) chemins . push ( chemin ) ;
}
}
return chemins . sort ( ) ;
}
// Lève quand un balayage n'examine aucun fichier (§ 14.2).
function exigerNonVide ( chemins , garde ) {
assert . ok ( chemins . length > 0 , ` ${ garde } : aucun fichier examiné ` ) ;
return chemins ;
}
// Numéro, à partir de 1, de la ligne qui porte le caractère d'indice donné.
const ligneDe = ( texte , indice ) => texte . slice ( 0 , indice ) . split ( '\n' ) . length ;
// Un extrait lisible d'un texte relevé : blancs réduits, quarante caractères
// au plus.
function extrait ( texte ) {
const court = String ( texte ) . replace ( /\s+/g , ' ' ) . trim ( ) ;
return ` « ${ court . length > 40 ? ` ${ court . slice ( 0 , 39 ) } \u {2026} ` : court } » ` ;
}
// Un composant : son arbre moderne, ses positions comptées dans son texte.
function analyserComposant ( racine , chemin ) {
const texte = readFileSync ( join ( racine , chemin ) , 'utf8' ) ;
try {
return { texte , ast : parse ( texte , { modern : true } ) } ;
} catch ( erreur ) {
throw new Error ( ` ${ chemin } : analyse impossible ( ${ erreur . code ? ? erreur . name } ) ` , { cause : erreur } ) ;
}
}
// Un module JavaScript, enveloppé dans un <script module> : le préfixe ne
// porte aucun saut de ligne, les numéros de ligne restent ceux du fichier.
function analyserModule ( racine , chemin ) {
const texte = ` <script module> ${ readFileSync ( join ( racine , chemin ) , 'utf8' ) } </script> ` ;
try {
return { texte , ast : parse ( texte , { modern : true } ) . module . content } ;
} catch ( erreur ) {
throw new Error ( ` ${ chemin } : analyse impossible ( ${ erreur . code ? ? erreur . name } ) ` , { cause : erreur } ) ;
}
}
// Parcourt un arbre d'analyse en profondeur, dans l'ordre des champs : visite
// chaque nœud typé avec la liste de ses ancêtres typés, du plus lointain au
// plus proche. Les champs de service des analyseurs ne sont pas descendus.
const CHAMPS _IGNORES = new Set ( [ 'parent' , 'loc' , 'metadata' , 'name_loc' , 'range' ] ) ;
function parcourir ( noeud , visiter , ancetres = [ ] ) {
if ( noeud === null || typeof noeud !== 'object' ) return ;
if ( Array . isArray ( noeud ) ) {
for ( const enfant of noeud ) parcourir ( enfant , visiter , ancetres ) ;
return ;
}
const type = typeof noeud . type === 'string' ;
if ( type ) visiter ( noeud , ancetres ) ;
const suivants = type ? [ ... ancetres , noeud ] : ancetres ;
for ( const [ champ , valeur ] of Object . entries ( noeud ) ) {
if ( ! CHAMPS _IGNORES . has ( champ ) && valeur !== null && typeof valeur === 'object' ) {
parcourir ( valeur , visiter , suivants ) ;
}
}
}
// Les parties JavaScript et le gabarit d'un composant, sans sa feuille.
const partiesCode = ( ast ) => [ ast . module , ast . instance , ast . fragment ] ;
// Les chaînes littérales qu'une expression peut rendre telle quelle : un
// littéral, les morceaux fixes d'un gabarit de chaîne, les deux branches
// d'une condition, les opérandes d'une concaténation et d'un « || » ou « ?? »,
[FIX] interface: ghost labels, keyboard, valid HTML, closed data-* list
The drag ghost names what releasing at the point changes, floor included.
Escape on a seat returns to its table, a refused Tab stays put, the legend
toggle exposes aria-expanded, and the header markup is valid HTML. Every
data-* attribute the interface sets belongs to one closed list that a guard
reads. The tree guards follow helpers a test imports, and the reporter says
the whole series stays watched under coverage on a small machine.
Checked: every new test red first; 3001 node, 635 browser and 63 long tests
green from the index alone.
--- FR ---
[FIX] interface : fantôme nommé, clavier, HTML valide, data-* fermés
Le fantôme du glisser nomme ce que relâcher au point modifie, sol compris.
Échap sur un siège remonte à sa table, un Tab refusé reste en place, la
bascule de la légende expose aria-expanded, et la tête est du HTML valide.
Chaque attribut data-* que l'interface pose appartient à une liste fermée
qu'une garde lit. Les gardes de l'arbre suivent les aides qu'une épreuve
importe, et le rapporteur dit la série entière surveillée sous couverture.
Vérifié : chaque épreuve rouge d'abord ; 3001 node, 635 navigateur et 63
longues vertes depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 04:01:57 -04:00
// le second d'un « && », le dernier terme d'une séquence, et chaque élément
// d'un tableau ou chaque valeur d'un objet littéral dont l'expression lit un
// membre (« ['Oui', 'Non'][i] »). Un appel, un autre membre, un identifiant
// ne rendent aucun littéral : t('cle') n'affiche pas sa clé. Rend
// [{ valeur, debut }].
[ADD] tests: guards for layers, clock, sources, colours, watch patterns
Guards scan the tree file by file and line by line, fail on an empty scan,
and are each shown red on a temporary tree. They cover the interface import
graph, the Electron bridge, one clock, refused dependencies, how tests are
written, hard-coded strings, translation keys, forbidden words, colours and
the conflict hue, markup, pruned selectors and watch patterns. Coverage
keeps the engine integration test on a small machine. Fixes: delays in
tests, the conflict hatch class; « la rend évitable » is a named exception
(§ 5.6). Checked: 2587 node, 465 browser tests, from the index alone.
--- FR ---
[ADD] épreuves : gardes des couches, horloge, sources, couleurs, motifs
Les gardes relèvent par fichier et par ligne, échouent sur un balayage vide
et sont chacune montrées rouges sur un arbre temporaire. Elles couvrent le
graphe d'imports de l'interface, le pont d'Electron, l'horloge unique, les
dépendances refusées, l'écriture des épreuves, les chaînes en dur, les clés
de traduction, les mots interdits, les couleurs et la teinte de conflit, le
balisage, les sélecteurs élagués et les motifs de surveillance. Corrigés :
délais d'épreuves, classe de la trame de conflit ; « la rend évitable » est
une exception nommée (§ 5.6). Vérifié : 2587 node, 465 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 13:21:21 -04:00
function litterauxRendus ( expression ) {
if ( expression === null || typeof expression !== 'object' ) return [ ] ;
switch ( expression . type ) {
case 'Literal' :
return typeof expression . value === 'string' ? [ { valeur : expression . value , debut : expression . start } ] : [ ] ;
case 'TemplateLiteral' :
return [
{ valeur : expression . quasis . map ( ( q ) => q . value . cooked ? ? q . value . raw ) . join ( '' ) , debut : expression . start } ,
... expression . expressions . flatMap ( litterauxRendus ) ,
] ;
case 'ConditionalExpression' :
return [ ... litterauxRendus ( expression . consequent ) , ... litterauxRendus ( expression . alternate ) ] ;
case 'LogicalExpression' :
return expression . operator === '&&'
? litterauxRendus ( expression . right )
: [ ... litterauxRendus ( expression . left ) , ... litterauxRendus ( expression . right ) ] ;
case 'BinaryExpression' :
return expression . operator === '+'
? [ ... litterauxRendus ( expression . left ) , ... litterauxRendus ( expression . right ) ]
: [ ] ;
case 'SequenceExpression' :
return litterauxRendus ( expression . expressions . at ( - 1 ) ) ;
[FIX] interface: ghost labels, keyboard, valid HTML, closed data-* list
The drag ghost names what releasing at the point changes, floor included.
Escape on a seat returns to its table, a refused Tab stays put, the legend
toggle exposes aria-expanded, and the header markup is valid HTML. Every
data-* attribute the interface sets belongs to one closed list that a guard
reads. The tree guards follow helpers a test imports, and the reporter says
the whole series stays watched under coverage on a small machine.
Checked: every new test red first; 3001 node, 635 browser and 63 long tests
green from the index alone.
--- FR ---
[FIX] interface : fantôme nommé, clavier, HTML valide, data-* fermés
Le fantôme du glisser nomme ce que relâcher au point modifie, sol compris.
Échap sur un siège remonte à sa table, un Tab refusé reste en place, la
bascule de la légende expose aria-expanded, et la tête est du HTML valide.
Chaque attribut data-* que l'interface pose appartient à une liste fermée
qu'une garde lit. Les gardes de l'arbre suivent les aides qu'une épreuve
importe, et le rapporteur dit la série entière surveillée sous couverture.
Vérifié : chaque épreuve rouge d'abord ; 3001 node, 635 navigateur et 63
longues vertes depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 04:01:57 -04:00
case 'MemberExpression' :
if ( expression . object . type === 'ArrayExpression' ) return expression . object . elements . flatMap ( litterauxRendus ) ;
if ( expression . object . type === 'ObjectExpression' ) {
return expression . object . properties . flatMap ( ( propriete ) => ( propriete . type === 'Property' ? litterauxRendus ( propriete . value ) : [ ] ) ) ;
}
return [ ] ;
[ADD] tests: guards for layers, clock, sources, colours, watch patterns
Guards scan the tree file by file and line by line, fail on an empty scan,
and are each shown red on a temporary tree. They cover the interface import
graph, the Electron bridge, one clock, refused dependencies, how tests are
written, hard-coded strings, translation keys, forbidden words, colours and
the conflict hue, markup, pruned selectors and watch patterns. Coverage
keeps the engine integration test on a small machine. Fixes: delays in
tests, the conflict hatch class; « la rend évitable » is a named exception
(§ 5.6). Checked: 2587 node, 465 browser tests, from the index alone.
--- FR ---
[ADD] épreuves : gardes des couches, horloge, sources, couleurs, motifs
Les gardes relèvent par fichier et par ligne, échouent sur un balayage vide
et sont chacune montrées rouges sur un arbre temporaire. Elles couvrent le
graphe d'imports de l'interface, le pont d'Electron, l'horloge unique, les
dépendances refusées, l'écriture des épreuves, les chaînes en dur, les clés
de traduction, les mots interdits, les couleurs et la teinte de conflit, le
balisage, les sélecteurs élagués et les motifs de surveillance. Corrigés :
délais d'épreuves, classe de la trame de conflit ; « la rend évitable » est
une exception nommée (§ 5.6). Vérifié : 2587 node, 465 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 13:21:21 -04:00
default :
return [ ] ;
}
}
// Les morceaux de la valeur d'un attribut du gabarit : { statique, debut }
// pour un texte, { expression } pour une expression. Un attribut sans valeur
// n'en a aucun.
function morceauxAttribut ( attribut ) {
const valeur = attribut . value ;
if ( valeur === true || valeur === undefined || valeur === null ) return [ ] ;
const morceaux = Array . isArray ( valeur ) ? valeur : [ valeur ] ;
return morceaux . map ( ( m ) =>
m . type === 'Text' ? { statique : m . data , debut : m . start } : { expression : m . expression , debut : m . start } ,
) ;
}
// Le plus proche ancêtre élément d'un nœud du gabarit, ou null.
const ELEMENTS = new Set ( [ 'RegularElement' , 'SvelteElement' , 'Component' , 'SvelteComponent' , 'SlotElement' ] ) ;
function elementParent ( ancetres ) {
for ( let i = ancetres . length - 1 ; i >= 0 ; i -= 1 ) if ( ELEMENTS . has ( ancetres [ i ] . type ) ) return ancetres [ i ] ;
return null ;
}
// Vrai quand le nœud est dans la valeur d'un attribut ou d'une directive.
const DANS _UN _ATTRIBUT = new Set ( [ 'Attribute' , 'StyleDirective' , 'SpreadAttribute' , 'BindDirective' , 'ClassDirective' ] ) ;
const dansUnAttribut = ( ancetres ) => ancetres . some ( ( a ) => DANS _UN _ATTRIBUT . has ( a . type ) ) ;
// --- Garde 1 : les chaînes visibles en dur (§ 14.6) ---------------------------
const LETTRE = /\p{L}/u ;
// Attributs qu'une technologie d'assistance annonce, ou que la page montre.
const ATTRIBUTS _ANNONCES = new Set ( [ 'aria-label' , 'aria-roledescription' , 'title' , 'placeholder' , 'alt' ] ) ;
[FIX] interface: ghost labels, keyboard, valid HTML, closed data-* list
The drag ghost names what releasing at the point changes, floor included.
Escape on a seat returns to its table, a refused Tab stays put, the legend
toggle exposes aria-expanded, and the header markup is valid HTML. Every
data-* attribute the interface sets belongs to one closed list that a guard
reads. The tree guards follow helpers a test imports, and the reporter says
the whole series stays watched under coverage on a small machine.
Checked: every new test red first; 3001 node, 635 browser and 63 long tests
green from the index alone.
--- FR ---
[FIX] interface : fantôme nommé, clavier, HTML valide, data-* fermés
Le fantôme du glisser nomme ce que relâcher au point modifie, sol compris.
Échap sur un siège remonte à sa table, un Tab refusé reste en place, la
bascule de la légende expose aria-expanded, et la tête est du HTML valide.
Chaque attribut data-* que l'interface pose appartient à une liste fermée
qu'une garde lit. Les gardes de l'arbre suivent les aides qu'une épreuve
importe, et le rapporteur dit la série entière surveillée sous couverture.
Vérifié : chaque épreuve rouge d'abord ; 3001 node, 635 navigateur et 63
longues vertes depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 04:01:57 -04:00
// Les types d'<input> dont la page montre la value comme libellé.
const INPUTS _BOUTONS = new Set ( [ 'button' , 'submit' , 'reset' ] ) ;
// Vrai quand l'attribut est la value d'un <input> bouton : un type statique
// button, submit ou reset.
function valeurDeBouton ( attribut , element ) {
if ( attribut . name !== 'value' || element ? . type !== 'RegularElement' || element . name !== 'input' ) return false ;
const type = element . attributes . find ( ( a ) => a . type === 'Attribute' && a . name === 'type' ) ;
const [ morceau ] = type === undefined ? [ ] : morceauxAttribut ( type ) ;
return morceau !== undefined && 'statique' in morceau && INPUTS _BOUTONS . has ( morceau . statique . trim ( ) . toLowerCase ( ) ) ;
}
[ADD] tests: guards for layers, clock, sources, colours, watch patterns
Guards scan the tree file by file and line by line, fail on an empty scan,
and are each shown red on a temporary tree. They cover the interface import
graph, the Electron bridge, one clock, refused dependencies, how tests are
written, hard-coded strings, translation keys, forbidden words, colours and
the conflict hue, markup, pruned selectors and watch patterns. Coverage
keeps the engine integration test on a small machine. Fixes: delays in
tests, the conflict hatch class; « la rend évitable » is a named exception
(§ 5.6). Checked: 2587 node, 465 browser tests, from the index alone.
--- FR ---
[ADD] épreuves : gardes des couches, horloge, sources, couleurs, motifs
Les gardes relèvent par fichier et par ligne, échouent sur un balayage vide
et sont chacune montrées rouges sur un arbre temporaire. Elles couvrent le
graphe d'imports de l'interface, le pont d'Electron, l'horloge unique, les
dépendances refusées, l'écriture des épreuves, les chaînes en dur, les clés
de traduction, les mots interdits, les couleurs et la teinte de conflit, le
balisage, les sélecteurs élagués et les motifs de surveillance. Corrigés :
délais d'épreuves, classe de la trame de conflit ; « la rend évitable » est
une exception nommée (§ 5.6). Vérifié : 2587 node, 465 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 13:21:21 -04:00
// Éléments du SVG dont le contenu est un texte : tout contenu statique non
// blanc y est une chaîne visible, ponctuation comprise.
const TEXTES _SVG = new Set ( [ 'title' , 'desc' , 'text' , 'tspan' ] ) ;
// Une chaîne entre guillemets d'une valeur CSS.
const CHAINE _CSS = /(["'])((?:\\.|(?!\1).)*)\1/g ;
// Vrai quand un texte placé dans cet élément est une chaîne visible : une
// lettre partout, tout caractère non blanc dans un texte du SVG.
function texteVisible ( texte , element ) {
if ( element !== null && element . type === 'RegularElement' && TEXTES _SVG . has ( element . name ) ) {
return texte . trim ( ) !== '' ;
}
return LETTRE . test ( texte ) ;
}
// Vrai quand une valeur de « content » porte une lettre dans une chaîne.
const contenuLettre = ( valeur ) => [ ... valeur . matchAll ( CHAINE _CSS ) ] . some ( ( [ , , chaine ] ) => LETTRE . test ( chaine ) ) ;
// Relevé des chaînes visibles écrites dans les composants de src/interface :
[FIX] interface: ghost labels, keyboard, valid HTML, closed data-* list
The drag ghost names what releasing at the point changes, floor included.
Escape on a seat returns to its table, a refused Tab stays put, the legend
toggle exposes aria-expanded, and the header markup is valid HTML. Every
data-* attribute the interface sets belongs to one closed list that a guard
reads. The tree guards follow helpers a test imports, and the reporter says
the whole series stays watched under coverage on a small machine.
Checked: every new test red first; 3001 node, 635 browser and 63 long tests
green from the index alone.
--- FR ---
[FIX] interface : fantôme nommé, clavier, HTML valide, data-* fermés
Le fantôme du glisser nomme ce que relâcher au point modifie, sol compris.
Échap sur un siège remonte à sa table, un Tab refusé reste en place, la
bascule de la légende expose aria-expanded, et la tête est du HTML valide.
Chaque attribut data-* que l'interface pose appartient à une liste fermée
qu'une garde lit. Les gardes de l'arbre suivent les aides qu'une épreuve
importe, et le rapporteur dit la série entière surveillée sous couverture.
Vérifié : chaque épreuve rouge d'abord ; 3001 node, 635 navigateur et 63
longues vertes depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 04:01:57 -04:00
// texte statique, attribut annoncé ou value d'un <input> bouton, contenu
// d'un texte du SVG, déclaration « content » lettrée, littéral que rend une
// expression du gabarit.
[ADD] tests: guards for layers, clock, sources, colours, watch patterns
Guards scan the tree file by file and line by line, fail on an empty scan,
and are each shown red on a temporary tree. They cover the interface import
graph, the Electron bridge, one clock, refused dependencies, how tests are
written, hard-coded strings, translation keys, forbidden words, colours and
the conflict hue, markup, pruned selectors and watch patterns. Coverage
keeps the engine integration test on a small machine. Fixes: delays in
tests, the conflict hatch class; « la rend évitable » is a named exception
(§ 5.6). Checked: 2587 node, 465 browser tests, from the index alone.
--- FR ---
[ADD] épreuves : gardes des couches, horloge, sources, couleurs, motifs
Les gardes relèvent par fichier et par ligne, échouent sur un balayage vide
et sont chacune montrées rouges sur un arbre temporaire. Elles couvrent le
graphe d'imports de l'interface, le pont d'Electron, l'horloge unique, les
dépendances refusées, l'écriture des épreuves, les chaînes en dur, les clés
de traduction, les mots interdits, les couleurs et la teinte de conflit, le
balisage, les sélecteurs élagués et les motifs de surveillance. Corrigés :
délais d'épreuves, classe de la trame de conflit ; « la rend évitable » est
une exception nommée (§ 5.6). Vérifié : 2587 node, 465 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 13:21:21 -04:00
function releverChainesEnDur ( racine ) {
const chemins = exigerNonVide ( sources ( racine , [ 'src/interface' ] , [ '.svelte' ] ) , 'chaînes en dur' ) ;
const releves = [ ] ;
for ( const chemin of chemins ) {
const { texte , ast } = analyserComposant ( racine , chemin ) ;
const relever = ( debut , motif , valeur ) =>
releves . push ( { chemin , ligne : ligneDe ( texte , debut ) , texte : ` ${ motif } ${ extrait ( valeur ) } ` } ) ;
parcourir ( ast . fragment , ( noeud , ancetres ) => {
if ( noeud . type === 'Text' && ! dansUnAttribut ( ancetres ) ) {
const element = elementParent ( ancetres ) ;
if ( texteVisible ( noeud . data , element ) ) relever ( noeud . start , 'texte' , noeud . data ) ;
} else if ( noeud . type === 'ExpressionTag' && ! dansUnAttribut ( ancetres ) ) {
const element = elementParent ( ancetres ) ;
for ( const { valeur , debut } of litterauxRendus ( noeud . expression ) ) {
if ( texteVisible ( valeur , element ) ) relever ( debut , 'expression littérale' , valeur ) ;
}
[FIX] interface: ghost labels, keyboard, valid HTML, closed data-* list
The drag ghost names what releasing at the point changes, floor included.
Escape on a seat returns to its table, a refused Tab stays put, the legend
toggle exposes aria-expanded, and the header markup is valid HTML. Every
data-* attribute the interface sets belongs to one closed list that a guard
reads. The tree guards follow helpers a test imports, and the reporter says
the whole series stays watched under coverage on a small machine.
Checked: every new test red first; 3001 node, 635 browser and 63 long tests
green from the index alone.
--- FR ---
[FIX] interface : fantôme nommé, clavier, HTML valide, data-* fermés
Le fantôme du glisser nomme ce que relâcher au point modifie, sol compris.
Échap sur un siège remonte à sa table, un Tab refusé reste en place, la
bascule de la légende expose aria-expanded, et la tête est du HTML valide.
Chaque attribut data-* que l'interface pose appartient à une liste fermée
qu'une garde lit. Les gardes de l'arbre suivent les aides qu'une épreuve
importe, et le rapporteur dit la série entière surveillée sous couverture.
Vérifié : chaque épreuve rouge d'abord ; 3001 node, 635 navigateur et 63
longues vertes depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 04:01:57 -04:00
} else if ( noeud . type === 'Attribute' && ( ATTRIBUTS _ANNONCES . has ( noeud . name ) || valeurDeBouton ( noeud , elementParent ( ancetres ) ) ) ) {
[ADD] tests: guards for layers, clock, sources, colours, watch patterns
Guards scan the tree file by file and line by line, fail on an empty scan,
and are each shown red on a temporary tree. They cover the interface import
graph, the Electron bridge, one clock, refused dependencies, how tests are
written, hard-coded strings, translation keys, forbidden words, colours and
the conflict hue, markup, pruned selectors and watch patterns. Coverage
keeps the engine integration test on a small machine. Fixes: delays in
tests, the conflict hatch class; « la rend évitable » is a named exception
(§ 5.6). Checked: 2587 node, 465 browser tests, from the index alone.
--- FR ---
[ADD] épreuves : gardes des couches, horloge, sources, couleurs, motifs
Les gardes relèvent par fichier et par ligne, échouent sur un balayage vide
et sont chacune montrées rouges sur un arbre temporaire. Elles couvrent le
graphe d'imports de l'interface, le pont d'Electron, l'horloge unique, les
dépendances refusées, l'écriture des épreuves, les chaînes en dur, les clés
de traduction, les mots interdits, les couleurs et la teinte de conflit, le
balisage, les sélecteurs élagués et les motifs de surveillance. Corrigés :
délais d'épreuves, classe de la trame de conflit ; « la rend évitable » est
une exception nommée (§ 5.6). Vérifié : 2587 node, 465 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 13:21:21 -04:00
for ( const morceau of morceauxAttribut ( noeud ) ) {
const valeurs = 'statique' in morceau
? [ { valeur : morceau . statique , debut : morceau . debut } ]
: litterauxRendus ( morceau . expression ) ;
for ( const { valeur , debut } of valeurs ) {
if ( LETTRE . test ( valeur ) ) relever ( debut , ` attribut ${ noeud . name } ` , valeur ) ;
}
}
} else if ( noeud . type === 'Attribute' && noeud . name === 'style' ) {
for ( const morceau of morceauxAttribut ( noeud ) ) {
if ( 'statique' in morceau && /content\s*:/ . test ( morceau . statique ) && contenuLettre ( morceau . statique ) ) {
relever ( morceau . debut , 'content' , morceau . statique ) ;
}
}
}
} ) ;
parcourir ( ast . css , ( noeud ) => {
if ( noeud . type === 'Declaration' && noeud . property === 'content' && contenuLettre ( noeud . value ) ) {
relever ( noeud . start , 'content' , noeud . value ) ;
}
} ) ;
}
return trier ( releves ) ;
}
// Relevé trié par chemin, puis par ligne, puis par texte ; rendu en chaînes.
function trier ( releves ) {
return releves
. sort ( ( a , b ) =>
a . chemin < b . chemin ? - 1 : a . chemin > b . chemin ? 1 : a . ligne - b . ligne || ( a . texte < b . texte ? - 1 : a . texte > b . texte ? 1 : 0 ) ,
)
. map ( ( { chemin , ligne , texte } ) => ` ${ chemin } : ${ ligne } ${ texte } ` ) ;
}
// --- Garde 2 : les clés littérales de t existent (§ 14.6, § 19.11) ------------
// Les clés de toutes les tables assemblées, celles qu'App donne à t.
const CLES = new Set ( creerTraduction ( [ ... TABLES _APPLICATION , ... TABLES _INTERFACE ] ) . cles ( ) ) ;
// La clé littérale du premier argument d'un appel de t — t(…) ou x.t(…) —, ou
// null : une clé composée à l'exécution échappe à cette garde.
function cleLitterale ( appel ) {
const appele = appel . callee ;
const estT =
( appele . type === 'Identifier' && appele . name === 't' ) ||
( appele . type === 'MemberExpression' && ! appele . computed && appele . property . name === 't' ) ;
if ( ! estT ) return null ;
const [ premier ] = appel . arguments ;
if ( premier ? . type === 'Literal' && typeof premier . value === 'string' ) return premier . value ;
if ( premier ? . type === 'TemplateLiteral' && premier . expressions . length === 0 ) return premier . quasis [ 0 ] . value . cooked ;
return null ;
}
// Relevé des clés littérales passées à t, dans les composants et les modules
// de src/interface, qu'aucune table de cles ne porte. Lève quand aucun fichier
// n'est examiné, et quand aucune clé littérale ne l'est : un accesseur renommé
// ou une forme d'appel que la garde ne reconnaît plus la viderait sans bruit.
function releverClesAbsentes ( racine , cles ) {
const chemins = exigerNonVide ( sources ( racine , [ 'src/interface' ] , [ '.svelte' , '.js' ] ) , 'clés de t' ) ;
const releves = [ ] ;
let nbCles = 0 ;
for ( const chemin of chemins ) {
const { texte , ast } = chemin . endsWith ( '.svelte' ) ? analyserComposant ( racine , chemin ) : analyserModule ( racine , chemin ) ;
const parties = chemin . endsWith ( '.svelte' ) ? partiesCode ( ast ) : [ ast ] ;
parcourir ( parties , ( noeud ) => {
if ( noeud . type !== 'CallExpression' ) return ;
const cle = cleLitterale ( noeud ) ;
if ( cle === null ) return ;
nbCles += 1 ;
if ( ! cles . has ( cle ) ) releves . push ( { chemin , ligne : ligneDe ( texte , noeud . start ) , texte : ` clé absente ${ extrait ( cle ) } ` } ) ;
} ) ;
}
assert . ok ( nbCles > 0 , 'clés de t : aucune clé examinée' ) ;
return trier ( releves ) ;
}
// --- Garde 3 : le contenu des tables de traduction ----------------------------
// Un texte comparé sans égard à la casse ni aux accents ; l'apostrophe
// typographique se lit comme la droite.
const normaliser = ( texte ) =>
texte . normalize ( 'NFD' ) . replace ( /\p{M}/gu , '' ) . toLowerCase ( ) . replace ( /[\u{2019}\u{02BC}]/gu , "'" ) ;
// Mots que le logiciel n'écrit pas, cherchés dans le texte normalisé. Un
// « écart » nu n'existe pas au glossaire (§ 5.5) : seuls l'écart
// d'itinéraire et l'écart au plafond a priori y figurent ; « écarté », du
// verbe, n'en est pas un.
const MOTS _INTERDITS = [
[ 'conforme' , /conform/ ] ,
[ 'écart hors du glossaire' , /\becarts?\b(?!\s+(?:d'itineraire|au plafond a priori))/ ] ,
[ 'satisfaction' , /satisfaction/ ] ,
[ 'écart-type' , /\becarts?[\s-]+types?\b/ ] ,
[ 'variance' , /\bvariances?\b/ ] ,
[ 'équité' , /\bequite\b/ ] ,
[ 'score' , /\bscores?\b/ ] ,
[ 'peut mieux faire' , /\bpeut mieux faire\b/ ] ,
[ 'manque minimal' , /\bmanques?\s+minima/ ] ,
[ 'récupérable' , /recuperable/ ] ,
[ 'évitable' , /(?<!in)evitable/ ] ,
] ;
const PICTOGRAMME = /\p{Extended_Pictographic}/u ;
// Les exceptions nommées aux mots refusés. Chacune admet une forme exacte du
// texte normalisé, pour un mot de MOTS_INTERDITS, dans les seules valeurs dont
// la clé répond à cles ; tout autre emploi du mot, dans la même valeur
// comprise, reste relevé. Une exception qu'aucune valeur ne sert se relève
// « sans objet ».
//
// Le diagnostic d'un manque énonce ce qui le rendrait évitable (§ 5.6) : ses
// remèdes disent qu'un changement « la rend » ou « la rendent évitable ».
// Ailleurs, « évitable » nommerait le second segment du manque (§ 12.4), que
// le logiciel n'écrit pas.
const EXCEPTIONS _DE _CONTENU = [
{
mot : 'évitable' ,
cles : /^diagnostic\.remede\./ ,
forme : /\bla rend(?:ent)? evitable\b/g ,
nom : 'diagnostic.remede.* « la rend(ent) évitable »' ,
} ,
] ;
// Une chaîne de forme clé — un code, LECTURE_SEULE, ou un libellé,
// plan.refus.fermer — : une fonction la passe à texte, ou la compare à un
// détail, et ne l'affiche pas.
const FORME _CLE = /^(?:[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)*|[a-z][A-Za-z0-9]*(?:\.[A-Za-z0-9]+)+)$/ ;
// Le texte d'une fonction de détails qu'un écran peut lire : ses chaînes
// littérales et les morceaux fixes de ses gabarits de chaîne, échappements
// décodés, joints par un blanc ; une chaîne de forme clé n'en est pas. Le nom
// d'une variable n'est pas un texte : une fonction qui nomme « ecart » une
// valeur intermédiaire n'écrit pas le mot. Un texte que l'analyseur refuse se
// lit en entier.
function texteDeFonction ( fonction ) {
const source = String ( fonction ) ;
let programme ;
try {
programme = parse ( ` <script module>( ${ source } );</script> ` , { modern : true } ) . module . content ;
} catch {
return source ;
}
const morceaux = [ ] ;
parcourir ( programme , ( noeud ) => {
if ( noeud . type === 'Literal' && typeof noeud . value === 'string' && ! FORME _CLE . test ( noeud . value ) ) morceaux . push ( noeud . value ) ;
if ( noeud . type === 'TemplateElement' ) morceaux . push ( noeud . value . cooked ? ? noeud . value . raw ) ;
} ) ;
return morceaux . join ( ' ' ) ;
}
// Relevé « clé : motif « extrait » » des valeurs des tables : chaque chaîne,
// et le texte de chaque fonction de détails (texteDeFonction). localiser(cle),
// quand il est donné, rend { chemin, ligne } de la clé, qui préfixe le relevé
// et l'ordonne ; sans lieu, le relevé s'ordonne par clé. Une forme qu'une
// exception admet est effacée du texte avant que son mot y soit cherché ; suit
// une ligne « exception sans objet : nom » par exception qui n'a rien effacé,
// dans l'ordre de la liste.
function releverContenuTables ( tables , localiser = ( ) => null , exceptions = EXCEPTIONS _DE _CONTENU ) {
const entrees = tables . flatMap ( ( table ) => [ ... table ] ) ;
assert . ok ( entrees . length > 0 , 'contenu des tables : aucune entrée examinée' ) ;
const releves = [ ] ;
const servies = new Set ( ) ;
// Le texte normalisé d'une valeur, privé des formes que les exceptions du
// mot admettent pour cette clé.
const sansFormesAdmises = ( normale , mot , cle ) =>
exceptions
. filter ( ( exception ) => exception . mot === mot && exception . cles . test ( cle ) )
. reduce ( ( texte , exception ) => {
const reste = texte . replace ( exception . forme , ' ' ) ;
if ( reste !== texte ) servies . add ( exception ) ;
return reste ;
} , normale ) ;
for ( const [ cle , entree ] of entrees ) {
const valeur = typeof entree === 'function' ? texteDeFonction ( entree ) : entree ;
const normale = normaliser ( valeur ) ;
const { chemin , ligne } = localiser ( cle ) ? ? { chemin : '' , ligne : 0 } ;
const relever = ( motif ) => releves . push ( { chemin , ligne , texte : ` ${ cle } : ${ motif } ${ extrait ( valeur ) } ` } ) ;
for ( const [ mot , motif ] of MOTS _INTERDITS ) if ( motif . test ( sansFormesAdmises ( normale , mot , cle ) ) ) relever ( mot ) ;
const pictogramme = valeur . match ( PICTOGRAMME ) ;
if ( pictogramme !== null ) {
relever ( ` pictogramme U+ ${ pictogramme [ 0 ] . codePointAt ( 0 ) . toString ( 16 ) . toUpperCase ( ) . padStart ( 4 , '0' ) } ` ) ;
}
}
const sansObjet = exceptions . filter ( ( exception ) => ! servies . has ( exception ) ) . map ( ( { nom } ) => ` exception sans objet : ${ nom } ` ) ;
return [ ... trier ( releves ) . map ( ( releve ) => releve . replace ( /^:0 / , '' ) ) , ... sansObjet ] ;
}
// { chemin, ligne } de l'entrée [cle, …] dans les tables de libelles/ de
// l'application et des écrans, ou null.
function localisateur ( racine ) {
const fichiers = sources ( racine , [ 'src/application/libelles' , 'src/interface/libelles' ] , [ '.js' ] ) . map ( ( chemin ) => ( {
chemin ,
texte : readFileSync ( join ( racine , chemin ) , 'utf8' ) ,
} ) ) ;
return ( cle ) => {
const motif = new RegExp ( ` \\ [ \\ s*(['" \` ]) ${ RegExp . escape ( cle ) } \\ 1 \\ s*, ` ) ;
for ( const { chemin , texte } of fichiers ) {
const trouve = motif . exec ( texte ) ;
if ( trouve !== null ) return { chemin , ligne : ligneDe ( texte , trouve . index ) } ;
}
return null ;
} ;
}
// --- Garde 4 : les couleurs (§ 12.7, § 13.3, § 13.5) --------------------------
const FEUILLE _DES _JETONS = 'src/interface/jetons.css' ;
[ADD] plan: one hue and pattern per conflict rule, legend, appartenance
The plan drew every conflict in one hue, so a table that seats colleagues
together looked like one where people meet again. Each active rule now has
its own reserved hue and pattern: same appartenance hatched one way, met
again hatched the other, back at a table dotted; rules on one table
overlap, the badge keeps the total. A legend from the view bar shows each
active rule's sample. A table's tooltip names the appartenance that brings
a pair together. The hues are validated by computation in both themes and
the spec (7.5, 12.7) follows. Checked: 2848 node, 580 browser tests.
--- FR ---
[ADD] plan : une teinte et une trame par règle, légende, appartenance
Le plan dessinait tout conflit d'une seule teinte : une table qui réunit
des collègues ressemblait à une table où des gens se revoient. Chaque
règle active a sa teinte réservée et sa trame : même appartenance hachurée
dans un sens, voisins revus dans l'autre, table revue en pointillé ; les
règles d'une table se superposent, la pastille garde le total. Une légende
dans la barre de vue montre l'échantillon de chaque règle active.
L'infobulle nomme l'appartenance qui réunit une paire. Teintes validées
par calcul dans les deux thèmes ; spec § 7.5, § 12.7. Vérifié : 2848, 580.
Assisted-by: Claude Opus 5.5
2026-10-08 04:33:43 -04:00
// La famille des teintes de conflit (§ 7.5, § 12.7) : --couleur-conflit et
// chaque --couleur-conflit-…, une par règle que le plan localise ; un nom
// qui les prolonge sans tiret n'en est pas.
const JETON _CONFLIT = /--couleur-conflit(?:-[\w-]+)?(?![\w-])/ ;
const litConflit = ( valeur ) => JETON _CONFLIT . test ( valeur ) ;
[FIX] interface: ghost labels, keyboard, valid HTML, closed data-* list
The drag ghost names what releasing at the point changes, floor included.
Escape on a seat returns to its table, a refused Tab stays put, the legend
toggle exposes aria-expanded, and the header markup is valid HTML. Every
data-* attribute the interface sets belongs to one closed list that a guard
reads. The tree guards follow helpers a test imports, and the reporter says
the whole series stays watched under coverage on a small machine.
Checked: every new test red first; 3001 node, 635 browser and 63 long tests
green from the index alone.
--- FR ---
[FIX] interface : fantôme nommé, clavier, HTML valide, data-* fermés
Le fantôme du glisser nomme ce que relâcher au point modifie, sol compris.
Échap sur un siège remonte à sa table, un Tab refusé reste en place, la
bascule de la légende expose aria-expanded, et la tête est du HTML valide.
Chaque attribut data-* que l'interface pose appartient à une liste fermée
qu'une garde lit. Les gardes de l'arbre suivent les aides qu'une épreuve
importe, et le rapporteur dit la série entière surveillée sous couverture.
Vérifié : chaque épreuve rouge d'abord ; 3001 node, 635 navigateur et 63
longues vertes depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 04:01:57 -04:00
// Les jetons de conflit qu'une valeur lit, chacun une fois.
const jetonsLus = ( valeur ) => [ ... new Set ( valeur . match ( new RegExp ( JETON _CONFLIT . source , 'g' ) ) ) ] ;
[ADD] tests: guards for layers, clock, sources, colours, watch patterns
Guards scan the tree file by file and line by line, fail on an empty scan,
and are each shown red on a temporary tree. They cover the interface import
graph, the Electron bridge, one clock, refused dependencies, how tests are
written, hard-coded strings, translation keys, forbidden words, colours and
the conflict hue, markup, pruned selectors and watch patterns. Coverage
keeps the engine integration test on a small machine. Fixes: delays in
tests, the conflict hatch class; « la rend évitable » is a named exception
(§ 5.6). Checked: 2587 node, 465 browser tests, from the index alone.
--- FR ---
[ADD] épreuves : gardes des couches, horloge, sources, couleurs, motifs
Les gardes relèvent par fichier et par ligne, échouent sur un balayage vide
et sont chacune montrées rouges sur un arbre temporaire. Elles couvrent le
graphe d'imports de l'interface, le pont d'Electron, l'horloge unique, les
dépendances refusées, l'écriture des épreuves, les chaînes en dur, les clés
de traduction, les mots interdits, les couleurs et la teinte de conflit, le
balisage, les sélecteurs élagués et les motifs de surveillance. Corrigés :
délais d'épreuves, classe de la trame de conflit ; « la rend évitable » est
une exception nommée (§ 5.6). Vérifié : 2587 node, 465 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 13:21:21 -04:00
// Les couleurs nommées de CSS, et les couleurs du système : chacune est un
// littéral. transparent et currentColor ne portent aucune teinte et restent
// admis.
const COULEURS _NOMMEES = (
'aliceblue antiquewhite aqua aquamarine azure beige bisque black blanchedalmond blue blueviolet brown ' +
'burlywood cadetblue chartreuse chocolate coral cornflowerblue cornsilk crimson cyan darkblue darkcyan ' +
'darkgoldenrod darkgray darkgreen darkgrey darkkhaki darkmagenta darkolivegreen darkorange darkorchid ' +
'darkred darksalmon darkseagreen darkslateblue darkslategray darkslategrey darkturquoise darkviolet ' +
'deeppink deepskyblue dimgray dimgrey dodgerblue firebrick floralwhite forestgreen fuchsia gainsboro ' +
'ghostwhite gold goldenrod gray green greenyellow grey honeydew hotpink indianred indigo ivory khaki ' +
'lavender lavenderblush lawngreen lemonchiffon lightblue lightcoral lightcyan lightgoldenrodyellow ' +
'lightgray lightgreen lightgrey lightpink lightsalmon lightseagreen lightskyblue lightslategray ' +
'lightslategrey lightsteelblue lightyellow lime limegreen linen magenta maroon mediumaquamarine ' +
'mediumblue mediumorchid mediumpurple mediumseagreen mediumslateblue mediumspringgreen mediumturquoise ' +
'mediumvioletred midnightblue mintcream mistyrose moccasin navajowhite navy oldlace olive olivedrab ' +
'orange orangered orchid palegoldenrod palegreen paleturquoise palevioletred papayawhip peachpuff peru ' +
'pink plum powderblue purple rebeccapurple red rosybrown royalblue saddlebrown salmon sandybrown ' +
'seagreen seashell sienna silver skyblue slateblue slategray slategrey snow springgreen steelblue tan ' +
'teal thistle tomato turquoise violet wheat white whitesmoke yellow yellowgreen ' +
'accentcolor accentcolortext activetext buttonborder buttonface buttontext canvas canvastext field ' +
'fieldtext graytext highlight highlighttext linktext mark marktext selecteditem selecteditemtext visitedtext'
) . split ( ' ' ) ;
const COULEUR _NOMMEE = new RegExp ( ` (?<![ \\ w-])(?: ${ COULEURS _NOMMEES . join ( '|' ) } )(?![ \\ w-]) ` , 'i' ) ;
const COULEUR _HEX = /#(?:[0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{3,4})(?![\w-])/i ;
const COULEUR _FONCTION = /(?<![\w-])(?:rgba?|hsla?|hwb|lab|lch|oklab|oklch|color|color-mix|light-dark)\s*\(/i ;
// Le littéral de couleur que porte une valeur CSS ou d'attribut, ou null. Les
// noms de propriétés personnalisées, les adresses url(…) et les chaînes
// entre guillemets sont retirés avant la recherche.
function couleurLitterale ( valeur ) {
const nette = valeur
. replace ( /url\([^)]*\)/gi , ' ' )
. replace ( CHAINE _CSS , ' ' )
. replace ( /--[\w-]+/g , ' ' ) ;
for ( const motif of [ COULEUR _HEX , COULEUR _FONCTION , COULEUR _NOMMEE ] ) {
const trouve = nette . match ( motif ) ;
if ( trouve !== null ) return trouve [ 0 ] ;
}
return null ;
}
// Une chaîne qui est en entier un littéral de couleur : un code hexadécimal,
// une fonction de couleur, un nom de couleur.
const COULEUR _ENTIERE = new RegExp (
` ^(?:#(?:[0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{3,4})| ${ COULEUR _FONCTION . source } .* \\ )| ${ COULEURS _NOMMEES . join ( '|' ) } ) $ ` ,
'is' ,
) ;
// Attributs du gabarit qui portent une couleur.
const ATTRIBUTS _COULEUR = new Set ( [ 'fill' , 'stroke' , 'color' , 'stop-color' , 'flood-color' , 'lighting-color' ] ) ;
// Les déclarations d'une feuille, chacune avec le texte du sélecteur de la
// règle la plus proche et l'arbre de ce sélecteur (null hors de toute règle) :
// { propriete, valeur, debut, selecteur, prelude }.
function declarations ( css , texte ) {
const trouvees = [ ] ;
parcourir ( css , ( noeud , ancetres ) => {
if ( noeud . type !== 'Declaration' ) return ;
const regle = ancetres . findLast ( ( a ) => a . type === 'Rule' ) ;
const selecteur = regle === undefined ? '' : texte . slice ( regle . prelude . start , regle . prelude . end ) ;
const prelude = regle === undefined ? null : regle . prelude ;
trouvees . push ( { propriete : noeud . property , valeur : noeud . value , debut : noeud . start , selecteur , prelude } ) ;
} ) ;
return trouvees ;
}
// Les déclarations d'un attribut style statique : « propriété: valeur » par
// point-virgule, positionnées au début du morceau.
const declarationsEnLigne = ( statique , debut ) =>
statique
. split ( ';' )
. map ( ( morceau ) => / ^ \ s * ( [ \ w - ] + ) \ s * : ( . * ) $ / s . exec ( morceau ) )
. filter ( ( d ) => d !== null )
. map ( ( [ , propriete , valeur ] ) => ( { propriete , valeur , debut } ) ) ;
[FIX] interface: ghost labels, keyboard, valid HTML, closed data-* list
The drag ghost names what releasing at the point changes, floor included.
Escape on a seat returns to its table, a refused Tab stays put, the legend
toggle exposes aria-expanded, and the header markup is valid HTML. Every
data-* attribute the interface sets belongs to one closed list that a guard
reads. The tree guards follow helpers a test imports, and the reporter says
the whole series stays watched under coverage on a small machine.
Checked: every new test red first; 3001 node, 635 browser and 63 long tests
green from the index alone.
--- FR ---
[FIX] interface : fantôme nommé, clavier, HTML valide, data-* fermés
Le fantôme du glisser nomme ce que relâcher au point modifie, sol compris.
Échap sur un siège remonte à sa table, un Tab refusé reste en place, la
bascule de la légende expose aria-expanded, et la tête est du HTML valide.
Chaque attribut data-* que l'interface pose appartient à une liste fermée
qu'une garde lit. Les gardes de l'arbre suivent les aides qu'une épreuve
importe, et le rapporteur dit la série entière surveillée sous couverture.
Vérifié : chaque épreuve rouge d'abord ; 3001 node, 635 navigateur et 63
longues vertes depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 04:01:57 -04:00
// Les classes de conflit, liste fermée, et le jeton que chacune lit :
// « conflit », l'état d'une table en conflit, --couleur-conflit ; puis
// « <sorte>-conflit », la trame d'une sorte de CONFLITS (marques.js), le
// jeton de cette sorte, --couleur-conflit-<sorte>. Une autre classe —
// « sans-conflit », « fin-de-conflit », « conflit-leve » — ne nomme pas un
// conflit.
const CLASSES _DE _CONFLIT = new Map ( [
[ 'conflit' , '--couleur-conflit' ] ,
... JETONS _CONFLIT . map ( ( jeton ) => [ ` ${ jeton . slice ( '--couleur-conflit-' . length ) } -conflit ` , jeton ] ) ,
] ) ;
[ADD] tests: guards for layers, clock, sources, colours, watch patterns
Guards scan the tree file by file and line by line, fail on an empty scan,
and are each shown red on a temporary tree. They cover the interface import
graph, the Electron bridge, one clock, refused dependencies, how tests are
written, hard-coded strings, translation keys, forbidden words, colours and
the conflict hue, markup, pruned selectors and watch patterns. Coverage
keeps the engine integration test on a small machine. Fixes: delays in
tests, the conflict hatch class; « la rend évitable » is a named exception
(§ 5.6). Checked: 2587 node, 465 browser tests, from the index alone.
--- FR ---
[ADD] épreuves : gardes des couches, horloge, sources, couleurs, motifs
Les gardes relèvent par fichier et par ligne, échouent sur un balayage vide
et sont chacune montrées rouges sur un arbre temporaire. Elles couvrent le
graphe d'imports de l'interface, le pont d'Electron, l'horloge unique, les
dépendances refusées, l'écriture des épreuves, les chaînes en dur, les clés
de traduction, les mots interdits, les couleurs et la teinte de conflit, le
balisage, les sélecteurs élagués et les motifs de surveillance. Corrigés :
délais d'épreuves, classe de la trame de conflit ; « la rend évitable » est
une exception nommée (§ 5.6). Vérifié : 2587 node, 465 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 13:21:21 -04:00
[FIX] interface: ghost labels, keyboard, valid HTML, closed data-* list
The drag ghost names what releasing at the point changes, floor included.
Escape on a seat returns to its table, a refused Tab stays put, the legend
toggle exposes aria-expanded, and the header markup is valid HTML. Every
data-* attribute the interface sets belongs to one closed list that a guard
reads. The tree guards follow helpers a test imports, and the reporter says
the whole series stays watched under coverage on a small machine.
Checked: every new test red first; 3001 node, 635 browser and 63 long tests
green from the index alone.
--- FR ---
[FIX] interface : fantôme nommé, clavier, HTML valide, data-* fermés
Le fantôme du glisser nomme ce que relâcher au point modifie, sol compris.
Échap sur un siège remonte à sa table, un Tab refusé reste en place, la
bascule de la légende expose aria-expanded, et la tête est du HTML valide.
Chaque attribut data-* que l'interface pose appartient à une liste fermée
qu'une garde lit. Les gardes de l'arbre suivent les aides qu'une épreuve
importe, et le rapporteur dit la série entière surveillée sous couverture.
Vérifié : chaque épreuve rouge d'abord ; 3001 node, 635 navigateur et 63
longues vertes depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 04:01:57 -04:00
// Vrai quand une déclaration qui lit les jetons de conflit jetons est l'une
// des règles de conflit du plan : dans un composant de src/interface/plan/,
// sous une règle dont CHAQUE sélecteur de la liste porte, dans l'un de ses
// sélecteurs composés, une classe de conflit dont le jeton est l'un de ceux
// lus, et dont aucun jeton lu n'appartient à une autre règle : la trame d'une
// sorte ne lit que la teinte de sa sorte. Seules les classes posées
// directement sur un composé comptent : celles qu'un :not(…), un :is(…) ou un
// :global(…) enveloppe sont des arguments et ne qualifient pas la règle.
function regleDeConflit ( chemin , prelude , jetons ) {
[ADD] tests: guards for layers, clock, sources, colours, watch patterns
Guards scan the tree file by file and line by line, fail on an empty scan,
and are each shown red on a temporary tree. They cover the interface import
graph, the Electron bridge, one clock, refused dependencies, how tests are
written, hard-coded strings, translation keys, forbidden words, colours and
the conflict hue, markup, pruned selectors and watch patterns. Coverage
keeps the engine integration test on a small machine. Fixes: delays in
tests, the conflict hatch class; « la rend évitable » is a named exception
(§ 5.6). Checked: 2587 node, 465 browser tests, from the index alone.
--- FR ---
[ADD] épreuves : gardes des couches, horloge, sources, couleurs, motifs
Les gardes relèvent par fichier et par ligne, échouent sur un balayage vide
et sont chacune montrées rouges sur un arbre temporaire. Elles couvrent le
graphe d'imports de l'interface, le pont d'Electron, l'horloge unique, les
dépendances refusées, l'écriture des épreuves, les chaînes en dur, les clés
de traduction, les mots interdits, les couleurs et la teinte de conflit, le
balisage, les sélecteurs élagués et les motifs de surveillance. Corrigés :
délais d'épreuves, classe de la trame de conflit ; « la rend évitable » est
une exception nommée (§ 5.6). Vérifié : 2587 node, 465 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 13:21:21 -04:00
if ( ! chemin . startsWith ( 'src/interface/plan/' ) || prelude ? . type !== 'SelectorList' ) return false ;
[FIX] interface: ghost labels, keyboard, valid HTML, closed data-* list
The drag ghost names what releasing at the point changes, floor included.
Escape on a seat returns to its table, a refused Tab stays put, the legend
toggle exposes aria-expanded, and the header markup is valid HTML. Every
data-* attribute the interface sets belongs to one closed list that a guard
reads. The tree guards follow helpers a test imports, and the reporter says
the whole series stays watched under coverage on a small machine.
Checked: every new test red first; 3001 node, 635 browser and 63 long tests
green from the index alone.
--- FR ---
[FIX] interface : fantôme nommé, clavier, HTML valide, data-* fermés
Le fantôme du glisser nomme ce que relâcher au point modifie, sol compris.
Échap sur un siège remonte à sa table, un Tab refusé reste en place, la
bascule de la légende expose aria-expanded, et la tête est du HTML valide.
Chaque attribut data-* que l'interface pose appartient à une liste fermée
qu'une garde lit. Les gardes de l'arbre suivent les aides qu'une épreuve
importe, et le rapporteur dit la série entière surveillée sous couverture.
Vérifié : chaque épreuve rouge d'abord ; 3001 node, 635 navigateur et 63
longues vertes depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 04:01:57 -04:00
return prelude . children . every ( ( complexe ) => {
const lus = complexe . children . flatMap ( ( relatif ) =>
relatif . selectors . filter ( ( simple ) => simple . type === 'ClassSelector' && CLASSES _DE _CONFLIT . has ( simple . name ) ) ,
) ;
const permis = new Set ( lus . map ( ( { name } ) => CLASSES _DE _CONFLIT . get ( name ) ) ) ;
return lus . length > 0 && jetons . every ( ( jeton ) => permis . has ( jeton ) ) ;
} ) ;
[ADD] tests: guards for layers, clock, sources, colours, watch patterns
Guards scan the tree file by file and line by line, fail on an empty scan,
and are each shown red on a temporary tree. They cover the interface import
graph, the Electron bridge, one clock, refused dependencies, how tests are
written, hard-coded strings, translation keys, forbidden words, colours and
the conflict hue, markup, pruned selectors and watch patterns. Coverage
keeps the engine integration test on a small machine. Fixes: delays in
tests, the conflict hatch class; « la rend évitable » is a named exception
(§ 5.6). Checked: 2587 node, 465 browser tests, from the index alone.
--- FR ---
[ADD] épreuves : gardes des couches, horloge, sources, couleurs, motifs
Les gardes relèvent par fichier et par ligne, échouent sur un balayage vide
et sont chacune montrées rouges sur un arbre temporaire. Elles couvrent le
graphe d'imports de l'interface, le pont d'Electron, l'horloge unique, les
dépendances refusées, l'écriture des épreuves, les chaînes en dur, les clés
de traduction, les mots interdits, les couleurs et la teinte de conflit, le
balisage, les sélecteurs élagués et les motifs de surveillance. Corrigés :
délais d'épreuves, classe de la trame de conflit ; « la rend évitable » est
une exception nommée (§ 5.6). Vérifié : 2587 node, 465 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 13:21:21 -04:00
}
// Relevé des couleurs : littéral de couleur dans une feuille, un attribut
// style ou de couleur, une directive style:, ou une chaîne du script d'un
[ADD] plan: one hue and pattern per conflict rule, legend, appartenance
The plan drew every conflict in one hue, so a table that seats colleagues
together looked like one where people meet again. Each active rule now has
its own reserved hue and pattern: same appartenance hatched one way, met
again hatched the other, back at a table dotted; rules on one table
overlap, the badge keeps the total. A legend from the view bar shows each
active rule's sample. A table's tooltip names the appartenance that brings
a pair together. The hues are validated by computation in both themes and
the spec (7.5, 12.7) follows. Checked: 2848 node, 580 browser tests.
--- FR ---
[ADD] plan : une teinte et une trame par règle, légende, appartenance
Le plan dessinait tout conflit d'une seule teinte : une table qui réunit
des collègues ressemblait à une table où des gens se revoient. Chaque
règle active a sa teinte réservée et sa trame : même appartenance hachurée
dans un sens, voisins revus dans l'autre, table revue en pointillé ; les
règles d'une table se superposent, la pastille garde le total. Une légende
dans la barre de vue montre l'échantillon de chaque règle active.
L'infobulle nomme l'appartenance qui réunit une paire. Teintes validées
par calcul dans les deux thèmes ; spec § 7.5, § 12.7. Vérifié : 2848, 580.
Assisted-by: Claude Opus 5.5
2026-10-08 04:33:43 -04:00
// composant ; déclaration de jeton hors de la feuille des jetons ; lecture
[FIX] interface: ghost labels, keyboard, valid HTML, closed data-* list
The drag ghost names what releasing at the point changes, floor included.
Escape on a seat returns to its table, a refused Tab stays put, the legend
toggle exposes aria-expanded, and the header markup is valid HTML. Every
data-* attribute the interface sets belongs to one closed list that a guard
reads. The tree guards follow helpers a test imports, and the reporter says
the whole series stays watched under coverage on a small machine.
Checked: every new test red first; 3001 node, 635 browser and 63 long tests
green from the index alone.
--- FR ---
[FIX] interface : fantôme nommé, clavier, HTML valide, data-* fermés
Le fantôme du glisser nomme ce que relâcher au point modifie, sol compris.
Échap sur un siège remonte à sa table, un Tab refusé reste en place, la
bascule de la légende expose aria-expanded, et la tête est du HTML valide.
Chaque attribut data-* que l'interface pose appartient à une liste fermée
qu'une garde lit. Les gardes de l'arbre suivent les aides qu'une épreuve
importe, et le rapporteur dit la série entière surveillée sous couverture.
Vérifié : chaque épreuve rouge d'abord ; 3001 node, 635 navigateur et 63
longues vertes depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 04:01:57 -04:00
// d'une teinte de conflit hors des règles de conflit du plan. Les composants et
// les modules JavaScript de src/interface, épreuves exclues, et les feuilles
// .css de src/ sont lus, la feuille des jetons exceptée : une chaîne d'un
// module voisin atteint fill ou stroke par une expression.
[ADD] tests: guards for layers, clock, sources, colours, watch patterns
Guards scan the tree file by file and line by line, fail on an empty scan,
and are each shown red on a temporary tree. They cover the interface import
graph, the Electron bridge, one clock, refused dependencies, how tests are
written, hard-coded strings, translation keys, forbidden words, colours and
the conflict hue, markup, pruned selectors and watch patterns. Coverage
keeps the engine integration test on a small machine. Fixes: delays in
tests, the conflict hatch class; « la rend évitable » is a named exception
(§ 5.6). Checked: 2587 node, 465 browser tests, from the index alone.
--- FR ---
[ADD] épreuves : gardes des couches, horloge, sources, couleurs, motifs
Les gardes relèvent par fichier et par ligne, échouent sur un balayage vide
et sont chacune montrées rouges sur un arbre temporaire. Elles couvrent le
graphe d'imports de l'interface, le pont d'Electron, l'horloge unique, les
dépendances refusées, l'écriture des épreuves, les chaînes en dur, les clés
de traduction, les mots interdits, les couleurs et la teinte de conflit, le
balisage, les sélecteurs élagués et les motifs de surveillance. Corrigés :
délais d'épreuves, classe de la trame de conflit ; « la rend évitable » est
une exception nommée (§ 5.6). Vérifié : 2587 node, 465 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 13:21:21 -04:00
function releverCouleurs ( racine ) {
const chemins = exigerNonVide (
[FIX] interface: ghost labels, keyboard, valid HTML, closed data-* list
The drag ghost names what releasing at the point changes, floor included.
Escape on a seat returns to its table, a refused Tab stays put, the legend
toggle exposes aria-expanded, and the header markup is valid HTML. Every
data-* attribute the interface sets belongs to one closed list that a guard
reads. The tree guards follow helpers a test imports, and the reporter says
the whole series stays watched under coverage on a small machine.
Checked: every new test red first; 3001 node, 635 browser and 63 long tests
green from the index alone.
--- FR ---
[FIX] interface : fantôme nommé, clavier, HTML valide, data-* fermés
Le fantôme du glisser nomme ce que relâcher au point modifie, sol compris.
Échap sur un siège remonte à sa table, un Tab refusé reste en place, la
bascule de la légende expose aria-expanded, et la tête est du HTML valide.
Chaque attribut data-* que l'interface pose appartient à une liste fermée
qu'une garde lit. Les gardes de l'arbre suivent les aides qu'une épreuve
importe, et le rapporteur dit la série entière surveillée sous couverture.
Vérifié : chaque épreuve rouge d'abord ; 3001 node, 635 navigateur et 63
longues vertes depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 04:01:57 -04:00
sources ( racine , [ 'src' ] , [ '.svelte' , '.css' , '.js' ] ) . filter (
( c ) => c !== FEUILLE _DES _JETONS && ( c . endsWith ( '.css' ) || c . startsWith ( 'src/interface/' ) ) ,
) ,
[ADD] tests: guards for layers, clock, sources, colours, watch patterns
Guards scan the tree file by file and line by line, fail on an empty scan,
and are each shown red on a temporary tree. They cover the interface import
graph, the Electron bridge, one clock, refused dependencies, how tests are
written, hard-coded strings, translation keys, forbidden words, colours and
the conflict hue, markup, pruned selectors and watch patterns. Coverage
keeps the engine integration test on a small machine. Fixes: delays in
tests, the conflict hatch class; « la rend évitable » is a named exception
(§ 5.6). Checked: 2587 node, 465 browser tests, from the index alone.
--- FR ---
[ADD] épreuves : gardes des couches, horloge, sources, couleurs, motifs
Les gardes relèvent par fichier et par ligne, échouent sur un balayage vide
et sont chacune montrées rouges sur un arbre temporaire. Elles couvrent le
graphe d'imports de l'interface, le pont d'Electron, l'horloge unique, les
dépendances refusées, l'écriture des épreuves, les chaînes en dur, les clés
de traduction, les mots interdits, les couleurs et la teinte de conflit, le
balisage, les sélecteurs élagués et les motifs de surveillance. Corrigés :
délais d'épreuves, classe de la trame de conflit ; « la rend évitable » est
une exception nommée (§ 5.6). Vérifié : 2587 node, 465 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 13:21:21 -04:00
'couleurs' ,
) ;
const releves = [ ] ;
for ( const chemin of chemins ) {
let texte ;
let decls ;
const relever = ( debut , motif ) => releves . push ( { chemin , ligne : ligneDe ( texte , debut ) , texte : motif } ) ;
// Une déclaration, d'une feuille ou en ligne ; enLigne : hors de toute règle.
const examiner = ( { propriete , valeur , debut , selecteur , prelude } , enLigne ) => {
const couleur = couleurLitterale ( valeur ) ;
if ( couleur !== null ) relever ( debut , ` couleur ${ extrait ( couleur ) } dans ${ propriete } ` ) ;
if ( propriete . startsWith ( '--' ) ) relever ( debut , ` jeton déclaré ${ propriete } ` ) ;
[FIX] interface: ghost labels, keyboard, valid HTML, closed data-* list
The drag ghost names what releasing at the point changes, floor included.
Escape on a seat returns to its table, a refused Tab stays put, the legend
toggle exposes aria-expanded, and the header markup is valid HTML. Every
data-* attribute the interface sets belongs to one closed list that a guard
reads. The tree guards follow helpers a test imports, and the reporter says
the whole series stays watched under coverage on a small machine.
Checked: every new test red first; 3001 node, 635 browser and 63 long tests
green from the index alone.
--- FR ---
[FIX] interface : fantôme nommé, clavier, HTML valide, data-* fermés
Le fantôme du glisser nomme ce que relâcher au point modifie, sol compris.
Échap sur un siège remonte à sa table, un Tab refusé reste en place, la
bascule de la légende expose aria-expanded, et la tête est du HTML valide.
Chaque attribut data-* que l'interface pose appartient à une liste fermée
qu'une garde lit. Les gardes de l'arbre suivent les aides qu'une épreuve
importe, et le rapporteur dit la série entière surveillée sous couverture.
Vérifié : chaque épreuve rouge d'abord ; 3001 node, 635 navigateur et 63
longues vertes depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 04:01:57 -04:00
if ( litConflit ( valeur ) && ( enLigne || ! regleDeConflit ( chemin , prelude , jetonsLus ( valeur ) ) ) ) {
[ADD] tests: guards for layers, clock, sources, colours, watch patterns
Guards scan the tree file by file and line by line, fail on an empty scan,
and are each shown red on a temporary tree. They cover the interface import
graph, the Electron bridge, one clock, refused dependencies, how tests are
written, hard-coded strings, translation keys, forbidden words, colours and
the conflict hue, markup, pruned selectors and watch patterns. Coverage
keeps the engine integration test on a small machine. Fixes: delays in
tests, the conflict hatch class; « la rend évitable » is a named exception
(§ 5.6). Checked: 2587 node, 465 browser tests, from the index alone.
--- FR ---
[ADD] épreuves : gardes des couches, horloge, sources, couleurs, motifs
Les gardes relèvent par fichier et par ligne, échouent sur un balayage vide
et sont chacune montrées rouges sur un arbre temporaire. Elles couvrent le
graphe d'imports de l'interface, le pont d'Electron, l'horloge unique, les
dépendances refusées, l'écriture des épreuves, les chaînes en dur, les clés
de traduction, les mots interdits, les couleurs et la teinte de conflit, le
balisage, les sélecteurs élagués et les motifs de surveillance. Corrigés :
délais d'épreuves, classe de la trame de conflit ; « la rend évitable » est
une exception nommée (§ 5.6). Vérifié : 2587 node, 465 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 13:21:21 -04:00
relever ( debut , ` jeton de conflit hors d'une règle de conflit du plan, ${ enLigne ? 'en ligne' : extrait ( selecteur ) } ` ) ;
}
} ;
[FIX] interface: ghost labels, keyboard, valid HTML, closed data-* list
The drag ghost names what releasing at the point changes, floor included.
Escape on a seat returns to its table, a refused Tab stays put, the legend
toggle exposes aria-expanded, and the header markup is valid HTML. Every
data-* attribute the interface sets belongs to one closed list that a guard
reads. The tree guards follow helpers a test imports, and the reporter says
the whole series stays watched under coverage on a small machine.
Checked: every new test red first; 3001 node, 635 browser and 63 long tests
green from the index alone.
--- FR ---
[FIX] interface : fantôme nommé, clavier, HTML valide, data-* fermés
Le fantôme du glisser nomme ce que relâcher au point modifie, sol compris.
Échap sur un siège remonte à sa table, un Tab refusé reste en place, la
bascule de la légende expose aria-expanded, et la tête est du HTML valide.
Chaque attribut data-* que l'interface pose appartient à une liste fermée
qu'une garde lit. Les gardes de l'arbre suivent les aides qu'une épreuve
importe, et le rapporteur dit la série entière surveillée sous couverture.
Vérifié : chaque épreuve rouge d'abord ; 3001 node, 635 navigateur et 63
longues vertes depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 04:01:57 -04:00
// Une chaîne du script qui est en entier un littéral de couleur, ou qui
// nomme le jeton de conflit.
const examinerScript = ( noeud ) => {
if ( noeud . type !== 'Literal' || typeof noeud . value !== 'string' ) return ;
const valeur = noeud . value . trim ( ) ;
if ( COULEUR _ENTIERE . test ( valeur ) ) {
relever ( noeud . start , ` couleur ${ extrait ( valeur ) } dans le script ` ) ;
}
if ( litConflit ( valeur ) ) relever ( noeud . start , "jeton de conflit hors d'une règle de conflit du plan, dans le script" ) ;
} ;
[ADD] tests: guards for layers, clock, sources, colours, watch patterns
Guards scan the tree file by file and line by line, fail on an empty scan,
and are each shown red on a temporary tree. They cover the interface import
graph, the Electron bridge, one clock, refused dependencies, how tests are
written, hard-coded strings, translation keys, forbidden words, colours and
the conflict hue, markup, pruned selectors and watch patterns. Coverage
keeps the engine integration test on a small machine. Fixes: delays in
tests, the conflict hatch class; « la rend évitable » is a named exception
(§ 5.6). Checked: 2587 node, 465 browser tests, from the index alone.
--- FR ---
[ADD] épreuves : gardes des couches, horloge, sources, couleurs, motifs
Les gardes relèvent par fichier et par ligne, échouent sur un balayage vide
et sont chacune montrées rouges sur un arbre temporaire. Elles couvrent le
graphe d'imports de l'interface, le pont d'Electron, l'horloge unique, les
dépendances refusées, l'écriture des épreuves, les chaînes en dur, les clés
de traduction, les mots interdits, les couleurs et la teinte de conflit, le
balisage, les sélecteurs élagués et les motifs de surveillance. Corrigés :
délais d'épreuves, classe de la trame de conflit ; « la rend évitable » est
une exception nommée (§ 5.6). Vérifié : 2587 node, 465 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 13:21:21 -04:00
if ( chemin . endsWith ( '.css' ) ) {
texte = readFileSync ( join ( racine , chemin ) , 'utf8' ) ;
decls = declarations ( parseCss ( texte ) , texte ) ;
for ( const d of decls ) examiner ( d , false ) ;
[FIX] interface: ghost labels, keyboard, valid HTML, closed data-* list
The drag ghost names what releasing at the point changes, floor included.
Escape on a seat returns to its table, a refused Tab stays put, the legend
toggle exposes aria-expanded, and the header markup is valid HTML. Every
data-* attribute the interface sets belongs to one closed list that a guard
reads. The tree guards follow helpers a test imports, and the reporter says
the whole series stays watched under coverage on a small machine.
Checked: every new test red first; 3001 node, 635 browser and 63 long tests
green from the index alone.
--- FR ---
[FIX] interface : fantôme nommé, clavier, HTML valide, data-* fermés
Le fantôme du glisser nomme ce que relâcher au point modifie, sol compris.
Échap sur un siège remonte à sa table, un Tab refusé reste en place, la
bascule de la légende expose aria-expanded, et la tête est du HTML valide.
Chaque attribut data-* que l'interface pose appartient à une liste fermée
qu'une garde lit. Les gardes de l'arbre suivent les aides qu'une épreuve
importe, et le rapporteur dit la série entière surveillée sous couverture.
Vérifié : chaque épreuve rouge d'abord ; 3001 node, 635 navigateur et 63
longues vertes depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 04:01:57 -04:00
} else if ( chemin . endsWith ( '.js' ) ) {
const analyse = analyserModule ( racine , chemin ) ;
texte = analyse . texte ;
parcourir ( analyse . ast , examinerScript ) ;
[ADD] tests: guards for layers, clock, sources, colours, watch patterns
Guards scan the tree file by file and line by line, fail on an empty scan,
and are each shown red on a temporary tree. They cover the interface import
graph, the Electron bridge, one clock, refused dependencies, how tests are
written, hard-coded strings, translation keys, forbidden words, colours and
the conflict hue, markup, pruned selectors and watch patterns. Coverage
keeps the engine integration test on a small machine. Fixes: delays in
tests, the conflict hatch class; « la rend évitable » is a named exception
(§ 5.6). Checked: 2587 node, 465 browser tests, from the index alone.
--- FR ---
[ADD] épreuves : gardes des couches, horloge, sources, couleurs, motifs
Les gardes relèvent par fichier et par ligne, échouent sur un balayage vide
et sont chacune montrées rouges sur un arbre temporaire. Elles couvrent le
graphe d'imports de l'interface, le pont d'Electron, l'horloge unique, les
dépendances refusées, l'écriture des épreuves, les chaînes en dur, les clés
de traduction, les mots interdits, les couleurs et la teinte de conflit, le
balisage, les sélecteurs élagués et les motifs de surveillance. Corrigés :
délais d'épreuves, classe de la trame de conflit ; « la rend évitable » est
une exception nommée (§ 5.6). Vérifié : 2587 node, 465 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 13:21:21 -04:00
} else {
const analyse = analyserComposant ( racine , chemin ) ;
texte = analyse . texte ;
for ( const d of declarations ( analyse . ast . css , texte ) ) examiner ( d , false ) ;
parcourir ( analyse . ast . fragment , ( noeud ) => {
if ( noeud . type === 'Attribute' && noeud . name . startsWith ( '--' ) ) relever ( noeud . start , ` jeton déclaré ${ noeud . name } ` ) ;
if ( noeud . type === 'StyleDirective' ) {
if ( noeud . name . startsWith ( '--' ) ) relever ( noeud . start , ` jeton déclaré ${ noeud . name } ` ) ;
for ( const morceau of morceauxAttribut ( noeud ) ) {
const valeurs = 'statique' in morceau ? [ morceau . statique ] : litterauxRendus ( morceau . expression ) . map ( ( l ) => l . valeur ) ;
for ( const valeur of valeurs ) examiner ( { propriete : noeud . name . replace ( /^--.*/ , 'style:' ) , valeur , debut : noeud . start , selecteur : '' } , true ) ;
}
}
if ( noeud . type !== 'Attribute' ) return ;
for ( const morceau of morceauxAttribut ( noeud ) ) {
if ( noeud . name === 'style' && 'statique' in morceau ) {
for ( const d of declarationsEnLigne ( morceau . statique , morceau . debut ) ) examiner ( d , true ) ;
} else if ( noeud . name === 'style' || ATTRIBUTS _COULEUR . has ( noeud . name ) ) {
const valeurs = 'statique' in morceau
? [ { valeur : morceau . statique , debut : morceau . debut } ]
: litterauxRendus ( morceau . expression ) ;
for ( const { valeur , debut } of valeurs ) {
const couleur = couleurLitterale ( valeur ) ;
if ( couleur !== null ) relever ( debut , ` couleur ${ extrait ( couleur ) } dans ${ noeud . name } ` ) ;
[ADD] plan: one hue and pattern per conflict rule, legend, appartenance
The plan drew every conflict in one hue, so a table that seats colleagues
together looked like one where people meet again. Each active rule now has
its own reserved hue and pattern: same appartenance hatched one way, met
again hatched the other, back at a table dotted; rules on one table
overlap, the badge keeps the total. A legend from the view bar shows each
active rule's sample. A table's tooltip names the appartenance that brings
a pair together. The hues are validated by computation in both themes and
the spec (7.5, 12.7) follows. Checked: 2848 node, 580 browser tests.
--- FR ---
[ADD] plan : une teinte et une trame par règle, légende, appartenance
Le plan dessinait tout conflit d'une seule teinte : une table qui réunit
des collègues ressemblait à une table où des gens se revoient. Chaque
règle active a sa teinte réservée et sa trame : même appartenance hachurée
dans un sens, voisins revus dans l'autre, table revue en pointillé ; les
règles d'une table se superposent, la pastille garde le total. Une légende
dans la barre de vue montre l'échantillon de chaque règle active.
L'infobulle nomme l'appartenance qui réunit une paire. Teintes validées
par calcul dans les deux thèmes ; spec § 7.5, § 12.7. Vérifié : 2848, 580.
Assisted-by: Claude Opus 5.5
2026-10-08 04:33:43 -04:00
if ( litConflit ( valeur ) ) relever ( debut , "jeton de conflit hors d'une règle de conflit du plan, en ligne" ) ;
[ADD] tests: guards for layers, clock, sources, colours, watch patterns
Guards scan the tree file by file and line by line, fail on an empty scan,
and are each shown red on a temporary tree. They cover the interface import
graph, the Electron bridge, one clock, refused dependencies, how tests are
written, hard-coded strings, translation keys, forbidden words, colours and
the conflict hue, markup, pruned selectors and watch patterns. Coverage
keeps the engine integration test on a small machine. Fixes: delays in
tests, the conflict hatch class; « la rend évitable » is a named exception
(§ 5.6). Checked: 2587 node, 465 browser tests, from the index alone.
--- FR ---
[ADD] épreuves : gardes des couches, horloge, sources, couleurs, motifs
Les gardes relèvent par fichier et par ligne, échouent sur un balayage vide
et sont chacune montrées rouges sur un arbre temporaire. Elles couvrent le
graphe d'imports de l'interface, le pont d'Electron, l'horloge unique, les
dépendances refusées, l'écriture des épreuves, les chaînes en dur, les clés
de traduction, les mots interdits, les couleurs et la teinte de conflit, le
balisage, les sélecteurs élagués et les motifs de surveillance. Corrigés :
délais d'épreuves, classe de la trame de conflit ; « la rend évitable » est
une exception nommée (§ 5.6). Vérifié : 2587 node, 465 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 13:21:21 -04:00
}
}
}
} ) ;
[FIX] interface: ghost labels, keyboard, valid HTML, closed data-* list
The drag ghost names what releasing at the point changes, floor included.
Escape on a seat returns to its table, a refused Tab stays put, the legend
toggle exposes aria-expanded, and the header markup is valid HTML. Every
data-* attribute the interface sets belongs to one closed list that a guard
reads. The tree guards follow helpers a test imports, and the reporter says
the whole series stays watched under coverage on a small machine.
Checked: every new test red first; 3001 node, 635 browser and 63 long tests
green from the index alone.
--- FR ---
[FIX] interface : fantôme nommé, clavier, HTML valide, data-* fermés
Le fantôme du glisser nomme ce que relâcher au point modifie, sol compris.
Échap sur un siège remonte à sa table, un Tab refusé reste en place, la
bascule de la légende expose aria-expanded, et la tête est du HTML valide.
Chaque attribut data-* que l'interface pose appartient à une liste fermée
qu'une garde lit. Les gardes de l'arbre suivent les aides qu'une épreuve
importe, et le rapporteur dit la série entière surveillée sous couverture.
Vérifié : chaque épreuve rouge d'abord ; 3001 node, 635 navigateur et 63
longues vertes depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 04:01:57 -04:00
parcourir ( [ analyse . ast . module , analyse . ast . instance ] , examinerScript ) ;
[ADD] tests: guards for layers, clock, sources, colours, watch patterns
Guards scan the tree file by file and line by line, fail on an empty scan,
and are each shown red on a temporary tree. They cover the interface import
graph, the Electron bridge, one clock, refused dependencies, how tests are
written, hard-coded strings, translation keys, forbidden words, colours and
the conflict hue, markup, pruned selectors and watch patterns. Coverage
keeps the engine integration test on a small machine. Fixes: delays in
tests, the conflict hatch class; « la rend évitable » is a named exception
(§ 5.6). Checked: 2587 node, 465 browser tests, from the index alone.
--- FR ---
[ADD] épreuves : gardes des couches, horloge, sources, couleurs, motifs
Les gardes relèvent par fichier et par ligne, échouent sur un balayage vide
et sont chacune montrées rouges sur un arbre temporaire. Elles couvrent le
graphe d'imports de l'interface, le pont d'Electron, l'horloge unique, les
dépendances refusées, l'écriture des épreuves, les chaînes en dur, les clés
de traduction, les mots interdits, les couleurs et la teinte de conflit, le
balisage, les sélecteurs élagués et les motifs de surveillance. Corrigés :
délais d'épreuves, classe de la trame de conflit ; « la rend évitable » est
une exception nommée (§ 5.6). Vérifié : 2587 node, 465 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 13:21:21 -04:00
}
}
return trier ( releves ) ;
}
// Relevé des jetons de couleur de la feuille des jetons qu'un des deux thèmes
// ne définit pas : chaque --couleur-…, où qu'il soit déclaré, l'est dans le
// bloc clair et dans le bloc sombre.
function releverThemes ( racine ) {
const texte = readFileSync ( join ( racine , FEUILLE _DES _JETONS ) , 'utf8' ) ;
const decls = declarations ( parseCss ( texte ) , texte ) . filter ( ( d ) => d . propriete . startsWith ( '--couleur-' ) ) ;
assert . ok ( decls . length > 0 , 'thèmes : aucun jeton de couleur examiné' ) ;
const theme = ( nom ) => new RegExp ( ` \\ [ \\ s*data-theme \\ s*= \\ s*(["']) ${ nom } \\ 1 \\ s* \\ ] ` ) ;
const blocs = { clair : theme ( 'clair' ) , sombre : theme ( 'sombre' ) } ;
const releves = [ ] ;
const premiere = new Map ( ) ;
for ( const d of decls ) if ( ! premiere . has ( d . propriete ) ) premiere . set ( d . propriete , d . debut ) ;
for ( const [ jeton , debut ] of premiere ) {
for ( const [ nom , motif ] of Object . entries ( blocs ) ) {
if ( ! decls . some ( ( d ) => d . propriete === jeton && motif . test ( d . selecteur ) ) ) {
releves . push ( { chemin : FEUILLE _DES _JETONS , ligne : ligneDe ( texte , debut ) , texte : ` ${ jeton } absent du thème ${ nom } ` } ) ;
}
}
}
return trier ( releves ) ;
}
// --- Garde 5 : balisage et fenêtres du navigateur (§ 13.1, § 2.1) -------------
const PROPRIETES _BALISAGE = new Set ( [ 'innerHTML' , 'outerHTML' , 'insertAdjacentHTML' ] ) ;
const FENETRES = new Set ( [ 'alert' , 'confirm' , 'prompt' ] ) ;
const GLOBAUX = new Set ( [ 'window' , 'globalThis' , 'self' ] ) ;
// Le nom d'un membre non calculé, ou d'un membre calculé par un littéral.
const nomDeMembre = ( membre ) =>
membre . computed ? ( membre . property . type === 'Literal' ? String ( membre . property . value ) : null ) : membre . property . name ;
// Relevé du balisage injecté, des fenêtres du navigateur, de beforeunload, et,
// sous src/interface, de la lecture de .message d'une erreur. Les composants
// et les modules de src/ sont lus.
function releverBalisageEtFenetres ( racine ) {
const chemins = exigerNonVide ( sources ( racine , [ 'src' ] , [ '.svelte' , '.js' ] ) , 'balisage et fenêtres' ) ;
const releves = [ ] ;
for ( const chemin of chemins ) {
const composant = chemin . endsWith ( '.svelte' ) ;
const { texte , ast } = composant ? analyserComposant ( racine , chemin ) : analyserModule ( racine , chemin ) ;
const relever = ( debut , motif ) => releves . push ( { chemin , ligne : ligneDe ( texte , debut ) , texte : motif } ) ;
const interface _ = chemin . startsWith ( 'src/interface/' ) ;
parcourir ( composant ? partiesCode ( ast ) : [ ast ] , ( noeud ) => {
switch ( noeud . type ) {
case 'HtmlTag' :
relever ( noeud . start , '{@html}' ) ;
break ;
case 'MemberExpression' : {
const nom = nomDeMembre ( noeud ) ;
if ( PROPRIETES _BALISAGE . has ( nom ) ) relever ( noeud . start , nom ) ;
if ( noeud . object . type === 'Identifier' && noeud . object . name === 'document' && ( nom === 'write' || nom === 'writeln' ) ) {
relever ( noeud . start , ` document. ${ nom } ` ) ;
}
if ( interface _ && nom === 'message' ) relever ( noeud . start , '.message' ) ;
break ;
}
case 'CallExpression' : {
const appele = noeud . callee ;
const nom =
appele . type === 'Identifier'
? appele . name
: appele . type === 'MemberExpression' && appele . object . type === 'Identifier' && GLOBAUX . has ( appele . object . name )
? nomDeMembre ( appele )
: null ;
if ( FENETRES . has ( nom ) ) relever ( noeud . start , ` ${ nom } ( ` ) ;
break ;
}
case 'Literal' :
if ( typeof noeud . value === 'string' && /beforeunload/i . test ( noeud . value ) ) relever ( noeud . start , 'beforeunload' ) ;
break ;
case 'TemplateElement' :
if ( /beforeunload/i . test ( noeud . value . raw ) ) relever ( noeud . start , 'beforeunload' ) ;
break ;
case 'Identifier' :
if ( /beforeunload/i . test ( noeud . name ) ) relever ( noeud . start , 'beforeunload' ) ;
break ;
case 'Attribute' :
case 'OnDirective' :
if ( /beforeunload/i . test ( noeud . name ) ) relever ( noeud . start , 'beforeunload' ) ;
break ;
default :
}
} ) ;
}
return trier ( releves ) ;
}
// --- Garde 6 : texte comprimé et animations (§ 7.4, § 19.2) ------------------
// Le motif qu'un nom ou une chaîne nomme, ou null : un mot entier, que ni
// lettre ni chiffre ne prolonge, sans égard à la casse — getComputedTextLength,
// la mesure du § 7.4, ne nomme pas textLength — ; un nom d'écouteur,
// ontransitionend, nomme son événement.
const nomme = ( texte , motifs ) =>
motifs . find ( ( motif ) => new RegExp ( ` (?<![ \\ p{L} \\ p{N}])(?:on)? ${ motif } (?![ \\ p{L} \\ p{N}]) ` , 'iu' ) . test ( texte ) ) ? ? null ;
// Les noms d'un arbre d'analyse — identifiants, attributs, directives,
// chaînes littérales — que les motifs nomment : [{ debut, motif }].
function nomsReleves ( parties , motifs ) {
const trouves = [ ] ;
parcourir ( parties , ( noeud ) => {
let nom = null ;
if ( noeud . type === 'Identifier' || noeud . type === 'Attribute' || noeud . type === 'OnDirective' ) nom = noeud . name ;
else if ( noeud . type === 'Literal' && typeof noeud . value === 'string' ) nom = noeud . value ;
else if ( noeud . type === 'TemplateElement' ) nom = noeud . value . raw ;
const motif = nom === null ? null : nomme ( nom , motifs ) ;
if ( motif !== null ) trouves . push ( { debut : noeud . start , motif } ) ;
} ) ;
return trouves ;
}
// Le mot qui ouvre une directive d'animation d'un composant — animate,
// transition, in, out —, ou null pour tout autre nœud.
function nomDeDirective ( noeud ) {
if ( noeud . type === 'AnimateDirective' ) return 'animate' ;
if ( noeud . type !== 'TransitionDirective' ) return null ;
if ( noeud . intro && ! noeud . outro ) return 'in' ;
if ( noeud . outro && ! noeud . intro ) return 'out' ;
return 'transition' ;
}
// Déclarations d'animation admises : celles qui l'éteignent.
const ANIMATION _ETEINTE = /^\s*(?:none|0m?s)\s*$/i ;
[IMP] home: list shown at once, computed columns loaded per event
The event list waited until every event's ranking was evaluated, about
10 ms per event with propositions, before showing anything. It now shows
as soon as the folder is read; each line's retained-plan columns and best
proposition load one event at a time, yielding to the browser between
events, behind a rotating SVG spinner, and show as each result arrives.
Work for a list no longer shown is dropped. The spinner is the one named
exception to the no-animation rule: still under reduced motion, cancelled
for captures, announced once. Checked: 2935 node, 616 browser, 62 long.
--- FR ---
[IMP] accueil : liste aussitôt montrée, colonnes calculées par événement
La liste des événements attendait l'évaluation du classement de chaque
événement, environ 10 ms par événement qui a des propositions, avant de
rien montrer. Elle paraît dès la lecture du dossier ; les colonnes du
retenu et la meilleure proposition de chaque ligne se chargent un
événement à la fois, le navigateur reprenant la main entre deux, derrière
une icône SVG qui tourne. Le travail d'une liste qui n'est plus montrée
est abandonné. L'icône est la seule exception nommée à l'interdit
d'animation. Vérifié : 2935 node, 616 navigateur, 62 longues.
Assisted-by: Claude Opus 5.5
2026-10-08 17:07:31 -04:00
// Les animations admises, chacune nommée par son fichier, le sélecteur de sa
// règle et le nom de ses images clés, avec sa raison. Une exception admet,
// dans son seul fichier, les déclarations animation… de sa règle et les
// @keyframes de son nom ; toute autre animation du fichier, et toute
// transition, restent relevées. La règle admise s'éteint sous
// prefers-reduced-motion: reduce, sinon elle se relève ; la feuille qui
// annule transitions et animations (§ 19.2) l'éteint comme toute autre. Une
// exception que rien ne sert se relève « sans objet ».
const ANIMATIONS _ADMISES = [
{
fichier : 'src/interface/accueil/Chargement.svelte' ,
selecteur : '.rouet' ,
images : 'rotation' ,
raison :
"le signe du chargement des résumés de la liste (§ 8.1) tourne pour dire qu'un calcul se poursuit : aucun état " +
"n'en dépend ni n'attend sa fin, et rien ne lit son rendu" ,
} ,
] ;
[FIX] interface: ghost labels, keyboard, valid HTML, closed data-* list
The drag ghost names what releasing at the point changes, floor included.
Escape on a seat returns to its table, a refused Tab stays put, the legend
toggle exposes aria-expanded, and the header markup is valid HTML. Every
data-* attribute the interface sets belongs to one closed list that a guard
reads. The tree guards follow helpers a test imports, and the reporter says
the whole series stays watched under coverage on a small machine.
Checked: every new test red first; 3001 node, 635 browser and 63 long tests
green from the index alone.
--- FR ---
[FIX] interface : fantôme nommé, clavier, HTML valide, data-* fermés
Le fantôme du glisser nomme ce que relâcher au point modifie, sol compris.
Échap sur un siège remonte à sa table, un Tab refusé reste en place, la
bascule de la légende expose aria-expanded, et la tête est du HTML valide.
Chaque attribut data-* que l'interface pose appartient à une liste fermée
qu'une garde lit. Les gardes de l'arbre suivent les aides qu'une épreuve
importe, et le rapporteur dit la série entière surveillée sous couverture.
Vérifié : chaque épreuve rouge d'abord ; 3001 node, 635 navigateur et 63
longues vertes depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 04:01:57 -04:00
// Vrai sous une règle @media qui demande le mouvement réduit, et lui seul :
// sa requête entière est (prefers-reduced-motion: reduce), à la casse et aux
// espaces près. Une requête niée, d'une autre préférence, ou jointe à une
// autre par une virgule ne l'est pas.
const REQUETE _MOUVEMENT _REDUIT = /^\(\s*prefers-reduced-motion\s*:\s*reduce\s*\)$/i ;
[IMP] home: list shown at once, computed columns loaded per event
The event list waited until every event's ranking was evaluated, about
10 ms per event with propositions, before showing anything. It now shows
as soon as the folder is read; each line's retained-plan columns and best
proposition load one event at a time, yielding to the browser between
events, behind a rotating SVG spinner, and show as each result arrives.
Work for a list no longer shown is dropped. The spinner is the one named
exception to the no-animation rule: still under reduced motion, cancelled
for captures, announced once. Checked: 2935 node, 616 browser, 62 long.
--- FR ---
[IMP] accueil : liste aussitôt montrée, colonnes calculées par événement
La liste des événements attendait l'évaluation du classement de chaque
événement, environ 10 ms par événement qui a des propositions, avant de
rien montrer. Elle paraît dès la lecture du dossier ; les colonnes du
retenu et la meilleure proposition de chaque ligne se chargent un
événement à la fois, le navigateur reprenant la main entre deux, derrière
une icône SVG qui tourne. Le travail d'une liste qui n'est plus montrée
est abandonné. L'icône est la seule exception nommée à l'interdit
d'animation. Vérifié : 2935 node, 616 navigateur, 62 longues.
Assisted-by: Claude Opus 5.5
2026-10-08 17:07:31 -04:00
const sousMouvementReduit = ( ancetres ) =>
[FIX] interface: ghost labels, keyboard, valid HTML, closed data-* list
The drag ghost names what releasing at the point changes, floor included.
Escape on a seat returns to its table, a refused Tab stays put, the legend
toggle exposes aria-expanded, and the header markup is valid HTML. Every
data-* attribute the interface sets belongs to one closed list that a guard
reads. The tree guards follow helpers a test imports, and the reporter says
the whole series stays watched under coverage on a small machine.
Checked: every new test red first; 3001 node, 635 browser and 63 long tests
green from the index alone.
--- FR ---
[FIX] interface : fantôme nommé, clavier, HTML valide, data-* fermés
Le fantôme du glisser nomme ce que relâcher au point modifie, sol compris.
Échap sur un siège remonte à sa table, un Tab refusé reste en place, la
bascule de la légende expose aria-expanded, et la tête est du HTML valide.
Chaque attribut data-* que l'interface pose appartient à une liste fermée
qu'une garde lit. Les gardes de l'arbre suivent les aides qu'une épreuve
importe, et le rapporteur dit la série entière surveillée sous couverture.
Vérifié : chaque épreuve rouge d'abord ; 3001 node, 635 navigateur et 63
longues vertes depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 04:01:57 -04:00
ancetres . some ( ( a ) => a . type === 'Atrule' && a . name === 'media' && REQUETE _MOUVEMENT _REDUIT . test ( a . prelude . trim ( ) ) ) ;
[IMP] home: list shown at once, computed columns loaded per event
The event list waited until every event's ranking was evaluated, about
10 ms per event with propositions, before showing anything. It now shows
as soon as the folder is read; each line's retained-plan columns and best
proposition load one event at a time, yielding to the browser between
events, behind a rotating SVG spinner, and show as each result arrives.
Work for a list no longer shown is dropped. The spinner is the one named
exception to the no-animation rule: still under reduced motion, cancelled
for captures, announced once. Checked: 2935 node, 616 browser, 62 long.
--- FR ---
[IMP] accueil : liste aussitôt montrée, colonnes calculées par événement
La liste des événements attendait l'évaluation du classement de chaque
événement, environ 10 ms par événement qui a des propositions, avant de
rien montrer. Elle paraît dès la lecture du dossier ; les colonnes du
retenu et la meilleure proposition de chaque ligne se chargent un
événement à la fois, le navigateur reprenant la main entre deux, derrière
une icône SVG qui tourne. Le travail d'une liste qui n'est plus montrée
est abandonné. L'icône est la seule exception nommée à l'interdit
d'animation. Vérifié : 2935 node, 616 navigateur, 62 longues.
Assisted-by: Claude Opus 5.5
2026-10-08 17:07:31 -04:00
[ADD] tests: guards for layers, clock, sources, colours, watch patterns
Guards scan the tree file by file and line by line, fail on an empty scan,
and are each shown red on a temporary tree. They cover the interface import
graph, the Electron bridge, one clock, refused dependencies, how tests are
written, hard-coded strings, translation keys, forbidden words, colours and
the conflict hue, markup, pruned selectors and watch patterns. Coverage
keeps the engine integration test on a small machine. Fixes: delays in
tests, the conflict hatch class; « la rend évitable » is a named exception
(§ 5.6). Checked: 2587 node, 465 browser tests, from the index alone.
--- FR ---
[ADD] épreuves : gardes des couches, horloge, sources, couleurs, motifs
Les gardes relèvent par fichier et par ligne, échouent sur un balayage vide
et sont chacune montrées rouges sur un arbre temporaire. Elles couvrent le
graphe d'imports de l'interface, le pont d'Electron, l'horloge unique, les
dépendances refusées, l'écriture des épreuves, les chaînes en dur, les clés
de traduction, les mots interdits, les couleurs et la teinte de conflit, le
balisage, les sélecteurs élagués et les motifs de surveillance. Corrigés :
délais d'épreuves, classe de la trame de conflit ; « la rend évitable » est
une exception nommée (§ 5.6). Vérifié : 2587 node, 465 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 13:21:21 -04:00
// Relevé de textLength et lengthAdjust sous src/interface et src/geometrie, de
// transitionend et animationend sous src/interface, et de toute transition
// ou animation de l'interface : déclaration transition…, animation… ou
// @keyframes d'une feuille, directive transition:, in:, out: ou animate:
[IMP] home: list shown at once, computed columns loaded per event
The event list waited until every event's ranking was evaluated, about
10 ms per event with propositions, before showing anything. It now shows
as soon as the folder is read; each line's retained-plan columns and best
proposition load one event at a time, yielding to the browser between
events, behind a rotating SVG spinner, and show as each result arrives.
Work for a list no longer shown is dropped. The spinner is the one named
exception to the no-animation rule: still under reduced motion, cancelled
for captures, announced once. Checked: 2935 node, 616 browser, 62 long.
--- FR ---
[IMP] accueil : liste aussitôt montrée, colonnes calculées par événement
La liste des événements attendait l'évaluation du classement de chaque
événement, environ 10 ms par événement qui a des propositions, avant de
rien montrer. Elle paraît dès la lecture du dossier ; les colonnes du
retenu et la meilleure proposition de chaque ligne se chargent un
événement à la fois, le navigateur reprenant la main entre deux, derrière
une icône SVG qui tourne. Le travail d'une liste qui n'est plus montrée
est abandonné. L'icône est la seule exception nommée à l'interdit
d'animation. Vérifié : 2935 node, 616 navigateur, 62 longues.
Assisted-by: Claude Opus 5.5
2026-10-08 17:07:31 -04:00
// d'un composant —, hors des animations admises (exceptions), suivi d'une
// ligne « exception sans objet » par exception que rien ne sert. Un état qui
// attendrait la fin d'une transition ne s'atteindrait plus sous la feuille
// qui les annule (§ 19.2).
function releverTexteEtAnimation ( racine , exceptions = ANIMATIONS _ADMISES ) {
[ADD] tests: guards for layers, clock, sources, colours, watch patterns
Guards scan the tree file by file and line by line, fail on an empty scan,
and are each shown red on a temporary tree. They cover the interface import
graph, the Electron bridge, one clock, refused dependencies, how tests are
written, hard-coded strings, translation keys, forbidden words, colours and
the conflict hue, markup, pruned selectors and watch patterns. Coverage
keeps the engine integration test on a small machine. Fixes: delays in
tests, the conflict hatch class; « la rend évitable » is a named exception
(§ 5.6). Checked: 2587 node, 465 browser tests, from the index alone.
--- FR ---
[ADD] épreuves : gardes des couches, horloge, sources, couleurs, motifs
Les gardes relèvent par fichier et par ligne, échouent sur un balayage vide
et sont chacune montrées rouges sur un arbre temporaire. Elles couvrent le
graphe d'imports de l'interface, le pont d'Electron, l'horloge unique, les
dépendances refusées, l'écriture des épreuves, les chaînes en dur, les clés
de traduction, les mots interdits, les couleurs et la teinte de conflit, le
balisage, les sélecteurs élagués et les motifs de surveillance. Corrigés :
délais d'épreuves, classe de la trame de conflit ; « la rend évitable » est
une exception nommée (§ 5.6). Vérifié : 2587 node, 465 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 13:21:21 -04:00
const chemins = exigerNonVide (
sources ( racine , [ 'src/interface' , 'src/geometrie' ] , [ '.svelte' , '.js' , '.css' ] ) ,
'texte et animation' ,
) ;
const releves = [ ] ;
[IMP] home: list shown at once, computed columns loaded per event
The event list waited until every event's ranking was evaluated, about
10 ms per event with propositions, before showing anything. It now shows
as soon as the folder is read; each line's retained-plan columns and best
proposition load one event at a time, yielding to the browser between
events, behind a rotating SVG spinner, and show as each result arrives.
Work for a list no longer shown is dropped. The spinner is the one named
exception to the no-animation rule: still under reduced motion, cancelled
for captures, announced once. Checked: 2935 node, 616 browser, 62 long.
--- FR ---
[IMP] accueil : liste aussitôt montrée, colonnes calculées par événement
La liste des événements attendait l'évaluation du classement de chaque
événement, environ 10 ms par événement qui a des propositions, avant de
rien montrer. Elle paraît dès la lecture du dossier ; les colonnes du
retenu et la meilleure proposition de chaque ligne se chargent un
événement à la fois, le navigateur reprenant la main entre deux, derrière
une icône SVG qui tourne. Le travail d'une liste qui n'est plus montrée
est abandonné. L'icône est la seule exception nommée à l'interdit
d'animation. Vérifié : 2935 node, 616 navigateur, 62 longues.
Assisted-by: Claude Opus 5.5
2026-10-08 17:07:31 -04:00
const servies = new Set ( ) ;
[ADD] tests: guards for layers, clock, sources, colours, watch patterns
Guards scan the tree file by file and line by line, fail on an empty scan,
and are each shown red on a temporary tree. They cover the interface import
graph, the Electron bridge, one clock, refused dependencies, how tests are
written, hard-coded strings, translation keys, forbidden words, colours and
the conflict hue, markup, pruned selectors and watch patterns. Coverage
keeps the engine integration test on a small machine. Fixes: delays in
tests, the conflict hatch class; « la rend évitable » is a named exception
(§ 5.6). Checked: 2587 node, 465 browser tests, from the index alone.
--- FR ---
[ADD] épreuves : gardes des couches, horloge, sources, couleurs, motifs
Les gardes relèvent par fichier et par ligne, échouent sur un balayage vide
et sont chacune montrées rouges sur un arbre temporaire. Elles couvrent le
graphe d'imports de l'interface, le pont d'Electron, l'horloge unique, les
dépendances refusées, l'écriture des épreuves, les chaînes en dur, les clés
de traduction, les mots interdits, les couleurs et la teinte de conflit, le
balisage, les sélecteurs élagués et les motifs de surveillance. Corrigés :
délais d'épreuves, classe de la trame de conflit ; « la rend évitable » est
une exception nommée (§ 5.6). Vérifié : 2587 node, 465 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 13:21:21 -04:00
for ( const chemin of chemins ) {
const interface _ = chemin . startsWith ( 'src/interface/' ) ;
const motifs = interface _ ? [ 'textLength' , 'lengthAdjust' , 'transitionend' , 'animationend' ] : [ 'textLength' , 'lengthAdjust' ] ;
let texte ;
let parties = [ ] ;
let css = null ;
if ( chemin . endsWith ( '.css' ) ) {
texte = readFileSync ( join ( racine , chemin ) , 'utf8' ) ;
css = parseCss ( texte ) ;
} else if ( chemin . endsWith ( '.svelte' ) ) {
const analyse = analyserComposant ( racine , chemin ) ;
texte = analyse . texte ;
parties = partiesCode ( analyse . ast ) ;
css = analyse . ast . css ;
} else {
const analyse = analyserModule ( racine , chemin ) ;
texte = analyse . texte ;
parties = [ analyse . ast ] ;
}
const relever = ( debut , motif ) => releves . push ( { chemin , ligne : ligneDe ( texte , debut ) , texte : motif } ) ;
for ( const { debut , motif } of nomsReleves ( parties , motifs ) ) relever ( debut , motif ) ;
if ( ! interface _ ) continue ;
parcourir ( parties , ( noeud ) => {
const directive = nomDeDirective ( noeud ) ;
if ( directive !== null ) relever ( noeud . start , ` directive ${ directive } : ${ noeud . name } ` ) ;
} ) ;
[IMP] home: list shown at once, computed columns loaded per event
The event list waited until every event's ranking was evaluated, about
10 ms per event with propositions, before showing anything. It now shows
as soon as the folder is read; each line's retained-plan columns and best
proposition load one event at a time, yielding to the browser between
events, behind a rotating SVG spinner, and show as each result arrives.
Work for a list no longer shown is dropped. The spinner is the one named
exception to the no-animation rule: still under reduced motion, cancelled
for captures, announced once. Checked: 2935 node, 616 browser, 62 long.
--- FR ---
[IMP] accueil : liste aussitôt montrée, colonnes calculées par événement
La liste des événements attendait l'évaluation du classement de chaque
événement, environ 10 ms par événement qui a des propositions, avant de
rien montrer. Elle paraît dès la lecture du dossier ; les colonnes du
retenu et la meilleure proposition de chaque ligne se chargent un
événement à la fois, le navigateur reprenant la main entre deux, derrière
une icône SVG qui tourne. Le travail d'une liste qui n'est plus montrée
est abandonné. L'icône est la seule exception nommée à l'interdit
d'animation. Vérifié : 2935 node, 616 navigateur, 62 longues.
Assisted-by: Claude Opus 5.5
2026-10-08 17:07:31 -04:00
// L'exception de ce fichier, la première déclaration qu'elle admet, et
// vrai quand sa règle s'éteint sous le mouvement réduit.
const admise = exceptions . find ( ( { fichier } ) => fichier === chemin ) ? ? null ;
let premiereAdmise = null ;
let arretee = false ;
parcourir ( css , ( noeud , ancetres ) => {
[ADD] tests: guards for layers, clock, sources, colours, watch patterns
Guards scan the tree file by file and line by line, fail on an empty scan,
and are each shown red on a temporary tree. They cover the interface import
graph, the Electron bridge, one clock, refused dependencies, how tests are
written, hard-coded strings, translation keys, forbidden words, colours and
the conflict hue, markup, pruned selectors and watch patterns. Coverage
keeps the engine integration test on a small machine. Fixes: delays in
tests, the conflict hatch class; « la rend évitable » is a named exception
(§ 5.6). Checked: 2587 node, 465 browser tests, from the index alone.
--- FR ---
[ADD] épreuves : gardes des couches, horloge, sources, couleurs, motifs
Les gardes relèvent par fichier et par ligne, échouent sur un balayage vide
et sont chacune montrées rouges sur un arbre temporaire. Elles couvrent le
graphe d'imports de l'interface, le pont d'Electron, l'horloge unique, les
dépendances refusées, l'écriture des épreuves, les chaînes en dur, les clés
de traduction, les mots interdits, les couleurs et la teinte de conflit, le
balisage, les sélecteurs élagués et les motifs de surveillance. Corrigés :
délais d'épreuves, classe de la trame de conflit ; « la rend évitable » est
une exception nommée (§ 5.6). Vérifié : 2587 node, 465 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 13:21:21 -04:00
const anime = noeud . type === 'Declaration' && /^(?:transition|animation)(?:-|$)/ . test ( noeud . property ) ;
[IMP] home: list shown at once, computed columns loaded per event
The event list waited until every event's ranking was evaluated, about
10 ms per event with propositions, before showing anything. It now shows
as soon as the folder is read; each line's retained-plan columns and best
proposition load one event at a time, yielding to the browser between
events, behind a rotating SVG spinner, and show as each result arrives.
Work for a list no longer shown is dropped. The spinner is the one named
exception to the no-animation rule: still under reduced motion, cancelled
for captures, announced once. Checked: 2935 node, 616 browser, 62 long.
--- FR ---
[IMP] accueil : liste aussitôt montrée, colonnes calculées par événement
La liste des événements attendait l'évaluation du classement de chaque
événement, environ 10 ms par événement qui a des propositions, avant de
rien montrer. Elle paraît dès la lecture du dossier ; les colonnes du
retenu et la meilleure proposition de chaque ligne se chargent un
événement à la fois, le navigateur reprenant la main entre deux, derrière
une icône SVG qui tourne. Le travail d'une liste qui n'est plus montrée
est abandonné. L'icône est la seule exception nommée à l'interdit
d'animation. Vérifié : 2935 node, 616 navigateur, 62 longues.
Assisted-by: Claude Opus 5.5
2026-10-08 17:07:31 -04:00
const regle = ancetres . findLast ( ( a ) => a . type === 'Rule' ) ;
const selecteur = regle === undefined ? '' : texte . slice ( regle . prelude . start , regle . prelude . end ) . trim ( ) ;
const deLAdmise = anime && admise !== null && selecteur === admise . selecteur && noeud . property . startsWith ( 'animation' ) ;
[ADD] tests: guards for layers, clock, sources, colours, watch patterns
Guards scan the tree file by file and line by line, fail on an empty scan,
and are each shown red on a temporary tree. They cover the interface import
graph, the Electron bridge, one clock, refused dependencies, how tests are
written, hard-coded strings, translation keys, forbidden words, colours and
the conflict hue, markup, pruned selectors and watch patterns. Coverage
keeps the engine integration test on a small machine. Fixes: delays in
tests, the conflict hatch class; « la rend évitable » is a named exception
(§ 5.6). Checked: 2587 node, 465 browser tests, from the index alone.
--- FR ---
[ADD] épreuves : gardes des couches, horloge, sources, couleurs, motifs
Les gardes relèvent par fichier et par ligne, échouent sur un balayage vide
et sont chacune montrées rouges sur un arbre temporaire. Elles couvrent le
graphe d'imports de l'interface, le pont d'Electron, l'horloge unique, les
dépendances refusées, l'écriture des épreuves, les chaînes en dur, les clés
de traduction, les mots interdits, les couleurs et la teinte de conflit, le
balisage, les sélecteurs élagués et les motifs de surveillance. Corrigés :
délais d'épreuves, classe de la trame de conflit ; « la rend évitable » est
une exception nommée (§ 5.6). Vérifié : 2587 node, 465 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 13:21:21 -04:00
if ( anime && ! ANIMATION _ETEINTE . test ( noeud . value ) ) {
[IMP] home: list shown at once, computed columns loaded per event
The event list waited until every event's ranking was evaluated, about
10 ms per event with propositions, before showing anything. It now shows
as soon as the folder is read; each line's retained-plan columns and best
proposition load one event at a time, yielding to the browser between
events, behind a rotating SVG spinner, and show as each result arrives.
Work for a list no longer shown is dropped. The spinner is the one named
exception to the no-animation rule: still under reduced motion, cancelled
for captures, announced once. Checked: 2935 node, 616 browser, 62 long.
--- FR ---
[IMP] accueil : liste aussitôt montrée, colonnes calculées par événement
La liste des événements attendait l'évaluation du classement de chaque
événement, environ 10 ms par événement qui a des propositions, avant de
rien montrer. Elle paraît dès la lecture du dossier ; les colonnes du
retenu et la meilleure proposition de chaque ligne se chargent un
événement à la fois, le navigateur reprenant la main entre deux, derrière
une icône SVG qui tourne. Le travail d'une liste qui n'est plus montrée
est abandonné. L'icône est la seule exception nommée à l'interdit
d'animation. Vérifié : 2935 node, 616 navigateur, 62 longues.
Assisted-by: Claude Opus 5.5
2026-10-08 17:07:31 -04:00
if ( deLAdmise ) {
servies . add ( admise ) ;
premiereAdmise ? ? = noeud . start ;
} else {
relever ( noeud . start , ` ${ noeud . property } : ${ noeud . value } ` ) ;
}
}
if ( deLAdmise && ANIMATION _ETEINTE . test ( noeud . value ) && sousMouvementReduit ( ancetres ) ) arretee = true ;
if ( noeud . type === 'Atrule' && /keyframes$/i . test ( noeud . name ) ) {
if ( admise !== null && noeud . prelude . trim ( ) === admise . images ) servies . add ( admise ) ;
else relever ( noeud . start , ` @ ${ noeud . name } ` ) ;
[ADD] tests: guards for layers, clock, sources, colours, watch patterns
Guards scan the tree file by file and line by line, fail on an empty scan,
and are each shown red on a temporary tree. They cover the interface import
graph, the Electron bridge, one clock, refused dependencies, how tests are
written, hard-coded strings, translation keys, forbidden words, colours and
the conflict hue, markup, pruned selectors and watch patterns. Coverage
keeps the engine integration test on a small machine. Fixes: delays in
tests, the conflict hatch class; « la rend évitable » is a named exception
(§ 5.6). Checked: 2587 node, 465 browser tests, from the index alone.
--- FR ---
[ADD] épreuves : gardes des couches, horloge, sources, couleurs, motifs
Les gardes relèvent par fichier et par ligne, échouent sur un balayage vide
et sont chacune montrées rouges sur un arbre temporaire. Elles couvrent le
graphe d'imports de l'interface, le pont d'Electron, l'horloge unique, les
dépendances refusées, l'écriture des épreuves, les chaînes en dur, les clés
de traduction, les mots interdits, les couleurs et la teinte de conflit, le
balisage, les sélecteurs élagués et les motifs de surveillance. Corrigés :
délais d'épreuves, classe de la trame de conflit ; « la rend évitable » est
une exception nommée (§ 5.6). Vérifié : 2587 node, 465 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 13:21:21 -04:00
}
} ) ;
[IMP] home: list shown at once, computed columns loaded per event
The event list waited until every event's ranking was evaluated, about
10 ms per event with propositions, before showing anything. It now shows
as soon as the folder is read; each line's retained-plan columns and best
proposition load one event at a time, yielding to the browser between
events, behind a rotating SVG spinner, and show as each result arrives.
Work for a list no longer shown is dropped. The spinner is the one named
exception to the no-animation rule: still under reduced motion, cancelled
for captures, announced once. Checked: 2935 node, 616 browser, 62 long.
--- FR ---
[IMP] accueil : liste aussitôt montrée, colonnes calculées par événement
La liste des événements attendait l'évaluation du classement de chaque
événement, environ 10 ms par événement qui a des propositions, avant de
rien montrer. Elle paraît dès la lecture du dossier ; les colonnes du
retenu et la meilleure proposition de chaque ligne se chargent un
événement à la fois, le navigateur reprenant la main entre deux, derrière
une icône SVG qui tourne. Le travail d'une liste qui n'est plus montrée
est abandonné. L'icône est la seule exception nommée à l'interdit
d'animation. Vérifié : 2935 node, 616 navigateur, 62 longues.
Assisted-by: Claude Opus 5.5
2026-10-08 17:07:31 -04:00
if ( premiereAdmise !== null && ! arretee ) {
relever ( premiereAdmise , ` animation admise sans arrêt sous prefers-reduced-motion : ${ admise . selecteur } ` ) ;
}
[ADD] tests: guards for layers, clock, sources, colours, watch patterns
Guards scan the tree file by file and line by line, fail on an empty scan,
and are each shown red on a temporary tree. They cover the interface import
graph, the Electron bridge, one clock, refused dependencies, how tests are
written, hard-coded strings, translation keys, forbidden words, colours and
the conflict hue, markup, pruned selectors and watch patterns. Coverage
keeps the engine integration test on a small machine. Fixes: delays in
tests, the conflict hatch class; « la rend évitable » is a named exception
(§ 5.6). Checked: 2587 node, 465 browser tests, from the index alone.
--- FR ---
[ADD] épreuves : gardes des couches, horloge, sources, couleurs, motifs
Les gardes relèvent par fichier et par ligne, échouent sur un balayage vide
et sont chacune montrées rouges sur un arbre temporaire. Elles couvrent le
graphe d'imports de l'interface, le pont d'Electron, l'horloge unique, les
dépendances refusées, l'écriture des épreuves, les chaînes en dur, les clés
de traduction, les mots interdits, les couleurs et la teinte de conflit, le
balisage, les sélecteurs élagués et les motifs de surveillance. Corrigés :
délais d'épreuves, classe de la trame de conflit ; « la rend évitable » est
une exception nommée (§ 5.6). Vérifié : 2587 node, 465 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 13:21:21 -04:00
}
[IMP] home: list shown at once, computed columns loaded per event
The event list waited until every event's ranking was evaluated, about
10 ms per event with propositions, before showing anything. It now shows
as soon as the folder is read; each line's retained-plan columns and best
proposition load one event at a time, yielding to the browser between
events, behind a rotating SVG spinner, and show as each result arrives.
Work for a list no longer shown is dropped. The spinner is the one named
exception to the no-animation rule: still under reduced motion, cancelled
for captures, announced once. Checked: 2935 node, 616 browser, 62 long.
--- FR ---
[IMP] accueil : liste aussitôt montrée, colonnes calculées par événement
La liste des événements attendait l'évaluation du classement de chaque
événement, environ 10 ms par événement qui a des propositions, avant de
rien montrer. Elle paraît dès la lecture du dossier ; les colonnes du
retenu et la meilleure proposition de chaque ligne se chargent un
événement à la fois, le navigateur reprenant la main entre deux, derrière
une icône SVG qui tourne. Le travail d'une liste qui n'est plus montrée
est abandonné. L'icône est la seule exception nommée à l'interdit
d'animation. Vérifié : 2935 node, 616 navigateur, 62 longues.
Assisted-by: Claude Opus 5.5
2026-10-08 17:07:31 -04:00
const sansObjet = exceptions
. filter ( ( exception ) => ! servies . has ( exception ) )
. map ( ( { fichier , selecteur } ) => ` exception sans objet : ${ fichier } ${ selecteur } ` ) ;
return [ ... trier ( releves ) , ... sansObjet ] ;
[ADD] tests: guards for layers, clock, sources, colours, watch patterns
Guards scan the tree file by file and line by line, fail on an empty scan,
and are each shown red on a temporary tree. They cover the interface import
graph, the Electron bridge, one clock, refused dependencies, how tests are
written, hard-coded strings, translation keys, forbidden words, colours and
the conflict hue, markup, pruned selectors and watch patterns. Coverage
keeps the engine integration test on a small machine. Fixes: delays in
tests, the conflict hatch class; « la rend évitable » is a named exception
(§ 5.6). Checked: 2587 node, 465 browser tests, from the index alone.
--- FR ---
[ADD] épreuves : gardes des couches, horloge, sources, couleurs, motifs
Les gardes relèvent par fichier et par ligne, échouent sur un balayage vide
et sont chacune montrées rouges sur un arbre temporaire. Elles couvrent le
graphe d'imports de l'interface, le pont d'Electron, l'horloge unique, les
dépendances refusées, l'écriture des épreuves, les chaînes en dur, les clés
de traduction, les mots interdits, les couleurs et la teinte de conflit, le
balisage, les sélecteurs élagués et les motifs de surveillance. Corrigés :
délais d'épreuves, classe de la trame de conflit ; « la rend évitable » est
une exception nommée (§ 5.6). Vérifié : 2587 node, 465 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 13:21:21 -04:00
}
// --- Garde 7 : le câblage (§ 12.9, § 13.3) ------------------------------------
const TRIS _ET _REDUCTIONS = new Set ( [ 'sort' , 'toSorted' , 'reduce' , 'reduceRight' ] ) ;
// Les mesures de la table d'évaluation, que src/application/evaluation.js
// seul importe (§ 12.9).
const MESURES = new Set ( [
'mesurer' ,
'plafondsRealises' ,
'plafondsAPriori' ,
'troisChiffres' ,
'manques' ,
'ecartsAuPlafondAPriori' ,
'minimumAtteint' ,
] ) ;
const EVALUATION = 'src/application/evaluation.js' ;
// Relevé des calculs écrits dans un composant de src/interface — Math., un tri,
// une réduction —, et des imports d'une mesure dans un module de
// src/application autre qu'evaluation.js : spécificateur d'import ou de
// réexportation, ou membre d'un espace de noms importé.
function releverCablage ( racine ) {
const composants = exigerNonVide ( sources ( racine , [ 'src/interface' ] , [ '.svelte' ] ) , 'câblage des composants' ) ;
const modules = exigerNonVide ( sources ( racine , [ 'src/application' ] , [ '.js' ] ) , 'imports des mesures' ) ;
const releves = [ ] ;
for ( const chemin of composants ) {
const { texte , ast } = analyserComposant ( racine , chemin ) ;
parcourir ( partiesCode ( ast ) , ( noeud ) => {
if ( noeud . type === 'MemberExpression' && noeud . object . type === 'Identifier' && noeud . object . name === 'Math' ) {
releves . push ( { chemin , ligne : ligneDe ( texte , noeud . start ) , texte : ` Math. ${ nomDeMembre ( noeud ) ? ? '[…]' } ` } ) ;
}
if ( noeud . type === 'CallExpression' && noeud . callee . type === 'MemberExpression' ) {
const nom = nomDeMembre ( noeud . callee ) ;
if ( TRIS _ET _REDUCTIONS . has ( nom ) ) releves . push ( { chemin , ligne : ligneDe ( texte , noeud . start ) , texte : ` . ${ nom } ( ` } ) ;
}
} ) ;
}
for ( const chemin of modules ) {
if ( chemin === EVALUATION ) continue ;
const { texte , ast } = analyserModule ( racine , chemin ) ;
const espaces = new Set ( ) ;
parcourir ( ast , ( noeud , ancetres ) => {
if ( noeud . type === 'ImportNamespaceSpecifier' ) espaces . add ( noeud . local . name ) ;
// Une réexportation porte la source de sa déclaration ; un
// « export { nom } » d'une valeur locale n'importe rien.
const reexporte = noeud . type === 'ExportSpecifier' && ( ancetres . at ( - 1 ) ? . source ? ? null ) !== null ;
const importe = noeud . type === 'ImportSpecifier' ? noeud . imported : reexporte ? noeud . local : null ;
const nom = importe === null ? null : ( importe . name ? ? importe . value ) ;
if ( MESURES . has ( nom ) ) releves . push ( { chemin , ligne : ligneDe ( texte , noeud . start ) , texte : ` import de ${ nom } ` } ) ;
} ) ;
parcourir ( ast , ( noeud ) => {
if (
noeud . type === 'MemberExpression' &&
noeud . object . type === 'Identifier' &&
espaces . has ( noeud . object . name ) &&
MESURES . has ( nomDeMembre ( noeud ) )
) {
releves . push ( { chemin , ligne : ligneDe ( texte , noeud . start ) , texte : ` import de ${ nomDeMembre ( noeud ) } ` } ) ;
}
} ) ;
}
return trier ( releves ) ;
}
// --- Garde 8 : les sélecteurs élagués (§ 13.3, § 14.2) ------------------------
// Relevé des avertissements css_unused_selector de chaque composant de
// src/interface, compilé par compile de svelte/compiler sans greffon : une
// règle que le compilateur élague ne peindrait jamais l'état qu'elle vise.
function releverSelecteursElagues ( racine ) {
const chemins = exigerNonVide ( sources ( racine , [ 'src/interface' ] , [ '.svelte' ] ) , 'sélecteurs élagués' ) ;
const releves = [ ] ;
for ( const chemin of chemins ) {
const texte = readFileSync ( join ( racine , chemin ) , 'utf8' ) ;
const { warnings } = compile ( texte , { filename : chemin , generate : false } ) ;
for ( const avertissement of warnings ) {
if ( avertissement . code !== 'css_unused_selector' ) continue ;
const debut = avertissement . start ? . character ? ? 0 ;
releves . push ( {
chemin ,
ligne : avertissement . start ? . line ? ? ligneDe ( texte , debut ) ,
texte : ` sélecteur élagué ${ extrait ( texte . slice ( debut , avertissement . end ? . character ? ? debut ) ) } ` ,
} ) ;
}
}
return trier ( releves ) ;
}
[FIX] interface: ghost labels, keyboard, valid HTML, closed data-* list
The drag ghost names what releasing at the point changes, floor included.
Escape on a seat returns to its table, a refused Tab stays put, the legend
toggle exposes aria-expanded, and the header markup is valid HTML. Every
data-* attribute the interface sets belongs to one closed list that a guard
reads. The tree guards follow helpers a test imports, and the reporter says
the whole series stays watched under coverage on a small machine.
Checked: every new test red first; 3001 node, 635 browser and 63 long tests
green from the index alone.
--- FR ---
[FIX] interface : fantôme nommé, clavier, HTML valide, data-* fermés
Le fantôme du glisser nomme ce que relâcher au point modifie, sol compris.
Échap sur un siège remonte à sa table, un Tab refusé reste en place, la
bascule de la légende expose aria-expanded, et la tête est du HTML valide.
Chaque attribut data-* que l'interface pose appartient à une liste fermée
qu'une garde lit. Les gardes de l'arbre suivent les aides qu'une épreuve
importe, et le rapporteur dit la série entière surveillée sous couverture.
Vérifié : chaque épreuve rouge d'abord ; 3001 node, 635 navigateur et 63
longues vertes depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 04:01:57 -04:00
// --- Les désignations stables (contrainte 16) -------------------------------
// Les poses d'un attribut data-… par le script, dans un arbre de JavaScript :
// element.setAttribute('data-x', valeur), element.toggleAttribute('data-x')
// et element.dataset.x = valeur (dataset['x'] aussi), le nom de dataset
// rendu en data-… à tirets. Chacune { debut, nom, valeur } : nom null quand il
// se calcule, valeur la chaîne littérale posée, '' pour toggleAttribute, null
// quand elle se calcule. Une lecture — getAttribute, dataset.x lu — n'est pas
// une pose.
function posesParLeScript ( arbre ) {
const poses = [ ] ;
const chaineDe = ( noeud ) => {
if ( noeud ? . type === 'Literal' && typeof noeud . value === 'string' ) return noeud . value ;
if ( noeud ? . type === 'TemplateLiteral' && noeud . expressions . length === 0 ) return noeud . quasis [ 0 ] . value . cooked ;
return null ;
} ;
parcourir ( arbre , ( noeud ) => {
if ( noeud . type === 'CallExpression' && noeud . callee . type === 'MemberExpression' && ! noeud . callee . computed ) {
const methode = noeud . callee . property . name ;
if ( methode !== 'setAttribute' && methode !== 'toggleAttribute' ) return ;
const [ premier , second ] = noeud . arguments ;
const nom = chaineDe ( premier ) ;
const calcule = nom === null && premier ? . type === 'TemplateLiteral' && premier . quasis [ 0 ] . value . cooked . startsWith ( 'data-' ) ;
if ( calcule ) poses . push ( { debut : noeud . start , nom : null , valeur : null } ) ;
else if ( nom ? . startsWith ( 'data-' ) ) poses . push ( { debut : noeud . start , nom , valeur : methode === 'toggleAttribute' ? '' : chaineDe ( second ) } ) ;
} else if ( noeud . type === 'AssignmentExpression' && noeud . left . type === 'MemberExpression' ) {
const { object , property , computed } = noeud . left ;
if ( object . type !== 'MemberExpression' || object . computed || object . property . name !== 'dataset' ) return ;
const propre = computed ? chaineDe ( property ) : property . name ;
const nom = propre === null ? null : ` data- ${ propre . replace ( /[A-Z]/g , ( lettre ) => ` - ${ lettre . toLowerCase ( ) } ` ) } ` ;
poses . push ( { debut : noeud . start , nom , valeur : chaineDe ( noeud . right ) } ) ;
}
} ) ;
return poses ;
}
// Relevé des désignations : chaque attribut data-… que pose un composant de
// src/interface, par son gabarit ou par son script, et chaque module
// JavaScript de src/interface, épreuves exclues, par son code
// (posesParLeScript), comparé à la liste fermée designations
// (test/navigateur/designations.js), dans les deux sens — « chemin:ligne
// attribut hors de la liste » ; « chemin:ligne data-… : nom calculé » pour un
// nom que le script compose ; pour une entrée à valeurs fermées, une valeur
// littérale hors de la liste, ou une pose calculée ; puis « attribut : aucun
// composant ne le pose » et « attribut="valeur" : aucun composant ne la
// pose ». Lève quand la liste est vide.
function releverDesignations ( racine , designations = DESIGNATIONS ) {
assert . ok ( designations . size > 0 , 'désignations : liste vide' ) ;
const chemins = exigerNonVide ( sources ( racine , [ 'src/interface' ] , [ '.svelte' , '.js' ] ) , 'désignations' ) ;
const releves = [ ] ;
const posees = new Map ( ) ;
// Une pose : valeur null quand elle se calcule.
const noter = ( ou , nom , valeur ) => {
const entree = designations . get ( nom ) ;
if ( entree === undefined ) {
releves . push ( ` ${ ou } hors de la liste ` ) ;
return ;
}
const valeurs = posees . get ( nom ) ? ? new Set ( ) ;
posees . set ( nom , valeurs ) ;
if ( valeur === null ) {
if ( entree . valeurs !== null ) releves . push ( ` ${ ou } : valeur calculée sous une liste fermée ` ) ;
return ;
}
valeurs . add ( valeur ) ;
if ( entree . valeurs !== null && ! entree . valeurs . includes ( valeur ) ) releves . push ( ` ${ ou } =" ${ valeur } " hors des valeurs de la liste ` ) ;
} ;
for ( const chemin of chemins ) {
const composant = chemin . endsWith ( '.svelte' ) ;
const { texte , ast } = composant ? analyserComposant ( racine , chemin ) : analyserModule ( racine , chemin ) ;
if ( composant ) {
parcourir ( ast . fragment , ( noeud ) => {
if ( noeud . type !== 'Attribute' || ! noeud . name . startsWith ( 'data-' ) ) return ;
const morceaux = noeud . value === true ? [ ] : [ noeud . value ] . flat ( ) ;
const litterale = morceaux . every ( ( morceau ) => morceau . type === 'Text' ) ;
noter ( ` ${ chemin } : ${ ligneDe ( texte , noeud . start ) } ${ noeud . name } ` , noeud . name , litterale ? morceaux . map ( ( morceau ) => morceau . data ) . join ( '' ) : null ) ;
} ) ;
}
for ( const { debut , nom , valeur } of posesParLeScript ( composant ? [ ast . module , ast . instance ] : ast ) ) {
const ligne = ` ${ chemin } : ${ ligneDe ( texte , debut ) } ` ;
if ( nom === null ) releves . push ( ` ${ ligne } data- \u {2026} : nom calculé ` ) ;
else noter ( ` ${ ligne } ${ nom } ` , nom , valeur ) ;
}
}
for ( const [ attribut , { valeurs } ] of designations ) {
if ( ! posees . has ( attribut ) ) {
releves . push ( ` ${ attribut } : aucun composant ne le pose ` ) ;
continue ;
}
for ( const valeur of valeurs ? ? [ ] ) {
if ( ! posees . get ( attribut ) . has ( valeur ) ) releves . push ( ` ${ attribut } =" ${ valeur } " : aucun composant ne la pose ` ) ;
}
}
return releves ;
}
2026-10-09 04:51:45 -04:00
// --- Garde 9 : les touches nommées (§ 7.3, § 19.8) ----------------------------
// La table des raccourcis, seul module de l'interface qui nomme une touche :
// le tableau engendré des raccourcis la lit, et une touche nommée ailleurs
// lui échappe.
const TABLE _RACCOURCIS = 'src/interface/raccourcis.js' ;
// Une valeur de KeyboardEvent.key qui nomme une touche sans caractère, seule
// ou précédée de modificateurs, comme « Maj+Tab » ; les noms anciens « Esc »
// et « Spacebar » comptent.
const TOUCHE _NOMMEE =
/^(?:(?:Maj|Shift|Ctrl|Control|Alt|Meta)\+)*(?:Arrow(?:Left|Right|Up|Down)|Escape|Esc|Enter|Tab|Home|End|PageUp|PageDown|Delete|Backspace|Insert|Spacebar|F(?:[1-9]|1\d|2[0-4]))$/ ;
// Vrai pour la lecture d'une touche : un membre key, comme evenement.key.
const litLaTouche = ( noeud ) => noeud ? . type === 'MemberExpression' && nomDeMembre ( noeud ) === 'key' ;
// La valeur d'une chaîne littérale, ou d'un gabarit sans expression ; null
// pour tout autre nœud.
function valeurDeChaine ( noeud ) {
if ( noeud ? . type === 'Literal' && typeof noeud . value === 'string' ) return noeud . value ;
if ( noeud ? . type === 'TemplateLiteral' && noeud . expressions . length === 0 ) return noeud . quasis [ 0 ] . value . cooked ? ? null ;
return null ;
}
// Les fichiers de src/interface qui nomment encore leurs touches, chacun avec
// les contextes de RACCOURCIS où il agit et le relevé de ses touches, ligne
// omise, trié : la garde les admet tels quels. Un écart — une touche de
// plus, une de moins — relève les touches du fichier et demande de revoir
// son entrée ; une entrée dont le fichier ne nomme plus rien est sans objet.
// Faire lire RACCOURCIS à l'un d'eux retire son entrée. Tant qu'il ne la lit
// pas, chacune de ses touches a son entrée dans RACCOURCIS sous l'un de ses
// contextes : le tableau engendré décrit ce que le fichier fait, et une
// entrée retirée ou renommée dans la table se relève ici.
const enAttente = ( contextes , touches ) => Object . freeze ( { contextes : Object . freeze ( contextes ) , touches : Object . freeze ( touches ) } ) ;
const TOUCHES _EN _ATTENTE = new Map ( [
[ 'src/interface/participants/Formulaire.svelte' , enAttente ( [ 'champ' ] , [ 'touche "Enter"' ] ) ] ,
[
'src/interface/participants/Grille.svelte' ,
enAttente (
[ 'grille' , 'champ' ] ,
[
'touche "ArrowDown"' ,
'touche "ArrowLeft"' ,
'touche "ArrowRight"' ,
'touche "ArrowUp"' ,
'touche "Enter"' ,
'touche "Enter"' ,
'touche "Escape"' ,
'touche "Escape"' ,
'touche "F2"' ,
'touche "Maj+Tab"' ,
'touche "Maj+Tab"' ,
'touche "Maj+Tab"' ,
'touche "Tab"' ,
'touche "Tab"' ,
'touche "Tab"' ,
'touche "Tab"' ,
'touche "Tab"' ,
] ,
) ,
] ,
[
'src/interface/participants/grille.js' ,
enAttente (
[ 'grille' ] ,
[ 'touche "ArrowDown"' , 'touche "ArrowLeft"' , 'touche "ArrowRight"' , 'touche "ArrowUp"' , 'touche "Maj+Tab"' , 'touche "Tab"' ] ,
) ,
] ,
[ 'src/interface/plan/ListesTravail.svelte' , enAttente ( [ 'plan' ] , [ 'touche "Escape"' ] ) ] ,
[ 'src/interface/plan/PanneauPlace.svelte' , enAttente ( [ 'fenetre' ] , [ 'touche "Escape"' ] ) ] ,
[ 'src/interface/plan/PanneauTable.svelte' , enAttente ( [ 'fenetre' ] , [ 'touche "Escape"' ] ) ] ,
[ 'src/interface/plan/Plan.svelte' , enAttente ( [ 'plan' , 'geste' ] , [ 'touche "Escape"' , 'touche "Escape"' ] ) ] ,
[ 'src/interface/plan/Reserve.svelte' , enAttente ( [ 'plan' ] , [ 'touche "Escape"' ] ) ] ,
[ 'src/interface/primitives/Champ.svelte' , enAttente ( [ 'champ' ] , [ 'touche "Enter"' , 'touche "Escape"' ] ) ] ,
[ 'src/interface/primitives/Commande.svelte' , enAttente ( [ 'commande' ] , [ 'touche " "' , 'touche "Enter"' ] ) ] ,
[ 'src/interface/primitives/Infobulle.svelte' , enAttente ( [ 'fenetre' ] , [ 'touche "Escape"' ] ) ] ,
[ 'src/interface/tables/ListeTables.svelte' , enAttente ( [ 'champ' ] , [ 'touche "Escape"' ] ) ] ,
] ) ;
// Les touches des fichiers en attente qu'aucune entrée de raccourcis ne
// décrit sous un des contextes du fichier : « chemin touche "…" hors de
// RACCOURCIS (contextes) », trié. Un contexte qui n'est pas dans
// contextesConnus se relève aussi.
function touchesSansEntree ( raccourcis , contextesConnus , enAttenteDe = TOUCHES _EN _ATTENTE ) {
const decrites = new Set ( raccourcis . map ( ( { contexte , touche } ) => ` ${ contexte } : ${ touche } ` ) ) ;
const releves = [ ] ;
for ( const [ chemin , { contextes , touches } ] of enAttenteDe ) {
for ( const contexte of contextes ) {
if ( ! contextesConnus . includes ( contexte ) ) releves . push ( ` ${ chemin } contexte ${ JSON . stringify ( contexte ) } inconnu ` ) ;
}
for ( const motif of new Set ( touches ) ) {
const touche = JSON . parse ( motif . slice ( 'touche ' . length ) ) ;
if ( ! contextes . some ( ( contexte ) => decrites . has ( ` ${ contexte } : ${ touche } ` ) ) ) {
releves . push ( ` ${ chemin } ${ motif } hors de RACCOURCIS ( ${ contextes . join ( ', ' ) } ) ` ) ;
}
}
}
return releves . sort ( ) ;
}
// Relevé des touches nommées dans les composants et les modules de
// src/interface, hors de la table des raccourcis : une chaîne qui nomme une
// touche (TOUCHE_NOMMEE), où qu'elle soit dans le code, et toute chaîne —
// l'espace, une lettre — comparée à une lecture de key ou cas d'un switch
// sur elle. Une touche se relève une fois, sous la forme « touche "…" ».
// Puis les fichiers en attente : admis quand leur relevé est le leur.
function releverTouches ( racine , enAttenteDe = TOUCHES _EN _ATTENTE ) {
const chemins = exigerNonVide (
sources ( racine , [ 'src/interface' ] , [ '.svelte' , '.js' ] ) . filter ( ( chemin ) => chemin !== TABLE _RACCOURCIS ) ,
'touches nommées' ,
) ;
const parFichier = new Map ( ) ;
for ( const chemin of chemins ) {
const composant = chemin . endsWith ( '.svelte' ) ;
const { texte , ast } = composant ? analyserComposant ( racine , chemin ) : analyserModule ( racine , chemin ) ;
const releves = [ ] ;
const vus = new Set ( ) ;
const relever = ( noeud ) => {
const valeur = valeurDeChaine ( noeud ) ;
if ( valeur === null || vus . has ( noeud ) ) return ;
vus . add ( noeud ) ;
releves . push ( { chemin , ligne : ligneDe ( texte , noeud . start ) , texte : ` touche ${ JSON . stringify ( valeur ) } ` } ) ;
} ;
parcourir ( composant ? partiesCode ( ast ) : [ ast ] , ( noeud ) => {
if ( noeud . type === 'BinaryExpression' && [ '===' , '!==' , '==' , '!=' ] . includes ( noeud . operator ) ) {
if ( litLaTouche ( noeud . left ) ) relever ( noeud . right ) ;
if ( litLaTouche ( noeud . right ) ) relever ( noeud . left ) ;
} else if ( noeud . type === 'SwitchStatement' && litLaTouche ( noeud . discriminant ) ) {
for ( const cas of noeud . cases ) relever ( cas . test ) ;
} else if ( TOUCHE _NOMMEE . test ( valeurDeChaine ( noeud ) ? ? '' ) ) {
relever ( noeud ) ;
}
} ) ;
if ( releves . length > 0 ) parFichier . set ( chemin , releves ) ;
}
const releves = [ ] ;
const aRevoir = [ ] ;
for ( const [ chemin , deFichier ] of parFichier ) {
const admis = enAttenteDe . get ( chemin ) ? . touches ;
const motifs = deFichier . map ( ( { texte } ) => texte ) . sort ( ) ;
if ( admis !== undefined && JSON . stringify ( motifs ) === JSON . stringify ( [ ... admis ] . sort ( ) ) ) continue ;
releves . push ( ... deFichier ) ;
if ( admis !== undefined ) aRevoir . push ( ` ${ chemin } : en attente, entrée à revoir ` ) ;
}
const sansObjet = [ ... enAttenteDe . keys ( ) ] . filter ( ( chemin ) => ! parFichier . has ( chemin ) ) . map ( ( chemin ) => ` ${ chemin } : en attente sans objet ` ) ;
return [ ... trier ( releves ) , ... [ ... aRevoir , ... sansObjet ] . sort ( ) ] ;
}
[ADD] tests: guards for layers, clock, sources, colours, watch patterns
Guards scan the tree file by file and line by line, fail on an empty scan,
and are each shown red on a temporary tree. They cover the interface import
graph, the Electron bridge, one clock, refused dependencies, how tests are
written, hard-coded strings, translation keys, forbidden words, colours and
the conflict hue, markup, pruned selectors and watch patterns. Coverage
keeps the engine integration test on a small machine. Fixes: delays in
tests, the conflict hatch class; « la rend évitable » is a named exception
(§ 5.6). Checked: 2587 node, 465 browser tests, from the index alone.
--- FR ---
[ADD] épreuves : gardes des couches, horloge, sources, couleurs, motifs
Les gardes relèvent par fichier et par ligne, échouent sur un balayage vide
et sont chacune montrées rouges sur un arbre temporaire. Elles couvrent le
graphe d'imports de l'interface, le pont d'Electron, l'horloge unique, les
dépendances refusées, l'écriture des épreuves, les chaînes en dur, les clés
de traduction, les mots interdits, les couleurs et la teinte de conflit, le
balisage, les sélecteurs élagués et les motifs de surveillance. Corrigés :
délais d'épreuves, classe de la trame de conflit ; « la rend évitable » est
une exception nommée (§ 5.6). Vérifié : 2587 node, 465 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 13:21:21 -04:00
// --- Arbres d'épreuve ---------------------------------------------------------
// Écrit dans un répertoire temporaire l'arbre { chemin relatif: contenu }, le
// passe à examiner, puis l'efface ; rend ce que rend examiner.
function avecArbre ( fichiers , examiner ) {
const racine = mkdtempSync ( join ( tmpdir ( ) , 'sources-interface-' ) ) ;
try {
for ( const [ chemin , contenu ] of Object . entries ( fichiers ) ) {
mkdirSync ( dirname ( join ( racine , chemin ) ) , { recursive : true } ) ;
writeFileSync ( join ( racine , chemin ) , contenu ) ;
}
return examiner ( racine ) ;
} finally {
rmSync ( racine , { recursive : true , force : true } ) ;
}
}
// Exige un relevé vide ; le message d'échec en donne chaque ligne.
function exigerVide ( releves , garde ) {
assert . ok ( releves . length === 0 , ` ${ garde } : ${ releves . length } relevé(s) \n ${ releves . join ( '\n' ) } ` ) ;
}
// Un composant d'épreuve : son script, son gabarit, sa feuille.
const composant = ( { script = '' , gabarit = '' , style = '' } ) =>
` ${ script === '' ? '' : ` <script> \n ${ script } \n </script> \n ` } ${ gabarit } \n ${ style === '' ? '' : ` <style> \n ${ style } \n </style> \n ` } ` ;
// Une feuille des jetons minimale, ses deux thèmes complets.
const JETONS _EPREUVE = [
[ADD] plan: one hue and pattern per conflict rule, legend, appartenance
The plan drew every conflict in one hue, so a table that seats colleagues
together looked like one where people meet again. Each active rule now has
its own reserved hue and pattern: same appartenance hatched one way, met
again hatched the other, back at a table dotted; rules on one table
overlap, the badge keeps the total. A legend from the view bar shows each
active rule's sample. A table's tooltip names the appartenance that brings
a pair together. The hues are validated by computation in both themes and
the spec (7.5, 12.7) follows. Checked: 2848 node, 580 browser tests.
--- FR ---
[ADD] plan : une teinte et une trame par règle, légende, appartenance
Le plan dessinait tout conflit d'une seule teinte : une table qui réunit
des collègues ressemblait à une table où des gens se revoient. Chaque
règle active a sa teinte réservée et sa trame : même appartenance hachurée
dans un sens, voisins revus dans l'autre, table revue en pointillé ; les
règles d'une table se superposent, la pastille garde le total. Une légende
dans la barre de vue montre l'échantillon de chaque règle active.
L'infobulle nomme l'appartenance qui réunit une paire. Teintes validées
par calcul dans les deux thèmes ; spec § 7.5, § 12.7. Vérifié : 2848, 580.
Assisted-by: Claude Opus 5.5
2026-10-08 04:33:43 -04:00
':root, :root[data-theme="clair"] { --couleur-a: #ffffff; --couleur-conflit: #b3261e; --couleur-conflit-voisins: #c0189f; --couleur-conflit-table: #6a9400; }' ,
":root[data-theme='sombre'] { --couleur-a: #000000; --couleur-conflit: #ff8a80; --couleur-conflit-voisins: #c0689c; --couleur-conflit-table: #549c00; }" ,
[ADD] tests: guards for layers, clock, sources, colours, watch patterns
Guards scan the tree file by file and line by line, fail on an empty scan,
and are each shown red on a temporary tree. They cover the interface import
graph, the Electron bridge, one clock, refused dependencies, how tests are
written, hard-coded strings, translation keys, forbidden words, colours and
the conflict hue, markup, pruned selectors and watch patterns. Coverage
keeps the engine integration test on a small machine. Fixes: delays in
tests, the conflict hatch class; « la rend évitable » is a named exception
(§ 5.6). Checked: 2587 node, 465 browser tests, from the index alone.
--- FR ---
[ADD] épreuves : gardes des couches, horloge, sources, couleurs, motifs
Les gardes relèvent par fichier et par ligne, échouent sur un balayage vide
et sont chacune montrées rouges sur un arbre temporaire. Elles couvrent le
graphe d'imports de l'interface, le pont d'Electron, l'horloge unique, les
dépendances refusées, l'écriture des épreuves, les chaînes en dur, les clés
de traduction, les mots interdits, les couleurs et la teinte de conflit, le
balisage, les sélecteurs élagués et les motifs de surveillance. Corrigés :
délais d'épreuves, classe de la trame de conflit ; « la rend évitable » est
une exception nommée (§ 5.6). Vérifié : 2587 node, 465 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 13:21:21 -04:00
'' ,
] . join ( '\n' ) ;
describe ( 'sources de l’ interface : les chaînes visibles en dur (§ 14.6)' , ( ) => {
test ( 'aucun composant de src/interface n’ écrit de chaîne visible' , ( ) => {
exigerVide ( releverChainesEnDur ( RACINE ) , 'chaînes en dur' ) ;
} ) ;
test ( 'la garde relève un bouton « Modifier » et chaque forme refusée, à toute profondeur, et pas leurs voisines' , ( ) => {
const fichiers = {
'src/interface/A.svelte' : '<button>Modifier</button>\n' ,
'src/interface/plan/sous/B.svelte' : composant ( {
script : " const t = (cle) => cle;\n let x = $state(true);" ,
gabarit : [
'<p title="Titre du plan" aria-label={x ? \'Oui\' : t(\'k.non\')}>{t(\'k.titre\')} — {42}</p>' ,
'<svg><text>…</text><tspan>{\'·\'}</tspan><desc> </desc><title>{t(\'k.svg\')}</title></svg>' ,
'<input placeholder="{x} places" alt={`Image ${x}`} aria-roledescription="table" />' ,
'<span>{`${x} places`}</span><span>{x ?? \'Aucun\'}</span><span>{x && \'Plein\'}</span>' ,
'<span class="vide" data-x="Nom">{x} / {x}</span><img alt="" />' ,
'<span style="content: \'Note\'">.</span>' ,
'<!-- Commentaire écrit en dur -->' ,
[FIX] interface: ghost labels, keyboard, valid HTML, closed data-* list
The drag ghost names what releasing at the point changes, floor included.
Escape on a seat returns to its table, a refused Tab stays put, the legend
toggle exposes aria-expanded, and the header markup is valid HTML. Every
data-* attribute the interface sets belongs to one closed list that a guard
reads. The tree guards follow helpers a test imports, and the reporter says
the whole series stays watched under coverage on a small machine.
Checked: every new test red first; 3001 node, 635 browser and 63 long tests
green from the index alone.
--- FR ---
[FIX] interface : fantôme nommé, clavier, HTML valide, data-* fermés
Le fantôme du glisser nomme ce que relâcher au point modifie, sol compris.
Échap sur un siège remonte à sa table, un Tab refusé reste en place, la
bascule de la légende expose aria-expanded, et la tête est du HTML valide.
Chaque attribut data-* que l'interface pose appartient à une liste fermée
qu'une garde lit. Les gardes de l'arbre suivent les aides qu'une épreuve
importe, et le rapporteur dit la série entière surveillée sous couverture.
Vérifié : chaque épreuve rouge d'abord ; 3001 node, 635 navigateur et 63
longues vertes depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 04:01:57 -04:00
'<input type="button" value="Envoyer" /><input type="text" value="Nom" /><input type="submit" value={x ? \'Valider\' : t(\'k.v\')} />' ,
'<span>{[\'Oui\', \'Non\'][x ? 0 : 1]}</span><span>{({ a: \'Ouvert\' }).a}</span><span>{[1, 2][0]}</span>' ,
[ADD] tests: guards for layers, clock, sources, colours, watch patterns
Guards scan the tree file by file and line by line, fail on an empty scan,
and are each shown red on a temporary tree. They cover the interface import
graph, the Electron bridge, one clock, refused dependencies, how tests are
written, hard-coded strings, translation keys, forbidden words, colours and
the conflict hue, markup, pruned selectors and watch patterns. Coverage
keeps the engine integration test on a small machine. Fixes: delays in
tests, the conflict hatch class; « la rend évitable » is a named exception
(§ 5.6). Checked: 2587 node, 465 browser tests, from the index alone.
--- FR ---
[ADD] épreuves : gardes des couches, horloge, sources, couleurs, motifs
Les gardes relèvent par fichier et par ligne, échouent sur un balayage vide
et sont chacune montrées rouges sur un arbre temporaire. Elles couvrent le
graphe d'imports de l'interface, le pont d'Electron, l'horloge unique, les
dépendances refusées, l'écriture des épreuves, les chaînes en dur, les clés
de traduction, les mots interdits, les couleurs et la teinte de conflit, le
balisage, les sélecteurs élagués et les motifs de surveillance. Corrigés :
délais d'épreuves, classe de la trame de conflit ; « la rend évitable » est
une exception nommée (§ 5.6). Vérifié : 2587 node, 465 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 13:21:21 -04:00
] . join ( '\n' ) ,
style : ' .vide::after { content: "Fin"; }\n .vide::before { content: counter(rang) ". "; }' ,
} ) ,
'src/interface/C.test.js' : "export const a = '<button>Modifier</button>';\n" ,
'src/interface/D.navigateur.test.js' : "export const a = '<button>Modifier</button>';\n" ,
} ;
assert . deepEqual ( avecArbre ( fichiers , releverChainesEnDur ) , [
'src/interface/A.svelte:1 texte « Modifier »' ,
'src/interface/plan/sous/B.svelte:5 attribut aria-label « Oui »' ,
'src/interface/plan/sous/B.svelte:5 attribut title « Titre du plan »' ,
'src/interface/plan/sous/B.svelte:6 expression littérale « · »' ,
'src/interface/plan/sous/B.svelte:6 texte « … »' ,
'src/interface/plan/sous/B.svelte:7 attribut alt « Image »' ,
'src/interface/plan/sous/B.svelte:7 attribut aria-roledescription « table »' ,
'src/interface/plan/sous/B.svelte:7 attribut placeholder « places »' ,
'src/interface/plan/sous/B.svelte:8 expression littérale « Aucun »' ,
'src/interface/plan/sous/B.svelte:8 expression littérale « Plein »' ,
'src/interface/plan/sous/B.svelte:8 expression littérale « places »' ,
'src/interface/plan/sous/B.svelte:10 content « content: \'Note\' »' ,
[FIX] interface: ghost labels, keyboard, valid HTML, closed data-* list
The drag ghost names what releasing at the point changes, floor included.
Escape on a seat returns to its table, a refused Tab stays put, the legend
toggle exposes aria-expanded, and the header markup is valid HTML. Every
data-* attribute the interface sets belongs to one closed list that a guard
reads. The tree guards follow helpers a test imports, and the reporter says
the whole series stays watched under coverage on a small machine.
Checked: every new test red first; 3001 node, 635 browser and 63 long tests
green from the index alone.
--- FR ---
[FIX] interface : fantôme nommé, clavier, HTML valide, data-* fermés
Le fantôme du glisser nomme ce que relâcher au point modifie, sol compris.
Échap sur un siège remonte à sa table, un Tab refusé reste en place, la
bascule de la légende expose aria-expanded, et la tête est du HTML valide.
Chaque attribut data-* que l'interface pose appartient à une liste fermée
qu'une garde lit. Les gardes de l'arbre suivent les aides qu'une épreuve
importe, et le rapporteur dit la série entière surveillée sous couverture.
Vérifié : chaque épreuve rouge d'abord ; 3001 node, 635 navigateur et 63
longues vertes depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 04:01:57 -04:00
'src/interface/plan/sous/B.svelte:12 attribut value « Envoyer »' ,
'src/interface/plan/sous/B.svelte:12 attribut value « Valider »' ,
'src/interface/plan/sous/B.svelte:13 expression littérale « Non »' ,
'src/interface/plan/sous/B.svelte:13 expression littérale « Oui »' ,
'src/interface/plan/sous/B.svelte:13 expression littérale « Ouvert »' ,
'src/interface/plan/sous/B.svelte:15 content « "Fin" »' ,
[ADD] tests: guards for layers, clock, sources, colours, watch patterns
Guards scan the tree file by file and line by line, fail on an empty scan,
and are each shown red on a temporary tree. They cover the interface import
graph, the Electron bridge, one clock, refused dependencies, how tests are
written, hard-coded strings, translation keys, forbidden words, colours and
the conflict hue, markup, pruned selectors and watch patterns. Coverage
keeps the engine integration test on a small machine. Fixes: delays in
tests, the conflict hatch class; « la rend évitable » is a named exception
(§ 5.6). Checked: 2587 node, 465 browser tests, from the index alone.
--- FR ---
[ADD] épreuves : gardes des couches, horloge, sources, couleurs, motifs
Les gardes relèvent par fichier et par ligne, échouent sur un balayage vide
et sont chacune montrées rouges sur un arbre temporaire. Elles couvrent le
graphe d'imports de l'interface, le pont d'Electron, l'horloge unique, les
dépendances refusées, l'écriture des épreuves, les chaînes en dur, les clés
de traduction, les mots interdits, les couleurs et la teinte de conflit, le
balisage, les sélecteurs élagués et les motifs de surveillance. Corrigés :
délais d'épreuves, classe de la trame de conflit ; « la rend évitable » est
une exception nommée (§ 5.6). Vérifié : 2587 node, 465 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 13:21:21 -04:00
] ) ;
} ) ;
test ( 'un arbre sans composant fait échouer la garde' , ( ) => {
assert . throws (
( ) => avecArbre ( { 'src/interface/a.js' : 'export const a = 1;\n' } , releverChainesEnDur ) ,
/chaînes en dur : aucun fichier examiné/ ,
) ;
} ) ;
} ) ;
describe ( 'sources de l’ interface : les clés littérales de t existent (§ 14.6, § 19.11)' , ( ) => {
test ( 'chaque clé littérale passée à t dans src/interface existe dans les tables assemblées' , ( ) => {
exigerVide ( releverClesAbsentes ( RACINE , CLES ) , 'clés de t' ) ;
} ) ;
test ( 'la garde relève une clé absente, dans un composant comme dans un module, et pas une clé présente ni composée' , ( ) => {
const cles = new Set ( [ 'k.present' ] ) ;
const fichiers = {
'src/interface/A.svelte' : composant ( {
script : " import { getContext } from 'svelte';\n const t = getContext('t');\n const a = t('k.absente');" ,
gabarit : "<p>{t('k.present')}</p>\n<p>{t(`k.gabarit`)}</p>\n<p>{t(`k.${a}`)}</p>\n<p>{traduction.t(\"k.membre\")}</p>" ,
} ) ,
'src/interface/plan/modele.js' : [
'// t(\'k.commentaire\') ne compte pas.' ,
"export const cle = (t) => t('k.module', { n: 1 });" ,
"export const autre = (texte) => texte('k.texte');" ,
'' ,
] . join ( '\n' ) ,
'src/interface/plan/modele.test.js' : "t('k.epreuve');\n" ,
} ;
assert . deepEqual ( avecArbre ( fichiers , ( racine ) => releverClesAbsentes ( racine , cles ) ) , [
'src/interface/A.svelte:4 clé absente « k.absente »' ,
'src/interface/A.svelte:7 clé absente « k.gabarit »' ,
'src/interface/A.svelte:9 clé absente « k.membre »' ,
'src/interface/plan/modele.js:2 clé absente « k.module »' ,
] ) ;
} ) ;
test ( 'un arbre sans source d’ interface fait échouer la garde' , ( ) => {
assert . throws ( ( ) => avecArbre ( { 'src/application/a.js' : '' } , ( r ) => releverClesAbsentes ( r , CLES ) ) , /aucun fichier examiné/ ) ;
} ) ;
test ( 'des sources d’ interface sans aucune clé littérale passée à t font échouer la garde' , ( ) => {
const fichiers = {
'src/interface/A.svelte' : "<p>{tr('k.absente')}</p>\n" ,
'src/interface/B.svelte' : composant ( {
script : " import { getContext } from 'svelte';\n const traduire = getContext('t');" ,
gabarit : "<p>{traduire('plan.x')}</p>\n<p>{t(cle)}</p>" ,
} ) ,
'src/interface/c.js' : "export const d = (t, n) => t(`k.${n}`);\n" ,
} ;
assert . throws ( ( ) => avecArbre ( fichiers , ( r ) => releverClesAbsentes ( r , CLES ) ) , /clés de t : aucune clé examinée/ ) ;
} ) ;
} ) ;
describe ( 'sources de l’ interface : le contenu des tables de traduction (§ 5.5, § 7.6, § 7.7, § 12.8)' , ( ) => {
test ( 'aucune valeur des tables assemblées ne porte un mot refusé ni un pictogramme' , ( ) => {
exigerVide ( releverContenuTables ( [ ... TABLES _APPLICATION , ... TABLES _INTERFACE ] , localisateur ( RACINE ) ) , 'contenu des tables' ) ;
} ) ;
test ( 'la garde relève chaque mot refusé, sans égard à la casse ni aux accents, dans une chaîne et dans une fonction' , ( ) => {
const refusees = new Map ( [
[ 'a.conforme' , 'Conforme' ] ,
[ 'a.nonConforme' , 'Plan non conforme' ] ,
[ 'a.conformite' , 'CONFORMITÉ' ] ,
[ 'a.ecart' , ( { n } ) => ` Un écart de ${ n } ` ] ,
[ 'a.ecartSeul' , ( details , texte ) => texte ( 'a.ecart' , details ) ] ,
[ 'a.ecartType' , 'Écart-type' ] ,
[ 'a.satisfaction' , 'Taux de satisfaction' ] ,
[ 'a.variance' , 'variance' ] ,
[ 'a.equite' , 'Équité' ] ,
[ 'a.score' , 'Score' ] ,
[ 'a.mieux' , 'Peut mieux faire' ] ,
[ 'a.minimal' , 'Manque minimal' ] ,
[ 'a.recuperable' , 'récupérable' ] ,
[ 'a.evitable' , 'Évitable' ] ,
[ 'a.emoji' , ( { n } ) => ` ${ n } \u {1F512} ` ] ,
[ 'a.copyright' , '\u{00A9} Club des Merles' ] ,
] ) ;
const admises = new Map ( [
[ 'b.itineraire' , 'Écart d\u{2019}itinéraire' ] ,
[ 'b.itineraires' , "écarts d'itinéraire maximal" ] ,
[ 'b.plafond' , 'Écart au plafond a priori' ] ,
[ 'b.inevitable' , 'Inévitable' ] ,
[ 'b.ecartee' , 'Proposition écartée' ] ,
[ 'b.fleche' , 'Tour 1 \u{2192} tour 2' ] ,
[ 'b.nom' , 'Iris Ombrelle' ] ,
[ 'b.variable' , ( { n } ) => {
const ecart = n + 1 ;
return ` Tour ${ ecart } ` ;
} ] ,
] ) ;
assert . deepEqual ( releverContenuTables ( [ refusees , admises ] , ( ) => null , [ ] ) , [
"a.conforme : conforme « Conforme »" ,
'a.conformite : conforme « CONFORMITÉ »' ,
'a.copyright : pictogramme U+00A9 « © Club des Merles »' ,
'a.ecart : écart hors du glossaire « Un écart de »' ,
'a.ecartType : écart hors du glossaire « Écart-type »' ,
'a.ecartType : écart-type « Écart-type »' ,
'a.emoji : pictogramme U+1F512 « \u{1F512} »' ,
'a.equite : équité « Équité »' ,
'a.evitable : évitable « Évitable »' ,
'a.mieux : peut mieux faire « Peut mieux faire »' ,
'a.minimal : manque minimal « Manque minimal »' ,
'a.nonConforme : conforme « Plan non conforme »' ,
'a.recuperable : récupérable « récupérable »' ,
'a.satisfaction : satisfaction « Taux de satisfaction »' ,
'a.score : score « Score »' ,
'a.variance : variance « variance »' ,
] ) ;
} ) ;
test ( 'la garde nomme le fichier et la ligne de la clé fautive, et lève sur des tables vides' , ( ) => {
const fichiers = {
'src/interface/libelles/plan.js' : "export const TABLE = new Map([\n ['plan.a', 'A'],\n [\n 'plan.b',\n 'Score',\n ],\n]);\n" ,
} ;
const releves = avecArbre ( fichiers , ( racine ) =>
releverContenuTables ( [ new Map ( [ [ 'plan.a' , 'A' ] , [ 'plan.b' , 'Score' ] ] ) ] , localisateur ( racine ) , [ ] ) ,
) ;
assert . deepEqual ( releves , [ 'src/interface/libelles/plan.js:3 plan.b : score « Score »' ] ) ;
assert . throws ( ( ) => releverContenuTables ( [ new Map ( ) ] ) , /aucune entrée examinée/ ) ;
} ) ;
test ( 'une exception nommée n’ admet que sa forme, sous ses clés ; tout autre « évitable » se relève, et une exception que rien ne sert est sans objet' , ( ) => {
const tables = new Map ( [
[ 'diagnostic.remede.un' , '2 chaises de plus la rend évitable' ] ,
[ 'diagnostic.remede.deux' , ( { n } ) => ( n === 1 ? 'une table la rend évitable' : ` ${ n } tables la rendent évitable ` ) ] ,
[ 'diagnostic.remede.reste' , 'la rend évitable, part évitable' ] ,
[ 'diagnostic.remede.autre' , 'la place devient évitable' ] ,
[ 'diagnostic.titre' , 'un tabouret la rend évitable' ] ,
[ 'plan.segment' , 'la rend évitable' ] ,
[ 'diagnostic.remede.inevitable' , 'inévitable' ] ,
] ) ;
assert . deepEqual ( releverContenuTables ( [ tables ] ) , [
'diagnostic.remede.autre : évitable « la place devient évitable »' ,
'diagnostic.remede.reste : évitable « la rend évitable, part évitable »' ,
'diagnostic.titre : évitable « un tabouret la rend évitable »' ,
'plan.segment : évitable « la rend évitable »' ,
] ) ;
const sansRemede = new Map ( [ [ 'diagnostic.remede.un' , 'ajouter une table' ] ] ) ;
assert . deepEqual ( releverContenuTables ( [ sansRemede ] ) , [ 'exception sans objet : diagnostic.remede.* « la rend(ent) évitable »' ] ) ;
} ) ;
} ) ;
describe ( 'sources de l’ interface : les couleurs (§ 12.7, § 13.3, § 13.5)' , ( ) => {
test ( 'aucun littéral de couleur, aucun jeton déclaré hors de jetons.css, le jeton de conflit lu par les seules règles de conflit du plan' , ( ) => {
exigerVide ( releverCouleurs ( RACINE ) , 'couleurs' ) ;
} ) ;
test ( 'chaque jeton de couleur est défini dans les deux thèmes' , ( ) => {
exigerVide ( releverThemes ( RACINE ) , 'thèmes' ) ;
} ) ;
test ( 'la garde relève stroke: #c00 et chaque littéral, et pas un jeton ni une adresse de motif' , ( ) => {
const fichiers = {
'src/interface/jetons.css' : JETONS _EPREUVE ,
'src/interface/plan/A.svelte' : composant ( {
gabarit : '<g class="table"><circle r="1" /></g>' ,
style : [
' .table { stroke: #c00; }' ,
' .table circle { fill: url(#hachure); stroke: var(--couleur-a); }' ,
' circle { color: rgb(1 2 3); background: Tomato; border-color: transparent currentColor; }' ,
' .table { outline-color: var(--couleur-a, white); --rang: 2; }' ,
] . join ( '\n' ) ,
} ) ,
'src/interface/accueil/sous/B.svelte' : composant ( {
script : " const couleur = '#ABCDEF';\n const code = 'orange-sanguine';" ,
gabarit : '<svg><rect fill="red" stroke={x ? \'hsl(0 0% 0%)\' : \'none\'} style="color: #fff; --ombre: 1px" style:fill="Canvas" /></svg>\n<Bouton --teinte="1" />' ,
} ) ,
'src/interface/autre.css' : '.x { color: #123456; }\n' ,
'src/interface/C.navigateur.test.js' : "const c = 'stroke: #c00';\n" ,
[FIX] interface: ghost labels, keyboard, valid HTML, closed data-* list
The drag ghost names what releasing at the point changes, floor included.
Escape on a seat returns to its table, a refused Tab stays put, the legend
toggle exposes aria-expanded, and the header markup is valid HTML. Every
data-* attribute the interface sets belongs to one closed list that a guard
reads. The tree guards follow helpers a test imports, and the reporter says
the whole series stays watched under coverage on a small machine.
Checked: every new test red first; 3001 node, 635 browser and 63 long tests
green from the index alone.
--- FR ---
[FIX] interface : fantôme nommé, clavier, HTML valide, data-* fermés
Le fantôme du glisser nomme ce que relâcher au point modifie, sol compris.
Échap sur un siège remonte à sa table, un Tab refusé reste en place, la
bascule de la légende expose aria-expanded, et la tête est du HTML valide.
Chaque attribut data-* que l'interface pose appartient à une liste fermée
qu'une garde lit. Les gardes de l'arbre suivent les aides qu'une épreuve
importe, et le rapporteur dit la série entière surveillée sous couverture.
Vérifié : chaque épreuve rouge d'abord ; 3001 node, 635 navigateur et 63
longues vertes depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 04:01:57 -04:00
// Un module voisin : sa chaîne atteint fill ou stroke par une expression.
'src/interface/plan/teinte.js' : "export const TEINTE = '#c00';\nexport const TRAME = 'var(--couleur-conflit)';\nexport const SORTE = 'mobile';\n" ,
'src/interface/plan/teinte.test.js' : "export const TEINTE = '#c00';\n" ,
[ADD] tests: guards for layers, clock, sources, colours, watch patterns
Guards scan the tree file by file and line by line, fail on an empty scan,
and are each shown red on a temporary tree. They cover the interface import
graph, the Electron bridge, one clock, refused dependencies, how tests are
written, hard-coded strings, translation keys, forbidden words, colours and
the conflict hue, markup, pruned selectors and watch patterns. Coverage
keeps the engine integration test on a small machine. Fixes: delays in
tests, the conflict hatch class; « la rend évitable » is a named exception
(§ 5.6). Checked: 2587 node, 465 browser tests, from the index alone.
--- FR ---
[ADD] épreuves : gardes des couches, horloge, sources, couleurs, motifs
Les gardes relèvent par fichier et par ligne, échouent sur un balayage vide
et sont chacune montrées rouges sur un arbre temporaire. Elles couvrent le
graphe d'imports de l'interface, le pont d'Electron, l'horloge unique, les
dépendances refusées, l'écriture des épreuves, les chaînes en dur, les clés
de traduction, les mots interdits, les couleurs et la teinte de conflit, le
balisage, les sélecteurs élagués et les motifs de surveillance. Corrigés :
délais d'épreuves, classe de la trame de conflit ; « la rend évitable » est
une exception nommée (§ 5.6). Vérifié : 2587 node, 465 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 13:21:21 -04:00
} ;
assert . deepEqual ( avecArbre ( fichiers , releverCouleurs ) , [
'src/interface/accueil/sous/B.svelte:2 couleur « #ABCDEF » dans le script' ,
'src/interface/accueil/sous/B.svelte:5 couleur « #fff » dans color' ,
'src/interface/accueil/sous/B.svelte:5 couleur « Canvas » dans fill' ,
'src/interface/accueil/sous/B.svelte:5 couleur « hsl( » dans stroke' ,
'src/interface/accueil/sous/B.svelte:5 couleur « red » dans fill' ,
'src/interface/accueil/sous/B.svelte:5 jeton déclaré --ombre' ,
'src/interface/accueil/sous/B.svelte:6 jeton déclaré --teinte' ,
'src/interface/autre.css:1 couleur « #123456 » dans color' ,
'src/interface/plan/A.svelte:3 couleur « #c00 » dans stroke' ,
'src/interface/plan/A.svelte:5 couleur « Tomato » dans background' ,
'src/interface/plan/A.svelte:5 couleur « rgb( » dans color' ,
'src/interface/plan/A.svelte:6 couleur « white » dans outline-color' ,
'src/interface/plan/A.svelte:6 jeton déclaré --rang' ,
[FIX] interface: ghost labels, keyboard, valid HTML, closed data-* list
The drag ghost names what releasing at the point changes, floor included.
Escape on a seat returns to its table, a refused Tab stays put, the legend
toggle exposes aria-expanded, and the header markup is valid HTML. Every
data-* attribute the interface sets belongs to one closed list that a guard
reads. The tree guards follow helpers a test imports, and the reporter says
the whole series stays watched under coverage on a small machine.
Checked: every new test red first; 3001 node, 635 browser and 63 long tests
green from the index alone.
--- FR ---
[FIX] interface : fantôme nommé, clavier, HTML valide, data-* fermés
Le fantôme du glisser nomme ce que relâcher au point modifie, sol compris.
Échap sur un siège remonte à sa table, un Tab refusé reste en place, la
bascule de la légende expose aria-expanded, et la tête est du HTML valide.
Chaque attribut data-* que l'interface pose appartient à une liste fermée
qu'une garde lit. Les gardes de l'arbre suivent les aides qu'une épreuve
importe, et le rapporteur dit la série entière surveillée sous couverture.
Vérifié : chaque épreuve rouge d'abord ; 3001 node, 635 navigateur et 63
longues vertes depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 04:01:57 -04:00
'src/interface/plan/teinte.js:1 couleur « #c00 » dans le script' ,
"src/interface/plan/teinte.js:2 jeton de conflit hors d'une règle de conflit du plan, dans le script" ,
[ADD] tests: guards for layers, clock, sources, colours, watch patterns
Guards scan the tree file by file and line by line, fail on an empty scan,
and are each shown red on a temporary tree. They cover the interface import
graph, the Electron bridge, one clock, refused dependencies, how tests are
written, hard-coded strings, translation keys, forbidden words, colours and
the conflict hue, markup, pruned selectors and watch patterns. Coverage
keeps the engine integration test on a small machine. Fixes: delays in
tests, the conflict hatch class; « la rend évitable » is a named exception
(§ 5.6). Checked: 2587 node, 465 browser tests, from the index alone.
--- FR ---
[ADD] épreuves : gardes des couches, horloge, sources, couleurs, motifs
Les gardes relèvent par fichier et par ligne, échouent sur un balayage vide
et sont chacune montrées rouges sur un arbre temporaire. Elles couvrent le
graphe d'imports de l'interface, le pont d'Electron, l'horloge unique, les
dépendances refusées, l'écriture des épreuves, les chaînes en dur, les clés
de traduction, les mots interdits, les couleurs et la teinte de conflit, le
balisage, les sélecteurs élagués et les motifs de surveillance. Corrigés :
délais d'épreuves, classe de la trame de conflit ; « la rend évitable » est
une exception nommée (§ 5.6). Vérifié : 2587 node, 465 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 13:21:21 -04:00
] ) ;
} ) ;
test ( 'la garde relève un bouton peint du jeton de conflit, et l’ admet dans une règle de conflit du plan' , ( ) => {
const fichiers = {
'src/interface/jetons.css' : JETONS _EPREUVE ,
'src/interface/accueil/Bouton.svelte' : composant ( {
gabarit : '<button class="danger">x</button>' ,
style : ' .danger { color: var(--couleur-conflit); }' ,
} ) ,
'src/interface/plan/Table.svelte' : composant ( {
gabarit : '<g class:conflit={true} class="table"><rect /></g><span style="color: var(--couleur-conflit)">1</span>' ,
style : ' .table.conflit rect { stroke: var(--couleur-conflit); }\n .table rect { fill: var(--couleur-conflit); }' ,
} ) ,
'src/interface/plan/Trou.svelte' : composant ( {
gabarit : '<g class="table b"><rect /></g><button class="sans-conflit">x</button><i class="non-conflit a"></i>' ,
style : [
' .table:not(.conflit) rect { fill: var(--couleur-conflit); }' ,
' .sans-conflit { color: var(--couleur-conflit); }' ,
' .non-conflit { color: var(--couleur-conflit); }' ,
' .a, .b.conflit { fill: var(--couleur-conflit); }' ,
' .conflit-leve { fill: var(--couleur-conflit); }' ,
[FIX] interface: ghost labels, keyboard, valid HTML, closed data-* list
The drag ghost names what releasing at the point changes, floor included.
Escape on a seat returns to its table, a refused Tab stays put, the legend
toggle exposes aria-expanded, and the header markup is valid HTML. Every
data-* attribute the interface sets belongs to one closed list that a guard
reads. The tree guards follow helpers a test imports, and the reporter says
the whole series stays watched under coverage on a small machine.
Checked: every new test red first; 3001 node, 635 browser and 63 long tests
green from the index alone.
--- FR ---
[FIX] interface : fantôme nommé, clavier, HTML valide, data-* fermés
Le fantôme du glisser nomme ce que relâcher au point modifie, sol compris.
Échap sur un siège remonte à sa table, un Tab refusé reste en place, la
bascule de la légende expose aria-expanded, et la tête est du HTML valide.
Chaque attribut data-* que l'interface pose appartient à une liste fermée
qu'une garde lit. Les gardes de l'arbre suivent les aides qu'une épreuve
importe, et le rapporteur dit la série entière surveillée sous couverture.
Vérifié : chaque épreuve rouge d'abord ; 3001 node, 635 navigateur et 63
longues vertes depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 04:01:57 -04:00
' .b.conflit, .c .conflit { stroke: var(--couleur-conflit); }' ,
' .fin-de-conflit { fill: var(--couleur-conflit); }' ,
' .trait-conflit { fill: var(--couleur-conflit); }' ,
[ADD] tests: guards for layers, clock, sources, colours, watch patterns
Guards scan the tree file by file and line by line, fail on an empty scan,
and are each shown red on a temporary tree. They cover the interface import
graph, the Electron bridge, one clock, refused dependencies, how tests are
written, hard-coded strings, translation keys, forbidden words, colours and
the conflict hue, markup, pruned selectors and watch patterns. Coverage
keeps the engine integration test on a small machine. Fixes: delays in
tests, the conflict hatch class; « la rend évitable » is a named exception
(§ 5.6). Checked: 2587 node, 465 browser tests, from the index alone.
--- FR ---
[ADD] épreuves : gardes des couches, horloge, sources, couleurs, motifs
Les gardes relèvent par fichier et par ligne, échouent sur un balayage vide
et sont chacune montrées rouges sur un arbre temporaire. Elles couvrent le
graphe d'imports de l'interface, le pont d'Electron, l'horloge unique, les
dépendances refusées, l'écriture des épreuves, les chaînes en dur, les clés
de traduction, les mots interdits, les couleurs et la teinte de conflit, le
balisage, les sélecteurs élagués et les motifs de surveillance. Corrigés :
délais d'épreuves, classe de la trame de conflit ; « la rend évitable » est
une exception nommée (§ 5.6). Vérifié : 2587 node, 465 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 13:21:21 -04:00
] . join ( '\n' ) ,
} ) ,
} ;
assert . deepEqual ( avecArbre ( fichiers , releverCouleurs ) , [
"src/interface/accueil/Bouton.svelte:3 jeton de conflit hors d'une règle de conflit du plan, « .danger »" ,
"src/interface/plan/Table.svelte:1 jeton de conflit hors d'une règle de conflit du plan, en ligne" ,
"src/interface/plan/Table.svelte:4 jeton de conflit hors d'une règle de conflit du plan, « .table rect »" ,
"src/interface/plan/Trou.svelte:3 jeton de conflit hors d'une règle de conflit du plan, « .table:not(.conflit) rect »" ,
"src/interface/plan/Trou.svelte:4 jeton de conflit hors d'une règle de conflit du plan, « .sans-conflit »" ,
"src/interface/plan/Trou.svelte:5 jeton de conflit hors d'une règle de conflit du plan, « .non-conflit »" ,
"src/interface/plan/Trou.svelte:6 jeton de conflit hors d'une règle de conflit du plan, « .a, .b.conflit »" ,
"src/interface/plan/Trou.svelte:7 jeton de conflit hors d'une règle de conflit du plan, « .conflit-leve »" ,
[FIX] interface: ghost labels, keyboard, valid HTML, closed data-* list
The drag ghost names what releasing at the point changes, floor included.
Escape on a seat returns to its table, a refused Tab stays put, the legend
toggle exposes aria-expanded, and the header markup is valid HTML. Every
data-* attribute the interface sets belongs to one closed list that a guard
reads. The tree guards follow helpers a test imports, and the reporter says
the whole series stays watched under coverage on a small machine.
Checked: every new test red first; 3001 node, 635 browser and 63 long tests
green from the index alone.
--- FR ---
[FIX] interface : fantôme nommé, clavier, HTML valide, data-* fermés
Le fantôme du glisser nomme ce que relâcher au point modifie, sol compris.
Échap sur un siège remonte à sa table, un Tab refusé reste en place, la
bascule de la légende expose aria-expanded, et la tête est du HTML valide.
Chaque attribut data-* que l'interface pose appartient à une liste fermée
qu'une garde lit. Les gardes de l'arbre suivent les aides qu'une épreuve
importe, et le rapporteur dit la série entière surveillée sous couverture.
Vérifié : chaque épreuve rouge d'abord ; 3001 node, 635 navigateur et 63
longues vertes depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 04:01:57 -04:00
"src/interface/plan/Trou.svelte:9 jeton de conflit hors d'une règle de conflit du plan, « .fin-de-conflit »" ,
"src/interface/plan/Trou.svelte:10 jeton de conflit hors d'une règle de conflit du plan, « .trait-conflit »" ,
[ADD] tests: guards for layers, clock, sources, colours, watch patterns
Guards scan the tree file by file and line by line, fail on an empty scan,
and are each shown red on a temporary tree. They cover the interface import
graph, the Electron bridge, one clock, refused dependencies, how tests are
written, hard-coded strings, translation keys, forbidden words, colours and
the conflict hue, markup, pruned selectors and watch patterns. Coverage
keeps the engine integration test on a small machine. Fixes: delays in
tests, the conflict hatch class; « la rend évitable » is a named exception
(§ 5.6). Checked: 2587 node, 465 browser tests, from the index alone.
--- FR ---
[ADD] épreuves : gardes des couches, horloge, sources, couleurs, motifs
Les gardes relèvent par fichier et par ligne, échouent sur un balayage vide
et sont chacune montrées rouges sur un arbre temporaire. Elles couvrent le
graphe d'imports de l'interface, le pont d'Electron, l'horloge unique, les
dépendances refusées, l'écriture des épreuves, les chaînes en dur, les clés
de traduction, les mots interdits, les couleurs et la teinte de conflit, le
balisage, les sélecteurs élagués et les motifs de surveillance. Corrigés :
délais d'épreuves, classe de la trame de conflit ; « la rend évitable » est
une exception nommée (§ 5.6). Vérifié : 2587 node, 465 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 13:21:21 -04:00
] ) ;
} ) ;
[ADD] plan: one hue and pattern per conflict rule, legend, appartenance
The plan drew every conflict in one hue, so a table that seats colleagues
together looked like one where people meet again. Each active rule now has
its own reserved hue and pattern: same appartenance hatched one way, met
again hatched the other, back at a table dotted; rules on one table
overlap, the badge keeps the total. A legend from the view bar shows each
active rule's sample. A table's tooltip names the appartenance that brings
a pair together. The hues are validated by computation in both themes and
the spec (7.5, 12.7) follows. Checked: 2848 node, 580 browser tests.
--- FR ---
[ADD] plan : une teinte et une trame par règle, légende, appartenance
Le plan dessinait tout conflit d'une seule teinte : une table qui réunit
des collègues ressemblait à une table où des gens se revoient. Chaque
règle active a sa teinte réservée et sa trame : même appartenance hachurée
dans un sens, voisins revus dans l'autre, table revue en pointillé ; les
règles d'une table se superposent, la pastille garde le total. Une légende
dans la barre de vue montre l'échantillon de chaque règle active.
L'infobulle nomme l'appartenance qui réunit une paire. Teintes validées
par calcul dans les deux thèmes ; spec § 7.5, § 12.7. Vérifié : 2848, 580.
Assisted-by: Claude Opus 5.5
2026-10-08 04:33:43 -04:00
test ( 'la garde relève chaque teinte de conflit — toute la famille --couleur-conflit-… — lue hors d’ une règle de conflit du plan, et l’ admet dans une règle de conflit du plan' , ( ) => {
const fichiers = {
'src/interface/jetons.css' : JETONS _EPREUVE ,
'src/interface/accueil/Alerte.svelte' : composant ( {
script : " const teinte = 'var(--couleur-conflit-table)';" ,
gabarit : '<p class="alerte">x</p>' ,
style : ' .alerte { color: var(--couleur-conflit-voisins); }' ,
} ) ,
'src/interface/plan/Motif.svelte' : composant ( {
gabarit : '<svg><line class="trait voisins-conflit" /><line class="trait table-conflit" /><circle class="trait" style="fill: var(--couleur-conflit-table)" /></svg>' ,
style : [
' .trait.voisins-conflit { stroke: var(--couleur-conflit-voisins); }' ,
' .trait.table-conflit { stroke: var(--couleur-conflit-table); }' ,
' .trait { fill: var(--couleur-conflit-voisins); }' ,
' .conflit-voisins { fill: var(--couleur-conflit-table); }' ,
[FIX] interface: ghost labels, keyboard, valid HTML, closed data-* list
The drag ghost names what releasing at the point changes, floor included.
Escape on a seat returns to its table, a refused Tab stays put, the legend
toggle exposes aria-expanded, and the header markup is valid HTML. Every
data-* attribute the interface sets belongs to one closed list that a guard
reads. The tree guards follow helpers a test imports, and the reporter says
the whole series stays watched under coverage on a small machine.
Checked: every new test red first; 3001 node, 635 browser and 63 long tests
green from the index alone.
--- FR ---
[FIX] interface : fantôme nommé, clavier, HTML valide, data-* fermés
Le fantôme du glisser nomme ce que relâcher au point modifie, sol compris.
Échap sur un siège remonte à sa table, un Tab refusé reste en place, la
bascule de la légende expose aria-expanded, et la tête est du HTML valide.
Chaque attribut data-* que l'interface pose appartient à une liste fermée
qu'une garde lit. Les gardes de l'arbre suivent les aides qu'une épreuve
importe, et le rapporteur dit la série entière surveillée sous couverture.
Vérifié : chaque épreuve rouge d'abord ; 3001 node, 635 navigateur et 63
longues vertes depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 04:01:57 -04:00
' .trait.table-conflit { fill: var(--couleur-conflit-voisins); }' ,
[ADD] plan: one hue and pattern per conflict rule, legend, appartenance
The plan drew every conflict in one hue, so a table that seats colleagues
together looked like one where people meet again. Each active rule now has
its own reserved hue and pattern: same appartenance hatched one way, met
again hatched the other, back at a table dotted; rules on one table
overlap, the badge keeps the total. A legend from the view bar shows each
active rule's sample. A table's tooltip names the appartenance that brings
a pair together. The hues are validated by computation in both themes and
the spec (7.5, 12.7) follows. Checked: 2848 node, 580 browser tests.
--- FR ---
[ADD] plan : une teinte et une trame par règle, légende, appartenance
Le plan dessinait tout conflit d'une seule teinte : une table qui réunit
des collègues ressemblait à une table où des gens se revoient. Chaque
règle active a sa teinte réservée et sa trame : même appartenance hachurée
dans un sens, voisins revus dans l'autre, table revue en pointillé ; les
règles d'une table se superposent, la pastille garde le total. Une légende
dans la barre de vue montre l'échantillon de chaque règle active.
L'infobulle nomme l'appartenance qui réunit une paire. Teintes validées
par calcul dans les deux thèmes ; spec § 7.5, § 12.7. Vérifié : 2848, 580.
Assisted-by: Claude Opus 5.5
2026-10-08 04:33:43 -04:00
] . join ( '\n' ) ,
} ) ,
} ;
assert . deepEqual ( avecArbre ( fichiers , releverCouleurs ) , [
"src/interface/accueil/Alerte.svelte:2 jeton de conflit hors d'une règle de conflit du plan, dans le script" ,
"src/interface/accueil/Alerte.svelte:6 jeton de conflit hors d'une règle de conflit du plan, « .alerte »" ,
"src/interface/plan/Motif.svelte:1 jeton de conflit hors d'une règle de conflit du plan, en ligne" ,
"src/interface/plan/Motif.svelte:5 jeton de conflit hors d'une règle de conflit du plan, « .trait »" ,
"src/interface/plan/Motif.svelte:6 jeton de conflit hors d'une règle de conflit du plan, « .conflit-voisins »" ,
[FIX] interface: ghost labels, keyboard, valid HTML, closed data-* list
The drag ghost names what releasing at the point changes, floor included.
Escape on a seat returns to its table, a refused Tab stays put, the legend
toggle exposes aria-expanded, and the header markup is valid HTML. Every
data-* attribute the interface sets belongs to one closed list that a guard
reads. The tree guards follow helpers a test imports, and the reporter says
the whole series stays watched under coverage on a small machine.
Checked: every new test red first; 3001 node, 635 browser and 63 long tests
green from the index alone.
--- FR ---
[FIX] interface : fantôme nommé, clavier, HTML valide, data-* fermés
Le fantôme du glisser nomme ce que relâcher au point modifie, sol compris.
Échap sur un siège remonte à sa table, un Tab refusé reste en place, la
bascule de la légende expose aria-expanded, et la tête est du HTML valide.
Chaque attribut data-* que l'interface pose appartient à une liste fermée
qu'une garde lit. Les gardes de l'arbre suivent les aides qu'une épreuve
importe, et le rapporteur dit la série entière surveillée sous couverture.
Vérifié : chaque épreuve rouge d'abord ; 3001 node, 635 navigateur et 63
longues vertes depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 04:01:57 -04:00
"src/interface/plan/Motif.svelte:7 jeton de conflit hors d'une règle de conflit du plan, « .trait.table-conflit »" ,
[ADD] plan: one hue and pattern per conflict rule, legend, appartenance
The plan drew every conflict in one hue, so a table that seats colleagues
together looked like one where people meet again. Each active rule now has
its own reserved hue and pattern: same appartenance hatched one way, met
again hatched the other, back at a table dotted; rules on one table
overlap, the badge keeps the total. A legend from the view bar shows each
active rule's sample. A table's tooltip names the appartenance that brings
a pair together. The hues are validated by computation in both themes and
the spec (7.5, 12.7) follows. Checked: 2848 node, 580 browser tests.
--- FR ---
[ADD] plan : une teinte et une trame par règle, légende, appartenance
Le plan dessinait tout conflit d'une seule teinte : une table qui réunit
des collègues ressemblait à une table où des gens se revoient. Chaque
règle active a sa teinte réservée et sa trame : même appartenance hachurée
dans un sens, voisins revus dans l'autre, table revue en pointillé ; les
règles d'une table se superposent, la pastille garde le total. Une légende
dans la barre de vue montre l'échantillon de chaque règle active.
L'infobulle nomme l'appartenance qui réunit une paire. Teintes validées
par calcul dans les deux thèmes ; spec § 7.5, § 12.7. Vérifié : 2848, 580.
Assisted-by: Claude Opus 5.5
2026-10-08 04:33:43 -04:00
] ) ;
} ) ;
[ADD] tests: guards for layers, clock, sources, colours, watch patterns
Guards scan the tree file by file and line by line, fail on an empty scan,
and are each shown red on a temporary tree. They cover the interface import
graph, the Electron bridge, one clock, refused dependencies, how tests are
written, hard-coded strings, translation keys, forbidden words, colours and
the conflict hue, markup, pruned selectors and watch patterns. Coverage
keeps the engine integration test on a small machine. Fixes: delays in
tests, the conflict hatch class; « la rend évitable » is a named exception
(§ 5.6). Checked: 2587 node, 465 browser tests, from the index alone.
--- FR ---
[ADD] épreuves : gardes des couches, horloge, sources, couleurs, motifs
Les gardes relèvent par fichier et par ligne, échouent sur un balayage vide
et sont chacune montrées rouges sur un arbre temporaire. Elles couvrent le
graphe d'imports de l'interface, le pont d'Electron, l'horloge unique, les
dépendances refusées, l'écriture des épreuves, les chaînes en dur, les clés
de traduction, les mots interdits, les couleurs et la teinte de conflit, le
balisage, les sélecteurs élagués et les motifs de surveillance. Corrigés :
délais d'épreuves, classe de la trame de conflit ; « la rend évitable » est
une exception nommée (§ 5.6). Vérifié : 2587 node, 465 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 13:21:21 -04:00
test ( 'la garde des thèmes relève un jeton qu’ un thème ne redéfinit pas' , ( ) => {
const fichiers = {
'src/interface/jetons.css' : [
':root { --espace-1: 1px; }' ,
':root, :root[data-theme="clair"] { --couleur-sol: #fff; --couleur-gel: #00f; }' ,
":root[data-theme='sombre'] { --couleur-sol: #000; --couleur-fantome: #0f0; }" ,
'' ,
] . join ( '\n' ) ,
} ;
assert . deepEqual ( avecArbre ( fichiers , releverThemes ) , [
'src/interface/jetons.css:2 --couleur-gel absent du thème sombre' ,
'src/interface/jetons.css:3 --couleur-fantome absent du thème clair' ,
] ) ;
} ) ;
test ( 'un arbre sans composant ni feuille fait échouer la garde' , ( ) => {
assert . throws ( ( ) => avecArbre ( { 'src/interface/jetons.css' : JETONS _EPREUVE } , releverCouleurs ) , /couleurs : aucun fichier examiné/ ) ;
} ) ;
[ADD] tests: spec coverage guards, delivery checks, spec corrections
New guards close the spec coverage gaps: only the configuration and the
diagnostic read reservations in the engine, no second language in the
sources, the engine and geometry never read the version, a test lives
beside its module, fixtures never copy a shipped file, and the property
library stays in a closed list. The delivery test pins the www output and
the French language passed by the entry point. The spec names three test
projects and the three shell files. Checked: 2778 node, 550 browser and
62 long tests from the index alone; the version check passes.
--- FR ---
[ADD] épreuves : gardes de couverture du spec, livrable, spec corrigé
De nouvelles gardes ferment les manques de couverture : seules la
configuration et le diagnostic lisent les réservations dans le moteur,
aucun second langage dans les sources, le moteur et la géométrie ne lisent
pas la version, une épreuve vit près de son module, aucune donnée d'épreuve
ne copie un fichier livré, la bibliothèque de propriétés reste en liste
fermée. Le livrable fige la sortie www et la langue française du point
d'entrée. Le spec nomme trois projets et trois fichiers de coquille.
Vérifié : 2778 node, 550 navigateur, 62 longues ; version conforme.
Assisted-by: Claude Opus 5.5
2026-10-07 17:51:49 -04:00
test ( 'une feuille des jetons sans jeton de couleur fait échouer la garde des thèmes' , ( ) => {
const fichiers = { 'src/interface/jetons.css' : ':root, :root[data-theme="clair"] { --espace-1: 1px; }\n' } ;
assert . throws ( ( ) => avecArbre ( fichiers , releverThemes ) , /thèmes : aucun jeton de couleur examiné/ ) ;
} ) ;
[ADD] tests: guards for layers, clock, sources, colours, watch patterns
Guards scan the tree file by file and line by line, fail on an empty scan,
and are each shown red on a temporary tree. They cover the interface import
graph, the Electron bridge, one clock, refused dependencies, how tests are
written, hard-coded strings, translation keys, forbidden words, colours and
the conflict hue, markup, pruned selectors and watch patterns. Coverage
keeps the engine integration test on a small machine. Fixes: delays in
tests, the conflict hatch class; « la rend évitable » is a named exception
(§ 5.6). Checked: 2587 node, 465 browser tests, from the index alone.
--- FR ---
[ADD] épreuves : gardes des couches, horloge, sources, couleurs, motifs
Les gardes relèvent par fichier et par ligne, échouent sur un balayage vide
et sont chacune montrées rouges sur un arbre temporaire. Elles couvrent le
graphe d'imports de l'interface, le pont d'Electron, l'horloge unique, les
dépendances refusées, l'écriture des épreuves, les chaînes en dur, les clés
de traduction, les mots interdits, les couleurs et la teinte de conflit, le
balisage, les sélecteurs élagués et les motifs de surveillance. Corrigés :
délais d'épreuves, classe de la trame de conflit ; « la rend évitable » est
une exception nommée (§ 5.6). Vérifié : 2587 node, 465 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 13:21:21 -04:00
} ) ;
describe ( 'sources de l’ interface : balisage et fenêtres du navigateur (§ 13.1, § 2.1)' , ( ) => {
test ( 'aucun balisage injecté, aucune fenêtre du navigateur, aucun beforeunload, aucun .message affiché' , ( ) => {
exigerVide ( releverBalisageEtFenetres ( RACINE ) , 'balisage et fenêtres' ) ;
} ) ;
test ( 'la garde relève {@html nom} et chaque forme refusée, et pas leurs voisines ni un commentaire' , ( ) => {
const fichiers = {
'src/interface/A.svelte' : composant ( {
script : " let nom = $state('');\n // aucun beforeunload, jamais alert(…) ni err.message" ,
gabarit : '<p>{@html nom}</p>\n<svelte:window onbeforeunload={() => {}} />' ,
} ) ,
'src/interface/plan/sous/b.js' : [
'export function f(el, erreur, seance) {' ,
" el.innerHTML = '';" ,
" el['outerHTML'] = '';" ,
" el.insertAdjacentHTML('beforeend', '');" ,
" document.write('x');" ,
" window.alert('x'); confirm('x'); globalThis.prompt('x');" ,
" addEventListener('beforeunload', f);" ,
' return erreur.message ?? erreur?.message;' ,
'}' ,
'export const voisins = (s) => [s.confirmer(), s.alert(), s.textContent, s.code, s.messages];' ,
'' ,
] . join ( '\n' ) ,
'src/application/c.js' : "export const m = (e) => e.message;\nexport const h = (el) => { el.innerHTML = ''; };\n" ,
'src/interface/A.test.js' : "window.confirm('x'); el.innerHTML = '';\n" ,
} ;
assert . deepEqual ( avecArbre ( fichiers , releverBalisageEtFenetres ) , [
'src/application/c.js:2 innerHTML' ,
'src/interface/A.svelte:5 {@html}' ,
'src/interface/A.svelte:6 beforeunload' ,
'src/interface/plan/sous/b.js:2 innerHTML' ,
'src/interface/plan/sous/b.js:3 outerHTML' ,
'src/interface/plan/sous/b.js:4 insertAdjacentHTML' ,
'src/interface/plan/sous/b.js:5 document.write' ,
'src/interface/plan/sous/b.js:6 alert(' ,
'src/interface/plan/sous/b.js:6 confirm(' ,
'src/interface/plan/sous/b.js:6 prompt(' ,
'src/interface/plan/sous/b.js:7 beforeunload' ,
'src/interface/plan/sous/b.js:8 .message' ,
'src/interface/plan/sous/b.js:8 .message' ,
] ) ;
} ) ;
test ( 'un arbre sans source fait échouer la garde' , ( ) => {
assert . throws ( ( ) => avecArbre ( { 'src/interface/a.css' : '' } , releverBalisageEtFenetres ) , /aucun fichier examiné/ ) ;
} ) ;
} ) ;
describe ( 'sources de l’ interface : texte comprimé et animations (§ 7.4, § 19.2)' , ( ) => {
test ( 'ni textLength ni lengthAdjust, ni transitionend ni animationend, ni transition ni animation' , ( ) => {
exigerVide ( releverTexteEtAnimation ( RACINE ) , 'texte et animation' ) ;
} ) ;
test ( 'la garde relève chaque forme dans son périmètre, et pas hors de lui ni dans un commentaire' , ( ) => {
const fichiers = {
'src/interface/plan/A.svelte' : composant ( {
script : " import { fade } from 'svelte/transition';\n // ni textLength ni transitionend" ,
gabarit : [
'<text textLength="10" lengthAdjust="spacing">{x}</text>' ,
'<div transition:fade ontransitionend={f}></div><p in:fade out:fade></p>' ,
] . join ( '\n' ) ,
style : ' div { transition: opacity 1s; animation: none; transition-duration: 0s; }\n @keyframes pulse { to { opacity: 1; } }' ,
} ) ,
'src/geometrie/texte.js' : [
"export const f = (el) => el.setAttribute('textLength', 1);" ,
"export const g = 'transitionend';" ,
'export const h = (el) => el.getComputedTextLength();' ,
"export const i = '[textLength], [lengthAdjust]';" ,
'' ,
] . join ( '\n' ) ,
'src/interface/b.js' : "export const f = (el) => el.addEventListener('animationend', f);\n" ,
'src/application/c.js' : "export const f = 'textLength';\n" ,
'src/interface/A.navigateur.test.js' : "export const f = '[textLength], [lengthAdjust]';\n" ,
} ;
[IMP] home: list shown at once, computed columns loaded per event
The event list waited until every event's ranking was evaluated, about
10 ms per event with propositions, before showing anything. It now shows
as soon as the folder is read; each line's retained-plan columns and best
proposition load one event at a time, yielding to the browser between
events, behind a rotating SVG spinner, and show as each result arrives.
Work for a list no longer shown is dropped. The spinner is the one named
exception to the no-animation rule: still under reduced motion, cancelled
for captures, announced once. Checked: 2935 node, 616 browser, 62 long.
--- FR ---
[IMP] accueil : liste aussitôt montrée, colonnes calculées par événement
La liste des événements attendait l'évaluation du classement de chaque
événement, environ 10 ms par événement qui a des propositions, avant de
rien montrer. Elle paraît dès la lecture du dossier ; les colonnes du
retenu et la meilleure proposition de chaque ligne se chargent un
événement à la fois, le navigateur reprenant la main entre deux, derrière
une icône SVG qui tourne. Le travail d'une liste qui n'est plus montrée
est abandonné. L'icône est la seule exception nommée à l'interdit
d'animation. Vérifié : 2935 node, 616 navigateur, 62 longues.
Assisted-by: Claude Opus 5.5
2026-10-08 17:07:31 -04:00
assert . deepEqual ( avecArbre ( fichiers , ( racine ) => releverTexteEtAnimation ( racine , [ ] ) ) , [
[ADD] tests: guards for layers, clock, sources, colours, watch patterns
Guards scan the tree file by file and line by line, fail on an empty scan,
and are each shown red on a temporary tree. They cover the interface import
graph, the Electron bridge, one clock, refused dependencies, how tests are
written, hard-coded strings, translation keys, forbidden words, colours and
the conflict hue, markup, pruned selectors and watch patterns. Coverage
keeps the engine integration test on a small machine. Fixes: delays in
tests, the conflict hatch class; « la rend évitable » is a named exception
(§ 5.6). Checked: 2587 node, 465 browser tests, from the index alone.
--- FR ---
[ADD] épreuves : gardes des couches, horloge, sources, couleurs, motifs
Les gardes relèvent par fichier et par ligne, échouent sur un balayage vide
et sont chacune montrées rouges sur un arbre temporaire. Elles couvrent le
graphe d'imports de l'interface, le pont d'Electron, l'horloge unique, les
dépendances refusées, l'écriture des épreuves, les chaînes en dur, les clés
de traduction, les mots interdits, les couleurs et la teinte de conflit, le
balisage, les sélecteurs élagués et les motifs de surveillance. Corrigés :
délais d'épreuves, classe de la trame de conflit ; « la rend évitable » est
une exception nommée (§ 5.6). Vérifié : 2587 node, 465 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 13:21:21 -04:00
'src/geometrie/texte.js:1 textLength' ,
'src/geometrie/texte.js:4 textLength' ,
'src/interface/b.js:1 animationend' ,
'src/interface/plan/A.svelte:5 lengthAdjust' ,
'src/interface/plan/A.svelte:5 textLength' ,
'src/interface/plan/A.svelte:6 directive in:fade' ,
'src/interface/plan/A.svelte:6 directive out:fade' ,
'src/interface/plan/A.svelte:6 directive transition:fade' ,
'src/interface/plan/A.svelte:6 transitionend' ,
'src/interface/plan/A.svelte:8 transition: opacity 1s' ,
'src/interface/plan/A.svelte:9 @keyframes' ,
] ) ;
} ) ;
test ( 'un arbre sans source fait échouer la garde' , ( ) => {
assert . throws ( ( ) => avecArbre ( { 'src/application/a.js' : '' } , releverTexteEtAnimation ) , /aucun fichier examiné/ ) ;
} ) ;
[IMP] home: list shown at once, computed columns loaded per event
The event list waited until every event's ranking was evaluated, about
10 ms per event with propositions, before showing anything. It now shows
as soon as the folder is read; each line's retained-plan columns and best
proposition load one event at a time, yielding to the browser between
events, behind a rotating SVG spinner, and show as each result arrives.
Work for a list no longer shown is dropped. The spinner is the one named
exception to the no-animation rule: still under reduced motion, cancelled
for captures, announced once. Checked: 2935 node, 616 browser, 62 long.
--- FR ---
[IMP] accueil : liste aussitôt montrée, colonnes calculées par événement
La liste des événements attendait l'évaluation du classement de chaque
événement, environ 10 ms par événement qui a des propositions, avant de
rien montrer. Elle paraît dès la lecture du dossier ; les colonnes du
retenu et la meilleure proposition de chaque ligne se chargent un
événement à la fois, le navigateur reprenant la main entre deux, derrière
une icône SVG qui tourne. Le travail d'une liste qui n'est plus montrée
est abandonné. L'icône est la seule exception nommée à l'interdit
d'animation. Vérifié : 2935 node, 616 navigateur, 62 longues.
Assisted-by: Claude Opus 5.5
2026-10-08 17:07:31 -04:00
test ( 'l’ animation admise : celle du signe du chargement, par son fichier et son sélecteur, arrêtée sous prefers-reduced-motion' , ( ) => {
assert . equal ( ANIMATIONS _ADMISES . length , 1 ) ;
const [ admise ] = ANIMATIONS _ADMISES ;
assert . equal ( admise . fichier , 'src/interface/accueil/Chargement.svelte' ) ;
assert . ok ( admise . raison . length > 0 ) ;
const tourne = ` ${ admise . selecteur } { animation: ${ admise . images } 1s linear infinite; } ` ;
const images = ` @keyframes ${ admise . images } { to { transform: rotate(1turn); } } ` ;
const arret = ` @media (prefers-reduced-motion: reduce) { ${ admise . selecteur } { animation: none; } } ` ;
const style = ( ... regles ) => composant ( { gabarit : '<svg></svg>' , style : regles . join ( '\n' ) } ) ;
const ailleurs = 'src/interface/accueil/Autre.svelte' ;
// Le fichier admis, sa règle, ses images et son arrêt : rien à relever.
assert . deepEqual ( avecArbre ( { [ admise . fichier ] : style ( tourne , images , arret ) } , releverTexteEtAnimation ) , [ ] ) ;
// Une autre règle du même fichier, une transition de la règle admise, et
// la même règle dans un autre fichier restent relevées.
assert . deepEqual (
avecArbre (
{
[ admise . fichier ] : style ( tourne , images , arret , ' .autre { animation: clignote 1s; }' , ` ${ admise . selecteur } { transition: transform 1s; } ` ) ,
[ ailleurs ] : style ( tourne , images ) ,
} ,
releverTexteEtAnimation ,
) ,
[
` ${ ailleurs } :3 animation: ${ admise . images } 1s linear infinite ` ,
` ${ ailleurs } :4 @keyframes ` ,
` ${ admise . fichier } :6 animation: clignote 1s ` ,
` ${ admise . fichier } :7 transition: transform 1s ` ,
] ,
) ;
// Sans son arrêt sous prefers-reduced-motion, l'animation admise se relève.
assert . deepEqual ( avecArbre ( { [ admise . fichier ] : style ( tourne , images ) } , releverTexteEtAnimation ) , [
` ${ admise . fichier } :3 animation admise sans arrêt sous prefers-reduced-motion : ${ admise . selecteur } ` ,
] ) ;
[FIX] interface: ghost labels, keyboard, valid HTML, closed data-* list
The drag ghost names what releasing at the point changes, floor included.
Escape on a seat returns to its table, a refused Tab stays put, the legend
toggle exposes aria-expanded, and the header markup is valid HTML. Every
data-* attribute the interface sets belongs to one closed list that a guard
reads. The tree guards follow helpers a test imports, and the reporter says
the whole series stays watched under coverage on a small machine.
Checked: every new test red first; 3001 node, 635 browser and 63 long tests
green from the index alone.
--- FR ---
[FIX] interface : fantôme nommé, clavier, HTML valide, data-* fermés
Le fantôme du glisser nomme ce que relâcher au point modifie, sol compris.
Échap sur un siège remonte à sa table, un Tab refusé reste en place, la
bascule de la légende expose aria-expanded, et la tête est du HTML valide.
Chaque attribut data-* que l'interface pose appartient à une liste fermée
qu'une garde lit. Les gardes de l'arbre suivent les aides qu'une épreuve
importe, et le rapporteur dit la série entière surveillée sous couverture.
Vérifié : chaque épreuve rouge d'abord ; 3001 node, 635 navigateur et 63
longues vertes depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 04:01:57 -04:00
// Un arrêt sous une requête qui ne demande pas le mouvement réduit — niée,
// ou de l'autre préférence — n'en est pas un.
for ( const requete of [ 'not (prefers-reduced-motion: reduce)' , '(prefers-reduced-motion: no-preference)' , 'print, (prefers-reduced-motion: reduce)' ] ) {
const faux = ` @media ${ requete } { ${ admise . selecteur } { animation: none; } } ` ;
assert . deepEqual ( avecArbre ( { [ admise . fichier ] : style ( tourne , images , faux ) } , releverTexteEtAnimation ) , [
` ${ admise . fichier } :3 animation admise sans arrêt sous prefers-reduced-motion : ${ admise . selecteur } ` ,
] , requete ) ;
}
// La requête admise, en minuscules ou non, avec ou sans espaces.
const serre = ` @media (PREFERS-REDUCED-MOTION:reduce) { ${ admise . selecteur } { animation: none; } } ` ;
assert . deepEqual ( avecArbre ( { [ admise . fichier ] : style ( tourne , images , serre ) } , releverTexteEtAnimation ) , [ ] ) ;
[IMP] home: list shown at once, computed columns loaded per event
The event list waited until every event's ranking was evaluated, about
10 ms per event with propositions, before showing anything. It now shows
as soon as the folder is read; each line's retained-plan columns and best
proposition load one event at a time, yielding to the browser between
events, behind a rotating SVG spinner, and show as each result arrives.
Work for a list no longer shown is dropped. The spinner is the one named
exception to the no-animation rule: still under reduced motion, cancelled
for captures, announced once. Checked: 2935 node, 616 browser, 62 long.
--- FR ---
[IMP] accueil : liste aussitôt montrée, colonnes calculées par événement
La liste des événements attendait l'évaluation du classement de chaque
événement, environ 10 ms par événement qui a des propositions, avant de
rien montrer. Elle paraît dès la lecture du dossier ; les colonnes du
retenu et la meilleure proposition de chaque ligne se chargent un
événement à la fois, le navigateur reprenant la main entre deux, derrière
une icône SVG qui tourne. Le travail d'une liste qui n'est plus montrée
est abandonné. L'icône est la seule exception nommée à l'interdit
d'animation. Vérifié : 2935 node, 616 navigateur, 62 longues.
Assisted-by: Claude Opus 5.5
2026-10-08 17:07:31 -04:00
// Un arbre qui ne la porte plus : l'exception est sans objet.
assert . deepEqual ( avecArbre ( { [ ailleurs ] : style ( ) } , releverTexteEtAnimation ) , [
` exception sans objet : ${ admise . fichier } ${ admise . selecteur } ` ,
] ) ;
} ) ;
[ADD] tests: guards for layers, clock, sources, colours, watch patterns
Guards scan the tree file by file and line by line, fail on an empty scan,
and are each shown red on a temporary tree. They cover the interface import
graph, the Electron bridge, one clock, refused dependencies, how tests are
written, hard-coded strings, translation keys, forbidden words, colours and
the conflict hue, markup, pruned selectors and watch patterns. Coverage
keeps the engine integration test on a small machine. Fixes: delays in
tests, the conflict hatch class; « la rend évitable » is a named exception
(§ 5.6). Checked: 2587 node, 465 browser tests, from the index alone.
--- FR ---
[ADD] épreuves : gardes des couches, horloge, sources, couleurs, motifs
Les gardes relèvent par fichier et par ligne, échouent sur un balayage vide
et sont chacune montrées rouges sur un arbre temporaire. Elles couvrent le
graphe d'imports de l'interface, le pont d'Electron, l'horloge unique, les
dépendances refusées, l'écriture des épreuves, les chaînes en dur, les clés
de traduction, les mots interdits, les couleurs et la teinte de conflit, le
balisage, les sélecteurs élagués et les motifs de surveillance. Corrigés :
délais d'épreuves, classe de la trame de conflit ; « la rend évitable » est
une exception nommée (§ 5.6). Vérifié : 2587 node, 465 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 13:21:21 -04:00
} ) ;
describe ( 'sources de l’ interface : le câblage (§ 12.9, § 13.3)' , ( ) => {
test ( 'aucun composant ne calcule, et seul evaluation.js importe les mesures' , ( ) => {
exigerVide ( releverCablage ( RACINE ) , 'câblage' ) ;
} ) ;
test ( 'la garde relève Math., .sort( et .reduce( dans un composant, et l’ import d’ une mesure hors d’ evaluation.js' , ( ) => {
const fichiers = {
'src/interface/plan/A.svelte' : composant ( {
script : ' let liste = $state([]);\n const tries = $derived(liste.toSorted());\n // Math.max, liste.sort()' ,
gabarit : '<p>{Math.round(liste.length)}</p>\n<p>{liste.reduce((s, x) => s + x, 0)}</p>\n<p>{liste.sort().join()}</p>\n<p>{liste.filter(Boolean).length}</p>' ,
} ) ,
'src/interface/b.js' : 'export const f = (l) => Math.max(...l.sort());\n' ,
'src/application/evaluation.js' : "import { mesurer, manques } from '../moteur/indicateurs.js';\nexport { mesurer };\n" ,
'src/application/generation.js' : [
"import { troisChiffres as chiffres } from '../moteur/indicateurs.js';" ,
"import * as indicateurs from '../moteur/indicateurs.js';" ,
"export { minimumAtteint } from '../moteur/indicateurs.js';" ,
'export const f = (x) => indicateurs.plafondsRealises(x) + indicateurs.autre(x);' ,
"import { evaluer } from './evaluation.js';" ,
'' ,
] . join ( '\n' ) ,
'src/application/generation.test.js' : "import { mesurer } from '../moteur/indicateurs.js';\n" ,
} ;
assert . deepEqual ( avecArbre ( fichiers , releverCablage ) , [
'src/application/generation.js:1 import de troisChiffres' ,
'src/application/generation.js:3 import de minimumAtteint' ,
'src/application/generation.js:4 import de plafondsRealises' ,
'src/interface/plan/A.svelte:3 .toSorted(' ,
'src/interface/plan/A.svelte:6 Math.round' ,
'src/interface/plan/A.svelte:7 .reduce(' ,
'src/interface/plan/A.svelte:8 .sort(' ,
] ) ;
} ) ;
test ( 'un arbre sans composant fait échouer la garde' , ( ) => {
assert . throws ( ( ) => avecArbre ( { 'src/application/a.js' : '' } , releverCablage ) , /câblage des composants : aucun fichier examiné/ ) ;
} ) ;
} ) ;
describe ( 'sources de l’ interface : les sélecteurs élagués (§ 13.3, § 14.2)' , ( ) => {
test ( 'aucun composant ne porte une règle que le compilateur élague' , ( ) => {
exigerVide ( releverSelecteursElagues ( RACINE ) , 'sélecteurs élagués' ) ;
} ) ;
test ( 'la garde relève une règle .conflit qui vise un élément sans classe dynamique, et admet class:conflit' , ( ) => {
const fichiers = {
'src/interface/plan/sous/Table.svelte' : composant ( {
gabarit : '<g class="table"><rect /></g>' ,
style : ' .table { stroke-width: 1; }\n .table.conflit rect { stroke-width: 3; }' ,
} ) ,
'src/interface/plan/Table2.svelte' : composant ( {
script : ' let { conflit } = $props();' ,
gabarit : '<g class="table" class:conflit><rect /></g>' ,
style : ' .table.conflit rect { stroke-width: 3; }' ,
} ) ,
} ;
assert . deepEqual ( avecArbre ( fichiers , releverSelecteursElagues ) , [
'src/interface/plan/sous/Table.svelte:4 sélecteur élagué « .table.conflit rect »' ,
] ) ;
} ) ;
test ( 'un arbre sans composant fait échouer la garde' , ( ) => {
assert . throws ( ( ) => avecArbre ( { 'src/interface/a.js' : '' } , releverSelecteursElagues ) , /aucun fichier examiné/ ) ;
} ) ;
} ) ;
[FIX] interface: ghost labels, keyboard, valid HTML, closed data-* list
The drag ghost names what releasing at the point changes, floor included.
Escape on a seat returns to its table, a refused Tab stays put, the legend
toggle exposes aria-expanded, and the header markup is valid HTML. Every
data-* attribute the interface sets belongs to one closed list that a guard
reads. The tree guards follow helpers a test imports, and the reporter says
the whole series stays watched under coverage on a small machine.
Checked: every new test red first; 3001 node, 635 browser and 63 long tests
green from the index alone.
--- FR ---
[FIX] interface : fantôme nommé, clavier, HTML valide, data-* fermés
Le fantôme du glisser nomme ce que relâcher au point modifie, sol compris.
Échap sur un siège remonte à sa table, un Tab refusé reste en place, la
bascule de la légende expose aria-expanded, et la tête est du HTML valide.
Chaque attribut data-* que l'interface pose appartient à une liste fermée
qu'une garde lit. Les gardes de l'arbre suivent les aides qu'une épreuve
importe, et le rapporteur dit la série entière surveillée sous couverture.
Vérifié : chaque épreuve rouge d'abord ; 3001 node, 635 navigateur et 63
longues vertes depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 04:01:57 -04:00
describe ( 'sources de l’ interface : les désignations stables (contrainte 16)' , ( ) => {
test ( 'chaque data-… que pose src/interface — gabarit ou script d’ un composant, code d’ un module — est dans DESIGNATIONS, et chaque entrée est posée quelque part' , ( ) => {
assert . ok ( DESIGNATIONS . size > 100 , ` ${ DESIGNATIONS . size } désignations ` ) ;
exigerVide ( releverDesignations ( RACINE ) , 'désignations' ) ;
} ) ;
test ( 'la garde relève un attribut inventé, par le gabarit, le script ou un module, un nom composé, une valeur hors d’ une liste fermée, une pose calculée sous elle, et une entrée que rien ne pose' , ( ) => {
const designations = new Map ( [
[ 'data-ecran' , { porteur : 'un écran' , valeurs : [ 'accueil' ] } ] ,
[ 'data-commande' , { porteur : 'une commande' , valeurs : null } ] ,
[ 'data-section' , { porteur : 'une section' , valeurs : [ 'plan' , 'tables' ] } ] ,
[ 'data-absente' , { porteur : 'rien' , valeurs : null } ] ,
[ 'data-theme' , { porteur : 'posé par le script seul' , valeurs : null } ] ,
] ) ;
const fichiers = {
// Des poses par le script d'un composant, et par un module voisin.
'src/interface/Theme.svelte' : composant ( {
script : [
" document.documentElement.setAttribute('data-theme', theme);" ,
" element.dataset.jeuDeDonnees = '';" ,
" element.setAttribute('data-section', cle);" ,
" element.setAttribute(`data-${nom}`, '');" ,
" const lu = element.getAttribute('data-lue');" ,
] . join ( '\n' ) ,
gabarit : '<p></p>' ,
} ) ,
'src/interface/pose.js' : "export const poser = (element) => element.setAttribute('data-module', 'oui');\n" ,
'src/interface/pose.test.js' : "element.setAttribute('data-epreuve', '');\n" ,
'src/interface/Ecran.svelte' : composant ( {
gabarit : [
'<section data-ecran="accueil" data-section="plan">' ,
' <button data-commande={nom} data-invente>x</button>' ,
' <p data-ecran="ailleurs"></p>' ,
' <div data-section={cle}></div>' ,
'</section>' ,
] . join ( '\n' ) ,
} ) ,
} ;
assert . deepEqual ( avecArbre ( fichiers , ( racine ) => releverDesignations ( racine , designations ) ) , [
'src/interface/Ecran.svelte:2 data-invente hors de la liste' ,
'src/interface/Ecran.svelte:3 data-ecran="ailleurs" hors des valeurs de la liste' ,
'src/interface/Ecran.svelte:4 data-section : valeur calculée sous une liste fermée' ,
'src/interface/Theme.svelte:3 data-jeu-de-donnees hors de la liste' ,
'src/interface/Theme.svelte:4 data-section : valeur calculée sous une liste fermée' ,
'src/interface/Theme.svelte:5 data-… : nom calculé' ,
'src/interface/pose.js:1 data-module hors de la liste' ,
'data-section="tables" : aucun composant ne la pose' ,
'data-absente : aucun composant ne le pose' ,
] ) ;
assert . throws ( ( ) => releverDesignations ( RACINE , new Map ( ) ) , /désignations : liste vide/ ) ;
} ) ;
test ( 'DESIGNATIONS est gelée : ni ajout, ni retrait, ni entrée modifiable' , ( ) => {
assert . throws ( ( ) => DESIGNATIONS . set ( 'data-x' , { } ) , TypeError ) ;
assert . throws ( ( ) => DESIGNATIONS . delete ( 'data-ecran' ) , TypeError ) ;
assert . throws ( ( ) => DESIGNATIONS . clear ( ) , TypeError ) ;
assert . ok ( Object . isFrozen ( DESIGNATIONS . get ( 'data-ecran' ) ) ) ;
assert . ok ( Object . isFrozen ( DESIGNATIONS . get ( 'data-ecran' ) . valeurs ) ) ;
} ) ;
} ) ;
2026-10-09 04:51:45 -04:00
describe ( 'sources de l’ interface : les touches nommées (§ 7.3, § 19.8)' , ( ) => {
test ( 'aucune touche nommée sous src/interface hors de raccourcis.js, sauf les fichiers en attente, tels quels' , ( ) => {
exigerVide ( releverTouches ( RACINE ) , 'touches nommées' ) ;
} ) ;
test ( 'la garde relève une touche nommée, et toute chaîne comparée à key ou cas d’ un switch sur elle, à toute profondeur, et pas leurs voisines' , ( ) => {
const fichiers = {
'src/interface/plan/sous/b.js' : [
'export function f(evenement, touche) {' ,
" if (evenement.key === 'Escape') return 1;" ,
" if (' ' === evenement.key || evenement['key'] !== 'a') return 2;" ,
" switch (evenement.key) { case 'q': return 3; default: }" ,
" const m = new Map([['ArrowLeft', 1], [`Maj+Tab`, 2]]);" ,
" // evenement.key === 'Enter' dans un commentaire" ,
" return [m, touche === 'F2', 'Entrée', 'Tableau', 'Enter ', ' ', evenement.code === 'KeyA', 'F25', touche === ' '];" ,
'}' ,
'' ,
] . join ( '\n' ) ,
'src/interface/A.svelte' : composant ( {
script : " const surTouche = (e) => { if (e.key === 'Enter') e.preventDefault(); };" ,
gabarit : "<input onkeydown={(e) => e.key === `Tab` && surTouche(e)} />\n<p>{' '}</p>" ,
} ) ,
'src/interface/raccourcis.js' : "export const R = ['Escape', 'Enter'];\nexport const lire = (e) => e.key === ' ';\n" ,
'src/interface/A.test.js' : "if (e.key === 'Escape') f();\n" ,
'src/application/c.js' : "export const k = (e) => e.key === 'Escape';\n" ,
} ;
assert . deepEqual ( avecArbre ( fichiers , ( racine ) => releverTouches ( racine , new Map ( ) ) ) , [
'src/interface/A.svelte:2 touche "Enter"' ,
'src/interface/A.svelte:4 touche "Tab"' ,
'src/interface/plan/sous/b.js:2 touche "Escape"' ,
'src/interface/plan/sous/b.js:3 touche " "' ,
'src/interface/plan/sous/b.js:3 touche "a"' ,
'src/interface/plan/sous/b.js:4 touche "q"' ,
'src/interface/plan/sous/b.js:5 touche "ArrowLeft"' ,
'src/interface/plan/sous/b.js:5 touche "Maj+Tab"' ,
'src/interface/plan/sous/b.js:7 touche "F2"' ,
] ) ;
} ) ;
test ( 'un fichier en attente n’ est admis qu’ avec son relevé exact ; un écart relève ses touches, une entrée que rien ne sert est sans objet' , ( ) => {
const fichiers = {
'src/interface/x.js' : "export const f = (e) => e.key === 'Escape' || e.key === 'Enter';\n" ,
'src/interface/y.js' : "export const g = (e) => e.key === 'Escape';\n" ,
'src/interface/z.js' : 'export const h = 1;\n' ,
} ;
const attente = new Map ( [
[ 'src/interface/x.js' , enAttente ( [ 'champ' ] , [ 'touche "Escape"' , 'touche "Enter"' ] ) ] ,
[ 'src/interface/y.js' , enAttente ( [ 'champ' ] , [ 'touche "Escape"' , 'touche "Tab"' ] ) ] ,
[ 'src/interface/z.js' , enAttente ( [ 'champ' ] , [ 'touche "Escape"' ] ) ] ,
[ 'src/interface/w.js' , enAttente ( [ 'champ' ] , [ 'touche "Escape"' ] ) ] ,
] ) ;
assert . deepEqual ( avecArbre ( fichiers , ( racine ) => releverTouches ( racine , attente ) ) , [
'src/interface/y.js:1 touche "Escape"' ,
'src/interface/w.js : en attente sans objet' ,
'src/interface/y.js : en attente, entrée à revoir' ,
'src/interface/z.js : en attente sans objet' ,
] ) ;
} ) ;
test ( 'chaque touche d’ un fichier en attente a son entrée dans RACCOURCIS sous un des contextes du fichier : le tableau engendré décrit ce qu’ il fait' , ( ) => {
assert . ok ( TOUCHES _EN _ATTENTE . size > 0 || RACCOURCIS . length > 0 ) ;
exigerVide ( touchesSansEntree ( RACCOURCIS , CONTEXTES _RACCOURCIS ) , 'touches en attente sans entrée' ) ;
} ) ;
test ( 'une touche en attente que la table ne décrit plus sous les contextes du fichier se relève, comme un contexte inconnu' , ( ) => {
const raccourcis = [
{ touche : 'Escape' , contexte : 'fenetre' , effet : 'fermer' } ,
{ touche : 'Enter' , contexte : 'champ' , effet : 'valider' } ,
{ touche : 'q' , contexte : 'geste' , effet : 'annuler' } ,
] ;
const attente = new Map ( [
[ 'src/interface/a.svelte' , enAttente ( [ 'fenetre' ] , [ 'touche "Escape"' , 'touche "Escape"' ] ) ] ,
[ 'src/interface/b.svelte' , enAttente ( [ 'geste' , 'champ' ] , [ 'touche "Enter"' , 'touche "Escape"' ] ) ] ,
[ 'src/interface/c.svelte' , enAttente ( [ 'fenetre' , 'tiroir' ] , [ 'touche "Escape"' ] ) ] ,
] ) ;
assert . deepEqual ( touchesSansEntree ( raccourcis , [ 'champ' , 'geste' , 'fenetre' ] , attente ) , [
'src/interface/b.svelte touche "Escape" hors de RACCOURCIS (geste, champ)' ,
'src/interface/c.svelte contexte "tiroir" inconnu' ,
] ) ;
// La table où Échap de la fenêtre devient « q » : le fichier qui ferme sur Échap se relève.
const deplacee = raccourcis . map ( ( entree ) => ( entree . contexte === 'fenetre' ? { ... entree , touche : 'q' } : entree ) ) ;
assert . deepEqual ( touchesSansEntree ( deplacee , [ 'champ' , 'geste' , 'fenetre' ] , new Map ( [ ... attente ] . slice ( 0 , 1 ) ) ) , [
'src/interface/a.svelte touche "Escape" hors de RACCOURCIS (fenetre)' ,
] ) ;
} ) ;
test ( 'un arbre sans source d’ interface, ou réduit à la table des raccourcis, fait échouer la garde' , ( ) => {
assert . throws ( ( ) => avecArbre ( { 'src/application/a.js' : '' } , releverTouches ) , /touches nommées : aucun fichier examiné/ ) ;
assert . throws (
( ) => avecArbre ( { 'src/interface/raccourcis.js' : "export const R = ['Escape'];\n" } , releverTouches ) ,
/touches nommées : aucun fichier examiné/ ,
) ;
} ) ;
} ) ;