[ADD] build: no remote load in the bundle, portable startup trial
The delivery build is scanned for test modules, runner traces, the Vite
client and any loading http(s) URL, the worker bundle included and quote
entities decoded. The startup trial runs the real shell in portable mode
from separate exe, home and temp folders: it creates an event, loads the
small demonstration, generates in the worker, closes, and checks that the
files sit under exe/data and no lock is left. It runs only when launched
as itself, symlinks resolved, so it never exits green without running.
Checked: 2699 node, 62 long tests, make essai_demarrage conforme.
--- FR ---
[ADD] construction : rien de distant, essai de démarrage portable
La construction de livraison est balayée : ni module d'épreuve, ni trace du
lanceur, ni client de Vite, ni URL http(s) qui charge, paquet du travailleur
compris et entités de guillemets décodées. L'essai lance la vraie coquille
en mode portable, depuis des dossiers exe, home et temp distincts : il crée
un événement, charge la petite démonstration, génère dans le travailleur,
ferme, et vérifie les fichiers sous exe/data et aucun verrou restant. Il ne
part que lancé lui-même, liens résolus : jamais un vert sans essai.
Vérifié : 2699 node, 62 longues, make essai_demarrage conforme.
Assisted-by: Claude Opus 5.5
2026-10-07 16:08:15 -04:00
// © 2026 TechnoLibre (http://www.technolibre.ca)
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
[IMP] interface: Noto Sans everywhere, faces loaded before mounting
The screen must match the screenshots and the text the tests measure. The
interface now uses the embedded Noto Sans for every element, form controls
included, with a 1.3 line height. The entry point and the test mount wait
for both faces before mounting, so no text measurer caches the fallback
width; a face that fails to load is reported and the app mounts anyway.
The screens the wider font overflowed have their own CSS adjusted.
Checked: red first; 3216 node, 655 browser and 71 long tests green from the
index; the Electron start-up check loads the font from file://.
--- FR ---
[IMP] interface : Noto Sans partout, faces chargées avant le montage
L'écran doit ressembler aux captures et au texte que mesurent les épreuves.
L'interface emploie désormais Noto Sans, embarquée, pour tout élément,
contrôles de formulaire compris, avec un interligne de 1,3. Le point
d'entrée et le montage d'épreuve attendent les deux faces avant de monter :
aucun mesureur ne garde la largeur de repli ; une face qui ne se charge pas
est signalée et l'application se monte quand même. Les écrans que la police
plus large débordait ajustent leur propre CSS.
Vérifié : rouge d'abord ; 3216 node, 655 navigateur et 71 longues vertes
depuis l'index ; l'essai de démarrage charge la police par file://.
Assisted-by: Claude Opus 5.5
2026-10-09 13:01:49 -04:00
// La police que l'essai relève dans la page chargée sous file:// (§ 19.4) :
// les deux faces de Noto Sans chargées, le corps du document en Noto Sans,
// et la chaîne témoin à la longueur des avances, dans la tolérance ; chaque
// écart se nomme.
//
[ADD] build: no remote load in the bundle, portable startup trial
The delivery build is scanned for test modules, runner traces, the Vite
client and any loading http(s) URL, the worker bundle included and quote
entities decoded. The startup trial runs the real shell in portable mode
from separate exe, home and temp folders: it creates an event, loads the
small demonstration, generates in the worker, closes, and checks that the
files sit under exe/data and no lock is left. It runs only when launched
as itself, symlinks resolved, so it never exits green without running.
Checked: 2699 node, 62 long tests, make essai_demarrage conforme.
--- FR ---
[ADD] construction : rien de distant, essai de démarrage portable
La construction de livraison est balayée : ni module d'épreuve, ni trace du
lanceur, ni client de Vite, ni URL http(s) qui charge, paquet du travailleur
compris et entités de guillemets décodées. L'essai lance la vraie coquille
en mode portable, depuis des dossiers exe, home et temp distincts : il crée
un événement, charge la petite démonstration, génère dans le travailleur,
ferme, et vérifie les fichiers sous exe/data et aucun verrou restant. Il ne
part que lancé lui-même, liens résolus : jamais un vert sans essai.
Vérifié : 2699 node, 62 longues, make essai_demarrage conforme.
Assisted-by: Claude Opus 5.5
2026-10-07 16:08:15 -04:00
// L'environnement où l'essai de démarrage lance la coquille (§ 8.6) : trois
// dossiers distincts sous la racine de l'essai, aucun sous un autre ; le
// lanceur portable publie exe, le poste a home pour dossier personnel et
// temp pour dossier temporaire, et la configuration XDG du poste réel n'y
// entre pas. Le module se charge sans rien lancer : l'essai ne part que
// lorsque node l'exécute.
[ADD] print: real PDF output proven, table sheets split by tour groups
Printing goes through Chromium's PDF with the CSS page size; a browser
command reads the PDF under node and returns pages, MediaBox and text
only. The tests prove pages = sheets for the cut plan in A4 and Letter,
the paper size within 0.25 mm, BROUILLON and the placement timestamp on
every page of a draft and none of a retained plan. The startup trial
prints the preview through the real shell. A table sheet whose tours do
not fit side by side splits them into groups of consecutive tours, so a
long name wraps inside its page instead of being clipped.
Checked: node 3776, browser 753, node-long 101; startup trial conforms.
--- FR ---
[ADD] impression : PDF réel prouvé, feuilles par table en groupes
L'impression passe par le PDF de Chromium à la taille de page CSS ; une
commande lit le PDF sous node et ne rend que pages, MediaBox et textes.
Les épreuves prouvent pages = feuilles du plan découpé en A4 et Lettre,
le papier à 0,25 mm, BROUILLON et l'horodatage du placement sur chaque
page d'un brouillon et sur aucune d'un retenu. L'essai de démarrage
imprime l'aperçu par la vraie coquille. Une feuille par table dont les
tours ne tiennent pas côte à côte les répartit en groupes consécutifs :
un long nom passe à la ligne dans sa page au lieu d'être coupé.
Vérifié : node 3776, navigateur 753, longues 101 ; essai conforme.
Assisted-by: Claude Opus 5.5
2026-10-10 13:33:35 -04:00
//
// L'impression de l'aperçu par la coquille (§ 11.7) : une page du PDF par
// feuille de l'aperçu, chacune aux dimensions de la sienne à la tolérance
// près ; chaque écart se nomme.
[ADD] build: no remote load in the bundle, portable startup trial
The delivery build is scanned for test modules, runner traces, the Vite
client and any loading http(s) URL, the worker bundle included and quote
entities decoded. The startup trial runs the real shell in portable mode
from separate exe, home and temp folders: it creates an event, loads the
small demonstration, generates in the worker, closes, and checks that the
files sit under exe/data and no lock is left. It runs only when launched
as itself, symlinks resolved, so it never exits green without running.
Checked: 2699 node, 62 long tests, make essai_demarrage conforme.
--- FR ---
[ADD] construction : rien de distant, essai de démarrage portable
La construction de livraison est balayée : ni module d'épreuve, ni trace du
lanceur, ni client de Vite, ni URL http(s) qui charge, paquet du travailleur
compris et entités de guillemets décodées. L'essai lance la vraie coquille
en mode portable, depuis des dossiers exe, home et temp distincts : il crée
un événement, charge la petite démonstration, génère dans le travailleur,
ferme, et vérifie les fichiers sous exe/data et aucun verrou restant. Il ne
part que lancé lui-même, liens résolus : jamais un vert sans essai.
Vérifié : 2699 node, 62 longues, make essai_demarrage conforme.
Assisted-by: Claude Opus 5.5
2026-10-07 16:08:15 -04:00
import assert from 'node:assert/strict' ;
import { mkdtempSync , rmSync , symlinkSync } from 'node:fs' ;
import { tmpdir } from 'node:os' ;
import path , { dirname , join , relative , isAbsolute } from 'node:path' ;
import { fileURLToPath } from 'node:url' ;
[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
import { decrireSysteme } from '../electron/fichiers.js' ;
[ADD] build: no remote load in the bundle, portable startup trial
The delivery build is scanned for test modules, runner traces, the Vite
client and any loading http(s) URL, the worker bundle included and quote
entities decoded. The startup trial runs the real shell in portable mode
from separate exe, home and temp folders: it creates an event, loads the
small demonstration, generates in the worker, closes, and checks that the
files sit under exe/data and no lock is left. It runs only when launched
as itself, symlinks resolved, so it never exits green without running.
Checked: 2699 node, 62 long tests, make essai_demarrage conforme.
--- FR ---
[ADD] construction : rien de distant, essai de démarrage portable
La construction de livraison est balayée : ni module d'épreuve, ni trace du
lanceur, ni client de Vite, ni URL http(s) qui charge, paquet du travailleur
compris et entités de guillemets décodées. L'essai lance la vraie coquille
en mode portable, depuis des dossiers exe, home et temp distincts : il crée
un événement, charge la petite démonstration, génère dans le travailleur,
ferme, et vérifie les fichiers sous exe/data et aucun verrou restant. Il ne
part que lancé lui-même, liens résolus : jamais un vert sans essai.
Vérifié : 2699 node, 62 longues, make essai_demarrage conforme.
Assisted-by: Claude Opus 5.5
2026-10-07 16:08:15 -04:00
import { estSous } from '../src/stockage/dossier_travail.js' ;
import { describe , test } from '../test/lanceur.js' ;
[IMP] interface: Noto Sans everywhere, faces loaded before mounting
The screen must match the screenshots and the text the tests measure. The
interface now uses the embedded Noto Sans for every element, form controls
included, with a 1.3 line height. The entry point and the test mount wait
for both faces before mounting, so no text measurer caches the fallback
width; a face that fails to load is reported and the app mounts anyway.
The screens the wider font overflowed have their own CSS adjusted.
Checked: red first; 3216 node, 655 browser and 71 long tests green from the
index; the Electron start-up check loads the font from file://.
--- FR ---
[IMP] interface : Noto Sans partout, faces chargées avant le montage
L'écran doit ressembler aux captures et au texte que mesurent les épreuves.
L'interface emploie désormais Noto Sans, embarquée, pour tout élément,
contrôles de formulaire compris, avec un interligne de 1,3. Le point
d'entrée et le montage d'épreuve attendent les deux faces avant de monter :
aucun mesureur ne garde la largeur de repli ; une face qui ne se charge pas
est signalée et l'application se monte quand même. Les écrans que la police
plus large débordait ajustent leur propre CSS.
Vérifié : rouge d'abord ; 3216 node, 655 navigateur et 71 longues vertes
depuis l'index ; l'essai de démarrage charge la police par file://.
Assisted-by: Claude Opus 5.5
2026-10-09 13:01:49 -04:00
import {
donneesApplicativesSurExe ,
dossiersDEssai ,
environnementDEssai ,
estLanceDirectement ,
[ADD] print: real PDF output proven, table sheets split by tour groups
Printing goes through Chromium's PDF with the CSS page size; a browser
command reads the PDF under node and returns pages, MediaBox and text
only. The tests prove pages = sheets for the cut plan in A4 and Letter,
the paper size within 0.25 mm, BROUILLON and the placement timestamp on
every page of a draft and none of a retained plan. The startup trial
prints the preview through the real shell. A table sheet whose tours do
not fit side by side splits them into groups of consecutive tours, so a
long name wraps inside its page instead of being clipped.
Checked: node 3776, browser 753, node-long 101; startup trial conforms.
--- FR ---
[ADD] impression : PDF réel prouvé, feuilles par table en groupes
L'impression passe par le PDF de Chromium à la taille de page CSS ; une
commande lit le PDF sous node et ne rend que pages, MediaBox et textes.
Les épreuves prouvent pages = feuilles du plan découpé en A4 et Lettre,
le papier à 0,25 mm, BROUILLON et l'horodatage du placement sur chaque
page d'un brouillon et sur aucune d'un retenu. L'essai de démarrage
imprime l'aperçu par la vraie coquille. Une feuille par table dont les
tours ne tiennent pas côte à côte les répartit en groupes consécutifs :
un long nom passe à la ligne dans sa page au lieu d'être coupé.
Vérifié : node 3776, navigateur 753, longues 101 ; essai conforme.
Assisted-by: Claude Opus 5.5
2026-10-10 13:33:35 -04:00
fautesDImpression ,
[IMP] interface: Noto Sans everywhere, faces loaded before mounting
The screen must match the screenshots and the text the tests measure. The
interface now uses the embedded Noto Sans for every element, form controls
included, with a 1.3 line height. The entry point and the test mount wait
for both faces before mounting, so no text measurer caches the fallback
width; a face that fails to load is reported and the app mounts anyway.
The screens the wider font overflowed have their own CSS adjusted.
Checked: red first; 3216 node, 655 browser and 71 long tests green from the
index; the Electron start-up check loads the font from file://.
--- FR ---
[IMP] interface : Noto Sans partout, faces chargées avant le montage
L'écran doit ressembler aux captures et au texte que mesurent les épreuves.
L'interface emploie désormais Noto Sans, embarquée, pour tout élément,
contrôles de formulaire compris, avec un interligne de 1,3. Le point
d'entrée et le montage d'épreuve attendent les deux faces avant de monter :
aucun mesureur ne garde la largeur de repli ; une face qui ne se charge pas
est signalée et l'application se monte quand même. Les écrans que la police
plus large débordait ajustent leur propre CSS.
Vérifié : rouge d'abord ; 3216 node, 655 navigateur et 71 longues vertes
depuis l'index ; l'essai de démarrage charge la police par file://.
Assisted-by: Claude Opus 5.5
2026-10-09 13:01:49 -04:00
fautesDePolice ,
} from './essai_demarrage.js' ;
[ADD] build: no remote load in the bundle, portable startup trial
The delivery build is scanned for test modules, runner traces, the Vite
client and any loading http(s) URL, the worker bundle included and quote
entities decoded. The startup trial runs the real shell in portable mode
from separate exe, home and temp folders: it creates an event, loads the
small demonstration, generates in the worker, closes, and checks that the
files sit under exe/data and no lock is left. It runs only when launched
as itself, symlinks resolved, so it never exits green without running.
Checked: 2699 node, 62 long tests, make essai_demarrage conforme.
--- FR ---
[ADD] construction : rien de distant, essai de démarrage portable
La construction de livraison est balayée : ni module d'épreuve, ni trace du
lanceur, ni client de Vite, ni URL http(s) qui charge, paquet du travailleur
compris et entités de guillemets décodées. L'essai lance la vraie coquille
en mode portable, depuis des dossiers exe, home et temp distincts : il crée
un événement, charge la petite démonstration, génère dans le travailleur,
ferme, et vérifie les fichiers sous exe/data et aucun verrou restant. Il ne
part que lancé lui-même, liens résolus : jamais un vert sans essai.
Vérifié : 2699 node, 62 longues, make essai_demarrage conforme.
Assisted-by: Claude Opus 5.5
2026-10-07 16:08:15 -04:00
const RACINE _ESSAI = join ( '/' , 'racine-essai' , 'gtt-essai-demarrage-x' ) ;
// Vrai quand b est a ou se trouve sous a.
const sous = ( a , b ) => {
const chemin = relative ( a , b ) ;
return chemin === '' || ( ! chemin . startsWith ( '..' ) && ! isAbsolute ( chemin ) ) ;
} ;
describe ( 'essai de démarrage : dossiers et environnement (§ 8.6)' , ( ) => {
test ( 'trois dossiers distincts sous la racine, aucun sous un autre' , ( ) => {
const dossiers = dossiersDEssai ( RACINE _ESSAI ) ;
const noms = [ 'exe' , 'home' , 'temp' ] ;
assert . deepEqual ( Object . keys ( dossiers ) . sort ( ) , noms ) ;
const chemins = noms . map ( ( nom ) => dossiers [ nom ] ) ;
assert . equal ( chemins . length , 3 ) ;
for ( const chemin of chemins ) assert . ok ( sous ( RACINE _ESSAI , chemin ) && chemin !== RACINE _ESSAI , chemin ) ;
for ( const a of chemins ) {
for ( const b of chemins ) if ( a !== b ) assert . ok ( ! sous ( a , b ) , ` ${ b } sous ${ a } ` ) ;
}
assert . equal ( new Set ( chemins ) . size , 3 ) ;
} ) ;
test ( 'exe publié, home et temp posés, XDG_CONFIG_HOME retiré, le reste gardé' , ( ) => {
const dossiers = dossiersDEssai ( RACINE _ESSAI ) ;
const poste = Object . freeze ( {
HOME : join ( '/' , 'maison-originale' ) ,
XDG _CONFIG _HOME : join ( '/' , 'maison-originale' , 'configuration' ) ,
TMPDIR : join ( '/' , 'temporaire-du-poste' ) ,
PATH : join ( '/' , 'outils' ) ,
LANG : 'fr_CA.UTF-8' ,
} ) ;
const env = environnementDEssai ( poste , dossiers ) ;
assert . equal ( env . PORTABLE _EXECUTABLE _DIR , dossiers . exe ) ;
assert . equal ( env . HOME , dossiers . home ) ;
assert . equal ( env . TMPDIR , dossiers . temp ) ;
assert . equal ( env . TEMP , dossiers . temp ) ;
assert . equal ( env . TMP , dossiers . temp ) ;
assert . ok ( ! Object . hasOwn ( env , 'XDG_CONFIG_HOME' ) ) ;
assert . equal ( env . PATH , poste . PATH ) ;
assert . equal ( env . LANG , poste . LANG ) ;
// L'environnement du poste reste tel quel.
assert . equal ( poste . HOME , join ( '/' , 'maison-originale' ) ) ;
} ) ;
test ( 'sous X11 sans XAUTHORITY, le fichier d’ autorisation reste celui du dossier personnel réel' , ( ) => {
const dossiers = dossiersDEssai ( RACINE _ESSAI ) ;
const maison = join ( '/' , 'maison-originale' ) ;
const avecX = environnementDEssai ( { HOME : maison , DISPLAY : ':9' } , dossiers ) ;
assert . equal ( avecX . XAUTHORITY , join ( maison , '.Xauthority' ) ) ;
const pose = join ( '/' , 'autorisation-posee' ) ;
assert . equal ( environnementDEssai ( { HOME : maison , DISPLAY : ':9' , XAUTHORITY : pose } , dossiers ) . XAUTHORITY , pose ) ;
assert . ok ( ! Object . hasOwn ( environnementDEssai ( { HOME : maison } , dossiers ) , 'XAUTHORITY' ) ) ;
} ) ;
test ( 'sous Windows, un temporaire du poste rangé sous LOCALAPPDATA ne met pas exe sous les données applicatives' , ( ) => {
const W = path . win32 ;
const profil = 'C:\\Users\\profil-essai' ;
const local = W . join ( profil , 'AppData' , 'Local' ) ;
const poste = Object . freeze ( {
USERPROFILE : profil ,
APPDATA : W . join ( profil , 'AppData' , 'Roaming' ) ,
LOCALAPPDATA : local ,
TEMP : W . join ( local , 'Temp' ) ,
TMP : W . join ( local , 'Temp' ) ,
} ) ;
const dossiers = dossiersDEssai ( W . join ( poste . TEMP , 'gtt-essai-demarrage-x' ) , W ) ;
const env = environnementDEssai ( poste , dossiers , W ) ;
// Les données applicatives de la coquille (§ 8.6 point 2) : AppData,
// dossier connu que l'environnement ne déplace pas, le LOCALAPPDATA de
// son environnement, et le temporaire, que TMP désigne.
const emplacements = { insensibleCasse : true , separateur : '\\' } ;
assert . equal ( env . PORTABLE _EXECUTABLE _DIR , W . join ( poste . TEMP , 'gtt-essai-demarrage-x' , 'exe' ) ) ;
assert . deepEqual (
[ poste . APPDATA , env . LOCALAPPDATA , env . TMP ] . filter ( ( dossier ) => estSous ( dossiers . exe , dossier , emplacements ) ) ,
[ ] ,
) ;
// Le profil, AppData et LOCALAPPDATA de la coquille sont sous home.
assert . equal ( env . USERPROFILE , dossiers . home ) ;
assert . ok ( estSous ( env . APPDATA , dossiers . home , emplacements ) && env . APPDATA !== dossiers . home , env . APPDATA ) ;
assert . ok ( estSous ( env . LOCALAPPDATA , dossiers . home , emplacements ) && env . LOCALAPPDATA !== dossiers . home , env . LOCALAPPDATA ) ;
assert . deepEqual ( donneesApplicativesSurExe ( env , poste , 'win32' ) , [ ] ) ;
assert . equal ( poste . LOCALAPPDATA , local ) ;
} ) ;
test ( "la garde nomme les données applicatives qui portent exe, avant tout lancement" , ( ) => {
// Sous Linux : des chemins composés par path.posix de bout en bout, que
// le système hôte de l'épreuve ne change pas.
const P = path . posix ;
const R = P . join ( '/' , 'racine-essai' , 'gtt-essai-demarrage-x' ) ;
const dossiers = dossiersDEssai ( R , P ) ;
const env = environnementDEssai ( { HOME : P . join ( '/' , 'maison-originale' ) } , dossiers , P ) ;
assert . deepEqual ( donneesApplicativesSurExe ( env , { } , 'linux' ) , [ ] ) ;
// Le temporaire de la coquille au-dessus d'exe.
assert . deepEqual ( donneesApplicativesSurExe ( { ... env , TMPDIR : R } , { } , 'linux' ) , [ R ] ) ;
// Le dossier de configuration XDG au-dessus d'exe.
assert . deepEqual ( donneesApplicativesSurExe ( { ... env , XDG _CONFIG _HOME : R } , { } , 'linux' ) , [ R ] ) ;
// Sous Windows : l'AppData du poste, dossier connu, que l'environnement
// de la coquille ne déplace pas ; puis le LOCALAPPDATA de la coquille.
const W = path . win32 ;
const racine = W . join ( 'D:\\' , 'essais' , 'gtt-essai-demarrage-x' ) ;
const dossiersW = dossiersDEssai ( racine , W ) ;
const envW = environnementDEssai ( { } , dossiersW , W ) ;
assert . deepEqual ( donneesApplicativesSurExe ( envW , { } , 'win32' ) , [ ] ) ;
assert . deepEqual ( donneesApplicativesSurExe ( envW , { APPDATA : W . join ( 'D:\\' , 'essais' ) } , 'win32' ) , [
W . join ( 'D:\\' , 'essais' ) ,
] ) ;
assert . deepEqual ( donneesApplicativesSurExe ( { ... envW , LOCALAPPDATA : 'd:\\ESSAIS' } , { } , 'win32' ) , [ 'd:\\ESSAIS' ] ) ;
// Sans exécutable publié, rien ne le porte.
assert . deepEqual ( donneesApplicativesSurExe ( { TMPDIR : '/' } , { } , 'linux' ) , [ ] ) ;
} ) ;
[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
[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
test ( 'sous macOS, Application Support de la coquille et du poste ; sous Windows, TMP l’ emporte sur TEMP' , ( ) => {
// macOS : un essai rangé sous le dossier Application Support du poste,
// que le HOME de la coquille ne déplace pas, puis sous celui de la
// coquille.
const P = path . posix ;
const maison = P . join ( '/' , 'maison-essai' ) ;
const support = P . join ( maison , 'Library' , 'Application Support' ) ;
const dossiers = dossiersDEssai ( P . join ( support , 'gtt-essai-demarrage-x' ) , P ) ;
const env = environnementDEssai ( { HOME : maison } , dossiers , P ) ;
assert . deepEqual ( donneesApplicativesSurExe ( env , { } , 'darwin' ) , [ ] ) ;
assert . deepEqual ( donneesApplicativesSurExe ( env , { HOME : maison } , 'darwin' ) , [ support ] ) ;
assert . deepEqual ( donneesApplicativesSurExe ( { ... env , HOME : maison } , { } , 'darwin' ) , [ support ] ) ;
// Sous Linux, le même poste ne range rien sous Application Support.
assert . deepEqual ( donneesApplicativesSurExe ( env , { HOME : maison } , 'linux' ) , [ ] ) ;
// Windows : le temporaire est TMP, puis TEMP à défaut.
const W = path . win32 ;
const racine = W . join ( 'D:\\' , 'essais' , 'gtt-essai-demarrage-x' ) ;
const envW = environnementDEssai ( { } , dossiersDEssai ( racine , W ) , W ) ;
const dessus = W . join ( 'D:\\' , 'essais' ) ;
const ailleurs = W . join ( 'E:\\' , 'temporaire' ) ;
assert . deepEqual ( donneesApplicativesSurExe ( { ... envW , TMP : dessus , TEMP : ailleurs } , { } , 'win32' ) , [ dessus ] ) ;
assert . deepEqual ( donneesApplicativesSurExe ( { ... envW , TMP : ailleurs , TEMP : dessus } , { } , 'win32' ) , [ ] ) ;
assert . deepEqual ( donneesApplicativesSurExe ( { ... envW , TMP : '' , TEMP : dessus } , { } , 'win32' ) , [ dessus ] ) ;
} ) ;
[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 ( 'la liste des données applicatives est celle que decrireSysteme compose : un emplacement qu’ elle ajoute est jugé, et un app.getPath que l’ essai ne sait pas émuler lève' , ( ) => {
const P = path . posix ;
const R = P . join ( '/' , 'racine-essai' , 'gtt-essai-demarrage-x' ) ;
const dossiers = dossiersDEssai ( R , P ) ;
const env = environnementDEssai ( { HOME : P . join ( '/' , 'maison-originale' ) } , dossiers , P ) ;
const autre = P . join ( '/' , 'racine-essai' ) ;
// Une coquille qui range aussi GTT_DONNEES parmi ses données applicatives.
const avecUnDossier = ( poste ) => {
const decrit = decrireSysteme ( poste ) ;
const donneesApplicatives = [ ... decrit . emplacements . donneesApplicatives , poste . env . GTT _DONNEES ] ;
return { ... decrit , emplacements : { ... decrit . emplacements , donneesApplicatives } } ;
} ;
assert . deepEqual ( donneesApplicativesSurExe ( { ... env , GTT _DONNEES : autre } , { } , 'linux' , avecUnDossier ) , [ autre ] ) ;
assert . deepEqual ( donneesApplicativesSurExe ( { ... env , GTT _DONNEES : P . join ( '/' , 'ailleurs' ) } , { } , 'linux' , avecUnDossier ) , [ ] ) ;
// Une coquille qui lit un autre dossier du système : l'essai le refuse
// plutôt que de juger une liste incomplète.
const avecUserData = ( poste ) => {
poste . cheminSysteme ( 'userData' ) ;
return decrireSysteme ( poste ) ;
} ;
assert . throws ( ( ) => donneesApplicativesSurExe ( env , { } , 'linux' , avecUserData ) , /userData/ ) ;
} ) ;
[ADD] build: no remote load in the bundle, portable startup trial
The delivery build is scanned for test modules, runner traces, the Vite
client and any loading http(s) URL, the worker bundle included and quote
entities decoded. The startup trial runs the real shell in portable mode
from separate exe, home and temp folders: it creates an event, loads the
small demonstration, generates in the worker, closes, and checks that the
files sit under exe/data and no lock is left. It runs only when launched
as itself, symlinks resolved, so it never exits green without running.
Checked: 2699 node, 62 long tests, make essai_demarrage conforme.
--- FR ---
[ADD] construction : rien de distant, essai de démarrage portable
La construction de livraison est balayée : ni module d'épreuve, ni trace du
lanceur, ni client de Vite, ni URL http(s) qui charge, paquet du travailleur
compris et entités de guillemets décodées. L'essai lance la vraie coquille
en mode portable, depuis des dossiers exe, home et temp distincts : il crée
un événement, charge la petite démonstration, génère dans le travailleur,
ferme, et vérifie les fichiers sous exe/data et aucun verrou restant. Il ne
part que lancé lui-même, liens résolus : jamais un vert sans essai.
Vérifié : 2699 node, 62 longues, make essai_demarrage conforme.
Assisted-by: Claude Opus 5.5
2026-10-07 16:08:15 -04:00
} ) ;
describe ( 'essai de démarrage : lancement direct' , ( ) => {
const MODULE = fileURLToPath ( new URL ( './essai_demarrage.js' , import . meta . url ) ) ;
// Le lien désigne le dossier du module : une jonction sous Windows, qui se
// crée sans privilège, un lien symbolique ailleurs.
test ( 'le script atteint par un lien symbolique est le module' , ( ) => {
const dossier = mkdtempSync ( join ( tmpdir ( ) , 'gtt-essai-lien-' ) ) ;
try {
const lien = join ( dossier , 'scripts' ) ;
symlinkSync ( dirname ( MODULE ) , lien , 'junction' ) ;
assert . equal ( estLanceDirectement ( join ( lien , 'essai_demarrage.js' ) ) , true ) ;
assert . equal ( estLanceDirectement ( MODULE ) , true ) ;
} finally {
rmSync ( dossier , { recursive : true , force : true } ) ;
}
} ) ;
test ( 'un autre script, un script absent ou aucun script ne lance pas l’ essai' , ( ) => {
assert . equal ( estLanceDirectement ( fileURLToPath ( import . meta . url ) ) , false ) ;
assert . equal ( estLanceDirectement ( join ( tmpdir ( ) , 'gtt-essai-absent' , 'essai_demarrage.js' ) ) , false ) ;
assert . equal ( estLanceDirectement ( undefined ) , false ) ;
} ) ;
} ) ;
[IMP] interface: Noto Sans everywhere, faces loaded before mounting
The screen must match the screenshots and the text the tests measure. The
interface now uses the embedded Noto Sans for every element, form controls
included, with a 1.3 line height. The entry point and the test mount wait
for both faces before mounting, so no text measurer caches the fallback
width; a face that fails to load is reported and the app mounts anyway.
The screens the wider font overflowed have their own CSS adjusted.
Checked: red first; 3216 node, 655 browser and 71 long tests green from the
index; the Electron start-up check loads the font from file://.
--- FR ---
[IMP] interface : Noto Sans partout, faces chargées avant le montage
L'écran doit ressembler aux captures et au texte que mesurent les épreuves.
L'interface emploie désormais Noto Sans, embarquée, pour tout élément,
contrôles de formulaire compris, avec un interligne de 1,3. Le point
d'entrée et le montage d'épreuve attendent les deux faces avant de monter :
aucun mesureur ne garde la largeur de repli ; une face qui ne se charge pas
est signalée et l'application se monte quand même. Les écrans que la police
plus large débordait ajustent leur propre CSS.
Vérifié : rouge d'abord ; 3216 node, 655 navigateur et 71 longues vertes
depuis l'index ; l'essai de démarrage charge la police par file://.
Assisted-by: Claude Opus 5.5
2026-10-09 13:01:49 -04:00
describe ( 'essai de démarrage : la police de la page (§ 19.4)' , ( ) => {
// Un relevé conforme : les deux faces chargées, une face d'une autre
// famille, le corps en Noto Sans, la chaîne témoin à 1 000 px.
const RELEVE = Object . freeze ( {
faces : [
{ famille : 'Noto Sans' , graisse : '400' , style : 'normal' , etat : 'loaded' } ,
{ famille : '"Noto Sans"' , graisse : '700' , style : 'normal' , etat : 'loaded' } ,
{ famille : 'Famille Inventée' , graisse : '400' , style : 'normal' , etat : 'unloaded' } ,
] ,
familleCorps : '"Noto Sans", system-ui, sans-serif' ,
largeurTemoin : 1000 ,
} ) ;
const TEMOIN = Object . freeze ( { longueurAttendue : 1000 , tolerance : 0.01 } ) ;
test ( 'un relevé conforme ne porte aucune faute ; un écart égal à la tolérance est admis' , ( ) => {
assert . deepEqual ( fautesDePolice ( RELEVE , TEMOIN ) , [ ] ) ;
assert . deepEqual ( fautesDePolice ( { ... RELEVE , largeurTemoin : 1010 } , TEMOIN ) , [ ] ) ;
assert . deepEqual ( fautesDePolice ( { ... RELEVE , largeurTemoin : 990 } , TEMOIN ) , [ ] ) ;
} ) ;
test ( 'une face absente ou non chargée se nomme par sa graisse ; une autre famille n’ y entre pas' , ( ) => {
const sans700 = RELEVE . faces . filter ( ( { graisse , famille } ) => ! ( graisse === '700' && famille . includes ( 'Noto' ) ) ) ;
assert . deepEqual ( fautesDePolice ( { ... RELEVE , faces : sans700 } , TEMOIN ) , [ 'face 700 de Noto Sans absente' ] ) ;
const enErreur = RELEVE . faces . map ( ( face ) => ( face . graisse === '400' && face . famille === 'Noto Sans' ? { ... face , etat : 'error' } : face ) ) ;
assert . deepEqual ( fautesDePolice ( { ... RELEVE , faces : enErreur } , TEMOIN ) , [ 'face 400 de Noto Sans : error' ] ) ;
assert . deepEqual ( fautesDePolice ( { ... RELEVE , faces : [ ] } , TEMOIN ) , [
'face 400 de Noto Sans absente' ,
'face 700 de Noto Sans absente' ,
] ) ;
} ) ;
test ( 'un corps du document dans une autre famille se nomme, telle que la page la calcule' , ( ) => {
assert . deepEqual ( fautesDePolice ( { ... RELEVE , familleCorps : 'system-ui, sans-serif' } , TEMOIN ) , [
'corps du document en system-ui, sans-serif' ,
] ) ;
} ) ;
test ( 'une chaîne témoin hors de la tolérance se nomme, mesurée et attendue' , ( ) => {
assert . deepEqual ( fautesDePolice ( { ... RELEVE , largeurTemoin : 1010.5 } , TEMOIN ) , [ 'chaîne témoin : 1010.5 px pour 1000 px' ] ) ;
assert . deepEqual ( fautesDePolice ( { ... RELEVE , largeurTemoin : 960 } , TEMOIN ) , [ 'chaîne témoin : 960 px pour 1000 px' ] ) ;
assert . deepEqual ( fautesDePolice ( { ... RELEVE , largeurTemoin : 0 } , TEMOIN ) , [ 'chaîne témoin : 0 px pour 1000 px' ] ) ;
} ) ;
} ) ;
[ADD] print: real PDF output proven, table sheets split by tour groups
Printing goes through Chromium's PDF with the CSS page size; a browser
command reads the PDF under node and returns pages, MediaBox and text
only. The tests prove pages = sheets for the cut plan in A4 and Letter,
the paper size within 0.25 mm, BROUILLON and the placement timestamp on
every page of a draft and none of a retained plan. The startup trial
prints the preview through the real shell. A table sheet whose tours do
not fit side by side splits them into groups of consecutive tours, so a
long name wraps inside its page instead of being clipped.
Checked: node 3776, browser 753, node-long 101; startup trial conforms.
--- FR ---
[ADD] impression : PDF réel prouvé, feuilles par table en groupes
L'impression passe par le PDF de Chromium à la taille de page CSS ; une
commande lit le PDF sous node et ne rend que pages, MediaBox et textes.
Les épreuves prouvent pages = feuilles du plan découpé en A4 et Lettre,
le papier à 0,25 mm, BROUILLON et l'horodatage du placement sur chaque
page d'un brouillon et sur aucune d'un retenu. L'essai de démarrage
imprime l'aperçu par la vraie coquille. Une feuille par table dont les
tours ne tiennent pas côte à côte les répartit en groupes consécutifs :
un long nom passe à la ligne dans sa page au lieu d'être coupé.
Vérifié : node 3776, navigateur 753, longues 101 ; essai conforme.
Assisted-by: Claude Opus 5.5
2026-10-10 13:33:35 -04:00
describe ( 'essai de démarrage : l’ impression de l’ aperçu (§ 11.7)' , ( ) => {
// Trois feuilles de 210 × 297 mm et la troisième à plat, et les boîtes de
// média qui leur répondent, chacune décalée de moins que la tolérance.
const FEUILLES = Object . freeze ( [
{ largeur : 210 , hauteur : 297 } ,
{ largeur : 210 , hauteur : 297 } ,
{ largeur : 297 , hauteur : 210 } ,
] ) ;
const PAGES = Object . freeze ( [
{ largeur : 209.9 , hauteur : 297.1 } ,
{ largeur : 210 , hauteur : 297 } ,
{ largeur : 297.2 , hauteur : 209.8 } ,
] ) ;
const TOLERANCE = 0.25 ;
test ( 'une page par feuille, chacune aux dimensions de la sienne : aucune faute ; un écart égal à la tolérance est admis' , ( ) => {
assert . deepEqual ( fautesDImpression ( FEUILLES , PAGES , TOLERANCE ) , [ ] ) ;
const auBord = PAGES . map ( ( { largeur , hauteur } , rang ) => ( rang === 0 ? { largeur : 210.25 , hauteur : 296.75 } : { largeur , hauteur } ) ) ;
assert . deepEqual ( fautesDImpression ( FEUILLES , auBord , TOLERANCE ) , [ ] ) ;
} ) ;
test ( 'un compte de pages qui n’ est pas celui des feuilles se nomme, et rien d’ autre ne se compare' , ( ) => {
assert . deepEqual ( fautesDImpression ( FEUILLES , PAGES . slice ( 0 , 1 ) , TOLERANCE ) , [ '1 page(s) pour 3 feuille(s)' ] ) ;
assert . deepEqual ( fautesDImpression ( FEUILLES . slice ( 0 , 1 ) , PAGES , TOLERANCE ) , [ '3 page(s) pour 1 feuille(s)' ] ) ;
} ) ;
test ( 'une page hors de la tolérance se nomme par son rang, lue puis attendue ; une orientation inversée aussi' , ( ) => {
const large = PAGES . map ( ( page , rang ) => ( rang === 1 ? { largeur : 210.3 , hauteur : 297 } : page ) ) ;
assert . deepEqual ( fautesDImpression ( FEUILLES , large , TOLERANCE ) , [ 'page 2 : 210.3 × 297 mm pour une feuille de 210 × 297 mm' ] ) ;
const debout = PAGES . map ( ( page , rang ) => ( rang === 2 ? { largeur : 210 , hauteur : 297 } : page ) ) ;
assert . deepEqual ( fautesDImpression ( FEUILLES , debout , TOLERANCE ) , [ 'page 3 : 210 × 297 mm pour une feuille de 297 × 210 mm' ] ) ;
} ) ;
test ( 'un aperçu sans feuille est une faute, même sans page' , ( ) => {
assert . deepEqual ( fautesDImpression ( [ ] , [ ] , TOLERANCE ) , [ 'aucune feuille dans l’ aperçu' ] ) ;
} ) ;
} ) ;