[ADD] install: Node 26 and system packages, per family and under Nix
./install.sh installs what is missing through apt, dnf, zypper or
pacman, then Node of the .node-version major from nodejs.org, SHA-256
checked, outside the repository, then npm ci and Electron's binary,
which its package no longer fetches at install; ldd refuses a missing
library. ./install_dev.sh adds Playwright's Chromium, fonts, and podman
on amd64. Under NixOS a pinned shell.nix provides Node, Electron and
Chromium, which the browser tests then launch. Build-script tests run
on Linux only, where § 16 builds.
Checked: ten library tests, three mutations caught; local install.
--- FR ---
[ADD] installation : Node 26 et paquets du système, par famille et Nix
./install.sh installe ce qui manque par apt, dnf, zypper ou pacman, puis
Node de la majeure de .node-version, de nodejs.org, somme SHA-256
vérifiée, hors du dépôt, puis npm ci et le binaire d'Electron, que son
paquet ne télécharge plus à l'installation ; ldd refuse une bibliothèque
absente. ./install_dev.sh ajoute le Chromium de Playwright, des polices,
et podman sur amd64. Sous NixOS, un shell.nix épinglé fournit Node,
Electron et Chromium, que lancent alors les épreuves du navigateur. Les
épreuves du script de construction ne se jouent que sous Linux (§ 16).
Vérifié : dix épreuves de la bibliothèque, trois mutations attrapées ;
installation locale.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 05:51:48 -04:00
|
|
|
// © 2026 TechnoLibre (http://www.technolibre.ca)
|
|
|
|
|
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
|
|
|
|
|
|
|
|
|
// Options du Chromium des épreuves du navigateur, que vitest.config.js passe
|
|
|
|
|
// au fournisseur Playwright. Playwright lance par défaut le Chromium qu'il a
|
|
|
|
|
// téléchargé ; GTT_CHROMIUM désigne un autre exécutable. Sous NixOS, où les
|
|
|
|
|
// binaires téléchargés ne démarrent pas, scripts/installation/shell.nix y
|
|
|
|
|
// met celui de nixpkgs.
|
[FIX] web storage: refuse a name Chromium did not convert, UTF-8 launch
Under a non-UTF-8 locale, Chromium returns the parent folder for an
accented folder name without raising, so a write meant for that folder
replaced a file of the same name in its parent. Each step of a path walk
now compares the handle with its parent and refuses NOM_NON_CONVERTI —
LECTURE on reads, ECRITURE on writes — and nothing lands elsewhere. The
test launcher gives Chromium LANG=C.UTF-8 when the locale is not UTF-8, as
the containers do, and the matrix installs ps where an image lacks it.
Checked: 1776 node and 45 browser tests; the guard holds under LC_ALL=C.
--- FR ---
[FIX] stockage web : refuser un nom non converti, lancement en UTF-8
Hors locale UTF-8, Chromium rend le dossier parent pour un nom de dossier
accentué, sans lever : une écriture destinée à ce dossier remplaçait un
fichier homonyme de son parent. Chaque pas d'un chemin compare désormais le
dossier obtenu à son parent et refuse NOM_NON_CONVERTI — LECTURE en lecture,
ECRITURE en écriture — : rien n'atterrit ailleurs. Le lanceur des épreuves
donne LANG=C.UTF-8 à Chromium quand la locale n'est pas UTF-8, comme les
conteneurs, et la matrice installe ps là où une image en manque.
Vérifié : 1776 node, 45 navigateur ; la garde tient sous LC_ALL=C.
Assisted-by: Claude Opus 5.5
2026-10-07 02:35:12 -04:00
|
|
|
//
|
|
|
|
|
// Sous Linux, Chromium convertit le nom d'une entrée de l'OPFS par les
|
|
|
|
|
// paramètres régionaux de son processus : hors UTF-8, un nom hors ASCII ne
|
|
|
|
|
// se convertit pas, et les épreuves du navigateur n'éprouvent plus le
|
|
|
|
|
// stockage. Les paramètres régionaux qui décident de cette conversion sont
|
|
|
|
|
// ceux de LC_CTYPE : LC_ALL, puis LC_CTYPE, puis LANG, la première variable
|
|
|
|
|
// non vide ; aucune, c'est « C ». Quand ils ne sont pas UTF-8, Chromium
|
|
|
|
|
// reçoit une copie de l'environnement sous LANG=C.UTF-8, LC_ALL et LC_CTYPE
|
|
|
|
|
// retirés puisqu'ils l'emporteraient sur LANG, comme
|
|
|
|
|
// scripts/verifier_systemes.sh le lance dans ses conteneurs. Seul le nom des
|
|
|
|
|
// paramètres régionaux se lit : un nom UTF-8 que l'hôte n'a pas générés
|
|
|
|
|
// laisse Chromium sous « C », et l'épreuve de l'OPFS du navigateur nomme
|
|
|
|
|
// alors cette cause. C.UTF-8 vient avec la glibc 2.35 et suivantes, et avec
|
|
|
|
|
// les distributions qui le livrent avant ; l'environnement reçu reste tel
|
|
|
|
|
// quel.
|
[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
|
|
|
//
|
|
|
|
|
// Le projet navigateur reçoit optionsNavigateur : les mêmes options, plus
|
|
|
|
|
// trois arguments de lancement et un contexte au facteur d'échelle 1
|
|
|
|
|
// (§ 19.2, § 19.4). Le résolveur de Chromium ne résout que localhost ; un
|
|
|
|
|
// mandataire mort, 127.0.0.1:9, reçoit toute requête vers une adresse
|
|
|
|
|
// littérale, que le résolveur ne voit pas. Chromium contourne d'office le
|
|
|
|
|
// mandataire pour la boucle locale — localhost, 127.0.0.0/8, [::1] — : la
|
|
|
|
|
// page des épreuves se charge, et une requête qui sortirait de la machine
|
|
|
|
|
// échoue en connexion, observable, au lieu de partir. La règle de
|
|
|
|
|
// contournement <-loopback> retirerait ce contournement ; elle n'est pas
|
|
|
|
|
// posée. Le facteur d'échelle se fixe au lancement, que ni Playwright ni
|
|
|
|
|
// une commande du standard ne fixent pour la fenêtre de la machine.
|
|
|
|
|
// suivreRequetes relève, page par page, les requêtes hors de la boucle
|
|
|
|
|
// locale que la page émet, refusées ou non, et la cause de chaque échec.
|
[FIX] web storage: refuse a name Chromium did not convert, UTF-8 launch
Under a non-UTF-8 locale, Chromium returns the parent folder for an
accented folder name without raising, so a write meant for that folder
replaced a file of the same name in its parent. Each step of a path walk
now compares the handle with its parent and refuses NOM_NON_CONVERTI —
LECTURE on reads, ECRITURE on writes — and nothing lands elsewhere. The
test launcher gives Chromium LANG=C.UTF-8 when the locale is not UTF-8, as
the containers do, and the matrix installs ps where an image lacks it.
Checked: 1776 node and 45 browser tests; the guard holds under LC_ALL=C.
--- FR ---
[FIX] stockage web : refuser un nom non converti, lancement en UTF-8
Hors locale UTF-8, Chromium rend le dossier parent pour un nom de dossier
accentué, sans lever : une écriture destinée à ce dossier remplaçait un
fichier homonyme de son parent. Chaque pas d'un chemin compare désormais le
dossier obtenu à son parent et refuse NOM_NON_CONVERTI — LECTURE en lecture,
ECRITURE en écriture — : rien n'atterrit ailleurs. Le lanceur des épreuves
donne LANG=C.UTF-8 à Chromium quand la locale n'est pas UTF-8, comme les
conteneurs, et la matrice installe ps là où une image en manque.
Vérifié : 1776 node, 45 navigateur ; la garde tient sous LC_ALL=C.
Assisted-by: Claude Opus 5.5
2026-10-07 02:35:12 -04:00
|
|
|
|
|
|
|
|
// Un nom de paramètres régionaux dont le jeu de caractères est UTF-8 :
|
|
|
|
|
// « fr_CA.UTF-8 », « C.utf8 », « de_DE.UTF-8@euro ».
|
|
|
|
|
const NOM_UTF8 = /\.utf-?8(@[^.]*)?$/i;
|
|
|
|
|
|
|
|
|
|
// Paramètres régionaux de LC_CTYPE que env donne, « C » sans aucun.
|
|
|
|
|
function caracteresDe(env) {
|
|
|
|
|
for (const variable of ['LC_ALL', 'LC_CTYPE', 'LANG']) {
|
|
|
|
|
if (env[variable]) return env[variable];
|
|
|
|
|
}
|
|
|
|
|
return 'C';
|
|
|
|
|
}
|
|
|
|
|
|
[ADD] install: Node 26 and system packages, per family and under Nix
./install.sh installs what is missing through apt, dnf, zypper or
pacman, then Node of the .node-version major from nodejs.org, SHA-256
checked, outside the repository, then npm ci and Electron's binary,
which its package no longer fetches at install; ldd refuses a missing
library. ./install_dev.sh adds Playwright's Chromium, fonts, and podman
on amd64. Under NixOS a pinned shell.nix provides Node, Electron and
Chromium, which the browser tests then launch. Build-script tests run
on Linux only, where § 16 builds.
Checked: ten library tests, three mutations caught; local install.
--- FR ---
[ADD] installation : Node 26 et paquets du système, par famille et Nix
./install.sh installe ce qui manque par apt, dnf, zypper ou pacman, puis
Node de la majeure de .node-version, de nodejs.org, somme SHA-256
vérifiée, hors du dépôt, puis npm ci et le binaire d'Electron, que son
paquet ne télécharge plus à l'installation ; ldd refuse une bibliothèque
absente. ./install_dev.sh ajoute le Chromium de Playwright, des polices,
et podman sur amd64. Sous NixOS, un shell.nix épinglé fournit Node,
Electron et Chromium, que lancent alors les épreuves du navigateur. Les
épreuves du script de construction ne se jouent que sous Linux (§ 16).
Vérifié : dix épreuves de la bibliothèque, trois mutations attrapées ;
installation locale.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 05:51:48 -04:00
|
|
|
export function optionsChromium(env) {
|
[FIX] web storage: refuse a name Chromium did not convert, UTF-8 launch
Under a non-UTF-8 locale, Chromium returns the parent folder for an
accented folder name without raising, so a write meant for that folder
replaced a file of the same name in its parent. Each step of a path walk
now compares the handle with its parent and refuses NOM_NON_CONVERTI —
LECTURE on reads, ECRITURE on writes — and nothing lands elsewhere. The
test launcher gives Chromium LANG=C.UTF-8 when the locale is not UTF-8, as
the containers do, and the matrix installs ps where an image lacks it.
Checked: 1776 node and 45 browser tests; the guard holds under LC_ALL=C.
--- FR ---
[FIX] stockage web : refuser un nom non converti, lancement en UTF-8
Hors locale UTF-8, Chromium rend le dossier parent pour un nom de dossier
accentué, sans lever : une écriture destinée à ce dossier remplaçait un
fichier homonyme de son parent. Chaque pas d'un chemin compare désormais le
dossier obtenu à son parent et refuse NOM_NON_CONVERTI — LECTURE en lecture,
ECRITURE en écriture — : rien n'atterrit ailleurs. Le lanceur des épreuves
donne LANG=C.UTF-8 à Chromium quand la locale n'est pas UTF-8, comme les
conteneurs, et la matrice installe ps là où une image en manque.
Vérifié : 1776 node, 45 navigateur ; la garde tient sous LC_ALL=C.
Assisted-by: Claude Opus 5.5
2026-10-07 02:35:12 -04:00
|
|
|
const lancement = {};
|
|
|
|
|
if (env.GTT_CHROMIUM) lancement.executablePath = env.GTT_CHROMIUM;
|
|
|
|
|
if (!NOM_UTF8.test(caracteresDe(env))) {
|
|
|
|
|
lancement.env = { ...env, LANG: 'C.UTF-8' };
|
|
|
|
|
delete lancement.env.LC_ALL;
|
|
|
|
|
delete lancement.env.LC_CTYPE;
|
|
|
|
|
}
|
|
|
|
|
return Object.keys(lancement).length > 0 ? { launchOptions: lancement } : {};
|
[ADD] install: Node 26 and system packages, per family and under Nix
./install.sh installs what is missing through apt, dnf, zypper or
pacman, then Node of the .node-version major from nodejs.org, SHA-256
checked, outside the repository, then npm ci and Electron's binary,
which its package no longer fetches at install; ldd refuses a missing
library. ./install_dev.sh adds Playwright's Chromium, fonts, and podman
on amd64. Under NixOS a pinned shell.nix provides Node, Electron and
Chromium, which the browser tests then launch. Build-script tests run
on Linux only, where § 16 builds.
Checked: ten library tests, three mutations caught; local install.
--- FR ---
[ADD] installation : Node 26 et paquets du système, par famille et Nix
./install.sh installe ce qui manque par apt, dnf, zypper ou pacman, puis
Node de la majeure de .node-version, de nodejs.org, somme SHA-256
vérifiée, hors du dépôt, puis npm ci et le binaire d'Electron, que son
paquet ne télécharge plus à l'installation ; ldd refuse une bibliothèque
absente. ./install_dev.sh ajoute le Chromium de Playwright, des polices,
et podman sur amd64. Sous NixOS, un shell.nix épinglé fournit Node,
Electron et Chromium, que lancent alors les épreuves du navigateur. Les
épreuves du script de construction ne se jouent que sous Linux (§ 16).
Vérifié : dix épreuves de la bibliothèque, trois mutations attrapées ;
installation locale.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 05:51:48 -04:00
|
|
|
}
|
[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
|
|
|
|
|
|
|
|
/** Les arguments de lancement du projet navigateur, dans cet ordre. */
|
|
|
|
|
export const ARGUMENTS_NAVIGATEUR = Object.freeze([
|
|
|
|
|
'--host-resolver-rules=MAP * ~NOTFOUND, EXCLUDE localhost',
|
|
|
|
|
'--proxy-server=127.0.0.1:9',
|
|
|
|
|
'--force-device-scale-factor=1',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
/** Le mode d'affichage du navigateur des captures, nommé au manifeste
|
|
|
|
|
* (§ 19.4) : une valeur de la configuration, jamais lue de la machine. */
|
|
|
|
|
export const MODE_AFFICHAGE = 'chromium-headless-shell';
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Les options du fournisseur Playwright du projet navigateur : celles de
|
|
|
|
|
* optionsChromium(env), ARGUMENTS_NAVIGATEUR ajoutés à la fin des arguments
|
|
|
|
|
* de lancement, et un contexte de navigation au facteur d'échelle 1. env
|
|
|
|
|
* n'est pas modifié.
|
|
|
|
|
*
|
|
|
|
|
* @param {Record<string, string|undefined>} env
|
|
|
|
|
*/
|
|
|
|
|
export function optionsNavigateur(env) {
|
|
|
|
|
const { launchOptions = {}, ...autres } = optionsChromium(env);
|
|
|
|
|
return {
|
|
|
|
|
...autres,
|
|
|
|
|
launchOptions: { ...launchOptions, args: [...(launchOptions.args ?? []), ...ARGUMENTS_NAVIGATEUR] },
|
|
|
|
|
contextOptions: { deviceScaleFactor: 1 },
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Les schémas d'une adresse qui ne passe pas par le réseau.
|
|
|
|
|
const SANS_RESEAU = new Set(['data:', 'blob:', 'about:']);
|
|
|
|
|
|
|
|
|
|
// Un nom d'hôte de la boucle locale, tel que l'URL le rend : localhost,
|
|
|
|
|
// une adresse IPv4 de 127.0.0.0/8, ou [::1].
|
|
|
|
|
const BOUCLE = /^(localhost|127(\.\d{1,3}){3}|\[::1\])$/;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Vrai quand une requête vers cette adresse sortirait de la machine : toute
|
|
|
|
|
* adresse dont l'hôte n'est pas de la boucle locale, hors des schémas sans
|
|
|
|
|
* réseau (data:, blob:, about:). Une adresse illisible compte comme sortante.
|
|
|
|
|
*
|
|
|
|
|
* @param {string} adresse
|
|
|
|
|
* @returns {boolean}
|
|
|
|
|
*/
|
|
|
|
|
export function horsBoucle(adresse) {
|
|
|
|
|
let url;
|
|
|
|
|
try {
|
|
|
|
|
url = new URL(adresse);
|
|
|
|
|
} catch {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (SANS_RESEAU.has(url.protocol)) return false;
|
|
|
|
|
return !BOUCLE.test(url.hostname);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Les requêtes hors boucle relevées par page, et leurs échecs, en attente
|
|
|
|
|
// d'être lus.
|
|
|
|
|
const RELEVES = new WeakMap();
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Suit les requêtes de la page Playwright : la première fois, deux
|
|
|
|
|
* écouteurs se posent sur la page, « request » et « requestfailed », qui
|
|
|
|
|
* relèvent chaque adresse hors de la boucle locale, cadres compris, et
|
|
|
|
|
* chaque échec d'une telle requête avec sa cause — l'errorText de
|
|
|
|
|
* Chromium, net::ERR_PROXY_CONNECTION_FAILED quand le mandataire mort l'a
|
|
|
|
|
* reçue. Rend { relever(), releverEchecs() } : les adresses, puis les
|
|
|
|
|
* échecs { adresse, cause }, relevés depuis la lecture précédente, dans
|
|
|
|
|
* l'ordre, chacun vidé par sa lecture. Deux suivis de la même page
|
|
|
|
|
* partagent le même relevé.
|
|
|
|
|
*
|
|
|
|
|
* @param {{on: (evenement: string, ecouteur: Function) => unknown}} page
|
|
|
|
|
* @returns {{relever: () => string[], releverEchecs: () => Array<{adresse: string, cause: string|null}>}}
|
|
|
|
|
*/
|
|
|
|
|
export function suivreRequetes(page) {
|
|
|
|
|
if (!RELEVES.has(page)) {
|
|
|
|
|
const releve = { requetes: [], echecs: [] };
|
|
|
|
|
RELEVES.set(page, releve);
|
|
|
|
|
page.on('request', (requete) => {
|
|
|
|
|
const adresse = requete.url();
|
|
|
|
|
if (horsBoucle(adresse)) releve.requetes.push(adresse);
|
|
|
|
|
});
|
|
|
|
|
page.on('requestfailed', (requete) => {
|
|
|
|
|
const adresse = requete.url();
|
|
|
|
|
if (horsBoucle(adresse)) releve.echecs.push({ adresse, cause: requete.failure()?.errorText ?? null });
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
const { requetes, echecs } = RELEVES.get(page);
|
|
|
|
|
return { relever: () => requetes.splice(0, requetes.length), releverEchecs: () => echecs.splice(0, echecs.length) };
|
|
|
|
|
}
|