[ADD] docs: photographed walkthrough publishes doc/captures
The screenshots must come from the tested walkthrough, not from a separate
script that can drift. The browser walkthrough runs on the simulated file
system with a driven clock and the direct executor, light theme, 1366 x
700 at scale 1, transitions off, and photographs each step once its
condition holds. make captures writes the images and the manifest; the
plain test run compares them instead. Eighteen captures are published, the
later ones named as postponed. The forged-names scan skips the manifest,
which records the demo names shown on screen.
Checked: red first; 3620 node, 681 browser and 101 long tests green from
the index alone.
--- FR ---
[ADD] docs : parcours photographié, publication de doc/captures
Les captures doivent venir du parcours éprouvé, non d'un script à part qui
dérive. Le parcours navigateur joue sur le système de fichiers simulé,
l'horloge pilotée et l'exécuteur direct, thème clair, 1366 × 700 au facteur
1, transitions coupées, et photographie chaque étape quand sa condition
tient. make captures écrit les images et le manifeste ; la série ordinaire
les compare au lieu de les écrire. Dix-huit captures sont publiées, les
suivantes nommées reportées. Le balayage des noms forgés saute le
manifeste, qui relève les noms de démonstration affichés.
Vérifié : rouge d'abord ; 3620 node, 681 navigateur et 101 longues vertes
depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 15:33:59 -04:00
// © 2026 TechnoLibre (http://www.technolibre.ca)
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
// Les captures d'écran du parcours, côté page (§ 19.2 à § 19.5, § 19.7,
// § 19.11, § 14.5). photographier(m, fichier) contrôle l'écran du montage
// m, relève ce que le manifeste en garde, puis, selon le mode, compare
// l'entrée à celle du manifeste ou dépose image et entrée pour la
// publication. Les fichiers, le manifeste et la capture d'élément sont de
// node : les commandes de test/navigateur/commandes_documentation.js.
//
// Le mode vient de provide (vitest.config.js, GTT_CAPTURES) : 'verifier'
// par défaut, 'ecrire' sous make captures ; une option le remplace pour une
// épreuve du mécanisme, sur un dossier d'épreuve.
//
// Les relevés de contraste des textes et des marques vivent ici, une seule
// fois : l'épreuve transversale les lit sur chaque écran, photographier les
// range en styles du manifeste, par jeton et par rôle, dans les deux thèmes.
// Un relevé complet amène dans la fenêtre l'élément qu'il mesure ; le
// relevé de la fenêtre ne fait rien défiler et ne lit que ce que l'image
// montre. photographier rend après chaque thème, à chaque élément qui
// défile, sa position d'avant.
import { inject } from 'vitest' ;
import { commands } from 'vitest/browser' ;
import { TABLES _APPLICATION } from '../../src/application/libelles.js' ;
import { MARQUE _TEMOIN , creerTraduction } from '../../src/application/traduction.js' ;
import { constante } from '../../src/geometrie/constantes.js' ;
import JETONS _CSS from '../../src/interface/jetons.css?raw' ;
import { TABLES _INTERFACE } from '../../src/interface/libelles.js' ;
import { MARQUES , TEXTES } from '../../src/interface/marques.js' ;
import { serialiserCharge } from '../../src/stockage/canonique.js' ;
import { analyser } from '../../src/stockage/document.js' ;
import { SUFFIXES } from '../../src/stockage/noms.js' ;
import { VERSION } from '../../src/version.genere.js' ;
import { MODE _AFFICHAGE } from '../chromium.js' ;
import { CHAINE _TEMOIN , longueurTemoin } from '../temoin.js' ;
import { feuillePosee } from './feuilles.js' ;
import {
attenuations ,
composantes ,
contraste ,
couleurTexte ,
couleursPeintes ,
designation ,
fondPeintSous ,
porteursDeTexte ,
sousTheme ,
visible ,
} from './rendu.js' ;
/** Le cadre d'affichage des captures, en px CSS (§ 19.4). */
export const CADRE = Object . freeze ( { largeur : 1366 , hauteur : 700 } ) ;
// Seuils du § 14.5 : le texte, les marques porteuses d'information.
const SEUIL _TEXTE = 4.5 ;
const SEUIL _MARQUE = 3 ;
// --- Les aides des relevés ---------------------------------------------------------
// Une couleur en texte, pour un message ; l'égalité de deux couleurs ; un
// texte aplati, réduit à soixante caractères ; un élément rendu.
export const rgb = ( c ) => ` rgb( ${ c . slice ( 0 , 3 ) . join ( ', ' ) } ${ c [ 3 ] === 1 ? '' : ` , ${ c [ 3 ] } ` } ) ` ;
export const egales = ( a , b ) => a . join ( ) === b . join ( ) ;
export const extrait = ( texte ) => {
const plat = texte . replace ( /\s+/g , ' ' ) . trim ( ) ;
return plat . length > 60 ? ` ${ plat . slice ( 0 , 57 ) } … ` : plat ;
} ;
export const rendu = ( element ) => element . checkVisibility ( { visibilityProperty : true , opacityProperty : true } ) ;
// Les conteneurs désignés qui situent un élément dans son écran.
const REPERES = '[data-liste], [data-siege], [data-table], [data-pastille], [data-panneau], [data-menu], [data-section], [data-ecran], [data-bandeau]' ;
// Le nom d'un élément, suivi de celui du plus proche conteneur désigné qui
// le porte : un tspan se situe par sa liste, une pastille par sa table.
export function situer ( element ) {
const repere = element . parentElement ? . closest ( REPERES ) ? ? null ;
return repere === null ? designation ( element ) : ` ${ designation ( element ) } dans ${ designation ( repere ) } ` ;
}
// --- Les contrastes des textes et les atténuations --------------------------------
// Vrai quand la boîte de l'élément coupe la fenêtre, bords exclus.
function coupeLaFenetre ( element ) {
const b = element . getBoundingClientRect ( ) ;
return b . right > 0 && b . bottom > 0 && b . left < innerWidth && b . top < innerHeight ;
}
2026-10-10 13:37:50 -04:00
/ * * V r a i q u a n d l a b o î t e d e l ' é l é m e n t t i e n t e n t i è r e d a n s l a f e n ê t r e e t d a n s
* chaque ancêtre qui rogne ce qui le déborde — overflow autre que visible ,
* ou un < svg > — , à un demi - pixel près : l ' image le montre entier . * /
export function entiereDansLaFenetre ( element ) {
[ADD] docs: photographed walkthrough publishes doc/captures
The screenshots must come from the tested walkthrough, not from a separate
script that can drift. The browser walkthrough runs on the simulated file
system with a driven clock and the direct executor, light theme, 1366 x
700 at scale 1, transitions off, and photographs each step once its
condition holds. make captures writes the images and the manifest; the
plain test run compares them instead. Eighteen captures are published, the
later ones named as postponed. The forged-names scan skips the manifest,
which records the demo names shown on screen.
Checked: red first; 3620 node, 681 browser and 101 long tests green from
the index alone.
--- FR ---
[ADD] docs : parcours photographié, publication de doc/captures
Les captures doivent venir du parcours éprouvé, non d'un script à part qui
dérive. Le parcours navigateur joue sur le système de fichiers simulé,
l'horloge pilotée et l'exécuteur direct, thème clair, 1366 × 700 au facteur
1, transitions coupées, et photographie chaque étape quand sa condition
tient. make captures écrit les images et le manifeste ; la série ordinaire
les compare au lieu de les écrire. Dix-huit captures sont publiées, les
suivantes nommées reportées. Le balayage des noms forgés saute le
manifeste, qui relève les noms de démonstration affichés.
Vérifié : rouge d'abord ; 3620 node, 681 navigateur et 101 longues vertes
depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 15:33:59 -04:00
const b = element . getBoundingClientRect ( ) ;
const dedans = ( r ) => b . left >= r . left - 0.5 && b . top >= r . top - 0.5 && b . right <= r . right + 0.5 && b . bottom <= r . bottom + 0.5 ;
if ( ! dedans ( { left : 0 , top : 0 , right : innerWidth , bottom : innerHeight } ) ) return false ;
for ( let ancetre = element . parentElement ; ancetre !== null ; ancetre = ancetre . parentElement ) {
const rogne = ancetre . localName === 'svg' || getComputedStyle ( ancetre ) . overflow !== 'visible' ;
if ( rogne && ! dedans ( ancetre . getBoundingClientRect ( ) ) ) return false ;
}
return true ;
}
// Amène un élément dans la fenêtre, s'il en sort.
function amener ( element ) {
const b = element . getBoundingClientRect ( ) ;
if ( b . left < 0 || b . top < 0 || b . right > innerWidth || b . bottom > innerHeight ) {
element . scrollIntoView ( { block : 'center' , inline : 'nearest' } ) ;
}
}
// Le <svg> le plus extérieur qui contient un élément SVG.
function svgExterieur ( element ) {
let svg = element . ownerSVGElement ? ? element ;
while ( svg . ownerSVGElement !== null ) svg = svg . ownerSVGElement ;
return svg ;
}
// Feuille qui rend chaque élément atteignable le temps d'une lecture, comme
// celle de rendu.js : elementsFromPoint suit l'atteinte du pointeur.
const ATTEIGNABLE = '*, *::before, *::after { pointer-events: auto !important; }' ;
// Le point (x, y) de la fenêtre montre-t-il le dessin qui porte l'élément ?
// Dans la fenêtre, et le <svg> extérieur, ou l'un de ses descendants, parmi
// les éléments présents au point : un ancêtre qui défile rogne ce qui
// déborde de sa boîte, et le point rogné n'atteint plus le dessin.
function montreLeDessin ( element , x , y ) {
if ( ! ( x >= 0 && y >= 0 && x < innerWidth && y < innerHeight ) ) return false ;
const svg = svgExterieur ( element ) ;
const feuille = document . createElement ( 'style' ) ;
feuille . textContent = ATTEIGNABLE ;
document . head . append ( feuille ) ;
try {
return document . elementsFromPoint ( x , y ) . some ( ( n ) => svg . contains ( n ) ) ;
} finally {
feuille . remove ( ) ;
}
}
// Le centre de la boîte d'un élément SVG montre-t-il son dessin ?
function centreDansLeDessin ( element ) {
const b = element . getBoundingClientRect ( ) ;
return montreLeDessin ( element , b . left + b . width / 2 , b . top + b . height / 2 ) ;
}
// Le plus faible contraste d'une couleur sur une liste de fonds.
const pire = ( couleur , fonds ) =>
fonds . map ( ( fond ) => ( { fond , ratio : contraste ( couleur , fond ) } ) ) . reduce ( ( a , b ) => ( b . ratio < a . ratio ? b : a ) ) ;
// Les champs de saisie dont la valeur ou le texte indicatif se peint.
export const CHAMPS = 'input:not([type="radio"]):not([type="checkbox"]):not([type="file"]):not([type="hidden"]), select, textarea' ;
/ * *
* Contrastes des textes rendus sous racine ( § 14.5 ) : chaque porteur de
* texte et chaque champ de saisie , son texte indicatif compris , sur le fond
* peint dessous ; un texte SVG par son fill sur la forme dessous . Un porteur
[ADD] print: preview screen, printable sheet, paper question
Printed pages must show exactly what will come out of the printer. An
Imprimer button in the event header opens a preview screen where each page
is a sheet at paper size, light whatever the theme, with its footer and,
under the strict draft rule, BROUILLON in outline; a proposal shown on a
retained plan prints as a draft. The paper is asked once per machine and
can be changed there. The table sheets offer one sheet per round. A failed
print shows IMPRESSION_ECHEC. The captures are republished with the button.
Checked: red first; 3651 node, 707 browser and 101 long tests green from
the index alone.
--- FR ---
[ADD] impression : aperçu, feuille imprimable, question du papier
Une page imprimée doit montrer exactement ce que l'imprimante rendra. Un
bouton Imprimer de la tête de l'événement ouvre un aperçu où chaque page
est une feuille au format du papier, claire quel que soit le thème, avec
son pied et, sous la règle stricte, BROUILLON au trait ; une proposition
montrée sur un plan retenu s'imprime en brouillon. Le papier se demande une
fois par machine et s'y change. Les feuilles par table offrent une feuille
par tour. Une impression échouée affiche IMPRESSION_ECHEC. Les captures
sont republiées avec le bouton.
Vérifié : rouge d'abord ; 3651 node, 707 navigateur et 101 longues vertes
depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 17:09:29 -04:00
* que l 'œil ne voit pas — couvert, rogné, de boîte vide — n' est pas mesuré ,
* ni un texte SVG du registre des marques — le filigrane BROUILLON , un texte
* au trait — , que releverMarques mesure à 3 : 1. Les textes du document
* imprimé de l 'aperçu ([data-impression]) se mesurent, mais n' entrent pas
* dans couleurs : le papier est clair dans les deux thèmes ( § 11.7 ) , et
* leurs couleurs ne diffèrent pas d 'un thème à l' autre .
[ADD] docs: photographed walkthrough publishes doc/captures
The screenshots must come from the tested walkthrough, not from a separate
script that can drift. The browser walkthrough runs on the simulated file
system with a driven clock and the direct executor, light theme, 1366 x
700 at scale 1, transitions off, and photographs each step once its
condition holds. make captures writes the images and the manifest; the
plain test run compares them instead. Eighteen captures are published, the
later ones named as postponed. The forged-names scan skips the manifest,
which records the demo names shown on screen.
Checked: red first; 3620 node, 681 browser and 101 long tests green from
the index alone.
--- FR ---
[ADD] docs : parcours photographié, publication de doc/captures
Les captures doivent venir du parcours éprouvé, non d'un script à part qui
dérive. Le parcours navigateur joue sur le système de fichiers simulé,
l'horloge pilotée et l'exécuteur direct, thème clair, 1366 × 700 au facteur
1, transitions coupées, et photographie chaque étape quand sa condition
tient. make captures écrit les images et le manifeste ; la série ordinaire
les compare au lieu de les écrire. Dix-huit captures sont publiées, les
suivantes nommées reportées. Le balayage des noms forgés saute le
manifeste, qui relève les noms de démonstration affichés.
Vérifié : rouge d'abord ; 3620 node, 681 navigateur et 101 longues vertes
depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 15:33:59 -04:00
* → { fautes , attenuations , couleurs : Map ( clé → couleurs ) , mesures , ratios } ,
* ratios : { couleur , fond , ratio } de chaque texte mesuré , le fond le plus
* défavorable , dans l ' ordre des mesures . fenetre vrai : seuls les éléments
* dont la boîte coupe la fenêtre se mesurent , sans rien amener — ce que
* montre une capture d ' écran .
* /
export function releverTextes ( racine , { fenetre = false } = { } ) {
const fautes = [ ] ;
const fautesAttenuation = [ ] ;
const couleurs = new Map ( ) ;
const ratios = [ ] ;
let mesures = 0 ;
[ADD] print: preview screen, printable sheet, paper question
Printed pages must show exactly what will come out of the printer. An
Imprimer button in the event header opens a preview screen where each page
is a sheet at paper size, light whatever the theme, with its footer and,
under the strict draft rule, BROUILLON in outline; a proposal shown on a
retained plan prints as a draft. The paper is asked once per machine and
can be changed there. The table sheets offer one sheet per round. A failed
print shows IMPRESSION_ECHEC. The captures are republished with the button.
Checked: red first; 3651 node, 707 browser and 101 long tests green from
the index alone.
--- FR ---
[ADD] impression : aperçu, feuille imprimable, question du papier
Une page imprimée doit montrer exactement ce que l'imprimante rendra. Un
bouton Imprimer de la tête de l'événement ouvre un aperçu où chaque page
est une feuille au format du papier, claire quel que soit le thème, avec
son pied et, sous la règle stricte, BROUILLON au trait ; une proposition
montrée sur un plan retenu s'imprime en brouillon. Le papier se demande une
fois par machine et s'y change. Les feuilles par table offrent une feuille
par tour. Une impression échouée affiche IMPRESSION_ECHEC. Les captures
sont republiées avec le bouton.
Vérifié : rouge d'abord ; 3651 node, 707 navigateur et 101 longues vertes
depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 17:09:29 -04:00
// cle null : un texte qui ne se compare pas d'un thème à l'autre.
[ADD] docs: photographed walkthrough publishes doc/captures
The screenshots must come from the tested walkthrough, not from a separate
script that can drift. The browser walkthrough runs on the simulated file
system with a driven clock and the direct executor, light theme, 1366 x
700 at scale 1, transitions off, and photographs each step once its
condition holds. make captures writes the images and the manifest; the
plain test run compares them instead. Eighteen captures are published, the
later ones named as postponed. The forged-names scan skips the manifest,
which records the demo names shown on screen.
Checked: red first; 3620 node, 681 browser and 101 long tests green from
the index alone.
--- FR ---
[ADD] docs : parcours photographié, publication de doc/captures
Les captures doivent venir du parcours éprouvé, non d'un script à part qui
dérive. Le parcours navigateur joue sur le système de fichiers simulé,
l'horloge pilotée et l'exécuteur direct, thème clair, 1366 × 700 au facteur
1, transitions coupées, et photographie chaque étape quand sa condition
tient. make captures écrit les images et le manifeste ; la série ordinaire
les compare au lieu de les écrire. Dix-huit captures sont publiées, les
suivantes nommées reportées. Le balayage des noms forgés saute le
manifeste, qui relève les noms de démonstration affichés.
Vérifié : rouge d'abord ; 3620 node, 681 navigateur et 101 longues vertes
depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 15:33:59 -04:00
const mesurer = ( cle , nom , couleurDe , fondDe , cible ) => {
let couleur ;
let fonds ;
try {
couleur = couleurDe ( ) ;
fonds = fondDe ( ) ;
} catch ( erreur ) {
fautes . push ( ` ${ nom } : ${ erreur . message } ` ) ;
return ;
}
mesures += 1 ;
if ( fonds . length === 0 ) {
fautes . push ( ` ${ nom } : aucun fond opaque dessous ` ) ;
return ;
}
try {
const { fond , ratio } = pire ( couleur , fonds ) ;
ratios . push ( { couleur , fond , ratio } ) ;
if ( ratio < SEUIL _TEXTE ) fautes . push ( ` ${ nom } : ${ ratio . toFixed ( 2 ) } :1, ${ rgb ( couleur ) } sur ${ rgb ( fond ) } ` ) ;
} catch ( erreur ) {
fautes . push ( ` ${ nom } : ${ erreur . message } ` ) ;
}
[ADD] print: preview screen, printable sheet, paper question
Printed pages must show exactly what will come out of the printer. An
Imprimer button in the event header opens a preview screen where each page
is a sheet at paper size, light whatever the theme, with its footer and,
under the strict draft rule, BROUILLON in outline; a proposal shown on a
retained plan prints as a draft. The paper is asked once per machine and
can be changed there. The table sheets offer one sheet per round. A failed
print shows IMPRESSION_ECHEC. The captures are republished with the button.
Checked: red first; 3651 node, 707 browser and 101 long tests green from
the index alone.
--- FR ---
[ADD] impression : aperçu, feuille imprimable, question du papier
Une page imprimée doit montrer exactement ce que l'imprimante rendra. Un
bouton Imprimer de la tête de l'événement ouvre un aperçu où chaque page
est une feuille au format du papier, claire quel que soit le thème, avec
son pied et, sous la règle stricte, BROUILLON au trait ; une proposition
montrée sur un plan retenu s'imprime en brouillon. Le papier se demande une
fois par machine et s'y change. Les feuilles par table offrent une feuille
par tour. Une impression échouée affiche IMPRESSION_ECHEC. Les captures
sont republiées avec le bouton.
Vérifié : rouge d'abord ; 3651 node, 707 navigateur et 101 longues vertes
depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 17:09:29 -04:00
if ( cle !== null ) couleurs . set ( cle , ` ${ rgb ( couleur ) } sur ${ fonds . map ( rgb ) . join ( ' + ' ) } ` ) ;
[ADD] docs: photographed walkthrough publishes doc/captures
The screenshots must come from the tested walkthrough, not from a separate
script that can drift. The browser walkthrough runs on the simulated file
system with a driven clock and the direct executor, light theme, 1366 x
700 at scale 1, transitions off, and photographs each step once its
condition holds. make captures writes the images and the manifest; the
plain test run compares them instead. Eighteen captures are published, the
later ones named as postponed. The forged-names scan skips the manifest,
which records the demo names shown on screen.
Checked: red first; 3620 node, 681 browser and 101 long tests green from
the index alone.
--- FR ---
[ADD] docs : parcours photographié, publication de doc/captures
Les captures doivent venir du parcours éprouvé, non d'un script à part qui
dérive. Le parcours navigateur joue sur le système de fichiers simulé,
l'horloge pilotée et l'exécuteur direct, thème clair, 1366 × 700 au facteur
1, transitions coupées, et photographie chaque étape quand sa condition
tient. make captures écrit les images et le manifeste ; la série ordinaire
les compare au lieu de les écrire. Dix-huit captures sont publiées, les
suivantes nommées reportées. Le balayage des noms forgés saute le
manifeste, qui relève les noms de démonstration affichés.
Vérifié : rouge d'abord ; 3620 node, 681 navigateur et 101 longues vertes
depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 15:33:59 -04:00
for ( const a of attenuations ( cible ) ) fautesAttenuation . push ( ` ${ nom } : ${ designation ( a ) } ${ a . propriete } : ${ a . valeur } ` ) ;
} ;
for ( const [ rang , porteur ] of porteursDeTexte ( racine ) . entries ( ) ) {
const { element } = porteur ;
if ( fenetre && ! coupeLaFenetre ( element ) ) continue ;
if ( ! rendu ( element ) ) continue ;
[ADD] print: preview screen, printable sheet, paper question
Printed pages must show exactly what will come out of the printer. An
Imprimer button in the event header opens a preview screen where each page
is a sheet at paper size, light whatever the theme, with its footer and,
under the strict draft rule, BROUILLON in outline; a proposal shown on a
retained plan prints as a draft. The paper is asked once per machine and
can be changed there. The table sheets offer one sheet per round. A failed
print shows IMPRESSION_ECHEC. The captures are republished with the button.
Checked: red first; 3651 node, 707 browser and 101 long tests green from
the index alone.
--- FR ---
[ADD] impression : aperçu, feuille imprimable, question du papier
Une page imprimée doit montrer exactement ce que l'imprimante rendra. Un
bouton Imprimer de la tête de l'événement ouvre un aperçu où chaque page
est une feuille au format du papier, claire quel que soit le thème, avec
son pied et, sous la règle stricte, BROUILLON au trait ; une proposition
montrée sur un plan retenu s'imprime en brouillon. Le papier se demande une
fois par machine et s'y change. Les feuilles par table offrent une feuille
par tour. Une impression échouée affiche IMPRESSION_ECHEC. Les captures
sont republiées avec le bouton.
Vérifié : rouge d'abord ; 3651 node, 707 navigateur et 101 longues vertes
depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 17:09:29 -04:00
if ( element instanceof SVGTextContentElement && element . hasAttribute ( 'data-marque' ) ) continue ;
[ADD] docs: photographed walkthrough publishes doc/captures
The screenshots must come from the tested walkthrough, not from a separate
script that can drift. The browser walkthrough runs on the simulated file
system with a driven clock and the direct executor, light theme, 1366 x
700 at scale 1, transitions off, and photographs each step once its
condition holds. make captures writes the images and the manifest; the
plain test run compares them instead. Eighteen captures are published, the
later ones named as postponed. The forged-names scan skips the manifest,
which records the demo names shown on screen.
Checked: red first; 3620 node, 681 browser and 101 long tests green from
the index alone.
--- FR ---
[ADD] docs : parcours photographié, publication de doc/captures
Les captures doivent venir du parcours éprouvé, non d'un script à part qui
dérive. Le parcours navigateur joue sur le système de fichiers simulé,
l'horloge pilotée et l'exécuteur direct, thème clair, 1366 × 700 au facteur
1, transitions coupées, et photographie chaque étape quand sa condition
tient. make captures écrit les images et le manifeste ; la série ordinaire
les compare au lieu de les écrire. Dix-huit captures sont publiées, les
suivantes nommées reportées. Le balayage des noms forgés saute le
manifeste, qui relève les noms de démonstration affichés.
Vérifié : rouge d'abord ; 3620 node, 681 navigateur et 101 longues vertes
depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 15:33:59 -04:00
const b = element . getBoundingClientRect ( ) ;
if ( b . width === 0 || b . height === 0 ) continue ;
if ( ! fenetre ) amener ( element ) ;
const svg = element instanceof SVGElement ;
if ( ! svg && visible ( porteur ) !== true ) continue ;
// Un texte SVG se lit par la géométrie du dessin, non par l'atteinte
// d'un point : son centre doit tomber dans le <svg> extérieur, qui rogne
// ce qui le déborde, et dans la fenêtre.
if ( svg && ! centreDansLeDessin ( element ) ) continue ;
const texte = porteur . pseudo === null ? [ ... element . childNodes ] . filter ( ( n ) => n . nodeType === Node . TEXT _NODE ) . map ( ( n ) => n . textContent ) . join ( '' ) : '' ;
const nom = ` ${ situer ( element ) } ${ porteur . pseudo ? ? '' } « ${ extrait ( texte ) } » ` ;
[ADD] print: preview screen, printable sheet, paper question
Printed pages must show exactly what will come out of the printer. An
Imprimer button in the event header opens a preview screen where each page
is a sheet at paper size, light whatever the theme, with its footer and,
under the strict draft rule, BROUILLON in outline; a proposal shown on a
retained plan prints as a draft. The paper is asked once per machine and
can be changed there. The table sheets offer one sheet per round. A failed
print shows IMPRESSION_ECHEC. The captures are republished with the button.
Checked: red first; 3651 node, 707 browser and 101 long tests green from
the index alone.
--- FR ---
[ADD] impression : aperçu, feuille imprimable, question du papier
Une page imprimée doit montrer exactement ce que l'imprimante rendra. Un
bouton Imprimer de la tête de l'événement ouvre un aperçu où chaque page
est une feuille au format du papier, claire quel que soit le thème, avec
son pied et, sous la règle stricte, BROUILLON au trait ; une proposition
montrée sur un plan retenu s'imprime en brouillon. Le papier se demande une
fois par machine et s'y change. Les feuilles par table offrent une feuille
par tour. Une impression échouée affiche IMPRESSION_ECHEC. Les captures
sont republiées avec le bouton.
Vérifié : rouge d'abord ; 3651 node, 707 navigateur et 101 longues vertes
depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 17:09:29 -04:00
const cle = element . closest ( '[data-impression]' ) === null ? ` ${ rang } : ${ designation ( porteur ) } ` : null ;
mesurer ( cle , nom , ( ) => couleurTexte ( porteur ) , ( ) => fondPeintSous ( porteur ) , porteur ) ;
[ADD] docs: photographed walkthrough publishes doc/captures
The screenshots must come from the tested walkthrough, not from a separate
script that can drift. The browser walkthrough runs on the simulated file
system with a driven clock and the direct executor, light theme, 1366 x
700 at scale 1, transitions off, and photographs each step once its
condition holds. make captures writes the images and the manifest; the
plain test run compares them instead. Eighteen captures are published, the
later ones named as postponed. The forged-names scan skips the manifest,
which records the demo names shown on screen.
Checked: red first; 3620 node, 681 browser and 101 long tests green from
the index alone.
--- FR ---
[ADD] docs : parcours photographié, publication de doc/captures
Les captures doivent venir du parcours éprouvé, non d'un script à part qui
dérive. Le parcours navigateur joue sur le système de fichiers simulé,
l'horloge pilotée et l'exécuteur direct, thème clair, 1366 × 700 au facteur
1, transitions coupées, et photographie chaque étape quand sa condition
tient. make captures écrit les images et le manifeste ; la série ordinaire
les compare au lieu de les écrire. Dix-huit captures sont publiées, les
suivantes nommées reportées. Le balayage des noms forgés saute le
manifeste, qui relève les noms de démonstration affichés.
Vérifié : rouge d'abord ; 3620 node, 681 navigateur et 101 longues vertes
depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 15:33:59 -04:00
}
for ( const [ rang , champ ] of [ ... racine . querySelectorAll ( CHAMPS ) ] . entries ( ) ) {
if ( fenetre && ! coupeLaFenetre ( champ ) ) continue ;
if ( ! rendu ( champ ) ) continue ;
if ( ! fenetre ) amener ( champ ) ;
if ( visible ( champ ) !== true ) continue ;
const nom = designation ( champ ) ;
mesurer ( ` champ ${ rang } : ${ nom } ` , ` ${ nom } (valeur) ` , ( ) => composantes ( getComputedStyle ( champ ) . color ) , ( ) => fondPeintSous ( champ ) , champ ) ;
if ( ( champ . getAttribute ( 'placeholder' ) ? ? '' ) !== '' ) {
mesurer ( ` indicatif ${ rang } : ${ nom } ` , ` ${ nom } ::placeholder ` , ( ) => composantes ( getComputedStyle ( champ , '::placeholder' ) . color ) , ( ) => fondPeintSous ( champ ) , champ ) ;
}
}
return { fautes , attenuations : fautesAttenuation , couleurs , mesures , ratios } ;
}
// --- Les marques -------------------------------------------------------------------
[ADD] print: seating plan cut into pages, with cartouche and scale
The printed plan draws every table in seat mode with its name lists,
whatever the zoom, and drops screen gestures: no selection, focus,
grab handle or highlight reaches paper. Each page carries a cartouche
(page p of n, column, row) and references to its neighbours; the cut
uses the band measured under the footer. Print marks resolve their
colour tokens from the printed document, so a dark screen theme
leaves the paper light.
Checked: node 3721, browser 720, node-long 101 from the index alone.
--- FR ---
[ADD] impression : plan découpé en pages, avec cartouche et échelle
Le plan imprimé dessine chaque table en régime sièges, avec ses listes
de noms, quel que soit le zoom, et laisse les gestes à l'écran :
sélection, focus, poignée et mise en évidence n'atteignent pas le
papier. Chaque page porte un cartouche (page p sur n, colonne, rangée)
et ses renvois aux voisines ; la découpe prend la bande mesurée sous le
pied. Les marques lisent leurs jetons dans le document imprimé : un
thème sombre à l'écran laisse le papier clair.
Vérifié : node 3721, navigateur 720, node-long 101 depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-10 11:03:12 -04:00
/ * * L a c o u l e u r r é s o l u e d ' u n j e t o n d a n s l e t h è m e c o u r a n t , o u s o u s h o t e : u n
* élément qui redéclare les jetons — le document imprimé de l ' aperçu ,
* [ data - impression ] , porte ceux du clair dans les deux thèmes ( § 11.7 ) . * /
export function couleurDeJeton ( jeton , hote = document . body ) {
[ADD] docs: photographed walkthrough publishes doc/captures
The screenshots must come from the tested walkthrough, not from a separate
script that can drift. The browser walkthrough runs on the simulated file
system with a driven clock and the direct executor, light theme, 1366 x
700 at scale 1, transitions off, and photographs each step once its
condition holds. make captures writes the images and the manifest; the
plain test run compares them instead. Eighteen captures are published, the
later ones named as postponed. The forged-names scan skips the manifest,
which records the demo names shown on screen.
Checked: red first; 3620 node, 681 browser and 101 long tests green from
the index alone.
--- FR ---
[ADD] docs : parcours photographié, publication de doc/captures
Les captures doivent venir du parcours éprouvé, non d'un script à part qui
dérive. Le parcours navigateur joue sur le système de fichiers simulé,
l'horloge pilotée et l'exécuteur direct, thème clair, 1366 × 700 au facteur
1, transitions coupées, et photographie chaque étape quand sa condition
tient. make captures écrit les images et le manifeste ; la série ordinaire
les compare au lieu de les écrire. Dix-huit captures sont publiées, les
suivantes nommées reportées. Le balayage des noms forgés saute le
manifeste, qui relève les noms de démonstration affichés.
Vérifié : rouge d'abord ; 3620 node, 681 navigateur et 101 longues vertes
depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 15:33:59 -04:00
const sonde = document . createElement ( 'span' ) ;
sonde . style . color = ` var( ${ jeton } ) ` ;
[ADD] print: seating plan cut into pages, with cartouche and scale
The printed plan draws every table in seat mode with its name lists,
whatever the zoom, and drops screen gestures: no selection, focus,
grab handle or highlight reaches paper. Each page carries a cartouche
(page p of n, column, row) and references to its neighbours; the cut
uses the band measured under the footer. Print marks resolve their
colour tokens from the printed document, so a dark screen theme
leaves the paper light.
Checked: node 3721, browser 720, node-long 101 from the index alone.
--- FR ---
[ADD] impression : plan découpé en pages, avec cartouche et échelle
Le plan imprimé dessine chaque table en régime sièges, avec ses listes
de noms, quel que soit le zoom, et laisse les gestes à l'écran :
sélection, focus, poignée et mise en évidence n'atteignent pas le
papier. Chaque page porte un cartouche (page p sur n, colonne, rangée)
et ses renvois aux voisines ; la découpe prend la bande mesurée sous le
pied. Les marques lisent leurs jetons dans le document imprimé : un
thème sombre à l'écran laisse le papier clair.
Vérifié : node 3721, navigateur 720, node-long 101 depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-10 11:03:12 -04:00
hote . append ( sonde ) ;
[ADD] docs: photographed walkthrough publishes doc/captures
The screenshots must come from the tested walkthrough, not from a separate
script that can drift. The browser walkthrough runs on the simulated file
system with a driven clock and the direct executor, light theme, 1366 x
700 at scale 1, transitions off, and photographs each step once its
condition holds. make captures writes the images and the manifest; the
plain test run compares them instead. Eighteen captures are published, the
later ones named as postponed. The forged-names scan skips the manifest,
which records the demo names shown on screen.
Checked: red first; 3620 node, 681 browser and 101 long tests green from
the index alone.
--- FR ---
[ADD] docs : parcours photographié, publication de doc/captures
Les captures doivent venir du parcours éprouvé, non d'un script à part qui
dérive. Le parcours navigateur joue sur le système de fichiers simulé,
l'horloge pilotée et l'exécuteur direct, thème clair, 1366 × 700 au facteur
1, transitions coupées, et photographie chaque étape quand sa condition
tient. make captures écrit les images et le manifeste ; la série ordinaire
les compare au lieu de les écrire. Dix-huit captures sont publiées, les
suivantes nommées reportées. Le balayage des noms forgés saute le
manifeste, qui relève les noms de démonstration affichés.
Vérifié : rouge d'abord ; 3620 node, 681 navigateur et 101 longues vertes
depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 15:33:59 -04:00
try {
return composantes ( getComputedStyle ( sonde ) . color ) ;
} finally {
sonde . remove ( ) ;
}
}
// Des points, dans le repère propre d'une forme, où elle peint quoi
// ('fill' ou 'stroke') et que la fenêtre montre, POINTS_MAX au plus : le
// centre de sa boîte, les milieux de ses bords, puis une grille de neuf sur
// neuf, dans cet ordre ; aucun pour une forme rognée en entier.
const POINTS _MAX = 8 ;
function pointsPeints ( forme , quoi ) {
const b = forme . getBBox ( ) ;
const candidats = [
[ b . x + b . width / 2 , b . y + b . height / 2 ] ,
[ b . x + b . width / 2 , b . y ] ,
[ b . x + b . width , b . y + b . height / 2 ] ,
[ b . x + b . width / 2 , b . y + b . height ] ,
[ b . x , b . y + b . height / 2 ] ,
] ;
for ( let i = 0 ; i <= 8 ; i += 1 ) for ( let j = 0 ; j <= 8 ; j += 1 ) candidats . push ( [ b . x + ( b . width * i ) / 8 , b . y + ( b . height * j ) / 8 ] ) ;
const versEcran = forme . getScreenCTM ( ) ;
const points = [ ] ;
for ( const [ x , y ] of candidats ) {
const point = new DOMPoint ( x , y ) ;
if ( ! ( quoi === 'stroke' ? forme . isPointInStroke ( point ) : forme . isPointInFill ( point ) ) ) continue ;
const ecran = point . matrixTransform ( versEcran ) ;
if ( montreLeDessin ( forme , ecran . x , ecran . y ) ) points . push ( point ) ;
if ( points . length === POINTS _MAX ) break ;
}
return points ;
}
// Ce qui est peint sous la plupart des points où la forme peint : les
// couleurs dessous en chaque point, puis la liste la plus fréquente, la
// première à égalité. Un trait qui croise un autre trait en un point se
// juge sur ce qui l'entoure, non sur ce croisement.
function dessousLePlusFrequent ( forme , quoi ) {
const comptes = new Map ( ) ;
const points = pointsPeints ( forme , quoi ) ;
if ( points . length === 0 ) return null ;
for ( const point of points ) {
const couleurs = dessousAuPoint ( forme , point ) ;
const cle = couleurs . map ( rgb ) . join ( ' + ' ) ;
const vu = comptes . get ( cle ) ? ? { couleurs , n : 0 } ;
vu . n += 1 ;
comptes . set ( cle , vu ) ;
}
return [ ... comptes . values ( ) ] . reduce ( ( a , b ) => ( b . n > a . n ? b : a ) ) . couleurs ;
}
// Les couleurs peintes sous une forme SVG au point donné de son repère : une
// sonde sans peinture, posée juste avant elle chez son parent, se mesure par
// fondPeintSous, qui lit les formes peintes avant elle à ce point, puis ce
// qui est sous le dessin.
function dessousAuPoint ( forme , point ) {
const ecran = point . matrixTransform ( forme . getScreenCTM ( ) ) ;
const parent = forme . parentNode ;
const matrice = parent . getScreenCTM ( ) ;
const local = ecran . matrixTransform ( matrice . inverse ( ) ) ;
const cote = 0.5 / Math . hypot ( matrice . a , matrice . b ) ;
const sonde = document . createElementNS ( 'http://www.w3.org/2000/svg' , 'rect' ) ;
sonde . setAttribute ( 'x' , String ( local . x - cote / 2 ) ) ;
sonde . setAttribute ( 'y' , String ( local . y - cote / 2 ) ) ;
sonde . setAttribute ( 'width' , String ( cote ) ) ;
sonde . setAttribute ( 'height' , String ( cote ) ) ;
sonde . style . fill = 'none' ;
sonde . style . stroke = 'none' ;
parent . insertBefore ( sonde , forme ) ;
try {
return fondPeintSous ( sonde ) ;
} finally {
sonde . remove ( ) ;
}
}
// Les bords d'un élément HTML qui se peignent, et leurs couleurs.
export function bordsPeints ( style ) {
return [ 'top' , 'right' , 'bottom' , 'left' ]
. filter ( ( cote ) => style . getPropertyValue ( ` border- ${ cote } -style ` ) !== 'none' && parseFloat ( style . getPropertyValue ( ` border- ${ cote } -width ` ) ) > 0 )
. map ( ( cote ) => composantes ( style . getPropertyValue ( ` border- ${ cote } -color ` ) ) ) ;
}
/ * *
* Ce qu 'une marque peint de son jeton, et ce qui l' entoure : { peinture ,
* voisins } . Une forme SVG : son remplissage — une trame se mesure contre
* les autres couleurs de son motif , un aplat contre ce qui est peint dessous
* en un point qu ' il couvre — ou son trait , contre ce qui est dessous en un
[ADD] print: preview screen, printable sheet, paper question
Printed pages must show exactly what will come out of the printer. An
Imprimer button in the event header opens a preview screen where each page
is a sheet at paper size, light whatever the theme, with its footer and,
under the strict draft rule, BROUILLON in outline; a proposal shown on a
retained plan prints as a draft. The paper is asked once per machine and
can be changed there. The table sheets offer one sheet per round. A failed
print shows IMPRESSION_ECHEC. The captures are republished with the button.
Checked: red first; 3651 node, 707 browser and 101 long tests green from
the index alone.
--- FR ---
[ADD] impression : aperçu, feuille imprimable, question du papier
Une page imprimée doit montrer exactement ce que l'imprimante rendra. Un
bouton Imprimer de la tête de l'événement ouvre un aperçu où chaque page
est une feuille au format du papier, claire quel que soit le thème, avec
son pied et, sous la règle stricte, BROUILLON au trait ; une proposition
montrée sur un plan retenu s'imprime en brouillon. Le papier se demande une
fois par machine et s'y change. Les feuilles par table offrent une feuille
par tour. Une impression échouée affiche IMPRESSION_ECHEC. Les captures
sont republiées avec le bouton.
Vérifié : rouge d'abord ; 3651 node, 707 navigateur et 101 longues vertes
depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 17:09:29 -04:00
* point du trait et contre son propre remplissage . Un texte SVG au trait —
* le filigrane — : son contour , contre ce qui est peint sous le centre de sa
* boîte . Un élément HTML : sa bordure ou son fond , contre ce qui est peint
* sous son parent , et son fond propre pour une bordure . peinture null : le
* jeton n ' est peint nulle part .
[ADD] docs: photographed walkthrough publishes doc/captures
The screenshots must come from the tested walkthrough, not from a separate
script that can drift. The browser walkthrough runs on the simulated file
system with a driven clock and the direct executor, light theme, 1366 x
700 at scale 1, transitions off, and photographs each step once its
condition holds. make captures writes the images and the manifest; the
plain test run compares them instead. Eighteen captures are published, the
later ones named as postponed. The forged-names scan skips the manifest,
which records the demo names shown on screen.
Checked: red first; 3620 node, 681 browser and 101 long tests green from
the index alone.
--- FR ---
[ADD] docs : parcours photographié, publication de doc/captures
Les captures doivent venir du parcours éprouvé, non d'un script à part qui
dérive. Le parcours navigateur joue sur le système de fichiers simulé,
l'horloge pilotée et l'exécuteur direct, thème clair, 1366 × 700 au facteur
1, transitions coupées, et photographie chaque étape quand sa condition
tient. make captures écrit les images et le manifeste ; la série ordinaire
les compare au lieu de les écrire. Dix-huit captures sont publiées, les
suivantes nommées reportées. Le balayage des noms forgés saute le
manifeste, qui relève les noms de démonstration affichés.
Vérifié : rouge d'abord ; 3620 node, 681 navigateur et 101 longues vertes
depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 15:33:59 -04:00
* /
function voisinage ( element , jeton ) {
[ADD] print: preview screen, printable sheet, paper question
Printed pages must show exactly what will come out of the printer. An
Imprimer button in the event header opens a preview screen where each page
is a sheet at paper size, light whatever the theme, with its footer and,
under the strict draft rule, BROUILLON in outline; a proposal shown on a
retained plan prints as a draft. The paper is asked once per machine and
can be changed there. The table sheets offer one sheet per round. A failed
print shows IMPRESSION_ECHEC. The captures are republished with the button.
Checked: red first; 3651 node, 707 browser and 101 long tests green from
the index alone.
--- FR ---
[ADD] impression : aperçu, feuille imprimable, question du papier
Une page imprimée doit montrer exactement ce que l'imprimante rendra. Un
bouton Imprimer de la tête de l'événement ouvre un aperçu où chaque page
est une feuille au format du papier, claire quel que soit le thème, avec
son pied et, sous la règle stricte, BROUILLON au trait ; une proposition
montrée sur un plan retenu s'imprime en brouillon. Le papier se demande une
fois par machine et s'y change. Les feuilles par table offrent une feuille
par tour. Une impression échouée affiche IMPRESSION_ECHEC. Les captures
sont republiées avec le bouton.
Vérifié : rouge d'abord ; 3651 node, 707 navigateur et 101 longues vertes
depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 17:09:29 -04:00
if ( element instanceof SVGTextContentElement ) {
const { stroke } = couleursPeintes ( element ) ;
const trace = Number . parseFloat ( getComputedStyle ( element ) . strokeWidth ) > 0 ;
return trace && stroke . some ( ( c ) => egales ( c , jeton ) ) ? { peinture : 'trait' , voisins : fondPeintSous ( element ) } : { peinture : null , voisins : [ ] } ;
}
[ADD] docs: photographed walkthrough publishes doc/captures
The screenshots must come from the tested walkthrough, not from a separate
script that can drift. The browser walkthrough runs on the simulated file
system with a driven clock and the direct executor, light theme, 1366 x
700 at scale 1, transitions off, and photographs each step once its
condition holds. make captures writes the images and the manifest; the
plain test run compares them instead. Eighteen captures are published, the
later ones named as postponed. The forged-names scan skips the manifest,
which records the demo names shown on screen.
Checked: red first; 3620 node, 681 browser and 101 long tests green from
the index alone.
--- FR ---
[ADD] docs : parcours photographié, publication de doc/captures
Les captures doivent venir du parcours éprouvé, non d'un script à part qui
dérive. Le parcours navigateur joue sur le système de fichiers simulé,
l'horloge pilotée et l'exécuteur direct, thème clair, 1366 × 700 au facteur
1, transitions coupées, et photographie chaque étape quand sa condition
tient. make captures écrit les images et le manifeste ; la série ordinaire
les compare au lieu de les écrire. Dix-huit captures sont publiées, les
suivantes nommées reportées. Le balayage des noms forgés saute le
manifeste, qui relève les noms de démonstration affichés.
Vérifié : rouge d'abord ; 3620 node, 681 navigateur et 101 longues vertes
depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 15:33:59 -04:00
if ( element instanceof SVGGeometryElement ) {
const motif = /^url\(/ . test ( getComputedStyle ( element ) . fill ) ;
const { fill , stroke } = couleursPeintes ( element ) ;
if ( fill . some ( ( c ) => egales ( c , jeton ) ) ) {
if ( motif ) return { peinture : 'trame' , voisins : fill . filter ( ( c ) => ! egales ( c , jeton ) ) } ;
const dessous = dessousLePlusFrequent ( element , 'fill' ) ;
return dessous === null ? { peinture : 'cachee' , voisins : [ ] } : { peinture : 'remplissage' , voisins : dessous } ;
}
if ( stroke . some ( ( c ) => egales ( c , jeton ) ) ) {
const dessous = dessousLePlusFrequent ( element , 'stroke' ) ;
return dessous === null ? { peinture : 'cachee' , voisins : [ ] } : { peinture : 'trait' , voisins : [ ... dessous , ... fill ] } ;
}
return { peinture : null , voisins : [ ] } ;
}
const style = getComputedStyle ( element ) ;
const fond = composantes ( style . backgroundColor ) ;
if ( bordsPeints ( style ) . some ( ( c ) => egales ( c , jeton ) ) ) {
return { peinture : 'bordure' , voisins : [ ... ( fond [ 3 ] === 1 ? [ fond ] : [ ] ) , ... fondPeintSous ( element . parentElement ) ] } ;
}
if ( egales ( fond , jeton ) ) return { peinture : 'fond' , voisins : fondPeintSous ( element . parentElement ) } ;
return { peinture : null , voisins : [ ] } ;
}
/ * *
* Les marques rendues sous racine ( § 14.5 , § 19.5 ) : chacune nommée au
* registre , peinte de son jeton , à 3 : 1 de ce qui l ' entoure , sans
[ADD] print: seating plan cut into pages, with cartouche and scale
The printed plan draws every table in seat mode with its name lists,
whatever the zoom, and drops screen gestures: no selection, focus,
grab handle or highlight reaches paper. Each page carries a cartouche
(page p of n, column, row) and references to its neighbours; the cut
uses the band measured under the footer. Print marks resolve their
colour tokens from the printed document, so a dark screen theme
leaves the paper light.
Checked: node 3721, browser 720, node-long 101 from the index alone.
--- FR ---
[ADD] impression : plan découpé en pages, avec cartouche et échelle
Le plan imprimé dessine chaque table en régime sièges, avec ses listes
de noms, quel que soit le zoom, et laisse les gestes à l'écran :
sélection, focus, poignée et mise en évidence n'atteignent pas le
papier. Chaque page porte un cartouche (page p sur n, colonne, rangée)
et ses renvois aux voisines ; la découpe prend la bande mesurée sous le
pied. Les marques lisent leurs jetons dans le document imprimé : un
thème sombre à l'écran laisse le papier clair.
Vérifié : node 3721, navigateur 720, node-long 101 depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-10 11:03:12 -04:00
* atténuation . Sous le document imprimé de l ' aperçu , le jeton se résout
* comme le papier le résout : au clair , dans les deux thèmes .
* → { vues : string [ ] , fautes , attenuations , mesures , ratios } ,
[ADD] docs: photographed walkthrough publishes doc/captures
The screenshots must come from the tested walkthrough, not from a separate
script that can drift. The browser walkthrough runs on the simulated file
system with a driven clock and the direct executor, light theme, 1366 x
700 at scale 1, transitions off, and photographs each step once its
condition holds. make captures writes the images and the manifest; the
plain test run compares them instead. Eighteen captures are published, the
later ones named as postponed. The forged-names scan skips the manifest,
which records the demo names shown on screen.
Checked: red first; 3620 node, 681 browser and 101 long tests green from
the index alone.
--- FR ---
[ADD] docs : parcours photographié, publication de doc/captures
Les captures doivent venir du parcours éprouvé, non d'un script à part qui
dérive. Le parcours navigateur joue sur le système de fichiers simulé,
l'horloge pilotée et l'exécuteur direct, thème clair, 1366 × 700 au facteur
1, transitions coupées, et photographie chaque étape quand sa condition
tient. make captures écrit les images et le manifeste ; la série ordinaire
les compare au lieu de les écrire. Dix-huit captures sont publiées, les
suivantes nommées reportées. Le balayage des noms forgés saute le
manifeste, qui relève les noms de démonstration affichés.
Vérifié : rouge d'abord ; 3620 node, 681 navigateur et 101 longues vertes
depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 15:33:59 -04:00
* ratios : { marque , jeton , ratio } de chaque marque mesurée , contre ce qui
* l 'entoure de plus défavorable, dans l' ordre des mesures . fenetre vrai :
* seules les marques que la fenêtre montre entières se lisent , sans rien
* amener : une marque rognée par le bord de la fenêtre ou d ' un ancêtre qui
* rogne ne montre qu 'une partie de ce qui l' entoure — une chaise coupée par
* le bord du plan ne garde parfois , de son trait , que le point où il touche
* le trait du plateau .
* /
export function releverMarques ( racine , { fenetre = false } = { } ) {
const vues = new Set ( ) ;
const ratios = [ ] ;
const fautes = [ ] ;
const fautesAttenuation = [ ] ;
let mesures = 0 ;
for ( const element of racine . querySelectorAll ( '[data-marque]' ) ) {
if ( fenetre && ! entiereDansLaFenetre ( element ) ) continue ;
if ( ! rendu ( element ) ) continue ;
const nom = element . dataset . marque ;
const entree = MARQUES . find ( ( m ) => m . nom === nom ) ;
if ( entree === undefined ) {
fautes . push ( ` ${ situer ( element ) } : marque hors du registre MARQUES ` ) ;
continue ;
}
for ( const a of attenuations ( element ) ) fautesAttenuation . push ( ` ${ situer ( element ) } : ${ designation ( a ) } ${ a . propriete } : ${ a . valeur } ` ) ;
// Sous une fenêtre modale, la page est inerte et le test d'atteinte
// rencontre la fenêtre d'abord : une marque HTML de la page ne s'y
// mesure pas, comme ses textes, et se mesure sur les écrans sans
// fenêtre. Le dessin SVG, lu par sa géométrie, se mesure partout. Une
// marque ne compte comme vue que là où elle se mesure.
const modale = document . querySelector ( 'dialog:modal' ) ;
if ( modale !== null && ! ( element instanceof SVGElement ) && ! modale . contains ( element ) ) continue ;
vues . add ( nom ) ;
if ( ! fenetre ) amener ( element ) ;
[ADD] print: seating plan cut into pages, with cartouche and scale
The printed plan draws every table in seat mode with its name lists,
whatever the zoom, and drops screen gestures: no selection, focus,
grab handle or highlight reaches paper. Each page carries a cartouche
(page p of n, column, row) and references to its neighbours; the cut
uses the band measured under the footer. Print marks resolve their
colour tokens from the printed document, so a dark screen theme
leaves the paper light.
Checked: node 3721, browser 720, node-long 101 from the index alone.
--- FR ---
[ADD] impression : plan découpé en pages, avec cartouche et échelle
Le plan imprimé dessine chaque table en régime sièges, avec ses listes
de noms, quel que soit le zoom, et laisse les gestes à l'écran :
sélection, focus, poignée et mise en évidence n'atteignent pas le
papier. Chaque page porte un cartouche (page p sur n, colonne, rangée)
et ses renvois aux voisines ; la découpe prend la bande mesurée sous le
pied. Les marques lisent leurs jetons dans le document imprimé : un
thème sombre à l'écran laisse le papier clair.
Vérifié : node 3721, navigateur 720, node-long 101 depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-10 11:03:12 -04:00
const jeton = couleurDeJeton ( entree . jeton , element . closest ( '[data-impression]' ) ? ? document . body ) ;
[ADD] docs: photographed walkthrough publishes doc/captures
The screenshots must come from the tested walkthrough, not from a separate
script that can drift. The browser walkthrough runs on the simulated file
system with a driven clock and the direct executor, light theme, 1366 x
700 at scale 1, transitions off, and photographs each step once its
condition holds. make captures writes the images and the manifest; the
plain test run compares them instead. Eighteen captures are published, the
later ones named as postponed. The forged-names scan skips the manifest,
which records the demo names shown on screen.
Checked: red first; 3620 node, 681 browser and 101 long tests green from
the index alone.
--- FR ---
[ADD] docs : parcours photographié, publication de doc/captures
Les captures doivent venir du parcours éprouvé, non d'un script à part qui
dérive. Le parcours navigateur joue sur le système de fichiers simulé,
l'horloge pilotée et l'exécuteur direct, thème clair, 1366 × 700 au facteur
1, transitions coupées, et photographie chaque étape quand sa condition
tient. make captures écrit les images et le manifeste ; la série ordinaire
les compare au lieu de les écrire. Dix-huit captures sont publiées, les
suivantes nommées reportées. Le balayage des noms forgés saute le
manifeste, qui relève les noms de démonstration affichés.
Vérifié : rouge d'abord ; 3620 node, 681 navigateur et 101 longues vertes
depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 15:33:59 -04:00
try {
const { peinture , voisins } = voisinage ( element , jeton ) ;
if ( peinture === null ) {
fautes . push ( ` ${ situer ( element ) } : ne peint pas son jeton ${ entree . jeton } ( ${ rgb ( jeton ) } ) ` ) ;
continue ;
}
// Rognée par un ancêtre qui défile, la marque ne se voit pas ici.
if ( peinture === 'cachee' ) continue ;
if ( voisins . length === 0 ) {
fautes . push ( ` ${ situer ( element ) } : ${ peinture } sans rien d'opaque autour ` ) ;
continue ;
}
mesures += 1 ;
const { fond , ratio } = pire ( jeton , voisins ) ;
ratios . push ( { marque : nom , jeton : entree . jeton , ratio } ) ;
if ( ratio < SEUIL _MARQUE ) fautes . push ( ` ${ situer ( element ) } ( ${ peinture } ) : ${ ratio . toFixed ( 2 ) } :1, ${ rgb ( jeton ) } contre ${ rgb ( fond ) } ` ) ;
} catch ( erreur ) {
fautes . push ( ` ${ situer ( element ) } : ${ erreur . message } ` ) ;
}
}
return { vues : [ ... vues ] , fautes , attenuations : fautesAttenuation , mesures , ratios } ;
}
// --- La capture d'écran d'une étape ---------------------------------------------------
const THEMES = Object . freeze ( [ 'clair' , 'sombre' ] ) ;
// Le corps, en px, de la chaîne témoin mesurée : grand, pour que l'écart
// relatif lise la police et non l'arrondi d'un pixel.
const CORPS _TEMOIN = 100 ;
const SVG = 'http://www.w3.org/2000/svg' ;
// Les éléments dont le texte ne se peint jamais : leurs nœuds de texte ne
// sont pas relevés.
const SANS _TEXTE = new Set ( [ 'script' , 'style' , 'template' , 'noscript' ] ) ;
// Les éléments qui n'existent que pour porter un texte : rendus vides, sans
// image ni nom annoncé, ils trahissent un libellé manquant (§ 19.11).
const PORTEURS _DE _LIBELLE = 'button, label, legend, caption, th, h1, h2, h3, h4, h5, h6, p, li, dt, dd, summary, option, a' ;
// Ce qui donne un nom à un élément sans texte.
const NOMMANTS = [ 'aria-label' , 'aria-labelledby' , 'title' ] ;
// Les clés de la traduction : un texte qui en égale une est une clé brute.
const CLES = new Set ( creerTraduction ( [ ... TABLES _APPLICATION , ... TABLES _INTERFACE ] ) . cles ( ) ) ;
// Les jetons de couleur que la feuille des jetons déclare, dans l'ordre de
// leur première déclaration ; les jetons des textes du registre (TEXTES)
// d'abord, qui nomment une couleur partagée par plusieurs jetons.
const JETONS = ( ( ) => {
const declares = [ ... new Set ( [ ... JETONS _CSS . matchAll ( /(--couleur-[a-z0-9-]+)\s*:/g ) ] . map ( ( [ , nom ] ) => nom ) ) ] ;
const deTexte = TEXTES . map ( ( { jeton } ) => jeton ) ;
return Object . freeze ( [ ... deTexte . filter ( ( jeton ) => declares . includes ( jeton ) ) , ... declares . filter ( ( jeton ) => ! deTexte . includes ( jeton ) ) ] ) ;
} ) ( ) ;
// Les captures déclarées, lues une fois de node.
let declarations = null ;
async function declaration ( fichier ) {
declarations ? ? = commands . declarationsCaptures ( ) ;
const { captures } = await declarations ;
return captures . find ( ( capture ) => capture . fichier === fichier ) ? ? null ;
}
// Les captures photographiées sur la racine du projet depuis le chargement
// du module : le contrôle de clôture du parcours.
const prises = new Set ( ) ;
/ * * L e s n o m s d e s c a p t u r e s q u e p h o t o g r a p h i e r a m e n é e s à l e u r t e r m e s u r l a
* racine du projet , dans l ' ordre . * /
export function capturesPrises ( ) {
return [ ... prises ] ;
}
/ * * L e m o d e d e s c a p t u r e s q u e f o u r n i t l a c o n f i g u r a t i o n : ' v e r i f i e r ' o u
* 'ecrire' . * /
export function modeFourni ( ) {
return inject ( 'captures' ) ? . mode ? ? 'verifier' ;
}
/ * *
* Les textes du conteneur , en ordre de document : chaque nœud de texte hors
* des éléments qui ne se peignent jamais , ses blancs réduits à une espace ,
* vides écartés ( § 19.5 ) .
*
* @ param { Element } racine
* @ returns { string [ ] }
* /
export function texteReleve ( racine ) {
const textes = [ ] ;
const marcheur = document . createTreeWalker ( racine , NodeFilter . SHOW _TEXT ) ;
for ( let noeud = marcheur . nextNode ( ) ; noeud !== null ; noeud = marcheur . nextNode ( ) ) {
if ( noeud . parentElement === null || noeud . parentElement . closest ( 'script, style, template, noscript' ) !== null ) continue ;
if ( SANS _TEXTE . has ( noeud . parentElement . localName ) ) continue ;
const texte = noeud . textContent . replace ( /\s+/g , ' ' ) . trim ( ) ;
if ( texte !== '' ) textes . push ( texte ) ;
}
return textes ;
}
/ * *
* Les fautes de libellé du conteneur ( § 19.11 ) : un porteur de libellé
* rendu sans texte , sans image ni contrôle dedans , sans nom annoncé , hors
* d 'une zone d' annonce ; un texte égal à une clé de la traduction ; une
* marque de la langue témoin .
*
* @ param { Element } racine
* @ returns { string [ ] }
* /
export function fautesDeLibelle ( racine ) {
const fautes = [ ] ;
for ( const element of racine . querySelectorAll ( PORTEURS _DE _LIBELLE ) ) {
if ( ! rendu ( element ) || element . textContent . trim ( ) !== '' ) continue ;
if ( element . querySelector ( 'svg, img, canvas, input, select, textarea' ) !== null ) continue ;
if ( NOMMANTS . some ( ( nom ) => ( element . getAttribute ( nom ) ? ? '' ) . trim ( ) !== '' ) ) continue ;
if ( element . closest ( '[aria-live], [role="status"], [role="alert"], [role="log"]' ) !== null ) continue ;
fautes . push ( ` élément de texte vide : ${ situer ( element ) } ` ) ;
}
for ( const texte of texteReleve ( racine ) ) {
if ( CLES . has ( texte ) ) fautes . push ( ` clé brute affichée : « ${ texte } » ` ) ;
if ( texte . includes ( MARQUE _TEMOIN . ouverture ) || texte . includes ( MARQUE _TEMOIN . fermeture ) ) {
fautes . push ( ` marque de la langue témoin : « ${ extrait ( texte ) } » ` ) ;
}
}
return fautes ;
}
// La largeur rendue de la chaîne témoin dans la police de l'interface, au
// corps CORPS_TEMOIN, crénage et ligatures coupés comme longueurTemoin les
// compte, sur un <text> posé dans le conteneur le temps de la lecture.
function largeurTemoin ( racine ) {
const svg = document . createElementNS ( SVG , 'svg' ) ;
const texte = document . createElementNS ( SVG , 'text' ) ;
texte . setAttribute (
'style' ,
` font-family: var(--police-interface); font-size: ${ CORPS _TEMOIN } px; font-weight: 400; font-kerning: none; font-variant-ligatures: none ` ,
) ;
texte . textContent = CHAINE _TEMOIN ;
svg . append ( texte ) ;
racine . append ( svg ) ;
try {
return texte . getComputedTextLength ( ) ;
} finally {
svg . remove ( ) ;
}
}
// La couleur en clé de comparaison.
const cleCouleur = ( couleur ) => couleur . join ( ',' ) ;
// Le jeton d'une couleur de texte dans le thème courant : le premier de
// JETONS qui la résout ; sa forme rgb(…) quand aucun ne la résout.
function jetonsDuTheme ( ) {
const parCouleur = new Map ( ) ;
for ( const jeton of JETONS ) {
const cle = cleCouleur ( couleurDeJeton ( jeton ) ) ;
if ( ! parCouleur . has ( cle ) ) parCouleur . set ( cle , jeton ) ;
}
return ( couleur ) => parCouleur . get ( cleCouleur ( couleur ) ) ? ? rgb ( couleur ) ;
}
// Un ratio arrondi au centième, comme le tableau des contrastes l'écrit.
const centieme = ( ratio ) => Math . round ( ratio * 100 ) / 100 ;
/ * *
* Les styles relevés dans le thème courant ( § 14.5 , § 19.5 ) , sur ce que la
* fenêtre montre — la région photographiée : pour chaque
* jeton et chaque rôle — texte , marque — , le plus petit ratio mesuré et le
* seuil du rôle , rangés par jeton puis rôle ; et les fautes : un ratio sous
* son seuil , une mesure impossible , une atténuation .
*
* @ param { Element } racine
* @ returns { { styles : Array < { jeton : string , role : string , ratio : number , seuil : number } > , fautes : string [ ] } }
* /
export function stylesDuTheme ( racine ) {
const textes = releverTextes ( racine , { fenetre : true } ) ;
const marques = releverMarques ( racine , { fenetre : true } ) ;
const jetonDe = jetonsDuTheme ( ) ;
const parCle = new Map ( ) ;
const retenir = ( jeton , role , ratio , seuil ) => {
const cle = ` ${ jeton } \u {0} ${ role } ` ;
const vu = parCle . get ( cle ) ;
if ( vu === undefined || ratio < vu . ratio ) parCle . set ( cle , { jeton , role , ratio , seuil } ) ;
} ;
for ( const { couleur , ratio } of textes . ratios ) retenir ( jetonDe ( couleur ) , 'texte' , ratio , SEUIL _TEXTE ) ;
for ( const { jeton , ratio } of marques . ratios ) retenir ( jeton , 'marque' , ratio , SEUIL _MARQUE ) ;
const styles = [ ... parCle . values ( ) ]
. map ( ( style ) => ( { ... style , ratio : centieme ( style . ratio ) } ) )
. sort ( ( a , b ) => ( a . jeton < b . jeton ? - 1 : a . jeton > b . jeton ? 1 : a . role < b . role ? - 1 : a . role > b . role ? 1 : 0 ) ) ;
return { styles , fautes : [ ... textes . fautes , ... textes . attenuations , ... marques . fautes , ... marques . attenuations ] } ;
}
// Les positions de défilement du document et de chaque élément du
// conteneur qui a défilé ; rend la fonction qui les restitue.
function retenirDefilements ( racine ) {
const positions = [ document . scrollingElement , racine , ... racine . querySelectorAll ( '*' ) ]
. filter ( ( element ) => element !== null && ( element . scrollTop !== 0 || element . scrollLeft !== 0 || element . scrollHeight > element . clientHeight ) )
. map ( ( element ) => [ element , element . scrollTop , element . scrollLeft ] ) ;
return ( ) => {
for ( const [ element , haut , gauche ] of positions ) {
element . scrollTop = haut ;
element . scrollLeft = gauche ;
}
} ;
}
const hex = ( tampon ) => [ ... new Uint8Array ( tampon ) ] . map ( ( octet ) => octet . toString ( 16 ) . padStart ( 2 , '0' ) ) . join ( '' ) ;
/ * *
* L 'empreinte d' état ( § 8.8 , § 19.5 ) : le SHA - 256 hexadécimal de
* serialiserCharge de l ' événement ouvert , lu sur le disque simulé du
* montage ; null sans événement ouvert .
*
* @ param { Object } m un montage de monterApp
* @ returns { Promise < string | null > }
* /
export async function empreinteEtat ( m ) {
const { evenement , racine } = m . seance . etat ( ) ;
if ( evenement === null ) return null ;
const texte = m . fs . contenu ( racine . id , ` ${ evenement . base } ${ SUFFIXES . etat } ` ) ;
if ( texte === null ) throw new Error ( ` état de ${ evenement . base } absent du disque simulé ` ) ;
const { charge } = analyser ( texte ) ;
return hex ( await crypto . subtle . digest ( 'SHA-256' , new TextEncoder ( ) . encode ( serialiserCharge ( charge ) ) ) ) ;
}
// Un élément que l'étape affirme est-il visible ? Un élément HTML : visible
// de rendu.js, son centre atteint et non couvert. Un élément SVG se lit par
// la géométrie du dessin, comme ses textes au relevé des contrastes : rendu,
// et le centre de sa boîte dans le <svg> extérieur et dans la fenêtre — un
2026-10-10 13:37:50 -04:00
// dessin sous le pointeur ou hors d'atteinte reste visible. Dans les deux
// cas, la boîte entière tient dans la fenêtre et dans chaque ancêtre qui
// rogne : un libellé dont le centre se voit mais que le bord du plan coupe
// n'est pas lisible sur l'image. Rend true, ou la raison.
[ADD] docs: photographed walkthrough publishes doc/captures
The screenshots must come from the tested walkthrough, not from a separate
script that can drift. The browser walkthrough runs on the simulated file
system with a driven clock and the direct executor, light theme, 1366 x
700 at scale 1, transitions off, and photographs each step once its
condition holds. make captures writes the images and the manifest; the
plain test run compares them instead. Eighteen captures are published, the
later ones named as postponed. The forged-names scan skips the manifest,
which records the demo names shown on screen.
Checked: red first; 3620 node, 681 browser and 101 long tests green from
the index alone.
--- FR ---
[ADD] docs : parcours photographié, publication de doc/captures
Les captures doivent venir du parcours éprouvé, non d'un script à part qui
dérive. Le parcours navigateur joue sur le système de fichiers simulé,
l'horloge pilotée et l'exécuteur direct, thème clair, 1366 × 700 au facteur
1, transitions coupées, et photographie chaque étape quand sa condition
tient. make captures écrit les images et le manifeste ; la série ordinaire
les compare au lieu de les écrire. Dix-huit captures sont publiées, les
suivantes nommées reportées. Le balayage des noms forgés saute le
manifeste, qui relève les noms de démonstration affichés.
Vérifié : rouge d'abord ; 3620 node, 681 navigateur et 101 longues vertes
depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 15:33:59 -04:00
function conditionVisible ( element ) {
2026-10-10 13:37:50 -04:00
if ( element instanceof SVGElement ) {
if ( ! rendu ( element ) ) return 'checkVisibility faux' ;
if ( ! centreDansLeDessin ( element ) ) return 'le centre de sa boîte est hors du dessin ou de la fenêtre' ;
} else {
const vu = visible ( element ) ;
if ( vu !== true ) return vu ;
}
return entiereDansLaFenetre ( element ) ? true : 'sa boîte déborde de la fenêtre ou d’ un ancêtre qui la rogne : l’ image la coupe' ;
}
// L'identifiant du cadre que pose cliche.
const CADRE _DE _CAPTURE = 'gtt-cadre-de-capture' ;
// L'image de l'écran du montage m, telle que la fenêtre la montre ; rend
// { chemin }. Un montage dont la boîte est la fenêtre se photographie
// lui-même. Un montage plus haut — l'aperçu avant impression, dont le
// document n'est jamais tronqué — se photographie par un cadre fixe de la
// taille de la fenêtre, posé le temps de la capture, sans peinture, sans
// atteinte du pointeur, caché aux technologies d'assistance : l'image est
// la fenêtre, à la position où le document a défilé. Le cadre ne sert que
// là : la capture d'un autre élément que le montage peint autrement
// l'élément qui a le focus — le contour de focus du plan, le curseur d'un
// champ —, et l'image d'un écran qui n'a pas changé changerait.
async function cliche ( m , fichier , racine ) {
const b = m . racine . getBoundingClientRect ( ) ;
if ( b . left === 0 && b . top === 0 && b . width === innerWidth && b . height === innerHeight ) {
return commands . prendreCliche ( { fichier , selecteur : ` [data-montage=" ${ m . racine . dataset . montage } "] ` , racine } ) ;
}
const cadre = document . createElement ( 'div' ) ;
cadre . id = CADRE _DE _CAPTURE ;
cadre . setAttribute ( 'aria-hidden' , 'true' ) ;
Object . assign ( cadre . style , { position : 'fixed' , inset : '0' , pointerEvents : 'none' } ) ;
document . body . append ( cadre ) ;
try {
return await commands . prendreCliche ( { fichier , selecteur : ` # ${ CADRE _DE _CAPTURE } ` , racine } ) ;
} finally {
cadre . remove ( ) ;
}
[ADD] docs: photographed walkthrough publishes doc/captures
The screenshots must come from the tested walkthrough, not from a separate
script that can drift. The browser walkthrough runs on the simulated file
system with a driven clock and the direct executor, light theme, 1366 x
700 at scale 1, transitions off, and photographs each step once its
condition holds. make captures writes the images and the manifest; the
plain test run compares them instead. Eighteen captures are published, the
later ones named as postponed. The forged-names scan skips the manifest,
which records the demo names shown on screen.
Checked: red first; 3620 node, 681 browser and 101 long tests green from
the index alone.
--- FR ---
[ADD] docs : parcours photographié, publication de doc/captures
Les captures doivent venir du parcours éprouvé, non d'un script à part qui
dérive. Le parcours navigateur joue sur le système de fichiers simulé,
l'horloge pilotée et l'exécuteur direct, thème clair, 1366 × 700 au facteur
1, transitions coupées, et photographie chaque étape quand sa condition
tient. make captures écrit les images et le manifeste ; la série ordinaire
les compare au lieu de les écrire. Dix-huit captures sont publiées, les
suivantes nommées reportées. Le balayage des noms forgés saute le
manifeste, qui relève les noms de démonstration affichés.
Vérifié : rouge d'abord ; 3620 node, 681 navigateur et 101 longues vertes
depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 15:33:59 -04:00
}
/ * *
* Photographie l ' écran du montage m sous le nom fichier , une capture de
* CAPTURES . Dans l ' ordre , chaque contrôle échoue en nommant la capture et
* la nature de la faute : capture déclarée ; feuille sans mouvement posée ;
* conditions visibles ; collecteur vide ; aucune requête hors de la boucle
* locale ; cadre relu au CADRE , facteur d ' échelle 1 ; thème clair ; chaîne
* témoin dans sa tolérance ; aucun chemin hors de la racine de travail
* dans le texte relevé ; aucune faute de libellé ; styles relevés dans les
* deux thèmes , sous leurs seuils ; empreinte d 'état. L' entrée se compose ,
* puis 'verifier' la compare au manifeste — un écart nomme l ' étape et sa
2026-10-10 13:37:50 -04:00
* nature — , et 'ecrire' dépose l ' image de la fenêtre , prise avant les
* relevés , et l ' entrée .
[ADD] docs: photographed walkthrough publishes doc/captures
The screenshots must come from the tested walkthrough, not from a separate
script that can drift. The browser walkthrough runs on the simulated file
system with a driven clock and the direct executor, light theme, 1366 x
700 at scale 1, transitions off, and photographs each step once its
condition holds. make captures writes the images and the manifest; the
plain test run compares them instead. Eighteen captures are published, the
later ones named as postponed. The forged-names scan skips the manifest,
which records the demo names shown on screen.
Checked: red first; 3620 node, 681 browser and 101 long tests green from
the index alone.
--- FR ---
[ADD] docs : parcours photographié, publication de doc/captures
Les captures doivent venir du parcours éprouvé, non d'un script à part qui
dérive. Le parcours navigateur joue sur le système de fichiers simulé,
l'horloge pilotée et l'exécuteur direct, thème clair, 1366 × 700 au facteur
1, transitions coupées, et photographie chaque étape quand sa condition
tient. make captures écrit les images et le manifeste ; la série ordinaire
les compare au lieu de les écrire. Dix-huit captures sont publiées, les
suivantes nommées reportées. Le balayage des noms forgés saute le
manifeste, qui relève les noms de démonstration affichés.
Vérifié : rouge d'abord ; 3620 node, 681 navigateur et 101 longues vertes
depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 15:33:59 -04:00
*
* @ param { Object } m un montage de monterApp
* @ param { string } fichier le nom de la capture , sans extension
* @ param { Object } [ options ]
* @ param { string | string [ ] } [ options . condition ] les sélecteurs que l ' étape
* affirme : présents dans le conteneur , visibles
* @ param { number | null } [ options . graine ] la graine écrite dans le scénario
* @ param { 'verifier' | 'ecrire' } [ options . mode ] sinon celui de la configuration
* @ param { string | null } [ options . racine ] un dossier d ' épreuve , sinon le projet
* @ returns { Promise < Object > } l ' entrée , sans image ni empreinte
* /
export async function photographier ( m , fichier , { condition = [ ] , graine = null , mode = modeFourni ( ) , racine = null } = { } ) {
const echec = ( nature ) => new Error ( ` capture ${ fichier } : ${ nature } ` ) ;
const declaree = await declaration ( fichier ) ;
if ( declaree === null ) throw echec ( 'absente de CAPTURES' ) ;
if ( ! feuillePosee ( ) ) throw echec ( 'la feuille qui annule transitions et animations n’ est pas posée' ) ;
for ( const selecteur of [ condition ] . flat ( ) ) {
const element = m . racine . querySelector ( selecteur ) ;
if ( element === null ) throw echec ( ` sélecteur absent « ${ selecteur } » ` ) ;
const vu = conditionVisible ( element ) ;
if ( vu !== true ) throw echec ( ` « ${ selecteur } » invisible : ${ vu } ` ) ;
}
const [ erreur ] = m . erreurs ( ) ;
if ( erreur !== undefined ) throw echec ( ` collecteur : ${ erreur . type } « ${ erreur . message } » ` ) ;
const [ requete ] = await commands . requetesHorsBoucle ( ) ;
if ( requete !== undefined ) throw echec ( ` requête hors de la boucle locale : ${ requete } ` ) ;
if ( innerWidth !== CADRE . largeur || innerHeight !== CADRE . hauteur ) {
throw echec ( ` cadre ${ innerWidth } × ${ innerHeight } , attendu ${ CADRE . largeur } × ${ CADRE . hauteur } ` ) ;
}
if ( devicePixelRatio !== 1 ) throw echec ( ` facteur d’ échelle ${ devicePixelRatio } , attendu 1 ` ) ;
const theme = document . documentElement . getAttribute ( 'data-theme' ) ;
if ( theme !== 'clair' ) throw echec ( ` thème « ${ theme } », l’ image se prend en thème clair ` ) ;
const { regulier } = await m . polices ( ) ;
const attendue = longueurTemoin ( regulier , CORPS _TEMOIN ) ;
const lue = largeurTemoin ( m . racine ) ;
const tolerance = constante ( 'TOLERANCE_CHAINE_TEMOIN' ) ;
if ( ! ( Math . abs ( lue - attendue ) / attendue <= tolerance ) ) {
throw echec ( ` chaîne témoin à ${ lue } px, ${ attendue } px attendus à ${ tolerance } près : la police rendue n’ est pas l’ embarquée ` ) ;
}
2026-10-10 13:37:50 -04:00
const image = mode === 'ecrire' ? await cliche ( m , fichier , racine ) : null ;
[ADD] docs: photographed walkthrough publishes doc/captures
The screenshots must come from the tested walkthrough, not from a separate
script that can drift. The browser walkthrough runs on the simulated file
system with a driven clock and the direct executor, light theme, 1366 x
700 at scale 1, transitions off, and photographs each step once its
condition holds. make captures writes the images and the manifest; the
plain test run compares them instead. Eighteen captures are published, the
later ones named as postponed. The forged-names scan skips the manifest,
which records the demo names shown on screen.
Checked: red first; 3620 node, 681 browser and 101 long tests green from
the index alone.
--- FR ---
[ADD] docs : parcours photographié, publication de doc/captures
Les captures doivent venir du parcours éprouvé, non d'un script à part qui
dérive. Le parcours navigateur joue sur le système de fichiers simulé,
l'horloge pilotée et l'exécuteur direct, thème clair, 1366 × 700 au facteur
1, transitions coupées, et photographie chaque étape quand sa condition
tient. make captures écrit les images et le manifeste ; la série ordinaire
les compare au lieu de les écrire. Dix-huit captures sont publiées, les
suivantes nommées reportées. Le balayage des noms forgés saute le
manifeste, qui relève les noms de démonstration affichés.
Vérifié : rouge d'abord ; 3620 node, 681 navigateur et 101 longues vertes
depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 15:33:59 -04:00
const texte = texteReleve ( m . racine ) ;
const racineNeutre = m . seance . etat ( ) . racine ? . chemin ? ? null ;
if ( racineNeutre === null ) throw echec ( 'aucune racine de travail : le texte relevé ne se contrôle pas' ) ;
const [ chemin ] = await commands . cheminsHorsRacine ( { texte , racine : racineNeutre } ) ;
if ( chemin !== undefined ) throw echec ( ` chemin hors de la racine neutre : « ${ chemin } » ` ) ;
const [ libelle ] = fautesDeLibelle ( m . racine ) ;
if ( libelle !== undefined ) throw echec ( libelle ) ;
// Chaque thème se relève sur la position où l'image a été prise.
const restituer = retenirDefilements ( m . racine ) ;
const styles = { } ;
for ( const nom of THEMES ) {
let releve ;
try {
releve = await sousTheme ( nom , ( ) => stylesDuTheme ( m . racine ) ) ;
} finally {
restituer ( ) ;
}
const { styles : releves , fautes } = releve ;
if ( fautes . length > 0 ) throw echec ( ` thème ${ nom } : ${ fautes . length } faute(s) de contraste ou d’ atténuation : \n ${ fautes . join ( '\n' ) } ` ) ;
if ( releves . length === 0 ) throw echec ( ` thème ${ nom } : aucun style relevé ` ) ;
styles [ nom ] = releves ;
}
const entree = {
fichier ,
etape : declaree . etape ,
source : 'navigateur' ,
versionReelle : VERSION . affichee ,
graine ,
cadre : { largeur : innerWidth , hauteur : innerHeight } ,
facteurEchelle : devicePixelRatio ,
mode : MODE _AFFICHAGE ,
theme ,
instant : m . horloge . horodatage ( ) ,
texte ,
styles ,
etat : await empreinteEtat ( m ) ,
pdf : null ,
} ;
if ( mode === 'ecrire' ) {
await commands . deposerCapture ( { fichier , entree , chemin : image . chemin , racine } ) ;
} else if ( mode === 'verifier' ) {
const { ecarts } = await commands . comparerCapture ( { fichier , entree , racine } ) ;
if ( ecarts . length > 0 ) throw echec ( ` écart au manifeste : \n ${ ecarts . join ( '\n' ) } ` ) ;
} else {
throw echec ( ` mode inconnu « ${ mode } » ` ) ;
}
if ( racine === null ) prises . add ( fichier ) ;
return entree ;
}
/ * *
* Clôt un parcours photographié ( § 19.1 , § 19.5 , § 19.7 ) . declarees : les
* captures que le parcours porte , CAPTURES hors A _VENIR par défaut . Une
* épreuve du fichier en échec , qu ' elle ait photographié ou non , refuse la
* clôture en se nommant : en écriture , le dossier d ' attente est jeté et
* rien n 'est publié. Sans échec, l' écriture publie d ' un bloc et rend le
* bilan de publierCaptures ; la vérification affirme que le manifeste porte
* exactement les déclarées .
*
* @ param { Object } options
* @ param { string [ ] } options . echecs les épreuves du fichier qui ont échoué
* @ param { string [ ] | null } [ options . declarees ]
* @ param { 'verifier' | 'ecrire' } [ options . mode ] sinon celui de la configuration
* @ param { string | null } [ options . racine ] un dossier d ' épreuve , sinon le projet
* @ param { string | null } options . racineNeutre la racine de travail du montage
* @ returns { Promise < { declarees : string [ ] , bilan : Object | null } > }
* /
export async function cloreCaptures ( { echecs , declarees = null , mode = modeFourni ( ) , racine = null , racineNeutre } ) {
let liste = declarees ;
if ( liste === null ) {
const { captures , aVenir } = await commands . declarationsCaptures ( ) ;
liste = captures . map ( ( { fichier } ) => fichier ) . filter ( ( fichier ) => ! aVenir . includes ( fichier ) ) ;
}
if ( liste . length === 0 ) throw new Error ( 'captures : aucune capture déclarée — zéro capture n’ est jamais un succès' ) ;
if ( echecs . length > 0 ) {
if ( mode === 'ecrire' ) await commands . viderAttente ( { racine } ) ;
const natures = echecs . map ( ( nom ) => ` « ${ nom } » ` ) . join ( ', ' ) ;
throw new Error ( ` captures non ${ mode === 'ecrire' ? 'publiées' : 'vérifiées' } : ${ echecs . length } épreuve(s) du parcours en échec : ${ natures } ` ) ;
}
const memes = ( a , b ) => [ ... a ] . sort ( ) . join ( '\n' ) === [ ... b ] . sort ( ) . join ( '\n' ) ;
if ( mode === 'ecrire' ) {
const bilan = await commands . publierCaptures ( { declarees : liste , racine , racineNeutre } ) ;
if ( ! memes ( [ ... bilan . ecrites , ... bilan . gardees ] , liste ) ) throw new Error ( ` captures : publiées ${ [ ... bilan . ecrites , ... bilan . gardees ] . join ( ', ' ) } , déclarées ${ liste . join ( ', ' ) } ` ) ;
return { declarees : liste , bilan } ;
}
if ( mode !== 'verifier' ) throw new Error ( ` captures : mode inconnu « ${ mode } » ` ) ;
const manifeste = await commands . lireManifeste ( { racine } ) ;
const portees = ( manifeste ? . captures ? ? [ ] ) . map ( ( { fichier } ) => fichier ) ;
if ( portees . join ( '\n' ) !== liste . join ( '\n' ) ) throw new Error ( ` captures : le manifeste porte ${ portees . join ( ', ' ) || '(rien)' } , déclarées ${ liste . join ( ', ' ) } ` ) ;
return { declarees : liste , bilan : null } ;
}