[ADD] application: session, named commands, French label table
The session holds one event, opens it read-only and writes only after
"Modifier" takes the lock (§ 8.4, § 8.8); calls run one after another, so a
double click writes two entries in order. Every change is a pure named
command with a frozen French label (§ 8.2); undo, redo and return write
entries and never truncate the journal (§ 8.3). A failed write — a gesture,
or the lock on a removed key — breaks the medium until the event is written
elsewhere (§ 8.6). The label table covers every code storage, CSV and the
session emit, and its test sweeps the sources so a new code needs a text.
Checked: 1416 node tests; depot.js at 100 % with the identifier lister keeps.
--- FR ---
[ADD] application : séance, commandes nommées, table des libellés
La séance tient un événement, l'ouvre en lecture et n'écrit qu'après que
« Modifier » a pris le verrou (§ 8.4, § 8.8) ; les appels passent l'un après
l'autre : un double clic écrit deux entrées, dans l'ordre. Toute modification
est une commande nommée pure au libellé français figé (§ 8.2) ; défaire,
refaire et revenir écrivent des entrées sans tronquer le journal (§ 8.3). Une
écriture refusée — un geste, ou le verrou sur une clé retirée — rompt le
support jusqu'à l'écriture ailleurs (§ 8.6). La table des libellés couvre
chaque code du stockage, du CSV et de la séance ; son épreuve balaie les
sources, et un code neuf exige un texte.
Vérifié : 1416 épreuves node ; depot.js à 100 %, lister gardant l'identifiant.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 23:17:36 -04:00
// © 2026 TechnoLibre (http://www.technolibre.ca)
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
// Épreuves de la table des libellés (§ 14.6, § 14.2). Le balayage relève, dans
// les sources de src/stockage, src/csv et src/application — épreuves et table
// exclues —, chaque code qu'elles émettent : le premier argument littéral d'un
// appel, constructeur d'erreur ou fabrique d'avertissement ; la valeur
// littérale d'une clé code ; celle d'une clé raison, cause ou remede, qui
// nomme un code de détail ; la valeur littérale d'une constante, qui nomme le
// code que le module émet par elle ; et les tables de codes des contrats,
// celle de src/stockage/types.js et celle de src/csv/erreurs.js quand il en
// porte une. Chaque code relevé a son texte, chaque texte a un émetteur, et
// un balayage vide échoue. Chaque texte se compose ensuite de détails
// d'exemple. Les noms d'épreuve sont inventés.
[ADD] application: clock, translation, formatting, displayed name
The screens need one clock, one translation mechanism and one way to
format and name, or each component grows its own. horloge.js is the only
code that reads time and arms timers, injected so tests drive it.
traduction.js keys texts by domain, in two families of tables, raises on a
missing key and never shows a raw key; libelles/ splits the label table by
domain. format.js formats numbers and dates in French, and personnes.js
composes the displayed name the frozen labels use. Nothing persists
between sessions.
Checked: 1773 node and 43 browser tests.
--- FR ---
[ADD] application : horloge, traduction, mise en forme, nom affiché
Les écrans exigent une horloge, un mécanisme de traduction et une façon de
mettre en forme et de nommer, sans quoi chaque composant ferait les siens.
horloge.js est le seul code qui lit l'heure et arme des minuteries, injecté
pour que les épreuves le pilotent. traduction.js range les textes par
domaine, en deux familles de tables, lève sur une clé absente et ne montre
jamais une clé brute ; libelles/ découpe la table des libellés par domaine.
format.js met en forme nombres et dates en français, et personnes.js
compose le nom affiché qu'emploient les libellés figés. Rien ne persiste
d'une séance à l'autre.
Vérifié : 1773 node et 43 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 02:33:39 -04:00
//
// La table se partage en fichiers, un par domaine, sous libelles/ : chacun
// exporte TABLE, une Map, et libelles.js les assemble par ordre alphabétique
// des fichiers ; les tables, comme l'assemblage, sont hors du balayage.
[ADD] application: generation controller, worker, evaluation table
Generating must never block the screen or record a plan that breaks a
rule. The controller refuses before launching, in a fixed order: an
out-of-range setting named by the engine's own check, the session's
refusals, then what makes the run useless. It runs the search in a worker
or in the thread, records only propositions whose invariants hold, skips
plans already present, and keeps the last duration for the session only.
The evaluation table ranks propositions on the minimum of encounters and
names the reservations a proposition does not honour.
Checked: 2112 node and 60 node-long tests; coverage floors kept.
--- FR ---
[ADD] application : génération, travailleur, table d'évaluation
Générer ne doit jamais bloquer l'écran ni enregistrer un plan qui enfreint
une règle. Le contrôleur refuse avant tout lancement, dans un ordre fixe :
un réglage hors de son domaine, nommé par la règle même du moteur, les
refus de la séance, puis ce qui rend le calcul vain. Il lance la recherche
dans un travailleur ou dans le fil, n'enregistre que les propositions dont
les invariants tiennent, écarte les plans déjà présents, et garde la
dernière durée pour la seule séance. La table d'évaluation classe les
propositions sur le minimum de rencontres et nomme les réservations
qu'une proposition ne tient pas.
Vérifié : 2112 node et 60 node-long ; seuils de couverture tenus.
Assisted-by: Claude Opus 5.5
2026-10-07 06:09:45 -04:00
//
// src/moteur se relève à part, épreuves exclues : chaque littéral en
// capitales de ses sources, hors commentaires, y est un code — celui d'une
// ErreurConfiguration ou d'une violation de ses vérificateurs —, que le
// moteur lève par une constante ou par une expression autant que par un
// appel. Une seule exception, nommée : les valeurs de domine dans manque.js,
// qui disent laquelle de deux propositions sert le mieux et ne s'affichent
// pas comme un code.
[ADD] application: session, named commands, French label table
The session holds one event, opens it read-only and writes only after
"Modifier" takes the lock (§ 8.4, § 8.8); calls run one after another, so a
double click writes two entries in order. Every change is a pure named
command with a frozen French label (§ 8.2); undo, redo and return write
entries and never truncate the journal (§ 8.3). A failed write — a gesture,
or the lock on a removed key — breaks the medium until the event is written
elsewhere (§ 8.6). The label table covers every code storage, CSV and the
session emit, and its test sweeps the sources so a new code needs a text.
Checked: 1416 node tests; depot.js at 100 % with the identifier lister keeps.
--- FR ---
[ADD] application : séance, commandes nommées, table des libellés
La séance tient un événement, l'ouvre en lecture et n'écrit qu'après que
« Modifier » a pris le verrou (§ 8.4, § 8.8) ; les appels passent l'un après
l'autre : un double clic écrit deux entrées, dans l'ordre. Toute modification
est une commande nommée pure au libellé français figé (§ 8.2) ; défaire,
refaire et revenir écrivent des entrées sans tronquer le journal (§ 8.3). Une
écriture refusée — un geste, ou le verrou sur une clé retirée — rompt le
support jusqu'à l'écriture ailleurs (§ 8.6). La table des libellés couvre
chaque code du stockage, du CSV et de la séance ; son épreuve balaie les
sources, et un code neuf exige un texte.
Vérifié : 1416 épreuves node ; depot.js à 100 %, lister gardant l'identifiant.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 23:17:36 -04:00
import assert from 'node:assert/strict' ;
import { readdirSync , readFileSync } from 'node:fs' ;
[ADD] application: clock, translation, formatting, displayed name
The screens need one clock, one translation mechanism and one way to
format and name, or each component grows its own. horloge.js is the only
code that reads time and arms timers, injected so tests drive it.
traduction.js keys texts by domain, in two families of tables, raises on a
missing key and never shows a raw key; libelles/ splits the label table by
domain. format.js formats numbers and dates in French, and personnes.js
composes the displayed name the frozen labels use. Nothing persists
between sessions.
Checked: 1773 node and 43 browser tests.
--- FR ---
[ADD] application : horloge, traduction, mise en forme, nom affiché
Les écrans exigent une horloge, un mécanisme de traduction et une façon de
mettre en forme et de nommer, sans quoi chaque composant ferait les siens.
horloge.js est le seul code qui lit l'heure et arme des minuteries, injecté
pour que les épreuves le pilotent. traduction.js range les textes par
domaine, en deux familles de tables, lève sur une clé absente et ne montre
jamais une clé brute ; libelles/ découpe la table des libellés par domaine.
format.js met en forme nombres et dates en français, et personnes.js
compose le nom affiché qu'emploient les libellés figés. Rien ne persiste
d'une séance à l'autre.
Vérifié : 1773 node et 43 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 02:33:39 -04:00
import { join , relative , sep } from 'node:path' ;
[ADD] application: session, named commands, French label table
The session holds one event, opens it read-only and writes only after
"Modifier" takes the lock (§ 8.4, § 8.8); calls run one after another, so a
double click writes two entries in order. Every change is a pure named
command with a frozen French label (§ 8.2); undo, redo and return write
entries and never truncate the journal (§ 8.3). A failed write — a gesture,
or the lock on a removed key — breaks the medium until the event is written
elsewhere (§ 8.6). The label table covers every code storage, CSV and the
session emit, and its test sweeps the sources so a new code needs a text.
Checked: 1416 node tests; depot.js at 100 % with the identifier lister keeps.
--- FR ---
[ADD] application : séance, commandes nommées, table des libellés
La séance tient un événement, l'ouvre en lecture et n'écrit qu'après que
« Modifier » a pris le verrou (§ 8.4, § 8.8) ; les appels passent l'un après
l'autre : un double clic écrit deux entrées, dans l'ordre. Toute modification
est une commande nommée pure au libellé français figé (§ 8.2) ; défaire,
refaire et revenir écrivent des entrées sans tronquer le journal (§ 8.3). Une
écriture refusée — un geste, ou le verrou sur une clé retirée — rompt le
support jusqu'à l'écriture ailleurs (§ 8.6). La table des libellés couvre
chaque code du stockage, du CSV et de la séance ; son épreuve balaie les
sources, et un code neuf exige un texte.
Vérifié : 1416 épreuves node ; depot.js à 100 %, lister gardant l'identifiant.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 23:17:36 -04:00
import { fileURLToPath } from 'node:url' ;
import { describe , test } from '../../test/lanceur.js' ;
[ADD] application: generation controller, worker, evaluation table
Generating must never block the screen or record a plan that breaks a
rule. The controller refuses before launching, in a fixed order: an
out-of-range setting named by the engine's own check, the session's
refusals, then what makes the run useless. It runs the search in a worker
or in the thread, records only propositions whose invariants hold, skips
plans already present, and keeps the last duration for the session only.
The evaluation table ranks propositions on the minimum of encounters and
names the reservations a proposition does not honour.
Checked: 2112 node and 60 node-long tests; coverage floors kept.
--- FR ---
[ADD] application : génération, travailleur, table d'évaluation
Générer ne doit jamais bloquer l'écran ni enregistrer un plan qui enfreint
une règle. Le contrôleur refuse avant tout lancement, dans un ordre fixe :
un réglage hors de son domaine, nommé par la règle même du moteur, les
refus de la séance, puis ce qui rend le calcul vain. Il lance la recherche
dans un travailleur ou dans le fil, n'enregistre que les propositions dont
les invariants tiennent, écarte les plans déjà présents, et garde la
dernière durée pour la seule séance. La table d'évaluation classe les
propositions sur le minimum de rencontres et nomme les réservations
qu'une proposition ne tient pas.
Vérifié : 2112 node et 60 node-long ; seuils de couverture tenus.
Assisted-by: Claude Opus 5.5
2026-10-07 06:09:45 -04:00
import { CRITERES } from '../moteur/classement.js' ;
import { TABLES _INTERFACE } from '../interface/libelles.js' ;
import { creerTraduction } from './traduction.js' ;
[ADD] application: clock, translation, formatting, displayed name
The screens need one clock, one translation mechanism and one way to
format and name, or each component grows its own. horloge.js is the only
code that reads time and arms timers, injected so tests drive it.
traduction.js keys texts by domain, in two families of tables, raises on a
missing key and never shows a raw key; libelles/ splits the label table by
domain. format.js formats numbers and dates in French, and personnes.js
composes the displayed name the frozen labels use. Nothing persists
between sessions.
Checked: 1773 node and 43 browser tests.
--- FR ---
[ADD] application : horloge, traduction, mise en forme, nom affiché
Les écrans exigent une horloge, un mécanisme de traduction et une façon de
mettre en forme et de nommer, sans quoi chaque composant ferait les siens.
horloge.js est le seul code qui lit l'heure et arme des minuteries, injecté
pour que les épreuves le pilotent. traduction.js range les textes par
domaine, en deux familles de tables, lève sur une clé absente et ne montre
jamais une clé brute ; libelles/ découpe la table des libellés par domaine.
format.js met en forme nombres et dates en français, et personnes.js
compose le nom affiché qu'emploient les libellés figés. Rien ne persiste
d'une séance à l'autre.
Vérifié : 1773 node et 43 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 02:33:39 -04:00
import { CODES , TABLES _APPLICATION , libelle } from './libelles.js' ;
[ADD] application: session, named commands, French label table
The session holds one event, opens it read-only and writes only after
"Modifier" takes the lock (§ 8.4, § 8.8); calls run one after another, so a
double click writes two entries in order. Every change is a pure named
command with a frozen French label (§ 8.2); undo, redo and return write
entries and never truncate the journal (§ 8.3). A failed write — a gesture,
or the lock on a removed key — breaks the medium until the event is written
elsewhere (§ 8.6). The label table covers every code storage, CSV and the
session emit, and its test sweeps the sources so a new code needs a text.
Checked: 1416 node tests; depot.js at 100 % with the identifier lister keeps.
--- FR ---
[ADD] application : séance, commandes nommées, table des libellés
La séance tient un événement, l'ouvre en lecture et n'écrit qu'après que
« Modifier » a pris le verrou (§ 8.4, § 8.8) ; les appels passent l'un après
l'autre : un double clic écrit deux entrées, dans l'ordre. Toute modification
est une commande nommée pure au libellé français figé (§ 8.2) ; défaire,
refaire et revenir écrivent des entrées sans tronquer le journal (§ 8.3). Une
écriture refusée — un geste, ou le verrou sur une clé retirée — rompt le
support jusqu'à l'écriture ailleurs (§ 8.6). La table des libellés couvre
chaque code du stockage, du CSV et de la séance ; son épreuve balaie les
sources, et un code neuf exige un texte.
Vérifié : 1416 épreuves node ; depot.js à 100 %, lister gardant l'identifiant.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 23:17:36 -04:00
const RACINE = fileURLToPath ( new URL ( '../..' , import . meta . url ) ) ;
[FIX] iteration 3 review: seat targets, gestures, warnings, settings
The wheel no longer zooms during a gesture, so a drop lands under the
ghost. A seat is a target the size of its pitch. A floor drop refuses to
remove an every-tour reservation. A seat reservation the retained plan
does not hold is flagged at the seat, with its swap as remedy. DERIVE names
the person; capacities have an upper bound; Generate waits for a pending
setting; a worker that fails to load shows a refusal; § 5.3 constraints
get their switches. Commands are not re-entrant and a double click does not
confirm. Checked: 2755 node, 550 browser tests, from the index alone.
--- FR ---
[FIX] revue de l'itération 3 : cibles, gestes, avertissements, réglages
La molette ne zoome plus pendant un geste : le dépôt tombe sous le fantôme.
Un siège est une cible de la taille de son pas. Un dépôt sur le sol refuse
de retirer une réservation de tous les tours. Une réservation à un siège
que le retenu ne tient pas est relevée au siège, l'échange en remède. La
dérive nomme la personne ; les capacités ont une borne ; Générer attend un
réglage en cours ; un travailleur qui ne se charge pas donne un refus ; le
§ 5.3 a ses bascules. Les commandes ne sont pas ré-entrantes et un double
clic ne confirme pas. Vérifié : 2755 node, 550 navigateur, index seul.
Assisted-by: Claude Opus 5.5
2026-10-07 17:50:35 -04:00
// Les arbres balayés, et les fichiers qui portent une table de codes : le
// dépôt du plan lève un refus que la commande n'a pas à lever, et son texte
// vit ici, avec ceux des commandes des places.
[ADD] application: session, named commands, French label table
The session holds one event, opens it read-only and writes only after
"Modifier" takes the lock (§ 8.4, § 8.8); calls run one after another, so a
double click writes two entries in order. Every change is a pure named
command with a frozen French label (§ 8.2); undo, redo and return write
entries and never truncate the journal (§ 8.3). A failed write — a gesture,
or the lock on a removed key — breaks the medium until the event is written
elsewhere (§ 8.6). The label table covers every code storage, CSV and the
session emit, and its test sweeps the sources so a new code needs a text.
Checked: 1416 node tests; depot.js at 100 % with the identifier lister keeps.
--- FR ---
[ADD] application : séance, commandes nommées, table des libellés
La séance tient un événement, l'ouvre en lecture et n'écrit qu'après que
« Modifier » a pris le verrou (§ 8.4, § 8.8) ; les appels passent l'un après
l'autre : un double clic écrit deux entrées, dans l'ordre. Toute modification
est une commande nommée pure au libellé français figé (§ 8.2) ; défaire,
refaire et revenir écrivent des entrées sans tronquer le journal (§ 8.3). Une
écriture refusée — un geste, ou le verrou sur une clé retirée — rompt le
support jusqu'à l'écriture ailleurs (§ 8.6). La table des libellés couvre
chaque code du stockage, du CSV et de la séance ; son épreuve balaie les
sources, et un code neuf exige un texte.
Vérifié : 1416 épreuves node ; depot.js à 100 %, lister gardant l'identifiant.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 23:17:36 -04:00
const ARBRES = [ 'src/stockage' , 'src/csv' , 'src/application' ] ;
[FIX] iteration 3 review: seat targets, gestures, warnings, settings
The wheel no longer zooms during a gesture, so a drop lands under the
ghost. A seat is a target the size of its pitch. A floor drop refuses to
remove an every-tour reservation. A seat reservation the retained plan
does not hold is flagged at the seat, with its swap as remedy. DERIVE names
the person; capacities have an upper bound; Generate waits for a pending
setting; a worker that fails to load shows a refusal; § 5.3 constraints
get their switches. Commands are not re-entrant and a double click does not
confirm. Checked: 2755 node, 550 browser tests, from the index alone.
--- FR ---
[FIX] revue de l'itération 3 : cibles, gestes, avertissements, réglages
La molette ne zoome plus pendant un geste : le dépôt tombe sous le fantôme.
Un siège est une cible de la taille de son pas. Un dépôt sur le sol refuse
de retirer une réservation de tous les tours. Une réservation à un siège
que le retenu ne tient pas est relevée au siège, l'échange en remède. La
dérive nomme la personne ; les capacités ont une borne ; Générer attend un
réglage en cours ; un travailleur qui ne se charge pas donne un refus ; le
§ 5.3 a ses bascules. Les commandes ne sont pas ré-entrantes et un double
clic ne confirme pas. Vérifié : 2755 node, 550 navigateur, index seul.
Assisted-by: Claude Opus 5.5
2026-10-07 17:50:35 -04:00
const TABLES = [ 'src/stockage/types.js' , 'src/csv/erreurs.js' , 'src/interface/plan/depot.js' ] ;
[ADD] application: clock, translation, formatting, displayed name
The screens need one clock, one translation mechanism and one way to
format and name, or each component grows its own. horloge.js is the only
code that reads time and arms timers, injected so tests drive it.
traduction.js keys texts by domain, in two families of tables, raises on a
missing key and never shows a raw key; libelles/ splits the label table by
domain. format.js formats numbers and dates in French, and personnes.js
composes the displayed name the frozen labels use. Nothing persists
between sessions.
Checked: 1773 node and 43 browser tests.
--- FR ---
[ADD] application : horloge, traduction, mise en forme, nom affiché
Les écrans exigent une horloge, un mécanisme de traduction et une façon de
mettre en forme et de nommer, sans quoi chaque composant ferait les siens.
horloge.js est le seul code qui lit l'heure et arme des minuteries, injecté
pour que les épreuves le pilotent. traduction.js range les textes par
domaine, en deux familles de tables, lève sur une clé absente et ne montre
jamais une clé brute ; libelles/ découpe la table des libellés par domaine.
format.js met en forme nombres et dates en français, et personnes.js
compose le nom affiché qu'emploient les libellés figés. Rien ne persiste
d'une séance à l'autre.
Vérifié : 1773 node et 43 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 02:33:39 -04:00
// L'assemblage et les tables des textes n'émettent rien : leurs clés sont ce
// qu'on éprouve, et un texte qui en compose un autre le nomme sans l'émettre.
[ADD] application: session, named commands, French label table
The session holds one event, opens it read-only and writes only after
"Modifier" takes the lock (§ 8.4, § 8.8); calls run one after another, so a
double click writes two entries in order. Every change is a pure named
command with a frozen French label (§ 8.2); undo, redo and return write
entries and never truncate the journal (§ 8.3). A failed write — a gesture,
or the lock on a removed key — breaks the medium until the event is written
elsewhere (§ 8.6). The label table covers every code storage, CSV and the
session emit, and its test sweeps the sources so a new code needs a text.
Checked: 1416 node tests; depot.js at 100 % with the identifier lister keeps.
--- FR ---
[ADD] application : séance, commandes nommées, table des libellés
La séance tient un événement, l'ouvre en lecture et n'écrit qu'après que
« Modifier » a pris le verrou (§ 8.4, § 8.8) ; les appels passent l'un après
l'autre : un double clic écrit deux entrées, dans l'ordre. Toute modification
est une commande nommée pure au libellé français figé (§ 8.2) ; défaire,
refaire et revenir écrivent des entrées sans tronquer le journal (§ 8.3). Une
écriture refusée — un geste, ou le verrou sur une clé retirée — rompt le
support jusqu'à l'écriture ailleurs (§ 8.6). La table des libellés couvre
chaque code du stockage, du CSV et de la séance ; son épreuve balaie les
sources, et un code neuf exige un texte.
Vérifié : 1416 épreuves node ; depot.js à 100 %, lister gardant l'identifiant.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 23:17:36 -04:00
const TABLE _DES _TEXTES = join ( 'src' , 'application' , 'libelles.js' ) ;
[ADD] application: clock, translation, formatting, displayed name
The screens need one clock, one translation mechanism and one way to
format and name, or each component grows its own. horloge.js is the only
code that reads time and arms timers, injected so tests drive it.
traduction.js keys texts by domain, in two families of tables, raises on a
missing key and never shows a raw key; libelles/ splits the label table by
domain. format.js formats numbers and dates in French, and personnes.js
composes the displayed name the frozen labels use. Nothing persists
between sessions.
Checked: 1773 node and 43 browser tests.
--- FR ---
[ADD] application : horloge, traduction, mise en forme, nom affiché
Les écrans exigent une horloge, un mécanisme de traduction et une façon de
mettre en forme et de nommer, sans quoi chaque composant ferait les siens.
horloge.js est le seul code qui lit l'heure et arme des minuteries, injecté
pour que les épreuves le pilotent. traduction.js range les textes par
domaine, en deux familles de tables, lève sur une clé absente et ne montre
jamais une clé brute ; libelles/ découpe la table des libellés par domaine.
format.js met en forme nombres et dates en français, et personnes.js
compose le nom affiché qu'emploient les libellés figés. Rien ne persiste
d'une séance à l'autre.
Vérifié : 1773 node et 43 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 02:33:39 -04:00
const DOSSIER _DES _TABLES = join ( 'src' , 'application' , 'libelles' ) ;
const horsDesTextes = ( chemin ) => chemin !== TABLE _DES _TEXTES && ! chemin . startsWith ( DOSSIER _DES _TABLES + sep ) ;
[ADD] application: session, named commands, French label table
The session holds one event, opens it read-only and writes only after
"Modifier" takes the lock (§ 8.4, § 8.8); calls run one after another, so a
double click writes two entries in order. Every change is a pure named
command with a frozen French label (§ 8.2); undo, redo and return write
entries and never truncate the journal (§ 8.3). A failed write — a gesture,
or the lock on a removed key — breaks the medium until the event is written
elsewhere (§ 8.6). The label table covers every code storage, CSV and the
session emit, and its test sweeps the sources so a new code needs a text.
Checked: 1416 node tests; depot.js at 100 % with the identifier lister keeps.
--- FR ---
[ADD] application : séance, commandes nommées, table des libellés
La séance tient un événement, l'ouvre en lecture et n'écrit qu'après que
« Modifier » a pris le verrou (§ 8.4, § 8.8) ; les appels passent l'un après
l'autre : un double clic écrit deux entrées, dans l'ordre. Toute modification
est une commande nommée pure au libellé français figé (§ 8.2) ; défaire,
refaire et revenir écrivent des entrées sans tronquer le journal (§ 8.3). Une
écriture refusée — un geste, ou le verrou sur une clé retirée — rompt le
support jusqu'à l'écriture ailleurs (§ 8.6). La table des libellés couvre
chaque code du stockage, du CSV et de la séance ; son épreuve balaie les
sources, et un code neuf exige un texte.
Vérifié : 1416 épreuves node ; depot.js à 100 %, lister gardant l'identifiant.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 23:17:36 -04:00
// --- Le balayage ----------------------------------------------------------
// Commentaires et littéraux de chaîne, dans l'ordre où ils s'ouvrent : un
// commentaire se blanchit, sauf ses fins de ligne, et une chaîne reste, si
// bien qu'un « // » dans une chaîne n'ouvre rien.
const JETONS = /\/\*[\s\S]*?\*\/|\/\/[^\n]*|'(?:\\.|[^'\\\n])*'|"(?:\\.|[^"\\\n])*"|`(?:\\[\s\S]|[^`\\])*`/g ;
const sansCommentaires = ( texte ) =>
texte . replace ( JETONS , ( jeton ) => ( jeton . startsWith ( '/' ) ? jeton . replace ( /[^\n]/g , ' ' ) : jeton ) ) ;
// Un code : des capitales, des chiffres, des soulignés entre deux mots.
const CODE = String . raw ` '([A-Z][A-Z0-9]*(?:_[A-Z0-9]+)*)' ` ;
const MOTIFS _EMISSION = [
// Premier argument d'un appel : new ErreurStockage('ABSENT', …),
// refuser('UTF8_INVALIDE'), ajouter('DERIVE', …), libelle('ENTREE_…', …).
// normalize reçoit le nom d'une forme Unicode, non un code.
new RegExp ( String . raw ` \b (?!normalize \b )[A-Za-z_ $ ][ \w $ ]* \s * \( \s * ${ CODE } ` , 'g' ) ,
// Un objet qui porte un code : { code: 'NOM_ABSENT', … }.
new RegExp ( String . raw ` \b code \s *: \s * ${ CODE } ` , 'g' ) ,
// Un code de détail : { raison: 'BLOQUE' }, { cause: 'NOMBRE_DE_CHAMPS' },
// { remede: 'ENREGISTRER_EN_UTF8' }.
new RegExp ( String . raw ` \b (?:raison|cause|remede) \s *: \s * ${ CODE } ` , 'g' ) ,
// Une constante qui nomme un code, que le module émet ensuite par elle :
// const RELU = 'RELECTURE_DIFFERENTE', puis { cause: RELU }.
new RegExp ( String . raw ` \b (?:const|let|var) \s +[A-Za-z_ $ ][ \w $ ]* \s *= \s * ${ CODE } ` , 'g' ) ,
] ;
/** Codes qu'un texte source émet, hors commentaires, dans l'ordre du texte. */
function codesEmis ( texte ) {
const net = sansCommentaires ( texte ) ;
return MOTIFS _EMISSION . flatMap ( ( motif ) => [ ... net . matchAll ( motif ) ] . map ( ( trouve ) => ( { indice : trouve . index , code : trouve [ 1 ] } ) ) )
. sort ( ( a , b ) => a . indice - b . indice )
. map ( ( { code } ) => code ) ;
}
/ * *
* Codes d ' une table de contrat : dans un commentaire de documentation qui
* s ' ouvre sur « Codes d … » , chaque ligne qui porte , trois espaces après
* l ' astérisque , un code en tête .
* /
function codesDesTables ( texte ) {
const codes = [ ] ;
for ( const [ bloc ] of texte . matchAll ( /\/\*\*[\s\S]*?\*\//g ) ) {
if ( ! /^\s*\*\s*Codes d/m . test ( bloc ) ) continue ;
for ( const [ , code ] of bloc . matchAll ( /^[ \t]*\*[ \t]{3}([A-Z][A-Z0-9]*(?:_[A-Z0-9]+)*)(?=\s|$)/gm ) ) codes . push ( code ) ;
}
return codes ;
}
// Modules d'un arbre, épreuves et table des textes exclues, triés.
function modulesDe ( arbre ) {
return readdirSync ( join ( RACINE , arbre ) , { recursive : true } )
. map ( ( nom ) => join ( arbre , nom ) )
[ADD] application: clock, translation, formatting, displayed name
The screens need one clock, one translation mechanism and one way to
format and name, or each component grows its own. horloge.js is the only
code that reads time and arms timers, injected so tests drive it.
traduction.js keys texts by domain, in two families of tables, raises on a
missing key and never shows a raw key; libelles/ splits the label table by
domain. format.js formats numbers and dates in French, and personnes.js
composes the displayed name the frozen labels use. Nothing persists
between sessions.
Checked: 1773 node and 43 browser tests.
--- FR ---
[ADD] application : horloge, traduction, mise en forme, nom affiché
Les écrans exigent une horloge, un mécanisme de traduction et une façon de
mettre en forme et de nommer, sans quoi chaque composant ferait les siens.
horloge.js est le seul code qui lit l'heure et arme des minuteries, injecté
pour que les épreuves le pilotent. traduction.js range les textes par
domaine, en deux familles de tables, lève sur une clé absente et ne montre
jamais une clé brute ; libelles/ découpe la table des libellés par domaine.
format.js met en forme nombres et dates en français, et personnes.js
compose le nom affiché qu'emploient les libellés figés. Rien ne persiste
d'une séance à l'autre.
Vérifié : 1773 node et 43 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 02:33:39 -04:00
. filter ( ( chemin ) => chemin . endsWith ( '.js' ) && ! /\.test\.js$/ . test ( chemin ) && horsDesTextes ( chemin ) )
[ADD] application: session, named commands, French label table
The session holds one event, opens it read-only and writes only after
"Modifier" takes the lock (§ 8.4, § 8.8); calls run one after another, so a
double click writes two entries in order. Every change is a pure named
command with a frozen French label (§ 8.2); undo, redo and return write
entries and never truncate the journal (§ 8.3). A failed write — a gesture,
or the lock on a removed key — breaks the medium until the event is written
elsewhere (§ 8.6). The label table covers every code storage, CSV and the
session emit, and its test sweeps the sources so a new code needs a text.
Checked: 1416 node tests; depot.js at 100 % with the identifier lister keeps.
--- FR ---
[ADD] application : séance, commandes nommées, table des libellés
La séance tient un événement, l'ouvre en lecture et n'écrit qu'après que
« Modifier » a pris le verrou (§ 8.4, § 8.8) ; les appels passent l'un après
l'autre : un double clic écrit deux entrées, dans l'ordre. Toute modification
est une commande nommée pure au libellé français figé (§ 8.2) ; défaire,
refaire et revenir écrivent des entrées sans tronquer le journal (§ 8.3). Une
écriture refusée — un geste, ou le verrou sur une clé retirée — rompt le
support jusqu'à l'écriture ailleurs (§ 8.6). La table des libellés couvre
chaque code du stockage, du CSV et de la séance ; son épreuve balaie les
sources, et un code neuf exige un texte.
Vérifié : 1416 épreuves node ; depot.js à 100 %, lister gardant l'identifiant.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 23:17:36 -04:00
. sort ( ) ;
}
[ADD] application: generation controller, worker, evaluation table
Generating must never block the screen or record a plan that breaks a
rule. The controller refuses before launching, in a fixed order: an
out-of-range setting named by the engine's own check, the session's
refusals, then what makes the run useless. It runs the search in a worker
or in the thread, records only propositions whose invariants hold, skips
plans already present, and keeps the last duration for the session only.
The evaluation table ranks propositions on the minimum of encounters and
names the reservations a proposition does not honour.
Checked: 2112 node and 60 node-long tests; coverage floors kept.
--- FR ---
[ADD] application : génération, travailleur, table d'évaluation
Générer ne doit jamais bloquer l'écran ni enregistrer un plan qui enfreint
une règle. Le contrôleur refuse avant tout lancement, dans un ordre fixe :
un réglage hors de son domaine, nommé par la règle même du moteur, les
refus de la séance, puis ce qui rend le calcul vain. Il lance la recherche
dans un travailleur ou dans le fil, n'enregistre que les propositions dont
les invariants tiennent, écarte les plans déjà présents, et garde la
dernière durée pour la seule séance. La table d'évaluation classe les
propositions sur le minimum de rencontres et nomme les réservations
qu'une proposition ne tient pas.
Vérifié : 2112 node et 60 node-long ; seuils de couverture tenus.
Assisted-by: Claude Opus 5.5
2026-10-07 06:09:45 -04:00
// --- Le relevé du moteur -----------------------------------------------------
const MOTEUR = 'src/moteur' ;
// Les littéraux en capitales du moteur qui ne sont pas des codes, par module.
const PAS _DES _CODES = new Map ( [ [ join ( MOTEUR , 'manque.js' ) , new Set ( [ 'A' , 'B' ] ) ] ] ) ;
// Les modules du moteur qui portent des codes.
const MODULES _A _CODES = [ 'configuration.js' , 'plafond.js' , 'recherche.js' , 'verification.js' ] . map ( ( nom ) => join ( MOTEUR , nom ) ) ;
const LITTERAL _CODE = new RegExp ( CODE , 'g' ) ;
/** Chaque littéral en capitales d'un texte source, hors commentaires. */
function litterauxEnCapitales ( texte ) {
return [ ... sansCommentaires ( texte ) . matchAll ( LITTERAL _CODE ) ] . map ( ( trouve ) => trouve [ 1 ] ) ;
}
// Relevé du moteur : chaque code de ses modules, hors exceptions nommées,
// le module qui émet chacun en premier, et les modules qui en portent.
function releverMoteur ( ) {
const codes = new Set ( ) ;
const emetteurs = new Map ( ) ;
const porteurs = [ ] ;
for ( const chemin of modulesDe ( MOTEUR ) ) {
const exclus = PAS _DES _CODES . get ( chemin ) ? ? new Set ( ) ;
const trouves = litterauxEnCapitales ( readFileSync ( join ( RACINE , chemin ) , 'utf8' ) ) . filter ( ( code ) => ! exclus . has ( code ) ) ;
if ( trouves . length > 0 ) porteurs . push ( chemin ) ;
for ( const code of trouves ) {
codes . add ( code ) ;
if ( ! emetteurs . has ( code ) ) emetteurs . set ( code , chemin ) ;
}
}
return { codes , emetteurs , porteurs } ;
}
[ADD] application: session, named commands, French label table
The session holds one event, opens it read-only and writes only after
"Modifier" takes the lock (§ 8.4, § 8.8); calls run one after another, so a
double click writes two entries in order. Every change is a pure named
command with a frozen French label (§ 8.2); undo, redo and return write
entries and never truncate the journal (§ 8.3). A failed write — a gesture,
or the lock on a removed key — breaks the medium until the event is written
elsewhere (§ 8.6). The label table covers every code storage, CSV and the
session emit, and its test sweeps the sources so a new code needs a text.
Checked: 1416 node tests; depot.js at 100 % with the identifier lister keeps.
--- FR ---
[ADD] application : séance, commandes nommées, table des libellés
La séance tient un événement, l'ouvre en lecture et n'écrit qu'après que
« Modifier » a pris le verrou (§ 8.4, § 8.8) ; les appels passent l'un après
l'autre : un double clic écrit deux entrées, dans l'ordre. Toute modification
est une commande nommée pure au libellé français figé (§ 8.2) ; défaire,
refaire et revenir écrivent des entrées sans tronquer le journal (§ 8.3). Une
écriture refusée — un geste, ou le verrou sur une clé retirée — rompt le
support jusqu'à l'écriture ailleurs (§ 8.6). La table des libellés couvre
chaque code du stockage, du CSV et de la séance ; son épreuve balaie les
sources, et un code neuf exige un texte.
Vérifié : 1416 épreuves node ; depot.js à 100 %, lister gardant l'identifiant.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 23:17:36 -04:00
// Relevé de l'arbre réel : par arbre, les codes émis et le fichier qui émet
[ADD] application: generation controller, worker, evaluation table
Generating must never block the screen or record a plan that breaks a
rule. The controller refuses before launching, in a fixed order: an
out-of-range setting named by the engine's own check, the session's
refusals, then what makes the run useless. It runs the search in a worker
or in the thread, records only propositions whose invariants hold, skips
plans already present, and keeps the last duration for the session only.
The evaluation table ranks propositions on the minimum of encounters and
names the reservations a proposition does not honour.
Checked: 2112 node and 60 node-long tests; coverage floors kept.
--- FR ---
[ADD] application : génération, travailleur, table d'évaluation
Générer ne doit jamais bloquer l'écran ni enregistrer un plan qui enfreint
une règle. Le contrôleur refuse avant tout lancement, dans un ordre fixe :
un réglage hors de son domaine, nommé par la règle même du moteur, les
refus de la séance, puis ce qui rend le calcul vain. Il lance la recherche
dans un travailleur ou dans le fil, n'enregistre que les propositions dont
les invariants tiennent, écarte les plans déjà présents, et garde la
dernière durée pour la seule séance. La table d'évaluation classe les
propositions sur le minimum de rencontres et nomme les réservations
qu'une proposition ne tient pas.
Vérifié : 2112 node et 60 node-long ; seuils de couverture tenus.
Assisted-by: Claude Opus 5.5
2026-10-07 06:09:45 -04:00
// chacun en premier ; puis ceux des tables de contrat ; puis ceux du moteur.
[ADD] application: session, named commands, French label table
The session holds one event, opens it read-only and writes only after
"Modifier" takes the lock (§ 8.4, § 8.8); calls run one after another, so a
double click writes two entries in order. Every change is a pure named
command with a frozen French label (§ 8.2); undo, redo and return write
entries and never truncate the journal (§ 8.3). A failed write — a gesture,
or the lock on a removed key — breaks the medium until the event is written
elsewhere (§ 8.6). The label table covers every code storage, CSV and the
session emit, and its test sweeps the sources so a new code needs a text.
Checked: 1416 node tests; depot.js at 100 % with the identifier lister keeps.
--- FR ---
[ADD] application : séance, commandes nommées, table des libellés
La séance tient un événement, l'ouvre en lecture et n'écrit qu'après que
« Modifier » a pris le verrou (§ 8.4, § 8.8) ; les appels passent l'un après
l'autre : un double clic écrit deux entrées, dans l'ordre. Toute modification
est une commande nommée pure au libellé français figé (§ 8.2) ; défaire,
refaire et revenir écrivent des entrées sans tronquer le journal (§ 8.3). Une
écriture refusée — un geste, ou le verrou sur une clé retirée — rompt le
support jusqu'à l'écriture ailleurs (§ 8.6). La table des libellés couvre
chaque code du stockage, du CSV et de la séance ; son épreuve balaie les
sources, et un code neuf exige un texte.
Vérifié : 1416 épreuves node ; depot.js à 100 %, lister gardant l'identifiant.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 23:17:36 -04:00
function releverArbre ( ) {
const parArbre = new Map ( ) ;
const emetteurs = new Map ( ) ;
for ( const arbre of ARBRES ) {
const codes = new Set ( ) ;
for ( const chemin of modulesDe ( arbre ) ) {
for ( const code of codesEmis ( readFileSync ( join ( RACINE , chemin ) , 'utf8' ) ) ) {
codes . add ( code ) ;
if ( ! emetteurs . has ( code ) ) emetteurs . set ( code , relative ( '.' , chemin ) ) ;
}
}
parArbre . set ( arbre , codes ) ;
}
const tables = new Set ( TABLES . flatMap ( ( chemin ) => codesDesTables ( readFileSync ( join ( RACINE , chemin ) , 'utf8' ) ) ) ) ;
for ( const code of tables ) if ( ! emetteurs . has ( code ) ) emetteurs . set ( code , 'table de contrat' ) ;
[ADD] application: generation controller, worker, evaluation table
Generating must never block the screen or record a plan that breaks a
rule. The controller refuses before launching, in a fixed order: an
out-of-range setting named by the engine's own check, the session's
refusals, then what makes the run useless. It runs the search in a worker
or in the thread, records only propositions whose invariants hold, skips
plans already present, and keeps the last duration for the session only.
The evaluation table ranks propositions on the minimum of encounters and
names the reservations a proposition does not honour.
Checked: 2112 node and 60 node-long tests; coverage floors kept.
--- FR ---
[ADD] application : génération, travailleur, table d'évaluation
Générer ne doit jamais bloquer l'écran ni enregistrer un plan qui enfreint
une règle. Le contrôleur refuse avant tout lancement, dans un ordre fixe :
un réglage hors de son domaine, nommé par la règle même du moteur, les
refus de la séance, puis ce qui rend le calcul vain. Il lance la recherche
dans un travailleur ou dans le fil, n'enregistre que les propositions dont
les invariants tiennent, écarte les plans déjà présents, et garde la
dernière durée pour la seule séance. La table d'évaluation classe les
propositions sur le minimum de rencontres et nomme les réservations
qu'une proposition ne tient pas.
Vérifié : 2112 node et 60 node-long ; seuils de couverture tenus.
Assisted-by: Claude Opus 5.5
2026-10-07 06:09:45 -04:00
const moteur = releverMoteur ( ) ;
for ( const [ code , chemin ] of moteur . emetteurs ) if ( ! emetteurs . has ( code ) ) emetteurs . set ( code , chemin ) ;
parArbre . set ( MOTEUR , moteur . codes ) ;
[ADD] application: session, named commands, French label table
The session holds one event, opens it read-only and writes only after
"Modifier" takes the lock (§ 8.4, § 8.8); calls run one after another, so a
double click writes two entries in order. Every change is a pure named
command with a frozen French label (§ 8.2); undo, redo and return write
entries and never truncate the journal (§ 8.3). A failed write — a gesture,
or the lock on a removed key — breaks the medium until the event is written
elsewhere (§ 8.6). The label table covers every code storage, CSV and the
session emit, and its test sweeps the sources so a new code needs a text.
Checked: 1416 node tests; depot.js at 100 % with the identifier lister keeps.
--- FR ---
[ADD] application : séance, commandes nommées, table des libellés
La séance tient un événement, l'ouvre en lecture et n'écrit qu'après que
« Modifier » a pris le verrou (§ 8.4, § 8.8) ; les appels passent l'un après
l'autre : un double clic écrit deux entrées, dans l'ordre. Toute modification
est une commande nommée pure au libellé français figé (§ 8.2) ; défaire,
refaire et revenir écrivent des entrées sans tronquer le journal (§ 8.3). Une
écriture refusée — un geste, ou le verrou sur une clé retirée — rompt le
support jusqu'à l'écriture ailleurs (§ 8.6). La table des libellés couvre
chaque code du stockage, du CSV et de la séance ; son épreuve balaie les
sources, et un code neuf exige un texte.
Vérifié : 1416 épreuves node ; depot.js à 100 %, lister gardant l'identifiant.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 23:17:36 -04:00
return { parArbre , tables , emetteurs } ;
}
[ADD] application: generation controller, worker, evaluation table
Generating must never block the screen or record a plan that breaks a
rule. The controller refuses before launching, in a fixed order: an
out-of-range setting named by the engine's own check, the session's
refusals, then what makes the run useless. It runs the search in a worker
or in the thread, records only propositions whose invariants hold, skips
plans already present, and keeps the last duration for the session only.
The evaluation table ranks propositions on the minimum of encounters and
names the reservations a proposition does not honour.
Checked: 2112 node and 60 node-long tests; coverage floors kept.
--- FR ---
[ADD] application : génération, travailleur, table d'évaluation
Générer ne doit jamais bloquer l'écran ni enregistrer un plan qui enfreint
une règle. Le contrôleur refuse avant tout lancement, dans un ordre fixe :
un réglage hors de son domaine, nommé par la règle même du moteur, les
refus de la séance, puis ce qui rend le calcul vain. Il lance la recherche
dans un travailleur ou dans le fil, n'enregistre que les propositions dont
les invariants tiennent, écarte les plans déjà présents, et garde la
dernière durée pour la seule séance. La table d'évaluation classe les
propositions sur le minimum de rencontres et nomme les réservations
qu'une proposition ne tient pas.
Vérifié : 2112 node et 60 node-long ; seuils de couverture tenus.
Assisted-by: Claude Opus 5.5
2026-10-07 06:09:45 -04:00
// Tous les codes relevés : tables de contrat, arbres balayés et moteur.
const tousReleves = ( { parArbre , tables } ) => new Set ( [ ... tables , ... [ ... parArbre . values ( ) ] . flatMap ( ( codes ) => [ ... codes ] ) ] ) ;
[ADD] application: session, named commands, French label table
The session holds one event, opens it read-only and writes only after
"Modifier" takes the lock (§ 8.4, § 8.8); calls run one after another, so a
double click writes two entries in order. Every change is a pure named
command with a frozen French label (§ 8.2); undo, redo and return write
entries and never truncate the journal (§ 8.3). A failed write — a gesture,
or the lock on a removed key — breaks the medium until the event is written
elsewhere (§ 8.6). The label table covers every code storage, CSV and the
session emit, and its test sweeps the sources so a new code needs a text.
Checked: 1416 node tests; depot.js at 100 % with the identifier lister keeps.
--- FR ---
[ADD] application : séance, commandes nommées, table des libellés
La séance tient un événement, l'ouvre en lecture et n'écrit qu'après que
« Modifier » a pris le verrou (§ 8.4, § 8.8) ; les appels passent l'un après
l'autre : un double clic écrit deux entrées, dans l'ordre. Toute modification
est une commande nommée pure au libellé français figé (§ 8.2) ; défaire,
refaire et revenir écrivent des entrées sans tronquer le journal (§ 8.3). Une
écriture refusée — un geste, ou le verrou sur une clé retirée — rompt le
support jusqu'à l'écriture ailleurs (§ 8.6). La table des libellés couvre
chaque code du stockage, du CSV et de la séance ; son épreuve balaie les
sources, et un code neuf exige un texte.
Vérifié : 1416 épreuves node ; depot.js à 100 %, lister gardant l'identifiant.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 23:17:36 -04:00
describe ( 'libellés : le balayage des codes (§ 14.6, § 14.2)' , ( ) => {
test ( "le relevé lit le premier argument d'un appel, une clé code, un code de détail, et rien d'un commentaire" , ( ) => {
const source = [
"// throw new ErreurStockage('DANS_UN_COMMENTAIRE');" ,
"/** refuser('DANS_UN_BLOC') */" ,
"throw new ErreurStockage('ABSENT', { chemin });" ,
"const refus = refuser(\n 'UTF8_INVALIDE');" ,
"avertissements.push({ code: 'DERIVE', details });" ,
"throw new ErreurStockage('LECTURE_SEULE', { raison: 'BLOQUE' });" ,
"const texte = brut.normalize('NFC');" ,
"const url = 'http://exemple.test'; ajouter('APRES_UNE_ADRESSE');" ,
"if (erreur.code === 'ECARTE') return 'PAS_UN_APPEL';" ,
"const minuscule = verifier('minuscule');" ,
"const RELU = 'CONSTANTE_CODE'; const motif = raisonLue ?? 'PAS_UNE_CONSTANTE';" ,
] . join ( '\n' ) ;
assert . deepEqual ( codesEmis ( source ) , [
'ABSENT' ,
'UTF8_INVALIDE' ,
'DERIVE' ,
'LECTURE_SEULE' ,
'BLOQUE' ,
'APRES_UNE_ADRESSE' ,
'CONSTANTE_CODE' ,
] ) ;
} ) ;
test ( "le relevé des tables lit chaque ligne de code d'un bloc « Codes d… », et ignore les autres blocs" , ( ) => {
const source = [
'/**' ,
" * Codes d'une erreur d'essai :" ,
' *' ,
' * PREMIER {chemin} un premier code' ,
' * qui continue ici' ,
' * SECOND_CODE' ,
' * {} un second' ,
' */' ,
'/**' ,
' * Autre chose :' ,
' * IGNORE {}' ,
' */' ,
] . join ( '\n' ) ;
assert . deepEqual ( codesDesTables ( source ) , [ 'PREMIER' , 'SECOND_CODE' ] ) ;
} ) ;
[FIX] storage, session: strict UTF-8, lost journal, lock, exclusions
The whole-iteration review found ways to lose or corrupt data. A state
saved in another encoding opened with its accents turned to U+FFFD and was
then overwritten; decoding is now strict, the state opens read-only, and a
gesture from its secours first moves it to the trash. A journal decodes line
by line, so one bad byte drops only the lines after it. A vanished journal
is rewritten whole, never appended to. Deleting refuses an event another
session holds; switching to writing rereads the disk; an import that
changes exclu says which reservations it suspends.
Checked: 1504 node, 35 browser, 29 node-long; depot.js, journal.js 100 %.
--- FR ---
[FIX] stockage, séance : UTF-8 strict, journal perdu, verrou, exclusions
La revue d'ensemble a trouvé des façons de perdre ou d'abîmer des données.
Un état enregistré dans un autre encodage s'ouvrait, ses accents changés en
U+FFFD, puis s'écrasait ; le décodage est strict, l'état s'ouvre en lecture
seule, et un geste depuis son secours le range d'abord à la corbeille. Le
journal se décode ligne par ligne : un octet fautif n'écarte que la suite.
Un journal disparu se réécrit en entier, jamais par ajout. Supprimer refuse
un événement qu'une autre séance tient ; passer en écriture relit le disque ;
un import qui change exclu dit quelles réservations il suspend.
Vérifié : 1504 node, 35 navigateur, 29 node-long ; depot.js, journal.js 100 %.
Assisted-by: Claude Opus 5.5
2026-10-07 01:20:42 -04:00
test ( "un motif de lecture seule ou d'illisibilité ne fait jamais lever le texte ni n'y laisse « undefined », quel que soit le code qui le porte" , ( ) => {
assert . ok ( CODES . length > 0 , 'table vide' ) ;
for ( const code of [ 'LECTURE_SEULE' , 'ETAT_ILLISIBLE' , 'JOURNAL_ILLISIBLE' ] ) {
for ( const raison of [ ... CODES , 'RAISON_INVENTEE' ] ) {
const texte = libelle ( code , { raison , base : 'Soirée des Lucioles' , chemin : null } ) ;
assert . equal ( typeof texte , 'string' , ` ${ code } / ${ raison } ` ) ;
assert . ok ( ! texte . includes ( 'undefined' ) , ` ${ code } / ${ raison } : ${ texte } ` ) ;
}
}
} ) ;
[ADD] application: clock, translation, formatting, displayed name
The screens need one clock, one translation mechanism and one way to
format and name, or each component grows its own. horloge.js is the only
code that reads time and arms timers, injected so tests drive it.
traduction.js keys texts by domain, in two families of tables, raises on a
missing key and never shows a raw key; libelles/ splits the label table by
domain. format.js formats numbers and dates in French, and personnes.js
composes the displayed name the frozen labels use. Nothing persists
between sessions.
Checked: 1773 node and 43 browser tests.
--- FR ---
[ADD] application : horloge, traduction, mise en forme, nom affiché
Les écrans exigent une horloge, un mécanisme de traduction et une façon de
mettre en forme et de nommer, sans quoi chaque composant ferait les siens.
horloge.js est le seul code qui lit l'heure et arme des minuteries, injecté
pour que les épreuves le pilotent. traduction.js range les textes par
domaine, en deux familles de tables, lève sur une clé absente et ne montre
jamais une clé brute ; libelles/ découpe la table des libellés par domaine.
format.js met en forme nombres et dates en français, et personnes.js
compose le nom affiché qu'emploient les libellés figés. Rien ne persiste
d'une séance à l'autre.
Vérifié : 1773 node et 43 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 02:33:39 -04:00
test ( "le balayage exclut l'assemblage et chaque table de libelles/, et garde les autres modules de l'application" , ( ) => {
const tables = readdirSync ( join ( RACINE , DOSSIER _DES _TABLES ) ) . filter ( ( nom ) => nom . endsWith ( '.js' ) ) ;
assert . ok ( tables . length >= 10 , ` ${ DOSSIER _DES _TABLES } : ${ tables . length } modules ` ) ;
const balayes = modulesDe ( 'src/application' ) ;
assert . ok ( balayes . length > 0 , 'src/application : aucun module balayé' ) ;
const dossier = join ( 'src' , 'application' , 'libelles' ) ;
assert . deepEqual ( balayes . filter ( ( chemin ) => chemin === ` ${ dossier } .js ` || chemin . startsWith ( dossier + sep ) ) , [ ] ) ;
for ( const garde of [ 'commandes.js' , 'seance.js' ] ) assert . ok ( balayes . includes ( join ( 'src' , 'application' , garde ) ) , garde ) ;
} ) ;
[ADD] application: session, named commands, French label table
The session holds one event, opens it read-only and writes only after
"Modifier" takes the lock (§ 8.4, § 8.8); calls run one after another, so a
double click writes two entries in order. Every change is a pure named
command with a frozen French label (§ 8.2); undo, redo and return write
entries and never truncate the journal (§ 8.3). A failed write — a gesture,
or the lock on a removed key — breaks the medium until the event is written
elsewhere (§ 8.6). The label table covers every code storage, CSV and the
session emit, and its test sweeps the sources so a new code needs a text.
Checked: 1416 node tests; depot.js at 100 % with the identifier lister keeps.
--- FR ---
[ADD] application : séance, commandes nommées, table des libellés
La séance tient un événement, l'ouvre en lecture et n'écrit qu'après que
« Modifier » a pris le verrou (§ 8.4, § 8.8) ; les appels passent l'un après
l'autre : un double clic écrit deux entrées, dans l'ordre. Toute modification
est une commande nommée pure au libellé français figé (§ 8.2) ; défaire,
refaire et revenir écrivent des entrées sans tronquer le journal (§ 8.3). Une
écriture refusée — un geste, ou le verrou sur une clé retirée — rompt le
support jusqu'à l'écriture ailleurs (§ 8.6). La table des libellés couvre
chaque code du stockage, du CSV et de la séance ; son épreuve balaie les
sources, et un code neuf exige un texte.
Vérifié : 1416 épreuves node ; depot.js à 100 %, lister gardant l'identifiant.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 23:17:36 -04:00
test ( 'chaque arbre émet des codes, et la table de contrat du stockage en porte' , ( ) => {
const { parArbre , tables } = releverArbre ( ) ;
for ( const arbre of ARBRES ) assert . ok ( parArbre . get ( arbre ) . size > 0 , ` ${ arbre } : balayage vide ` ) ;
assert . ok ( tables . size > 0 , 'tables de contrat : balayage vide' ) ;
assert . ok ( tables . has ( 'VERROU_PRIS' ) && tables . has ( 'IDENTIFIANT_HORS_COMPTEUR' ) ) ;
} ) ;
[ADD] application: generation controller, worker, evaluation table
Generating must never block the screen or record a plan that breaks a
rule. The controller refuses before launching, in a fixed order: an
out-of-range setting named by the engine's own check, the session's
refusals, then what makes the run useless. It runs the search in a worker
or in the thread, records only propositions whose invariants hold, skips
plans already present, and keeps the last duration for the session only.
The evaluation table ranks propositions on the minimum of encounters and
names the reservations a proposition does not honour.
Checked: 2112 node and 60 node-long tests; coverage floors kept.
--- FR ---
[ADD] application : génération, travailleur, table d'évaluation
Générer ne doit jamais bloquer l'écran ni enregistrer un plan qui enfreint
une règle. Le contrôleur refuse avant tout lancement, dans un ordre fixe :
un réglage hors de son domaine, nommé par la règle même du moteur, les
refus de la séance, puis ce qui rend le calcul vain. Il lance la recherche
dans un travailleur ou dans le fil, n'enregistre que les propositions dont
les invariants tiennent, écarte les plans déjà présents, et garde la
dernière durée pour la seule séance. La table d'évaluation classe les
propositions sur le minimum de rencontres et nomme les réservations
qu'une proposition ne tient pas.
Vérifié : 2112 node et 60 node-long ; seuils de couverture tenus.
Assisted-by: Claude Opus 5.5
2026-10-07 06:09:45 -04:00
test ( 'chaque code que src/stockage, src/csv, src/application et src/moteur émettent a son texte' , ( ) => {
const releve = releverArbre ( ) ;
const { emetteurs } = releve ;
const releves = tousReleves ( releve ) ;
[ADD] application: session, named commands, French label table
The session holds one event, opens it read-only and writes only after
"Modifier" takes the lock (§ 8.4, § 8.8); calls run one after another, so a
double click writes two entries in order. Every change is a pure named
command with a frozen French label (§ 8.2); undo, redo and return write
entries and never truncate the journal (§ 8.3). A failed write — a gesture,
or the lock on a removed key — breaks the medium until the event is written
elsewhere (§ 8.6). The label table covers every code storage, CSV and the
session emit, and its test sweeps the sources so a new code needs a text.
Checked: 1416 node tests; depot.js at 100 % with the identifier lister keeps.
--- FR ---
[ADD] application : séance, commandes nommées, table des libellés
La séance tient un événement, l'ouvre en lecture et n'écrit qu'après que
« Modifier » a pris le verrou (§ 8.4, § 8.8) ; les appels passent l'un après
l'autre : un double clic écrit deux entrées, dans l'ordre. Toute modification
est une commande nommée pure au libellé français figé (§ 8.2) ; défaire,
refaire et revenir écrivent des entrées sans tronquer le journal (§ 8.3). Une
écriture refusée — un geste, ou le verrou sur une clé retirée — rompt le
support jusqu'à l'écriture ailleurs (§ 8.6). La table des libellés couvre
chaque code du stockage, du CSV et de la séance ; son épreuve balaie les
sources, et un code neuf exige un texte.
Vérifié : 1416 épreuves node ; depot.js à 100 %, lister gardant l'identifiant.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 23:17:36 -04:00
assert . ok ( releves . size > 0 , 'balayage vide' ) ;
const connus = new Set ( CODES ) ;
const sansTexte = [ ... releves ] . filter ( ( code ) => ! connus . has ( code ) ) . sort ( ) ;
assert . deepEqual (
sansTexte . map ( ( code ) => ` ${ code } ( ${ emetteurs . get ( code ) } ) ` ) ,
[ ] ,
'codes émis sans texte dans src/application/libelles.js' ,
) ;
} ) ;
test ( 'chaque texte de la table a un émetteur : aucune entrée morte' , ( ) => {
[ADD] application: generation controller, worker, evaluation table
Generating must never block the screen or record a plan that breaks a
rule. The controller refuses before launching, in a fixed order: an
out-of-range setting named by the engine's own check, the session's
refusals, then what makes the run useless. It runs the search in a worker
or in the thread, records only propositions whose invariants hold, skips
plans already present, and keeps the last duration for the session only.
The evaluation table ranks propositions on the minimum of encounters and
names the reservations a proposition does not honour.
Checked: 2112 node and 60 node-long tests; coverage floors kept.
--- FR ---
[ADD] application : génération, travailleur, table d'évaluation
Générer ne doit jamais bloquer l'écran ni enregistrer un plan qui enfreint
une règle. Le contrôleur refuse avant tout lancement, dans un ordre fixe :
un réglage hors de son domaine, nommé par la règle même du moteur, les
refus de la séance, puis ce qui rend le calcul vain. Il lance la recherche
dans un travailleur ou dans le fil, n'enregistre que les propositions dont
les invariants tiennent, écarte les plans déjà présents, et garde la
dernière durée pour la seule séance. La table d'évaluation classe les
propositions sur le minimum de rencontres et nomme les réservations
qu'une proposition ne tient pas.
Vérifié : 2112 node et 60 node-long ; seuils de couverture tenus.
Assisted-by: Claude Opus 5.5
2026-10-07 06:09:45 -04:00
const releves = tousReleves ( releverArbre ( ) ) ;
[ADD] application: session, named commands, French label table
The session holds one event, opens it read-only and writes only after
"Modifier" takes the lock (§ 8.4, § 8.8); calls run one after another, so a
double click writes two entries in order. Every change is a pure named
command with a frozen French label (§ 8.2); undo, redo and return write
entries and never truncate the journal (§ 8.3). A failed write — a gesture,
or the lock on a removed key — breaks the medium until the event is written
elsewhere (§ 8.6). The label table covers every code storage, CSV and the
session emit, and its test sweeps the sources so a new code needs a text.
Checked: 1416 node tests; depot.js at 100 % with the identifier lister keeps.
--- FR ---
[ADD] application : séance, commandes nommées, table des libellés
La séance tient un événement, l'ouvre en lecture et n'écrit qu'après que
« Modifier » a pris le verrou (§ 8.4, § 8.8) ; les appels passent l'un après
l'autre : un double clic écrit deux entrées, dans l'ordre. Toute modification
est une commande nommée pure au libellé français figé (§ 8.2) ; défaire,
refaire et revenir écrivent des entrées sans tronquer le journal (§ 8.3). Une
écriture refusée — un geste, ou le verrou sur une clé retirée — rompt le
support jusqu'à l'écriture ailleurs (§ 8.6). La table des libellés couvre
chaque code du stockage, du CSV et de la séance ; son épreuve balaie les
sources, et un code neuf exige un texte.
Vérifié : 1416 épreuves node ; depot.js à 100 %, lister gardant l'identifiant.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 23:17:36 -04:00
assert . ok ( CODES . length > 0 , 'table vide' ) ;
assert . deepEqual ( CODES . filter ( ( code ) => ! releves . has ( code ) ) , [ ] , 'textes sans émetteur' ) ;
} ) ;
} ) ;
// --- Les textes -----------------------------------------------------------
// Détails d'exemple de chaque code de la table : chaque texte s'y compose une
// fois. Un code ajouté à la table sans exemple fait échouer l'épreuve.
const EXEMPLES = new Map ( [
// Système de fichiers et dépôt
[ 'ABSENT' , { chemin : 'Soirée des Lucioles.gtt.json' } ] ,
[ 'ECRITURE' , { chemin : 'Soirée des Lucioles.gtt.json' , dossier : 'E:\\data' , cause : 'EROFS' } ] ,
[ 'LECTURE' , { chemin : 'Soirée des Lucioles.gtt.json' , dossier : 'E:\\data' , cause : 'EACCES' } ] ,
[ 'EXISTE' , { chemin : 'Soirée des Lucioles.gtt.json' } ] ,
[ 'CHEMIN_REFUSE' , { chemin : '../ailleurs.gtt.json' } ] ,
[ 'ETAT_ILLISIBLE' , { base : 'Soirée des Lucioles' , raison : 'JSON' , chemin : null , secours : { precedent : 4 , journal : 5 } } ] ,
[ 'CORRECTIF' , { rang : 2 } ] ,
[ 'CHEMIN_TROP_LONG' , { racine : 'E:\\un\\dossier\\profond' } ] ,
[ 'IDENTIFIANT_PRESENT' , { id : 'evt-lucioles' , base : 'Soirée des Lucioles' } ] ,
[ 'VERROU_PRIS' , { seance : 'seance-autre' , depuis : '2031-05-17T08:05:00-04:00' , vivant : true } ] ,
[ 'LECTURE_SEULE' , { raison : 'BLOQUE' } ] ,
[ 'ETAT_NON_ECRIT' , { chemin : 'Soirée des Lucioles.gtt.json' , dossier : 'E:\\data' , cause : 'EIO' } ] ,
[ 'CORBEILLE_SATUREE' , { dossier : 'corbeille/2031-05-17_08-05-00_99' } ] ,
[ 'COMPTEUR_SATURE' , { compteur : 'proposition' } ] ,
[ 'NON_DISPONIBLE' , { primitive : 'ouvrirDansExplorateur' } ] ,
// Raisons d'un état illisible
[ 'VIDE' , { } ] ,
[ 'JSON' , { } ] ,
[ 'FORME' , { chemin : 'charge.participants[2].nom' } ] ,
[ 'COMPTES' , { chemin : 'entete.comptes.tables' } ] ,
[ 'REFERENCE' , { chemin : 'charge.reservations[0].table' } ] ,
[ 'FORMAT_INCONNU' , { chemin : 'entete.format' } ] ,
[ 'FORMAT_PLUS_RECENT' , { format : 2 } ] ,
[FIX] storage, session: strict UTF-8, lost journal, lock, exclusions
The whole-iteration review found ways to lose or corrupt data. A state
saved in another encoding opened with its accents turned to U+FFFD and was
then overwritten; decoding is now strict, the state opens read-only, and a
gesture from its secours first moves it to the trash. A journal decodes line
by line, so one bad byte drops only the lines after it. A vanished journal
is rewritten whole, never appended to. Deleting refuses an event another
session holds; switching to writing rereads the disk; an import that
changes exclu says which reservations it suspends.
Checked: 1504 node, 35 browser, 29 node-long; depot.js, journal.js 100 %.
--- FR ---
[FIX] stockage, séance : UTF-8 strict, journal perdu, verrou, exclusions
La revue d'ensemble a trouvé des façons de perdre ou d'abîmer des données.
Un état enregistré dans un autre encodage s'ouvrait, ses accents changés en
U+FFFD, puis s'écrasait ; le décodage est strict, l'état s'ouvre en lecture
seule, et un geste depuis son secours le range d'abord à la corbeille. Le
journal se décode ligne par ligne : un octet fautif n'écarte que la suite.
Un journal disparu se réécrit en entier, jamais par ajout. Supprimer refuse
un événement qu'une autre séance tient ; passer en écriture relit le disque ;
un import qui change exclu dit quelles réservations il suspend.
Vérifié : 1504 node, 35 navigateur, 29 node-long ; depot.js, journal.js 100 %.
Assisted-by: Claude Opus 5.5
2026-10-07 01:20:42 -04:00
[ 'ENCODAGE' , { } ] ,
[ADD] application: session, named commands, French label table
The session holds one event, opens it read-only and writes only after
"Modifier" takes the lock (§ 8.4, § 8.8); calls run one after another, so a
double click writes two entries in order. Every change is a pure named
command with a frozen French label (§ 8.2); undo, redo and return write
entries and never truncate the journal (§ 8.3). A failed write — a gesture,
or the lock on a removed key — breaks the medium until the event is written
elsewhere (§ 8.6). The label table covers every code storage, CSV and the
session emit, and its test sweeps the sources so a new code needs a text.
Checked: 1416 node tests; depot.js at 100 % with the identifier lister keeps.
--- FR ---
[ADD] application : séance, commandes nommées, table des libellés
La séance tient un événement, l'ouvre en lecture et n'écrit qu'après que
« Modifier » a pris le verrou (§ 8.4, § 8.8) ; les appels passent l'un après
l'autre : un double clic écrit deux entrées, dans l'ordre. Toute modification
est une commande nommée pure au libellé français figé (§ 8.2) ; défaire,
refaire et revenir écrivent des entrées sans tronquer le journal (§ 8.3). Une
écriture refusée — un geste, ou le verrou sur une clé retirée — rompt le
support jusqu'à l'écriture ailleurs (§ 8.6). La table des libellés couvre
chaque code du stockage, du CSV et de la séance ; son épreuve balaie les
sources, et un code neuf exige un texte.
Vérifié : 1416 épreuves node ; depot.js à 100 %, lister gardant l'identifiant.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 23:17:36 -04:00
// Fautes d'un placement
[ 'LONGUEUR' , { tour : 2 , table : 4 , declare : 8 , ecrit : 9 } ] ,
[ 'DOUBLON' , { tour : 1 , participant : 3 } ] ,
[ 'INCONNU' , { tour : 1 , participant : 12 } ] ,
[ 'MANQUANT' , { tour : 2 , participant : 7 } ] ,
[ 'IDENTIFIANT_REPETE' , { } ] ,
[ 'IDENTIFIANT_HORS_COMPTEUR' , { } ] ,
// Raisons de dérive
[ 'PARTICIPANT_EXCLU' , { participant : 3 } ] ,
[ 'PARTICIPANT_SUPPRIME' , { participant : 4 } ] ,
[ 'PARTICIPANT_NON_PLACE' , { participant : 9 } ] ,
[ 'TABLE_SUPPRIMEE' , { table : 2 } ] ,
[ 'TABLE_AJOUTEE' , { table : 5 } ] ,
[ 'CAPACITE_CHANGEE' , { table : 1 , avant : 8 , maintenant : 6 } ] ,
[ 'TOURS_CHANGES' , { avant : 4 , maintenant : 3 } ] ,
// Avertissements de l'ouverture
[ 'RETENU_FAUTIF' , { } ] ,
[ 'JOURNAL_REPRIS' , { } ] ,
[ 'JOURNAL_EN_AVANCE_DE' , { n : 3 } ] ,
[ 'JOURNAL_INCOMPLET' , { etat : 9 , journal : 6 } ] ,
[ 'JOURNAL_ECARTEES' , { n : 64 } ] ,
[FIX] storage, session: strict UTF-8, lost journal, lock, exclusions
The whole-iteration review found ways to lose or corrupt data. A state
saved in another encoding opened with its accents turned to U+FFFD and was
then overwritten; decoding is now strict, the state opens read-only, and a
gesture from its secours first moves it to the trash. A journal decodes line
by line, so one bad byte drops only the lines after it. A vanished journal
is rewritten whole, never appended to. Deleting refuses an event another
session holds; switching to writing rereads the disk; an import that
changes exclu says which reservations it suspends.
Checked: 1504 node, 35 browser, 29 node-long; depot.js, journal.js 100 %.
--- FR ---
[FIX] stockage, séance : UTF-8 strict, journal perdu, verrou, exclusions
La revue d'ensemble a trouvé des façons de perdre ou d'abîmer des données.
Un état enregistré dans un autre encodage s'ouvrait, ses accents changés en
U+FFFD, puis s'écrasait ; le décodage est strict, l'état s'ouvre en lecture
seule, et un geste depuis son secours le range d'abord à la corbeille. Le
journal se décode ligne par ligne : un octet fautif n'écarte que la suite.
Un journal disparu se réécrit en entier, jamais par ajout. Supprimer refuse
un événement qu'une autre séance tient ; passer en écriture relit le disque ;
un import qui change exclu dit quelles réservations il suspend.
Vérifié : 1504 node, 35 navigateur, 29 node-long ; depot.js, journal.js 100 %.
Assisted-by: Claude Opus 5.5
2026-10-07 01:20:42 -04:00
[ 'JOURNAL_RECREE' , { } ] ,
[ADD] application: session, named commands, French label table
The session holds one event, opens it read-only and writes only after
"Modifier" takes the lock (§ 8.4, § 8.8); calls run one after another, so a
double click writes two entries in order. Every change is a pure named
command with a frozen French label (§ 8.2); undo, redo and return write
entries and never truncate the journal (§ 8.3). A failed write — a gesture,
or the lock on a removed key — breaks the medium until the event is written
elsewhere (§ 8.6). The label table covers every code storage, CSV and the
session emit, and its test sweeps the sources so a new code needs a text.
Checked: 1416 node tests; depot.js at 100 % with the identifier lister keeps.
--- FR ---
[ADD] application : séance, commandes nommées, table des libellés
La séance tient un événement, l'ouvre en lecture et n'écrit qu'après que
« Modifier » a pris le verrou (§ 8.4, § 8.8) ; les appels passent l'un après
l'autre : un double clic écrit deux entrées, dans l'ordre. Toute modification
est une commande nommée pure au libellé français figé (§ 8.2) ; défaire,
refaire et revenir écrivent des entrées sans tronquer le journal (§ 8.3). Une
écriture refusée — un geste, ou le verrou sur une clé retirée — rompt le
support jusqu'à l'écriture ailleurs (§ 8.6). La table des libellés couvre
chaque code du stockage, du CSV et de la séance ; son épreuve balaie les
sources, et un code neuf exige un texte.
Vérifié : 1416 épreuves node ; depot.js à 100 %, lister gardant l'identifiant.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 23:17:36 -04:00
[ 'JOURNAL_FORMAT_PLUS_RECENT' , { format : 2 } ] ,
[FIX] storage, session: strict UTF-8, lost journal, lock, exclusions
The whole-iteration review found ways to lose or corrupt data. A state
saved in another encoding opened with its accents turned to U+FFFD and was
then overwritten; decoding is now strict, the state opens read-only, and a
gesture from its secours first moves it to the trash. A journal decodes line
by line, so one bad byte drops only the lines after it. A vanished journal
is rewritten whole, never appended to. Deleting refuses an event another
session holds; switching to writing rereads the disk; an import that
changes exclu says which reservations it suspends.
Checked: 1504 node, 35 browser, 29 node-long; depot.js, journal.js 100 %.
--- FR ---
[FIX] stockage, séance : UTF-8 strict, journal perdu, verrou, exclusions
La revue d'ensemble a trouvé des façons de perdre ou d'abîmer des données.
Un état enregistré dans un autre encodage s'ouvrait, ses accents changés en
U+FFFD, puis s'écrasait ; le décodage est strict, l'état s'ouvre en lecture
seule, et un geste depuis son secours le range d'abord à la corbeille. Le
journal se décode ligne par ligne : un octet fautif n'écarte que la suite.
Un journal disparu se réécrit en entier, jamais par ajout. Supprimer refuse
un événement qu'une autre séance tient ; passer en écriture relit le disque ;
un import qui change exclu dit quelles réservations il suspend.
Vérifié : 1504 node, 35 navigateur, 29 node-long ; depot.js, journal.js 100 %.
Assisted-by: Claude Opus 5.5
2026-10-07 01:20:42 -04:00
[ 'JOURNAL_ILLISIBLE' , { raison : 'ENCODAGE' } ] ,
[ADD] application: session, named commands, French label table
The session holds one event, opens it read-only and writes only after
"Modifier" takes the lock (§ 8.4, § 8.8); calls run one after another, so a
double click writes two entries in order. Every change is a pure named
command with a frozen French label (§ 8.2); undo, redo and return write
entries and never truncate the journal (§ 8.3). A failed write — a gesture,
or the lock on a removed key — breaks the medium until the event is written
elsewhere (§ 8.6). The label table covers every code storage, CSV and the
session emit, and its test sweeps the sources so a new code needs a text.
Checked: 1416 node tests; depot.js at 100 % with the identifier lister keeps.
--- FR ---
[ADD] application : séance, commandes nommées, table des libellés
La séance tient un événement, l'ouvre en lecture et n'écrit qu'après que
« Modifier » a pris le verrou (§ 8.4, § 8.8) ; les appels passent l'un après
l'autre : un double clic écrit deux entrées, dans l'ordre. Toute modification
est une commande nommée pure au libellé français figé (§ 8.2) ; défaire,
refaire et revenir écrivent des entrées sans tronquer le journal (§ 8.3). Une
écriture refusée — un geste, ou le verrou sur une clé retirée — rompt le
support jusqu'à l'écriture ailleurs (§ 8.6). La table des libellés couvre
chaque code du stockage, du CSV et de la séance ; son épreuve balaie les
sources, et un code neuf exige un texte.
Vérifié : 1416 épreuves node ; depot.js à 100 %, lister gardant l'identifiant.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 23:17:36 -04:00
[ 'JOURNAL_ETRANGER' , { evenement : 'evt-autre' } ] ,
[ 'PROPOSITIONS_FAUTIVES' , { ids : [ 3 , null ] } ] ,
[FIX] storage, session: strict UTF-8, lost journal, lock, exclusions
The whole-iteration review found ways to lose or corrupt data. A state
saved in another encoding opened with its accents turned to U+FFFD and was
then overwritten; decoding is now strict, the state opens read-only, and a
gesture from its secours first moves it to the trash. A journal decodes line
by line, so one bad byte drops only the lines after it. A vanished journal
is rewritten whole, never appended to. Deleting refuses an event another
session holds; switching to writing rereads the disk; an import that
changes exclu says which reservations it suspends.
Checked: 1504 node, 35 browser, 29 node-long; depot.js, journal.js 100 %.
--- FR ---
[FIX] stockage, séance : UTF-8 strict, journal perdu, verrou, exclusions
La revue d'ensemble a trouvé des façons de perdre ou d'abîmer des données.
Un état enregistré dans un autre encodage s'ouvrait, ses accents changés en
U+FFFD, puis s'écrasait ; le décodage est strict, l'état s'ouvre en lecture
seule, et un geste depuis son secours le range d'abord à la corbeille. Le
journal se décode ligne par ligne : un octet fautif n'écarte que la suite.
Un journal disparu se réécrit en entier, jamais par ajout. Supprimer refuse
un événement qu'une autre séance tient ; passer en écriture relit le disque ;
un import qui change exclu dit quelles réservations il suspend.
Vérifié : 1504 node, 35 navigateur, 29 node-long ; depot.js, journal.js 100 %.
Assisted-by: Claude Opus 5.5
2026-10-07 01:20:42 -04:00
[ 'PROPOSITIONS_MANQUANTES' , { n : 2 } ] ,
[ADD] application: session, named commands, French label table
The session holds one event, opens it read-only and writes only after
"Modifier" takes the lock (§ 8.4, § 8.8); calls run one after another, so a
double click writes two entries in order. Every change is a pure named
command with a frozen French label (§ 8.2); undo, redo and return write
entries and never truncate the journal (§ 8.3). A failed write — a gesture,
or the lock on a removed key — breaks the medium until the event is written
elsewhere (§ 8.6). The label table covers every code storage, CSV and the
session emit, and its test sweeps the sources so a new code needs a text.
Checked: 1416 node tests; depot.js at 100 % with the identifier lister keeps.
--- FR ---
[ADD] application : séance, commandes nommées, table des libellés
La séance tient un événement, l'ouvre en lecture et n'écrit qu'après que
« Modifier » a pris le verrou (§ 8.4, § 8.8) ; les appels passent l'un après
l'autre : un double clic écrit deux entrées, dans l'ordre. Toute modification
est une commande nommée pure au libellé français figé (§ 8.2) ; défaire,
refaire et revenir écrivent des entrées sans tronquer le journal (§ 8.3). Une
écriture refusée — un geste, ou le verrou sur une clé retirée — rompt le
support jusqu'à l'écriture ailleurs (§ 8.6). La table des libellés couvre
chaque code du stockage, du CSV et de la séance ; son épreuve balaie les
sources, et un code neuf exige un texte.
Vérifié : 1416 épreuves node ; depot.js à 100 %, lister gardant l'identifiant.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 23:17:36 -04:00
[ 'DERIVE' , { ids : [ 1 , 2 ] , retenu : true } ] ,
// Dossier de travail et réglages locaux
[ 'NON_PUBLIE' , { } ] ,
[ 'DONNEES_APPLICATIVES' , { } ] ,
[ 'SONDE_ECHOUEE' , { cause : 'EROFS' } ] ,
[ 'RELECTURE_DIFFERENTE' , { } ] ,
[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
[ 'NOM_NON_CONVERTI' , { } ] ,
[ADD] application: session, named commands, French label table
The session holds one event, opens it read-only and writes only after
"Modifier" takes the lock (§ 8.4, § 8.8); calls run one after another, so a
double click writes two entries in order. Every change is a pure named
command with a frozen French label (§ 8.2); undo, redo and return write
entries and never truncate the journal (§ 8.3). A failed write — a gesture,
or the lock on a removed key — breaks the medium until the event is written
elsewhere (§ 8.6). The label table covers every code storage, CSV and the
session emit, and its test sweeps the sources so a new code needs a text.
Checked: 1416 node tests; depot.js at 100 % with the identifier lister keeps.
--- FR ---
[ADD] application : séance, commandes nommées, table des libellés
La séance tient un événement, l'ouvre en lecture et n'écrit qu'après que
« Modifier » a pris le verrou (§ 8.4, § 8.8) ; les appels passent l'un après
l'autre : un double clic écrit deux entrées, dans l'ordre. Toute modification
est une commande nommée pure au libellé français figé (§ 8.2) ; défaire,
refaire et revenir écrivent des entrées sans tronquer le journal (§ 8.3). Une
écriture refusée — un geste, ou le verrou sur une clé retirée — rompt le
support jusqu'à l'écriture ailleurs (§ 8.6). La table des libellés couvre
chaque code du stockage, du CSV et de la séance ; son épreuve balaie les
sources, et un code neuf exige un texte.
Vérifié : 1416 épreuves node ; depot.js à 100 %, lister gardant l'identifiant.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 23:17:36 -04:00
[ 'PORTABLE_NON_INSCRIPTIBLE' , { racine : { id : 'portable' , chemin : 'E:\\data' } , evenements : 3 , cause : 'EROFS' } ] ,
[ 'DOCUMENTS_NON_INSCRIPTIBLE' , { racine : { id : 'documents' , chemin : 'C:\\Documents\\Table' } , evenements : 0 , cause : 'EACCES' } ] ,
[ 'PLATEFORME_WEB' , { } ] ,
[ 'SUPPORT_AMOVIBLE' , { } ] ,
[ 'REGLAGES_ILLISIBLES' , { chemin : 'reglages_locaux.json' , raison : 'FORME' } ] ,
// CSV
[ 'UTF16_SANS_MARQUE' , { remede : 'ENREGISTRER_EN_UTF8' } ] ,
[ 'UTF16_INVALIDE' , { remede : 'ENREGISTRER_EN_UTF8' } ] ,
[ 'UTF8_INVALIDE' , { remede : 'ENREGISTRER_EN_UTF8' } ] ,
[ 'CARACTERE_NUL' , { remede : 'ENREGISTRER_EN_UTF8' } ] ,
[ 'ENREGISTRER_EN_UTF8' , { } ] ,
[ 'SEPARATEUR_INTROUVABLE' , { separateur : ';' , ligne : 3 , cause : 'NOMBRE_DE_CHAMPS' } ] ,
[ 'NOMBRE_DE_CHAMPS' , { ligne : 3 } ] ,
[ 'GUILLEMET_OUVERT' , { ligne : 2 } ] ,
[ 'AUCUN_ENTETE' , { } ] ,
[ 'NOM_NON_ASSOCIE' , { } ] ,
[ 'AUCUNE_LIGNE_VALIDE' , { } ] ,
[ 'CHAMPS_EN_TROP' , { ligne : 4 , valeur : null } ] ,
[ 'NOM_ABSENT' , { ligne : 5 , valeur : null } ] ,
[ 'EXCLU_INCONNU' , { ligne : 6 , valeur : 'peut-être' } ] ,
[ 'PLUSIEURS_CORRESPONDENT' , { ligne : 7 , valeur : null , participants : [ 3 , 8 ] } ] ,
[ 'DEJA_DESIGNE' , { ligne : 9 , valeur : null , participants : [ 3 ] , premiereLigne : 4 } ] ,
[ 'PLAN_BLOQUE' , { remede : { geste : 'debloquer' } } ] ,
[ 'AMBIGUITE' , { champs : [ 'appartenance' , 'courriel' ] } ] ,
// Séance
[ 'AUCUN_EVENEMENT' , { } ] ,
[ 'MODE_LECTURE' , { } ] ,
[ 'FORMAT' , { } ] ,
[ 'BLOQUE' , { } ] ,
[ 'SUPPORT_ROMPU' , { echec : 'ECRITURE' , chemin : 'Soirée des Lucioles.gtt-journal.jsonl' , dossier : 'E:\\data' , cause : 'EROFS' } ] ,
[ 'SUPPORT_INTACT' , { } ] ,
[FIX] storage, session: strict UTF-8, lost journal, lock, exclusions
The whole-iteration review found ways to lose or corrupt data. A state
saved in another encoding opened with its accents turned to U+FFFD and was
then overwritten; decoding is now strict, the state opens read-only, and a
gesture from its secours first moves it to the trash. A journal decodes line
by line, so one bad byte drops only the lines after it. A vanished journal
is rewritten whole, never appended to. Deleting refuses an event another
session holds; switching to writing rereads the disk; an import that
changes exclu says which reservations it suspends.
Checked: 1504 node, 35 browser, 29 node-long; depot.js, journal.js 100 %.
--- FR ---
[FIX] stockage, séance : UTF-8 strict, journal perdu, verrou, exclusions
La revue d'ensemble a trouvé des façons de perdre ou d'abîmer des données.
Un état enregistré dans un autre encodage s'ouvrait, ses accents changés en
U+FFFD, puis s'écrasait ; le décodage est strict, l'état s'ouvre en lecture
seule, et un geste depuis son secours le range d'abord à la corbeille. Le
journal se décode ligne par ligne : un octet fautif n'écarte que la suite.
Un journal disparu se réécrit en entier, jamais par ajout. Supprimer refuse
un événement qu'une autre séance tient ; passer en écriture relit le disque ;
un import qui change exclu dit quelles réservations il suspend.
Vérifié : 1504 node, 35 navigateur, 29 node-long ; depot.js, journal.js 100 %.
Assisted-by: Claude Opus 5.5
2026-10-07 01:20:42 -04:00
[ 'SUPPORT_ROMPU_NON_SAUVE' , { echec : 'ETAT_NON_ECRIT' , chemin : 'Soirée des Lucioles.gtt.json' , dossier : 'E:\\data' , cause : 'EIO' } ] ,
[ 'EVENEMENT_CHANGE' , { revision : 7 } ] ,
[ADD] application: session, named commands, French label table
The session holds one event, opens it read-only and writes only after
"Modifier" takes the lock (§ 8.4, § 8.8); calls run one after another, so a
double click writes two entries in order. Every change is a pure named
command with a frozen French label (§ 8.2); undo, redo and return write
entries and never truncate the journal (§ 8.3). A failed write — a gesture,
or the lock on a removed key — breaks the medium until the event is written
elsewhere (§ 8.6). The label table covers every code storage, CSV and the
session emit, and its test sweeps the sources so a new code needs a text.
Checked: 1416 node tests; depot.js at 100 % with the identifier lister keeps.
--- FR ---
[ADD] application : séance, commandes nommées, table des libellés
La séance tient un événement, l'ouvre en lecture et n'écrit qu'après que
« Modifier » a pris le verrou (§ 8.4, § 8.8) ; les appels passent l'un après
l'autre : un double clic écrit deux entrées, dans l'ordre. Toute modification
est une commande nommée pure au libellé français figé (§ 8.2) ; défaire,
refaire et revenir écrivent des entrées sans tronquer le journal (§ 8.3). Une
écriture refusée — un geste, ou le verrou sur une clé retirée — rompt le
support jusqu'à l'écriture ailleurs (§ 8.6). La table des libellés couvre
chaque code du stockage, du CSV et de la séance ; son épreuve balaie les
sources, et un code neuf exige un texte.
Vérifié : 1416 épreuves node ; depot.js à 100 %, lister gardant l'identifiant.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 23:17:36 -04:00
[ 'VERROU_REPRIS' , { seance : 'seance-morte' , depuis : '2031-05-01T21:40:00-04:00' } ] ,
[ 'VERROU_NON_RENDU' , { chemin : 'Soirée des Lucioles.gtt.verrou' , dossier : 'E:\\data' , cause : 'EROFS' } ] ,
[ 'RIEN_A_DEFAIRE' , { } ] ,
[ 'RIEN_A_REFAIRE' , { } ] ,
[ 'INSTANT_NON_RESTITUABLE' , { revision : 3 } ] ,
[ 'PLAN_NON_BLOQUE' , { remede : null } ] ,
[ 'JALON_NON_ECRIT' , { chemin : 'Soirée des Lucioles.gtt-journal.jsonl' , dossier : 'E:\\data' , cause : 'EROFS' } ] ,
[ 'SANS_EFFET' , { commande : 'modifierParticipant' , remede : null } ] ,
[ADD] application: observable session, events facade, places and seats
The screens of iteration 3 need state they can subscribe to and commands
that keep the plan sound. The session follows the Svelte store contract,
unblocks a plan, cancels a gesture, keeps the framing, and orders the
warnings of a gesture. The facade lists events, settles the working folder
and runs copy, rename, import and delete. Places cover reservations, titles,
designation and the retained plan: each gesture reads seats as the display
shows them, reserved people keep their table, and a person removed frees
her chair while every other seat stays.
Checked: 1973 node, 127 browser, 60 node-long tests; coverage floors kept.
--- FR ---
[ADD] application : séance observable, façade des événements, places
Les écrans de l'itération 3 exigent un état auquel s'abonner et des
commandes qui gardent le plan sain. La séance suit le contrat des magasins
de Svelte, débloque un plan, annule un geste, garde le cadrage et ordonne
les avertissements d'un geste. La façade liste les événements, règle le
dossier de travail et mène copie, renommage, import et suppression. Les
places couvrent réservations, titres, désignation et plan retenu : chaque
geste lit les sièges comme l'écran les montre, une personne réservée garde
sa table, et une personne retirée libère sa chaise sans déplacer les autres.
Vérifié : 1973 node, 127 navigateur, 60 node-long ; seuils de couverture.
Assisted-by: Claude Opus 5.5
2026-10-07 04:14:24 -04:00
[ 'RETOUR_AUTOMATIQUE' , { } ] ,
[
'DERIVE_NOUVELLE' ,
{
touchees : [
{ id : 'retenu' , raisons : [ { code : 'TABLE_SUPPRIMEE' , table : 2 } ] } ,
{ id : 3 , raisons : [ { code : 'CAPACITE_CHANGEE' , table : 1 , avant : 8 , maintenant : 6 } ] } ,
] ,
} ,
] ,
[ 'RESERVATION_NON_HONOREE' , { participant : 4 , table : 5 , tour : 1 } ] ,
[
'INVARIANT_VIOLE' ,
{
proposition : 1 ,
violations : [
{ code : 'RESERVATION' , participant : 4 , table : 5 , tour : 1 } ,
{ code : 'CAPACITE' , table : 5 , tour : 2 } ,
] ,
remede : null ,
} ,
] ,
[ 'CONFIGURATION_INCOHERENTE' , { code : 'SURRESERVATION' , details : { table : 2 , tour : 1 , reservees : 9 , capacite : 8 } } ] ,
// Moteur
[ 'PLACES_MANQUANTES' , { placesManquantes : 3 } ] ,
[ 'SURRESERVATION' , { table : 2 , tour : 1 , reservees : 9 , capacite : 8 , remede : null } ] ,
[ADD] application: generation controller, worker, evaluation table
Generating must never block the screen or record a plan that breaks a
rule. The controller refuses before launching, in a fixed order: an
out-of-range setting named by the engine's own check, the session's
refusals, then what makes the run useless. It runs the search in a worker
or in the thread, records only propositions whose invariants hold, skips
plans already present, and keeps the last duration for the session only.
The evaluation table ranks propositions on the minimum of encounters and
names the reservations a proposition does not honour.
Checked: 2112 node and 60 node-long tests; coverage floors kept.
--- FR ---
[ADD] application : génération, travailleur, table d'évaluation
Générer ne doit jamais bloquer l'écran ni enregistrer un plan qui enfreint
une règle. Le contrôleur refuse avant tout lancement, dans un ordre fixe :
un réglage hors de son domaine, nommé par la règle même du moteur, les
refus de la séance, puis ce qui rend le calcul vain. Il lance la recherche
dans un travailleur ou dans le fil, n'enregistre que les propositions dont
les invariants tiennent, écarte les plans déjà présents, et garde la
dernière durée pour la seule séance. La table d'évaluation classe les
propositions sur le minimum de rencontres et nomme les réservations
qu'une proposition ne tient pas.
Vérifié : 2112 node et 60 node-long ; seuils de couverture tenus.
Assisted-by: Claude Opus 5.5
2026-10-07 06:09:45 -04:00
[ 'CAPACITE' , { table : 2 , capacite : 1 } ] ,
[ 'TOURS' , { tours : 0 } ] ,
[ 'PARTICIPANT_DOUBLON' , { participant : 4 } ] ,
[ 'TABLE_DOUBLON' , { table : 3 } ] ,
[ 'RESERVATION_INCONNUE' , { reservation : 0 , participant : 42 } ] ,
[ 'RESERVATION_TOUR' , { reservation : 2 , tour : 5 } ] ,
[ 'RESERVATION_CONFLIT' , { participant : 4 , tour : 2 , tables : [ 1 , 3 ] } ] ,
[ 'ENUMERATION_TROP_GRANDE' , { participant : 7 , tablesAdmissibles : 40 , toursLibres : 9 , limite : 1000000 } ] ,
[ 'PLAN_TOURS' , { attendu : 4 , tours : 3 , reserves : 3 } ] ,
[ 'PLAN_TABLE_INCONNUE' , { table : 9 } ] ,
[ 'PLAN_TABLE_DOUBLON' , { table : 2 } ] ,
[ 'PLAN_TABLE_ABSENTE' , { table : 5 } ] ,
[ 'PLAN_LISTES' , { tour : 2 , listes : 3 , tables : 4 } ] ,
[ 'PLAN_INCONNU' , { participant : 42 , tour : 1 } ] ,
[ 'PLAN_EXCLU_PLACE' , { participant : 3 , tour : 2 } ] ,
[ 'PLAN_DOUBLE_PLACE' , { participant : 5 , tour : 3 } ] ,
[ 'PLAN_NON_ASSIS' , { participant : 6 , tour : 4 } ] ,
[ 'TABLE_INCONNUE' , { table : 9 , detail : 'inconnue' } ] ,
[ 'DOUBLE_PLACE' , { participant : 5 , tour : 3 } ] ,
[ 'EXCLU_PLACE' , { participant : 3 , tour : 2 } ] ,
[ 'NON_ASSIS' , { participant : 6 , tour : 4 } ] ,
[ 'RESERVATION' , { participant : 4 , table : 5 , tour : 1 } ] ,
[ 'PLAFOND_A_PRIORI' , { index : 2 , participant : 3 } ] ,
[ 'ORDRE_PLAFONDS' , { index : 2 , participant : 3 } ] ,
[ 'PLAFOND_REALISE' , { index : 0 , participant : 1 } ] ,
[ 'DEPASSEMENT' , { index : 4 , participant : 9 } ] ,
// Génération
[ 'GENERATION_EN_COURS' , { remede : null } ] ,
[FIX] iteration 3 review: seat targets, gestures, warnings, settings
The wheel no longer zooms during a gesture, so a drop lands under the
ghost. A seat is a target the size of its pitch. A floor drop refuses to
remove an every-tour reservation. A seat reservation the retained plan
does not hold is flagged at the seat, with its swap as remedy. DERIVE names
the person; capacities have an upper bound; Generate waits for a pending
setting; a worker that fails to load shows a refusal; § 5.3 constraints
get their switches. Commands are not re-entrant and a double click does not
confirm. Checked: 2755 node, 550 browser tests, from the index alone.
--- FR ---
[FIX] revue de l'itération 3 : cibles, gestes, avertissements, réglages
La molette ne zoome plus pendant un geste : le dépôt tombe sous le fantôme.
Un siège est une cible de la taille de son pas. Un dépôt sur le sol refuse
de retirer une réservation de tous les tours. Une réservation à un siège
que le retenu ne tient pas est relevée au siège, l'échange en remède. La
dérive nomme la personne ; les capacités ont une borne ; Générer attend un
réglage en cours ; un travailleur qui ne se charge pas donne un refus ; le
§ 5.3 a ses bascules. Les commandes ne sont pas ré-entrantes et un double
clic ne confirme pas. Vérifié : 2755 node, 550 navigateur, index seul.
Assisted-by: Claude Opus 5.5
2026-10-07 17:50:35 -04:00
[ 'TRAVAILLEUR_EN_ECHEC' , { remede : null } ] ,
[ADD] application: generation controller, worker, evaluation table
Generating must never block the screen or record a plan that breaks a
rule. The controller refuses before launching, in a fixed order: an
out-of-range setting named by the engine's own check, the session's
refusals, then what makes the run useless. It runs the search in a worker
or in the thread, records only propositions whose invariants hold, skips
plans already present, and keeps the last duration for the session only.
The evaluation table ranks propositions on the minimum of encounters and
names the reservations a proposition does not honour.
Checked: 2112 node and 60 node-long tests; coverage floors kept.
--- FR ---
[ADD] application : génération, travailleur, table d'évaluation
Générer ne doit jamais bloquer l'écran ni enregistrer un plan qui enfreint
une règle. Le contrôleur refuse avant tout lancement, dans un ordre fixe :
un réglage hors de son domaine, nommé par la règle même du moteur, les
refus de la séance, puis ce qui rend le calcul vain. Il lance la recherche
dans un travailleur ou dans le fil, n'enregistre que les propositions dont
les invariants tiennent, écarte les plans déjà présents, et garde la
dernière durée pour la seule séance. La table d'évaluation classe les
propositions sur le minimum de rencontres et nomme les réservations
qu'une proposition ne tient pas.
Vérifié : 2112 node et 60 node-long ; seuils de couverture tenus.
Assisted-by: Claude Opus 5.5
2026-10-07 06:09:45 -04:00
[ 'AUCUN_PARTICIPANT' , { remede : { geste : 'ouvrirSection' , section : 'participants' } } ] ,
[ 'AUCUNE_TABLE' , { remede : { geste : 'ouvrirSection' , section : 'tables' } } ] ,
[
'CONFIRMATION_REQUISE' ,
{
estimation : {
secondes : 1200 ,
longue : true ,
auDelaDuPlafond : false ,
mesure : { dureeMs : 2000 , mouvements : 1000000 , participants : 260 , nombre : 5 , arret : 200000 } ,
} ,
remede : null ,
} ,
] ,
[ 'HORS_DOMAINE' , { min : 1 , max : null , remede : null } ] ,
[ 'REGLAGE_HORS_DOMAINE' , { reglage : 'arret' , remede : null } ] ,
[
'GENERATION_INVARIANT_VIOLE' ,
{
rang : 2 ,
violations : [
{ code : 'RESERVATION' , participant : 4 , table : 5 , tour : 1 } ,
{ code : 'CAPACITE' , table : 5 , tour : 2 } ,
] ,
remede : null ,
} ,
] ,
[ADD] application: session, named commands, French label table
The session holds one event, opens it read-only and writes only after
"Modifier" takes the lock (§ 8.4, § 8.8); calls run one after another, so a
double click writes two entries in order. Every change is a pure named
command with a frozen French label (§ 8.2); undo, redo and return write
entries and never truncate the journal (§ 8.3). A failed write — a gesture,
or the lock on a removed key — breaks the medium until the event is written
elsewhere (§ 8.6). The label table covers every code storage, CSV and the
session emit, and its test sweeps the sources so a new code needs a text.
Checked: 1416 node tests; depot.js at 100 % with the identifier lister keeps.
--- FR ---
[ADD] application : séance, commandes nommées, table des libellés
La séance tient un événement, l'ouvre en lecture et n'écrit qu'après que
« Modifier » a pris le verrou (§ 8.4, § 8.8) ; les appels passent l'un après
l'autre : un double clic écrit deux entrées, dans l'ordre. Toute modification
est une commande nommée pure au libellé français figé (§ 8.2) ; défaire,
refaire et revenir écrivent des entrées sans tronquer le journal (§ 8.3). Une
écriture refusée — un geste, ou le verrou sur une clé retirée — rompt le
support jusqu'à l'écriture ailleurs (§ 8.6). La table des libellés couvre
chaque code du stockage, du CSV et de la séance ; son épreuve balaie les
sources, et un code neuf exige un texte.
Vérifié : 1416 épreuves node ; depot.js à 100 %, lister gardant l'identifiant.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 23:17:36 -04:00
// Refus et avertissements des commandes
[ 'NOM_REQUIS' , { remede : null } ] ,
[ 'DATE_INVALIDE' , { date : '2031-02-30' , remede : null } ] ,
[ 'SIEGES_INVALIDES' , { sieges : 1 , remede : null } ] ,
[ 'TOURS_INVALIDES' , { tours : 0 , remede : null } ] ,
[ 'PARTICIPANT_INCONNU' , { id : 42 , remede : null } ] ,
[ 'PROPOSITION_INCONNUE' , { id : 17 , remede : null } ] ,
[ 'AUCUNE_PROPOSITION' , { remede : { geste : 'ouvrirSection' , section : 'generation' } } ] ,
[ 'AUCUN_RETENU' , { remede : { geste : 'ouvrirSection' , section : 'propositions' } } ] ,
[ 'RESERVATIONS_SUSPENDUES' , { participant : 1 , personne : 'Iris Ombrelle' , reservations : 2 , titres : 1 } ] ,
[ 'RESERVATIONS_RETABLIES' , { participant : 1 , personne : 'Iris Ombrelle' , reservations : 2 , titres : 1 } ] ,
[ 'RESERVATIONS_RETIREES' , { participant : 1 , personne : 'Iris Ombrelle' , reservations : 1 , titres : 1 } ] ,
[ 'IMPORT_EN_RETENU' , { } ] ,
[ADD] application: observable session, events facade, places and seats
The screens of iteration 3 need state they can subscribe to and commands
that keep the plan sound. The session follows the Svelte store contract,
unblocks a plan, cancels a gesture, keeps the framing, and orders the
warnings of a gesture. The facade lists events, settles the working folder
and runs copy, rename, import and delete. Places cover reservations, titles,
designation and the retained plan: each gesture reads seats as the display
shows them, reserved people keep their table, and a person removed frees
her chair while every other seat stays.
Checked: 1973 node, 127 browser, 60 node-long tests; coverage floors kept.
--- FR ---
[ADD] application : séance observable, façade des événements, places
Les écrans de l'itération 3 exigent un état auquel s'abonner et des
commandes qui gardent le plan sain. La séance suit le contrat des magasins
de Svelte, débloque un plan, annule un geste, garde le cadrage et ordonne
les avertissements d'un geste. La façade liste les événements, règle le
dossier de travail et mène copie, renommage, import et suppression. Les
places couvrent réservations, titres, désignation et plan retenu : chaque
geste lit les sièges comme l'écran les montre, une personne réservée garde
sa table, et une personne retirée libère sa chaise sans déplacer les autres.
Vérifié : 1973 node, 127 navigateur, 60 node-long ; seuils de couverture.
Assisted-by: Claude Opus 5.5
2026-10-07 04:14:24 -04:00
// Façade des événements
[ 'QUESTION_EN_ATTENTE' , { question : 'PORTABLE_NON_INSCRIPTIBLE' , remede : null } ] ,
[ 'EXPLORATEUR_ABSENT' , { remede : null } ] ,
[ADD] application: session, named commands, French label table
The session holds one event, opens it read-only and writes only after
"Modifier" takes the lock (§ 8.4, § 8.8); calls run one after another, so a
double click writes two entries in order. Every change is a pure named
command with a frozen French label (§ 8.2); undo, redo and return write
entries and never truncate the journal (§ 8.3). A failed write — a gesture,
or the lock on a removed key — breaks the medium until the event is written
elsewhere (§ 8.6). The label table covers every code storage, CSV and the
session emit, and its test sweeps the sources so a new code needs a text.
Checked: 1416 node tests; depot.js at 100 % with the identifier lister keeps.
--- FR ---
[ADD] application : séance, commandes nommées, table des libellés
La séance tient un événement, l'ouvre en lecture et n'écrit qu'après que
« Modifier » a pris le verrou (§ 8.4, § 8.8) ; les appels passent l'un après
l'autre : un double clic écrit deux entrées, dans l'ordre. Toute modification
est une commande nommée pure au libellé français figé (§ 8.2) ; défaire,
refaire et revenir écrivent des entrées sans tronquer le journal (§ 8.3). Une
écriture refusée — un geste, ou le verrou sur une clé retirée — rompt le
support jusqu'à l'écriture ailleurs (§ 8.6). La table des libellés couvre
chaque code du stockage, du CSV et de la séance ; son épreuve balaie les
sources, et un code neuf exige un texte.
Vérifié : 1416 épreuves node ; depot.js à 100 %, lister gardant l'identifiant.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 23:17:36 -04:00
// Libellés figés du journal
[ 'ENTREE_CREATION' , { nom : "Soirée d'essai" } ] ,
[ 'JALON_CREATION' , { } ] ,
[ 'JALON_ETAT' , { etat : 'retenu' } ] ,
[ 'JALON_AVANT_GENERATION' , { } ] ,
[ 'ENTREE_REPRISE' , { } ] ,
[ 'ENTREE_DEFAIRE' , { libelle : 'Ajouté Théo Pervenche' } ] ,
[ 'ENTREE_REFAIRE' , { libelle : 'Ajouté Théo Pervenche' } ] ,
[ 'ENTREE_REVENIR' , { libelle : 'Ajouté Iris Ombrelle' } ] ,
[ 'ENTREE_AJOUTER_PARTICIPANT' , { personne : 'Iris Ombrelle' } ] ,
[ 'ENTREE_MODIFIER_PARTICIPANT' , { personne : 'Iris Ombrelle' , champs : [ 'courriel' ] , devenu : null } ] ,
[ 'ENTREE_EXCLURE_PARTICIPANT' , { personne : 'Iris Ombrelle' } ] ,
[ 'ENTREE_REINTEGRER_PARTICIPANT' , { personne : 'Iris Ombrelle' } ] ,
[ 'ENTREE_SUPPRIMER_PARTICIPANT' , { personne : 'Iris Ombrelle' } ] ,
[ 'ENTREE_IMPORTER_PARTICIPANTS' , { mode : 'ajouter' , ajoutes : 12 , misAJour : 0 , retires : 0 } ] ,
[ 'ENTREE_CHANGER_ETAT' , { etat : 'bloque' } ] ,
[ 'ENTREE_DEBLOQUER' , { etat : 'retenu' } ] ,
[ADD] application: observable session, events facade, places and seats
The screens of iteration 3 need state they can subscribe to and commands
that keep the plan sound. The session follows the Svelte store contract,
unblocks a plan, cancels a gesture, keeps the framing, and orders the
warnings of a gesture. The facade lists events, settles the working folder
and runs copy, rename, import and delete. Places cover reservations, titles,
designation and the retained plan: each gesture reads seats as the display
shows them, reserved people keep their table, and a person removed frees
her chair while every other seat stays.
Checked: 1973 node, 127 browser, 60 node-long tests; coverage floors kept.
--- FR ---
[ADD] application : séance observable, façade des événements, places
Les écrans de l'itération 3 exigent un état auquel s'abonner et des
commandes qui gardent le plan sain. La séance suit le contrat des magasins
de Svelte, débloque un plan, annule un geste, garde le cadrage et ordonne
les avertissements d'un geste. La façade liste les événements, règle le
dossier de travail et mène copie, renommage, import et suppression. Les
places couvrent réservations, titres, désignation et plan retenu : chaque
geste lit les sièges comme l'écran les montre, une personne réservée garde
sa table, et une personne retirée libère sa chaise sans déplacer les autres.
Vérifié : 1973 node, 127 navigateur, 60 node-long ; seuils de couverture.
Assisted-by: Claude Opus 5.5
2026-10-07 04:14:24 -04:00
[ 'ENTREE_COPIE' , { source : 'Soirée des Lucioles' , instant : "Création de l'événement « Soirée des Lucioles »" } ] ,
[ 'ENTREE_RENOMMER' , { ancien : 'Soirée des Lucioles' , nom : 'Soirée des Cerfs' } ] ,
[ 'ENTREE_DEMONSTRATION' , { nom : 'Petite démonstration' } ] ,
[ADD] application: session, named commands, French label table
The session holds one event, opens it read-only and writes only after
"Modifier" takes the lock (§ 8.4, § 8.8); calls run one after another, so a
double click writes two entries in order. Every change is a pure named
command with a frozen French label (§ 8.2); undo, redo and return write
entries and never truncate the journal (§ 8.3). A failed write — a gesture,
or the lock on a removed key — breaks the medium until the event is written
elsewhere (§ 8.6). The label table covers every code storage, CSV and the
session emit, and its test sweeps the sources so a new code needs a text.
Checked: 1416 node tests; depot.js at 100 % with the identifier lister keeps.
--- FR ---
[ADD] application : séance, commandes nommées, table des libellés
La séance tient un événement, l'ouvre en lecture et n'écrit qu'après que
« Modifier » a pris le verrou (§ 8.4, § 8.8) ; les appels passent l'un après
l'autre : un double clic écrit deux entrées, dans l'ordre. Toute modification
est une commande nommée pure au libellé français figé (§ 8.2) ; défaire,
refaire et revenir écrivent des entrées sans tronquer le journal (§ 8.3). Une
écriture refusée — un geste, ou le verrou sur une clé retirée — rompt le
support jusqu'à l'écriture ailleurs (§ 8.6). La table des libellés couvre
chaque code du stockage, du CSV et de la séance ; son épreuve balaie les
sources, et un code neuf exige un texte.
Vérifié : 1416 épreuves node ; depot.js à 100 %, lister gardant l'identifiant.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 23:17:36 -04:00
[ 'ENTREE_ENREGISTRER_GENERATION' , { n : 5 , premier : 3 , dernier : 7 } ] ,
[ 'ENTREE_RETENIR_PROPOSITION' , { id : 3 } ] ,
[ 'ENTREE_EFFACER_PROPOSITIONS' , { n : 4 , gardee : 2 } ] ,
[ADD] application: observable session, events facade, places and seats
The screens of iteration 3 need state they can subscribe to and commands
that keep the plan sound. The session follows the Svelte store contract,
unblocks a plan, cancels a gesture, keeps the framing, and orders the
warnings of a gesture. The facade lists events, settles the working folder
and runs copy, rename, import and delete. Places cover reservations, titles,
designation and the retained plan: each gesture reads seats as the display
shows them, reserved people keep their table, and a person removed frees
her chair while every other seat stays.
Checked: 1973 node, 127 browser, 60 node-long tests; coverage floors kept.
--- FR ---
[ADD] application : séance observable, façade des événements, places
Les écrans de l'itération 3 exigent un état auquel s'abonner et des
commandes qui gardent le plan sain. La séance suit le contrat des magasins
de Svelte, débloque un plan, annule un geste, garde le cadrage et ordonne
les avertissements d'un geste. La façade liste les événements, règle le
dossier de travail et mène copie, renommage, import et suppression. Les
places couvrent réservations, titres, désignation et plan retenu : chaque
geste lit les sièges comme l'écran les montre, une personne réservée garde
sa table, et une personne retirée libère sa chaise sans déplacer les autres.
Vérifié : 1973 node, 127 navigateur, 60 node-long ; seuils de couverture.
Assisted-by: Claude Opus 5.5
2026-10-07 04:14:24 -04:00
// Places : refus d'une pose, titres, réservations, retenu
[ 'SIEGE_INEXISTANT' , { table : 2 , siege : 9 , capacite : 8 , remede : null } ] ,
[ 'DEUX_PLACES_MEME_TOUR' , { participant : 1 , tour : 2 , autre : { table : 1 , siege : null , portee : 'tous' , tour : null } , remede : null } ] ,
[ 'PERSONNE_EXCLUE' , { participant : 3 , remede : null } ] ,
[ 'PLACE_RESERVEE' , { table : 2 , siege : 3 , tour : 2 , titulaire : 2 , portee : 'tour' , remede : null } ] ,
[ 'RESERVEE_AILLEURS' , { participant : 1 , tour : 2 , reservation : { table : 1 , siege : null , portee : 'tous' , tour : null } , remede : null } ] ,
[ 'TABLE_GELEE' , { table : 1 } ] ,
[ 'LIBELLE_VIDE' , { remede : null } ] ,
[ 'PLACE_TITREE' , { table : 3 , siege : 1 , libelle : 'accueil' , remede : null } ] ,
[ 'AUCUN_SIEGE_SANS_TITRE' , { table : 1 , capacite : 2 , remede : null } ] ,
[ 'TITRE_ABSENT' , { table : 3 , siege : 1 , remede : null } ] ,
[ 'RESERVATION_ABSENTE' , { participant : 1 , table : 3 , portee : 'tour' , tour : 2 , remede : null } ] ,
[ 'CONVERSION_IMPOSSIBLE' , { participant : 1 , table : 3 , remede : null } ] ,
[ 'PROPOSITION_MONTREE' , { proposition : 4 , remede : null } ] ,
[ 'SIEGE_OCCUPE' , { table : 5 , siege : 2 , tour : 1 , occupant : 2 , remede : null } ] ,
[ 'TABLE_PLEINE' , { table : 5 , tour : 1 , capacite : 6 , remede : null } ] ,
[FIX] iteration 3 review: seat targets, gestures, warnings, settings
The wheel no longer zooms during a gesture, so a drop lands under the
ghost. A seat is a target the size of its pitch. A floor drop refuses to
remove an every-tour reservation. A seat reservation the retained plan
does not hold is flagged at the seat, with its swap as remedy. DERIVE names
the person; capacities have an upper bound; Generate waits for a pending
setting; a worker that fails to load shows a refusal; § 5.3 constraints
get their switches. Commands are not re-entrant and a double click does not
confirm. Checked: 2755 node, 550 browser tests, from the index alone.
--- FR ---
[FIX] revue de l'itération 3 : cibles, gestes, avertissements, réglages
La molette ne zoome plus pendant un geste : le dépôt tombe sous le fantôme.
Un siège est une cible de la taille de son pas. Un dépôt sur le sol refuse
de retirer une réservation de tous les tours. Une réservation à un siège
que le retenu ne tient pas est relevée au siège, l'échange en remède. La
dérive nomme la personne ; les capacités ont une borne ; Générer attend un
réglage en cours ; un travailleur qui ne se charge pas donne un refus ; le
§ 5.3 a ses bascules. Les commandes ne sont pas ré-entrantes et un double
clic ne confirme pas. Vérifié : 2755 node, 550 navigateur, index seul.
Assisted-by: Claude Opus 5.5
2026-10-07 17:50:35 -04:00
[ 'RESERVATION_TOUS_LES_TOURS' , { participant : 1 , table : 3 , remede : null } ] ,
[ADD] application: observable session, events facade, places and seats
The screens of iteration 3 need state they can subscribe to and commands
that keep the plan sound. The session follows the Svelte store contract,
unblocks a plan, cancels a gesture, keeps the framing, and orders the
warnings of a gesture. The facade lists events, settles the working folder
and runs copy, rename, import and delete. Places cover reservations, titles,
designation and the retained plan: each gesture reads seats as the display
shows them, reserved people keep their table, and a person removed frees
her chair while every other seat stays.
Checked: 1973 node, 127 browser, 60 node-long tests; coverage floors kept.
--- FR ---
[ADD] application : séance observable, façade des événements, places
Les écrans de l'itération 3 exigent un état auquel s'abonner et des
commandes qui gardent le plan sain. La séance suit le contrat des magasins
de Svelte, débloque un plan, annule un geste, garde le cadrage et ordonne
les avertissements d'un geste. La façade liste les événements, règle le
dossier de travail et mène copie, renommage, import et suppression. Les
places couvrent réservations, titres, désignation et plan retenu : chaque
geste lit les sièges comme l'écran les montre, une personne réservée garde
sa table, et une personne retirée libère sa chaise sans déplacer les autres.
Vérifié : 1973 node, 127 navigateur, 60 node-long ; seuils de couverture.
Assisted-by: Claude Opus 5.5
2026-10-07 04:14:24 -04:00
[ 'ENTREE_RESERVER' , { personne : 'Théo Pervenche' , numero : 4 , siege : 2 , portee : 'tour' , tour : 2 } ] ,
[ 'ENTREE_RETIRER_RESERVATION' , { personne : 'Théo Pervenche' , numero : 4 , portee : 'tous' , tour : null } ] ,
[ 'ENTREE_DEPLACER_RESERVATION' , { personne : 'Théo Pervenche' , de : 4 , vers : 7 , siege : null , portee : 'tour' , tour : 2 } ] ,
[ 'ENTREE_POSER_TITRE' , { libelle : 'accueil' , numero : 3 , siege : 1 } ] ,
[ 'ENTREE_RETIRER_TITRE' , { libelle : 'accueil' , numero : 3 , siege : 1 } ] ,
[ 'ENTREE_DESIGNER' , { personne : 'Iris Ombrelle' , numero : 3 , siege : 1 , libelle : 'accueil' } ] ,
[ 'ENTREE_CONVERTIR_EN_TOUS' , { personne : 'Iris Ombrelle' , numero : 3 } ] ,
[ 'ENTREE_DEPLACER_DANS_RETENU' , { personne : 'Iris Ombrelle' , de : 4 , vers : 7 , siege : null , tour : 2 } ] ,
[ 'ENTREE_ECHANGER_DANS_RETENU' , { a : 'Iris Ombrelle' , deA : 4 , b : 'Théo Pervenche' , deB : null , tour : 2 } ] ,
[ 'ENTREE_RETIRER_DU_TOUR' , { personne : 'Iris Ombrelle' , numero : 4 , tour : 2 } ] ,
[ADD] application: furniture — tables, default, tours, capacity checks
Changing a table, the default seat count or the number of tours reshapes
a plan the operator may already have retained. Eight commands create,
remove and resize tables and set the default and the tours; every change
of capacity first passes controlerCapacite, which refuses to cut a reserved
or titled seat, states who goes to the reserve, and only brings back a
reserved person the display showed seated (§ 6.1, § 6.2). bilans.js states
the figures of a change before it applies.
Checked: 2033 node and 127 browser tests.
--- FR ---
[ADD] application : mobilier — tables, défaut, tours, contrôles
Changer une table, le défaut de sièges ou le nombre de tours remodèle un
plan que l'opérateur a peut-être retenu. Huit commandes créent, retirent et
redimensionnent les tables, et règlent le défaut et les tours ; tout
changement de capacité passe d'abord par controlerCapacite, qui refuse de
couper un siège réservé ou titré, dit qui part en réserve, et ne ramène une
personne réservée que si l'écran la montrait assise (§ 6.1, § 6.2).
bilans.js énonce les chiffres d'un changement avant qu'il s'applique.
Vérifié : 2033 node et 127 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 05:12:43 -04:00
// Mobilier : saisies, capacité, sièges retirés, libellés figés
[ 'NON_ENTIER' , { saisie : '8abc' , remede : null } ] ,
[ 'MOINS_DE_DEUX' , { remede : null } ] ,
[ 'MOINS_D_UN_TOUR' , { tours : 0 , remede : null } ] ,
[FIX] iteration 3 review: seat targets, gestures, warnings, settings
The wheel no longer zooms during a gesture, so a drop lands under the
ghost. A seat is a target the size of its pitch. A floor drop refuses to
remove an every-tour reservation. A seat reservation the retained plan
does not hold is flagged at the seat, with its swap as remedy. DERIVE names
the person; capacities have an upper bound; Generate waits for a pending
setting; a worker that fails to load shows a refusal; § 5.3 constraints
get their switches. Commands are not re-entrant and a double click does not
confirm. Checked: 2755 node, 550 browser tests, from the index alone.
--- FR ---
[FIX] revue de l'itération 3 : cibles, gestes, avertissements, réglages
La molette ne zoome plus pendant un geste : le dépôt tombe sous le fantôme.
Un siège est une cible de la taille de son pas. Un dépôt sur le sol refuse
de retirer une réservation de tous les tours. Une réservation à un siège
que le retenu ne tient pas est relevée au siège, l'échange en remède. La
dérive nomme la personne ; les capacités ont une borne ; Générer attend un
réglage en cours ; un travailleur qui ne se charge pas donne un refus ; le
§ 5.3 a ses bascules. Les commandes ne sont pas ré-entrantes et un double
clic ne confirme pas. Vérifié : 2755 node, 550 navigateur, index seul.
Assisted-by: Claude Opus 5.5
2026-10-07 17:50:35 -04:00
[ 'TROP_DE_PLACES' , { max : 1000 , remede : null } ] ,
[ 'TROP_DE_TOURS' , { max : 100 , remede : null } ] ,
[ADD] application: furniture — tables, default, tours, capacity checks
Changing a table, the default seat count or the number of tours reshapes
a plan the operator may already have retained. Eight commands create,
remove and resize tables and set the default and the tours; every change
of capacity first passes controlerCapacite, which refuses to cut a reserved
or titled seat, states who goes to the reserve, and only brings back a
reserved person the display showed seated (§ 6.1, § 6.2). bilans.js states
the figures of a change before it applies.
Checked: 2033 node and 127 browser tests.
--- FR ---
[ADD] application : mobilier — tables, défaut, tours, contrôles
Changer une table, le défaut de sièges ou le nombre de tours remodèle un
plan que l'opérateur a peut-être retenu. Huit commandes créent, retirent et
redimensionnent les tables, et règlent le défaut et les tours ; tout
changement de capacité passe d'abord par controlerCapacite, qui refuse de
couper un siège réservé ou titré, dit qui part en réserve, et ne ramène une
personne réservée que si l'écran la montrait assise (§ 6.1, § 6.2).
bilans.js énonce les chiffres d'un changement avant qu'il s'applique.
Vérifié : 2033 node et 127 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 05:12:43 -04:00
[ 'SOUS_RESERVATIONS' , { table : 2 , n : 7 , tour : 1 , remede : { geste : 'afficherTour' , tour : 1 , proposition : null } } ] ,
[ 'SOUS_OCCUPES' , { table : 2 , n : 7 , tour : 3 , remede : { geste : 'afficherTour' , tour : 3 , proposition : null } } ] ,
[
'SIEGES_RETIRES' ,
{
table : 2 ,
sieges : [
{ siege : 8 , cause : 'SIEGE_TITRE' , libelle : 'animateur' } ,
{ siege : 7 , cause : 'SIEGE_RESERVE' , participant : 1 , suspendue : true } ,
] ,
remede : { commande : 'retirerTitre' , arguments : { table : 2 , siege : 8 } } ,
} ,
] ,
[ 'SIEGE_RESERVE' , { siege : 7 , participant : 1 , suspendue : false } ] ,
[ 'SIEGE_TITRE' , { siege : 8 , libelle : 'animateur' } ] ,
[ 'SURNUMERAIRES' , { table : 3 , tour : 2 , personnes : [ 16 , 17 ] } ] ,
[ 'AU_DELA_LISIBLE' , { seuil : 16 } ] ,
[FIX] core: bounded geometry, dated lock, empty seat, neighbour lists
Non-finite scale ratios, view translations and handle counts now raise
instead of returning 0, NaN or looping forever. The lock carries the
instant from the single clock, a lock taken back in this session's name is
its own, and quitting releases held locks. An empty chair is null only, at
its exact path. A table grown over a neighbour warns LISTE_SUR_VOISINE once
per pair; successive remedies never aim at the same free cell.
Checked: every new test red first; 2983 node and 617 browser tests green
from the index alone.
--- FR ---
[FIX] cœur : géométrie bornée, verrou daté, chaise vide, listes voisines
Un rapport d'échelle, une translation de vue ou un compte de poignée non
finis lèvent au lieu de rendre 0, NaN ou de boucler sans fin. Le verrou
porte l'instant de l'horloge unique, un verrou repris au nom de la séance
est le sien, et quitter rend les verrous tenus. Une chaise vide est null
seulement, à son chemin exact. Une table agrandie sur sa voisine avertit
LISTE_SUR_VOISINE une fois par paire ; les remèdes successifs ne visent
jamais la même case libre.
Vérifié : chaque épreuve rouge d'abord ; 2983 node et 617 navigateur verts
depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 04:00:22 -04:00
[ 'LISTE_SUR_VOISINE' , { table : 4 , voisine : 7 , numero : 3 , numeroVoisine : 5 , remede : { commande : 'deplacerTable' , arguments : { id : 7 , position : { x : 1626 , y : 0 } } } } ] ,
[ADD] application: furniture — tables, default, tours, capacity checks
Changing a table, the default seat count or the number of tours reshapes
a plan the operator may already have retained. Eight commands create,
remove and resize tables and set the default and the tours; every change
of capacity first passes controlerCapacite, which refuses to cut a reserved
or titled seat, states who goes to the reserve, and only brings back a
reserved person the display showed seated (§ 6.1, § 6.2). bilans.js states
the figures of a change before it applies.
Checked: 2033 node and 127 browser tests.
--- FR ---
[ADD] application : mobilier — tables, défaut, tours, contrôles
Changer une table, le défaut de sièges ou le nombre de tours remodèle un
plan que l'opérateur a peut-être retenu. Huit commandes créent, retirent et
redimensionnent les tables, et règlent le défaut et les tours ; tout
changement de capacité passe d'abord par controlerCapacite, qui refuse de
couper un siège réservé ou titré, dit qui part en réserve, et ne ramène une
personne réservée que si l'écran la montrait assise (§ 6.1, § 6.2).
bilans.js énonce les chiffres d'un changement avant qu'il s'applique.
Vérifié : 2033 node et 127 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 05:12:43 -04:00
[ 'ENTREE_CREER_TABLE' , { numero : 3 } ] ,
[ 'ENTREE_SUPPRIMER_TABLE' , { numero : 2 , reservations : 2 , titres : 1 } ] ,
[ 'ENTREE_DEPLACER_TABLE' , { numero : 3 } ] ,
[ 'ENTREE_CHANGER_PLACES_TABLE' , { numero : 5 , sieges : 10 } ] ,
[ 'ENTREE_REALIGNER_TABLE' , { numero : 5 , sieges : 8 } ] ,
[ 'ENTREE_CHANGER_DEFAUT' , { sieges : 10 , realignees : 4 } ] ,
[ 'ENTREE_CHANGER_TOURS' , { tours : 3 , retirees : 2 } ] ,
[ 'ENTREE_MODIFIER_REGLAGE' , { cle : 'separerAppartenances' , valeur : false } ] ,
[ADD] home: retained plan's collisions and redundancy gap per event
The event list showed no outcome, so comparing events meant opening each.
Each line now shows, for the retained plan only, the cumulated collisions
(pair and tour) and the redundancy gap |target - sum of r(p)|, with an
explanation tooltip; an event without a measurable retained plan shows a
dash. The target is set per event in Propositions, 1000 by default: an
optional settings field, absent in every existing file and read as 1000,
written through one undoable command refused in reading mode. Listing
stays read-only. Checked: 2909 node, 604 browser, 62 long tests.
--- FR ---
[ADD] accueil : collisions et Diff de redondance du retenu par événement
La liste des événements ne montrait aucun résultat : comparer obligeait à
ouvrir chacun. Chaque ligne montre, pour le retenu seul, les collisions
cumulées (paire et tour) et la Diff somme d'appartenance, |cible - somme
de r(p)|, avec une infobulle ; sans retenu mesurable, un tiret. La cible
se règle par événement dans Propositions, 1000 par défaut : champ
facultatif des réglages, absent de tout fichier existant et lu 1000, écrit
par une commande qui se défait et se refuse en lecture. La liste reste en
lecture seule. Vérifié : 2909 node, 604 navigateur, 62 longues.
Assisted-by: Claude Opus 5.5
2026-10-08 05:35:48 -04:00
[ 'ENTREE_CHANGER_CIBLE_REDONDANCE' , { cible : 1200 } ] ,
[ADD] badges: sheet model from the retained plan or a chosen proposal
Badges hang on guests' necks: their content must be exact and stable. The
model builds one badge per non-excluded person, from the retained plan or,
on demand, from a proposal, which always prints as a draft. Each round says
table, seat when seats are assigned, and a title only where a reservation
makes the person its holder there; a constant itinerary takes one line.
Names sort by code point, never by locale; namesakes carry their id, and a
reprint gives the same texts. Without any placement it refuses, pointing at
the proposals.
Checked: red first; 3343 node tests green from the index alone.
--- FR ---
[ADD] badges : modèle de planche, du retenu ou d'une proposition choisie
Les badges pendent au cou des invités : leur contenu doit être exact et
stable. Le modèle fait un badge par personne non exclue, depuis le retenu
ou, sur demande, une proposition, toujours imprimée en brouillon. Chaque
tour dit la table, le siège sous attribution, et un titre seulement là où
une réservation en fait la personne titulaire ; un itinéraire constant tient
en une ligne. Les noms se classent par points de code, jamais selon la
locale ; les homonymes portent leur identifiant, et une réédition rend les
mêmes textes. Sans aucun placement, il refuse et renvoie aux propositions.
Vérifié : rouge d'abord ; 3343 node verts depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 13:03:04 -04:00
// Planche de badges
[ 'PLANCHE_SANS_PLACEMENT' , { remede : { geste : 'ouvrirSection' , section : 'propositions' } } ] ,
[
'PLANCHE_DERIVE' ,
{
raisons : [ { code : 'PARTICIPANT_NON_PLACE' , participant : 6 , personne : 'Ondine Grisaille' } ] ,
remede : { geste : 'ouvrirSection' , section : 'propositions' } ,
} ,
] ,
[ 'PLANCHE_TROP_DE_TOURS' , { tours : 40 , remede : null } ] ,
[ 'PLANCHE_NOM_TROP_LONG' , { participant : 3 , personne : 'Ondine Lacasse' , remede : { geste : 'ouvrirSection' , section : 'participants' } } ] ,
[ADD] badges: prepare, produce and save the sheet from the session
The operator needs the badge sheet as a file next to the event, built
from what the session shows at that moment. The session prepares an
announcement (sheets per paper, counts, namesakes, missing glyphs, draft),
then produces the PDF through a single deferred import of the PDF code and
writes it atomically; under the web build it downloads instead. A gesture
started during production waits its turn. A proposal sheet is always a
draft, and an unwritable file or a missing paper choice gives its refusal
with a remedy.
Checked: red first; 3670 node, 707 browser and 101 long tests green from
the index alone.
--- FR ---
[ADD] badges : préparer, produire et déposer la planche depuis la séance
L'opérateur a besoin de la planche en fichier, à côté de l'événement, tirée
de ce que la séance montre à cet instant. La séance prépare une annonce
(planches par papier, comptes, homonymes, glyphes absents, brouillon), puis
produit le PDF par un seul import différé du code PDF et l'écrit de façon
atomique ; sous web, elle le télécharge. Un geste lancé pendant la
production attend son tour. Une planche tirée d'une proposition est
toujours un brouillon, et un fichier non écrit ou un papier non choisi
donnent leur refus avec un remède.
Vérifié : rouge d'abord ; 3670 node, 707 navigateur et 101 longues vertes
depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 17:11:48 -04:00
[ 'PLANCHE_NON_ECRITE' , { chemin : 'Soirée des Lucioles.badges_A4_feuille_2031-05-17_20-15-07.pdf' , dossier : 'E:\\data' , cause : 'EROFS' , remede : { geste : 'enregistrerSous' } } ] ,
[ 'PAPIER_REQUIS' , { remede : { geste : 'choisirPapier' } } ] ,
[ADD] platform: print bridge, atomic byte writes, badge sheet file name
Printed views and the badge sheet leave the app through the platform. Under
Electron a gtt:imprimer channel prints with the paper and orientation asked
for and reports sent, cancelled, failed or unknown; an exception is logged
to the console and never shown raw to the operator. Every file system,
real or simulated, writes bytes atomically from a copy taken at the call.
The sheet name carries event, date and imposition, and a failed print
becomes IMPRESSION_ECHEC.
Checked: red first, twelve mutations caught; 3502 node, 659 browser and 79
long tests green from the index alone.
--- FR ---
[ADD] plateforme : pont d'impression, octets atomiques, nom de planche
Les vues imprimables et la planche sortent de l'application par la
plateforme. Sous Electron, un canal gtt:imprimer imprime au papier et à
l'orientation demandés et rend envoyé, annulé, échec ou inconnu ; une
exception va à la console, jamais brute à l'opérateur. Chaque système de
fichiers, réel ou simulé, écrit des octets de façon atomique, depuis une
copie prise à l'appel. Le nom de la planche porte événement, date et
imposition, et une impression échouée devient IMPRESSION_ECHEC.
Vérifié : rouge d'abord, douze mutations attrapées ; 3502 node, 659
navigateur et 79 longues vertes depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 15:26:13 -04:00
// Sorties
[ 'IMPRESSION_ECHEC' , { cause : 'failed' , remede : null } ] ,
[ADD] application: session, named commands, French label table
The session holds one event, opens it read-only and writes only after
"Modifier" takes the lock (§ 8.4, § 8.8); calls run one after another, so a
double click writes two entries in order. Every change is a pure named
command with a frozen French label (§ 8.2); undo, redo and return write
entries and never truncate the journal (§ 8.3). A failed write — a gesture,
or the lock on a removed key — breaks the medium until the event is written
elsewhere (§ 8.6). The label table covers every code storage, CSV and the
session emit, and its test sweeps the sources so a new code needs a text.
Checked: 1416 node tests; depot.js at 100 % with the identifier lister keeps.
--- FR ---
[ADD] application : séance, commandes nommées, table des libellés
La séance tient un événement, l'ouvre en lecture et n'écrit qu'après que
« Modifier » a pris le verrou (§ 8.4, § 8.8) ; les appels passent l'un après
l'autre : un double clic écrit deux entrées, dans l'ordre. Toute modification
est une commande nommée pure au libellé français figé (§ 8.2) ; défaire,
refaire et revenir écrivent des entrées sans tronquer le journal (§ 8.3). Une
écriture refusée — un geste, ou le verrou sur une clé retirée — rompt le
support jusqu'à l'écriture ailleurs (§ 8.6). La table des libellés couvre
chaque code du stockage, du CSV et de la séance ; son épreuve balaie les
sources, et un code neuf exige un texte.
Vérifié : 1416 épreuves node ; depot.js à 100 %, lister gardant l'identifiant.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 23:17:36 -04:00
] ) ;
// Un texte composé ne laisse paraître ni valeur absente, ni booléen, ni
// objet : chaque trace est un mot entier, « nulle part » n'en est pas une.
const TRACES = [ /\bundefined\b/ , /\bnull\b/ , /\bNaN\b/ , /\b(?:true|false)\b/ , /\[object / ] ;
describe ( 'libellés : les textes (§ 14.6, § 8.2)' , ( ) => {
test ( 'un code inconnu lève, et le message nomme le code' , ( ) => {
assert . throws ( ( ) => libelle ( 'CODE_QUI_N_EXISTE_PAS' , { } ) , ( erreur ) => {
assert . ok ( erreur instanceof RangeError ) ;
assert . match ( erreur . message , /CODE_QUI_N_EXISTE_PAS/ ) ;
return true ;
} ) ;
assert . throws ( ( ) => libelle ( undefined ) , RangeError ) ;
} ) ;
test ( "chaque texte de la table se compose de ses détails d'exemple" , ( ) => {
assert . ok ( CODES . length > 0 , 'table vide' ) ;
assert . deepEqual ( CODES . filter ( ( code ) => ! EXEMPLES . has ( code ) ) , [ ] , "codes de la table sans détails d'exemple" ) ;
assert . deepEqual ( [ ... EXEMPLES . keys ( ) ] . filter ( ( code ) => ! CODES . includes ( code ) ) , [ ] , 'exemples sans code dans la table' ) ;
for ( const code of CODES ) {
const texte = libelle ( code , EXEMPLES . get ( code ) ) ;
assert . equal ( typeof texte , 'string' , code ) ;
assert . ok ( texte . trim ( ) . length > 0 , ` ${ code } : texte vide ` ) ;
assert . notEqual ( texte , code , ` ${ code } : le code brut tient lieu de texte ` ) ;
for ( const trace of TRACES ) assert . doesNotMatch ( texte , trace , ` ${ code } : « ${ texte } » ` ) ;
}
} ) ;
[FIX] core: bounded geometry, dated lock, empty seat, neighbour lists
Non-finite scale ratios, view translations and handle counts now raise
instead of returning 0, NaN or looping forever. The lock carries the
instant from the single clock, a lock taken back in this session's name is
its own, and quitting releases held locks. An empty chair is null only, at
its exact path. A table grown over a neighbour warns LISTE_SUR_VOISINE once
per pair; successive remedies never aim at the same free cell.
Checked: every new test red first; 2983 node and 617 browser tests green
from the index alone.
--- FR ---
[FIX] cœur : géométrie bornée, verrou daté, chaise vide, listes voisines
Un rapport d'échelle, une translation de vue ou un compte de poignée non
finis lèvent au lieu de rendre 0, NaN ou de boucler sans fin. Le verrou
porte l'instant de l'horloge unique, un verrou repris au nom de la séance
est le sien, et quitter rend les verrous tenus. Une chaise vide est null
seulement, à son chemin exact. Une table agrandie sur sa voisine avertit
LISTE_SUR_VOISINE une fois par paire ; les remèdes successifs ne visent
jamais la même case libre.
Vérifié : chaque épreuve rouge d'abord ; 2983 node et 617 navigateur verts
depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 04:00:22 -04:00
test ( "RESERVATION_TOUS_LES_TOURS ne nomme aucun geste : le même refus suit un dépôt sur le sol et « Retirer du tour », où rien ne se dépose" , ( ) => {
const texte = libelle ( 'RESERVATION_TOUS_LES_TOURS' , { participant : 1 , table : 3 , remede : null } ) ;
assert . doesNotMatch ( texte , /d[ée]pos/i ) ;
assert . match ( texte , /tous les tours/ ) ;
assert . match ( texte , /« Retirer la réservation »/ ) ;
} ) ;
test ( 'CONVERSION_IMPOSSIBLE nomme chacune de ses raisons : des tours non couverts, une réservation « tous les tours » déjà là, une personne exclue' , ( ) => {
const texte = libelle ( 'CONVERSION_IMPOSSIBLE' , { participant : 1 , table : 3 , remede : null } ) ;
for ( const motif of [ /chaque tour/ , /« tous les tours »/ , /exclue/ ] ) assert . match ( texte , motif ) ;
} ) ;
[ADD] application: session, named commands, French label table
The session holds one event, opens it read-only and writes only after
"Modifier" takes the lock (§ 8.4, § 8.8); calls run one after another, so a
double click writes two entries in order. Every change is a pure named
command with a frozen French label (§ 8.2); undo, redo and return write
entries and never truncate the journal (§ 8.3). A failed write — a gesture,
or the lock on a removed key — breaks the medium until the event is written
elsewhere (§ 8.6). The label table covers every code storage, CSV and the
session emit, and its test sweeps the sources so a new code needs a text.
Checked: 1416 node tests; depot.js at 100 % with the identifier lister keeps.
--- FR ---
[ADD] application : séance, commandes nommées, table des libellés
La séance tient un événement, l'ouvre en lecture et n'écrit qu'après que
« Modifier » a pris le verrou (§ 8.4, § 8.8) ; les appels passent l'un après
l'autre : un double clic écrit deux entrées, dans l'ordre. Toute modification
est une commande nommée pure au libellé français figé (§ 8.2) ; défaire,
refaire et revenir écrivent des entrées sans tronquer le journal (§ 8.3). Une
écriture refusée — un geste, ou le verrou sur une clé retirée — rompt le
support jusqu'à l'écriture ailleurs (§ 8.6). La table des libellés couvre
chaque code du stockage, du CSV et de la séance ; son épreuve balaie les
sources, et un code neuf exige un texte.
Vérifié : 1416 épreuves node ; depot.js à 100 %, lister gardant l'identifiant.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 23:17:36 -04:00
test ( 'la table est triée, sans doublon, et figée' , ( ) => {
assert . deepEqual ( [ ... CODES ] , [ ... new Set ( CODES ) ] . sort ( ( a , b ) => ( a < b ? - 1 : a > b ? 1 : 0 ) ) ) ;
assert . ok ( Object . isFrozen ( CODES ) ) ;
} ) ;
test ( 'les libellés figés des exemples du contrat, détails interpolés' , ( ) => {
assert . equal ( libelle ( 'ENTREE_CREATION' , { nom : "Soirée d'essai" } ) , "Création de l'événement « Soirée d'essai »" ) ;
assert . equal ( libelle ( 'JALON_CREATION' ) , 'Création' ) ;
assert . equal ( libelle ( 'ENTREE_AJOUTER_PARTICIPANT' , { personne : 'Iris Ombrelle' } ) , 'Ajouté Iris Ombrelle' ) ;
assert . equal (
libelle ( 'ENTREE_MODIFIER_PARTICIPANT' , { personne : 'Ondine Lacasse' , champs : [ 'courriel' ] , devenu : null } ) ,
'Modifié Ondine Lacasse : courriel' ,
) ;
assert . equal (
libelle ( 'ENTREE_MODIFIER_PARTICIPANT' , { personne : 'Iris Ombrelle' , champs : [ 'nom' , 'titrePressenti' ] , devenu : 'Iris Parasol' } ) ,
'Modifié Iris Ombrelle : nom, titre pressenti (désormais Iris Parasol)' ,
) ;
assert . equal ( libelle ( 'ENTREE_REVENIR' , { libelle : 'Ajouté Iris Ombrelle' } ) , 'Retour à « Ajouté Iris Ombrelle »' ) ;
assert . equal ( libelle ( 'ENTREE_DEFAIRE' , { libelle : 'Ajouté Théo Pervenche' } ) , 'Défait : « Ajouté Théo Pervenche »' ) ;
assert . equal ( libelle ( 'ENTREE_REFAIRE' , { libelle : 'Ajouté Théo Pervenche' } ) , 'Refait : « Ajouté Théo Pervenche »' ) ;
assert . equal ( libelle ( 'ENTREE_REPRISE' ) , "Reprise de l'état trouvé sur le disque" ) ;
assert . equal ( libelle ( 'JALON_ETAT' , { etat : 'propose' } ) , 'État « Proposé »' ) ;
assert . equal ( libelle ( 'ENTREE_CHANGER_ETAT' , { etat : 'bloque' } ) , "Plan passé à l'état « Bloqué »" ) ;
assert . equal ( libelle ( 'ENTREE_ENREGISTRER_GENERATION' , { n : 5 , premier : 3 , dernier : 7 } ) , 'Généré 5 propositions (n° 3 à 7)' ) ;
assert . equal ( libelle ( 'ENTREE_ENREGISTRER_GENERATION' , { n : 1 , premier : 4 , dernier : 4 } ) , 'Généré 1 proposition (n° 4)' ) ;
assert . equal ( libelle ( 'ENTREE_IMPORTER_PARTICIPANTS' , { mode : 'ajouter' , ajoutes : 1 , misAJour : 0 , retires : 0 } ) , 'Importé 1 participant' ) ;
assert . equal (
libelle ( 'ENTREE_IMPORTER_PARTICIPANTS' , { mode : 'remplacer' , ajoutes : 250 , misAJour : 0 , retires : 260 } ) ,
'Remplacé la liste par import : 260 retirés, 250 ajoutés' ,
) ;
} ) ;
[FIX] plan: named warning, one places state, highlight, table spacing
The unheld-reservation warning names the person and the table number
instead of their identifiers. The places state is computed once per change
and handed to the side panels, not once per panel. A person can be
highlighted from their menu or from the grid, and the view bar names them.
A created table takes a cell a full pitch away from every table, its own
capacity included, so no name list runs over a neighbour's chairs. The
rendering guards fail on an empty walk and also check focus and selection.
Checked: 2692 node, 496 browser, 60 long tests, from the index alone.
--- FR ---
[FIX] plan : avertissement nommé, état des places unique, évidence, pas
L'avertissement de réservation non honorée nomme la personne et le numéro
de la table plutôt que leurs identifiants. L'état des places se calcule une
fois par changement et passe aux panneaux, non une fois par panneau. Une
personne se met en évidence par son menu ou depuis la grille, et la barre
de vue la nomme. Une table créée prend une case à un pas plein de chaque
table, sa capacité comprise : aucune liste ne court sur les chaises
voisines. Les gardes du rendu échouent sur un parcours vide et éprouvent
aussi le focus et la sélection. Vérifié : 2692 node, 496 navigateur, 60.
Assisted-by: Claude Opus 5.5
2026-10-07 15:05:01 -04:00
test ( 'RESERVATION_NON_HONOREE nomme la personne et le numéro de la table quand ils sont donnés, sinon leurs identifiants' , ( ) => {
assert . equal (
libelle ( 'RESERVATION_NON_HONOREE' , { participant : 4 , table : 5 , tour : 1 , personne : 'Iris Ombrelle' , numero : 3 } ) ,
"Le placement retenu n'honore pas la réservation de Iris Ombrelle à la table 3 au tour 1 : il la place ailleurs." ,
) ;
assert . equal (
libelle ( 'RESERVATION_NON_HONOREE' , { participant : 4 , table : 5 , tour : 1 } ) ,
"Le placement retenu n'honore pas la réservation de la personne n° 4 à la table d'identifiant 5 au tour 1 : il la place ailleurs." ,
) ;
} ) ;
[FIX] iteration 3 review: seat targets, gestures, warnings, settings
The wheel no longer zooms during a gesture, so a drop lands under the
ghost. A seat is a target the size of its pitch. A floor drop refuses to
remove an every-tour reservation. A seat reservation the retained plan
does not hold is flagged at the seat, with its swap as remedy. DERIVE names
the person; capacities have an upper bound; Generate waits for a pending
setting; a worker that fails to load shows a refusal; § 5.3 constraints
get their switches. Commands are not re-entrant and a double click does not
confirm. Checked: 2755 node, 550 browser tests, from the index alone.
--- FR ---
[FIX] revue de l'itération 3 : cibles, gestes, avertissements, réglages
La molette ne zoome plus pendant un geste : le dépôt tombe sous le fantôme.
Un siège est une cible de la taille de son pas. Un dépôt sur le sol refuse
de retirer une réservation de tous les tours. Une réservation à un siège
que le retenu ne tient pas est relevée au siège, l'échange en remède. La
dérive nomme la personne ; les capacités ont une borne ; Générer attend un
réglage en cours ; un travailleur qui ne se charge pas donne un refus ; le
§ 5.3 a ses bascules. Les commandes ne sont pas ré-entrantes et un double
clic ne confirme pas. Vérifié : 2755 node, 550 navigateur, index seul.
Assisted-by: Claude Opus 5.5
2026-10-07 17:50:35 -04:00
test ( 'RESERVATION_NON_HONOREE au siège nomme le siège et ce qu’ il porte' , ( ) => {
assert . equal (
libelle ( 'RESERVATION_NON_HONOREE' , { participant : 4 , table : 5 , tour : 1 , personne : 'Iris Ombrelle' , numero : 3 , siege : 2 , occupant : 7 } ) ,
"Le placement retenu n'honore pas la réservation de Iris Ombrelle au siège 2 de la table 3 au tour 1 : " +
"il l'assied à un autre siège de cette table, et donne le siège 2 à une autre personne." ,
) ;
assert . equal (
libelle ( 'RESERVATION_NON_HONOREE' , { participant : 4 , table : 5 , tour : 1 , siege : 2 , occupant : null } ) ,
"Le placement retenu n'honore pas la réservation de la personne n° 4 au siège 2 de la table d'identifiant 5 au tour 1 : " +
"il l'assied à un autre siège de cette table, et laisse le siège 2 vide." ,
) ;
} ) ;
test ( 'une raison de dérive nomme la personne et la table quand ses détails les portent, sinon leurs identifiants' , ( ) => {
const nommees = [
[ 'PARTICIPANT_EXCLU' , { participant : 14 , personne : 'Iris Ombrelle' } , 'Iris Ombrelle est exclue depuis.' ] ,
[ 'PARTICIPANT_SUPPRIME' , { participant : 14 , personne : 'Iris Ombrelle' } , 'Iris Ombrelle a été supprimée depuis.' ] ,
[
'PARTICIPANT_NON_PLACE' ,
{ participant : 14 , personne : 'Iris Ombrelle' } ,
"Iris Ombrelle, ajoutée ou réintégrée depuis, n'y est pas placée." ,
] ,
[ 'TABLE_SUPPRIMEE' , { table : 9 , numero : 3 } , 'La table 3 a été supprimée depuis.' ] ,
[ 'TABLE_AJOUTEE' , { table : 9 , numero : 3 } , 'La table 3 a été ajoutée depuis.' ] ,
[ 'CAPACITE_CHANGEE' , { table : 9 , numero : 3 , avant : 8 , maintenant : 6 } , 'La table 3 est passée de 8 à 6 places.' ] ,
] ;
assert . equal ( nommees . length , 6 ) ;
for ( const [ code , details , attendu ] of nommees ) {
assert . equal ( libelle ( code , details ) , attendu , code ) ;
const { personne , numero , ... nus } = details ;
assert . doesNotMatch ( libelle ( code , nus ) , /Iris|table 3/ , code ) ;
assert . match ( libelle ( code , nus ) , personne === undefined ? /identifiant 9/ : /n° 14/ , code ) ;
void numero ;
}
} ) ;
[ADD] application: session, named commands, French label table
The session holds one event, opens it read-only and writes only after
"Modifier" takes the lock (§ 8.4, § 8.8); calls run one after another, so a
double click writes two entries in order. Every change is a pure named
command with a frozen French label (§ 8.2); undo, redo and return write
entries and never truncate the journal (§ 8.3). A failed write — a gesture,
or the lock on a removed key — breaks the medium until the event is written
elsewhere (§ 8.6). The label table covers every code storage, CSV and the
session emit, and its test sweeps the sources so a new code needs a text.
Checked: 1416 node tests; depot.js at 100 % with the identifier lister keeps.
--- FR ---
[ADD] application : séance, commandes nommées, table des libellés
La séance tient un événement, l'ouvre en lecture et n'écrit qu'après que
« Modifier » a pris le verrou (§ 8.4, § 8.8) ; les appels passent l'un après
l'autre : un double clic écrit deux entrées, dans l'ordre. Toute modification
est une commande nommée pure au libellé français figé (§ 8.2) ; défaire,
refaire et revenir écrivent des entrées sans tronquer le journal (§ 8.3). Une
écriture refusée — un geste, ou le verrou sur une clé retirée — rompt le
support jusqu'à l'écriture ailleurs (§ 8.6). La table des libellés couvre
chaque code du stockage, du CSV et de la séance ; son épreuve balaie les
sources, et un code neuf exige un texte.
Vérifié : 1416 épreuves node ; depot.js à 100 %, lister gardant l'identifiant.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 23:17:36 -04:00
test ( 'le séparateur introuvable nomme la ligne et la cause ; le guillemet ouvert tombe dès la ligne 2' , ( ) => {
const nombre = libelle ( 'SEPARATEUR_INTROUVABLE' , { separateur : ';' , ligne : 3 , cause : 'NOMBRE_DE_CHAMPS' } ) ;
assert . match ( nombre , /la ligne 3 n'a pas le nombre de champs de l'en-tête/ ) ;
assert . match ( libelle ( 'SEPARATEUR_INTROUVABLE' , { ligne : 3 } ) , /la ligne 3 n'a pas le nombre de champs de l'en-tête/ ) ;
assert . match ( libelle ( 'SEPARATEUR_INTROUVABLE' , { separateur : ',' , ligne : 2 , cause : 'GUILLEMET_OUVERT' } ) , /ligne 2/ ) ;
assert . match ( libelle ( 'GUILLEMET_OUVERT' , { ligne : 2 } ) , /ligne 2/ ) ;
assert . doesNotMatch ( libelle ( 'SEPARATEUR_INTROUVABLE' , { } ) , /ligne/ ) ;
} ) ;
test ( "les quatre refus d'encodage nomment le même remède : réenregistrer en UTF-8" , ( ) => {
const remede = libelle ( 'ENREGISTRER_EN_UTF8' ) ;
assert . match ( remede , /UTF-8/ ) ;
for ( const code of [ 'UTF16_SANS_MARQUE' , 'UTF16_INVALIDE' , 'UTF8_INVALIDE' , 'CARACTERE_NUL' ] ) {
const texte = libelle ( code , { remede : 'ENREGISTRER_EN_UTF8' } ) ;
assert . ok ( texte . endsWith ( remede ) , ` ${ code } : « ${ texte } » ` ) ;
}
} ) ;
[FIX] storage, session: strict UTF-8, lost journal, lock, exclusions
The whole-iteration review found ways to lose or corrupt data. A state
saved in another encoding opened with its accents turned to U+FFFD and was
then overwritten; decoding is now strict, the state opens read-only, and a
gesture from its secours first moves it to the trash. A journal decodes line
by line, so one bad byte drops only the lines after it. A vanished journal
is rewritten whole, never appended to. Deleting refuses an event another
session holds; switching to writing rereads the disk; an import that
changes exclu says which reservations it suspends.
Checked: 1504 node, 35 browser, 29 node-long; depot.js, journal.js 100 %.
--- FR ---
[FIX] stockage, séance : UTF-8 strict, journal perdu, verrou, exclusions
La revue d'ensemble a trouvé des façons de perdre ou d'abîmer des données.
Un état enregistré dans un autre encodage s'ouvrait, ses accents changés en
U+FFFD, puis s'écrasait ; le décodage est strict, l'état s'ouvre en lecture
seule, et un geste depuis son secours le range d'abord à la corbeille. Le
journal se décode ligne par ligne : un octet fautif n'écarte que la suite.
Un journal disparu se réécrit en entier, jamais par ajout. Supprimer refuse
un événement qu'une autre séance tient ; passer en écriture relit le disque ;
un import qui change exclu dit quelles réservations il suspend.
Vérifié : 1504 node, 35 navigateur, 29 node-long ; depot.js, journal.js 100 %.
Assisted-by: Claude Opus 5.5
2026-10-07 01:20:42 -04:00
test ( "le support amovible nomme le dossier quand il est donné ; l'événement changé nomme la révision adoptée" , ( ) => {
assert . doesNotMatch ( libelle ( 'SUPPORT_AMOVIBLE' , { } ) , /«/ ) ;
assert . match ( libelle ( 'SUPPORT_AMOVIBLE' , { dossier : 'F:\\soirees' } ) , /« F:\\soirees »/ ) ;
assert . match ( libelle ( 'EVENEMENT_CHANGE' , { revision : 7 } ) , /\b7\b/ ) ;
} ) ;
test ( "quitter un événement au support rompu nomme « Écrire ailleurs », et ce qui serait perdu" , ( ) => {
const details = { chemin : 'Soirée des Lucioles.gtt-journal.jsonl' , dossier : 'E:\\data' , cause : 'EROFS' } ;
for ( const echec of [ 'ECRITURE' , 'ETAT_NON_ECRIT' , 'JALON_NON_ECRIT' , 'VERROU_NON_ECRIT' ] ) {
assert . match ( libelle ( 'SUPPORT_ROMPU_NON_SAUVE' , { echec , ... details } ) , /« Écrire ailleurs »/ , echec ) ;
}
} ) ;
[ADD] application: session, named commands, French label table
The session holds one event, opens it read-only and writes only after
"Modifier" takes the lock (§ 8.4, § 8.8); calls run one after another, so a
double click writes two entries in order. Every change is a pure named
command with a frozen French label (§ 8.2); undo, redo and return write
entries and never truncate the journal (§ 8.3). A failed write — a gesture,
or the lock on a removed key — breaks the medium until the event is written
elsewhere (§ 8.6). The label table covers every code storage, CSV and the
session emit, and its test sweeps the sources so a new code needs a text.
Checked: 1416 node tests; depot.js at 100 % with the identifier lister keeps.
--- FR ---
[ADD] application : séance, commandes nommées, table des libellés
La séance tient un événement, l'ouvre en lecture et n'écrit qu'après que
« Modifier » a pris le verrou (§ 8.4, § 8.8) ; les appels passent l'un après
l'autre : un double clic écrit deux entrées, dans l'ordre. Toute modification
est une commande nommée pure au libellé français figé (§ 8.2) ; défaire,
refaire et revenir écrivent des entrées sans tronquer le journal (§ 8.3). Une
écriture refusée — un geste, ou le verrou sur une clé retirée — rompt le
support jusqu'à l'écriture ailleurs (§ 8.6). La table des libellés couvre
chaque code du stockage, du CSV et de la séance ; son épreuve balaie les
sources, et un code neuf exige un texte.
Vérifié : 1416 épreuves node ; depot.js à 100 %, lister gardant l'identifiant.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 23:17:36 -04:00
test ( 'le plan bloqué nomme « Débloquer », le mode lecture nomme « Modifier »' , ( ) => {
assert . match ( libelle ( 'LECTURE_SEULE' , { raison : 'BLOQUE' } ) , /« Débloquer »/ ) ;
assert . match ( libelle ( 'PLAN_BLOQUE' , { remede : { geste : 'debloquer' } } ) , /« Débloquer »/ ) ;
assert . match ( libelle ( 'MODE_LECTURE' ) , /« Modifier »/ ) ;
assert . doesNotMatch ( libelle ( 'LECTURE_SEULE' , { raison : 'FORMAT' } ) , /Débloquer/ ) ;
for ( const trace of TRACES ) assert . doesNotMatch ( libelle ( 'LECTURE_SEULE' , { raison : 'FORMAT' } ) , trace ) ;
assert . equal ( libelle ( 'LECTURE_SEULE' , { raison : 'FORMAT' } ) , libelle ( 'LECTURE_SEULE' , { raison : 'FORMAT_PLUS_RECENT' } ) ) ;
} ) ;
test ( "un état illisible nomme sa raison et ses secours ; chaque raison du contrat a son texte" , ( ) => {
const texte = libelle ( 'ETAT_ILLISIBLE' , { base : 'Soirée des Lucioles' , raison : 'VIDE' , chemin : null , secours : { precedent : 4 , journal : null } } ) ;
assert . match ( texte , /« Soirée des Lucioles »/ ) ;
assert . match ( texte , new RegExp ( libelle ( 'VIDE' ) ) ) ;
assert . match ( texte , /révision 4/ ) ;
const sansSecours = libelle ( 'ETAT_ILLISIBLE' , { base : 'Soirée des Lucioles' , raison : 'JSON' , chemin : null , secours : { precedent : null , journal : null } } ) ;
assert . match ( sansSecours , /Aucune version de secours/ ) ;
for ( const raison of [ 'ABSENT' , 'VIDE' , 'JSON' , 'FORME' , 'COMPTES' , 'REFERENCE' , 'FORMAT_INCONNU' , 'FORMAT_PLUS_RECENT' ] ) {
const details = { base : 'Soirée des Lucioles' , raison , chemin : raison === 'FORME' ? 'charge.tables[0].forme' : null , secours : { precedent : null , journal : 2 } } ;
assert . match ( libelle ( 'ETAT_ILLISIBLE' , details ) , /révision 2/ , raison ) ;
}
[FIX] storage, session: strict UTF-8, lost journal, lock, exclusions
The whole-iteration review found ways to lose or corrupt data. A state
saved in another encoding opened with its accents turned to U+FFFD and was
then overwritten; decoding is now strict, the state opens read-only, and a
gesture from its secours first moves it to the trash. A journal decodes line
by line, so one bad byte drops only the lines after it. A vanished journal
is rewritten whole, never appended to. Deleting refuses an event another
session holds; switching to writing rereads the disk; an import that
changes exclu says which reservations it suspends.
Checked: 1504 node, 35 browser, 29 node-long; depot.js, journal.js 100 %.
--- FR ---
[FIX] stockage, séance : UTF-8 strict, journal perdu, verrou, exclusions
La revue d'ensemble a trouvé des façons de perdre ou d'abîmer des données.
Un état enregistré dans un autre encodage s'ouvrait, ses accents changés en
U+FFFD, puis s'écrasait ; le décodage est strict, l'état s'ouvre en lecture
seule, et un geste depuis son secours le range d'abord à la corbeille. Le
journal se décode ligne par ligne : un octet fautif n'écarte que la suite.
Un journal disparu se réécrit en entier, jamais par ajout. Supprimer refuse
un événement qu'une autre séance tient ; passer en écriture relit le disque ;
un import qui change exclu dit quelles réservations il suspend.
Vérifié : 1504 node, 35 navigateur, 29 node-long ; depot.js, journal.js 100 %.
Assisted-by: Claude Opus 5.5
2026-10-07 01:20:42 -04:00
const encodage = libelle ( 'ETAT_ILLISIBLE' , { base : 'Soirée des Lucioles' , raison : 'ENCODAGE' , chemin : null , secours : { precedent : 4 , journal : null } } ) ;
assert . ok ( encodage . includes ( libelle ( 'ENCODAGE' ) ) , encodage ) ;
} ) ;
test ( "une raison qu'un texte ne connaît pas ne lève jamais : une phrase générique la nomme, par son texte quand la table en porte un, telle quelle sinon" , ( ) => {
const inventee = libelle ( 'ETAT_ILLISIBLE' , { base : 'x' , raison : 'RAISON_INVENTEE' , chemin : null } ) ;
assert . match ( inventee , /« RAISON_INVENTEE »/ ) ;
const seule = libelle ( 'LECTURE_SEULE' , { raison : 'RAISON_INVENTEE' } ) ;
assert . match ( seule , /« RAISON_INVENTEE »/ ) ;
assert . match ( seule , /lecture seule/ ) ;
const parSonTexte = libelle ( 'LECTURE_SEULE' , { raison : 'VIDE' } ) ;
assert . ok ( parSonTexte . includes ( libelle ( 'VIDE' ) ) , parSonTexte ) ;
assert . doesNotMatch ( parSonTexte , /VIDE/ ) ;
assert . match ( libelle ( 'JOURNAL_ILLISIBLE' , { raison : 'RAISON_INVENTEE' } ) , /« RAISON_INVENTEE »/ ) ;
for ( const texte of [ inventee , seule , parSonTexte ] ) for ( const trace of TRACES ) assert . doesNotMatch ( texte , trace ) ;
} ) ;
test ( "un état aux octets hors UTF-8 se dit par son encodage : en lecture seule, il nomme le secours qui le met de côté ; le journal illisible, l'historique laissé tel quel" , ( ) => {
const seule = libelle ( 'LECTURE_SEULE' , { raison : 'ENCODAGE' } ) ;
assert . ok ( seule . includes ( libelle ( 'ENCODAGE' ) ) , seule ) ;
assert . match ( seule , /secours/ ) ;
assert . match ( seule , /corbeille/ ) ;
assert . notEqual ( seule , libelle ( 'LECTURE_SEULE' , { raison : 'FORMAT' } ) ) ;
const journal = libelle ( 'JOURNAL_ILLISIBLE' , { raison : 'ENCODAGE' } ) ;
assert . ok ( journal . includes ( libelle ( 'ENCODAGE' ) ) , journal ) ;
assert . match ( libelle ( 'JOURNAL_RECREE' ) , /historique/ ) ;
} ) ;
test ( 'les propositions manquantes se disent par leur nombre, au singulier comme au pluriel, sans jamais les énumérer' , ( ) => {
assert . match ( libelle ( 'PROPOSITIONS_MANQUANTES' , { n : 1 } ) , /^1 proposition que l'en-tête annonce manque/ ) ;
const enorme = libelle ( 'PROPOSITIONS_MANQUANTES' , { n : 2 * * 40 } ) ;
assert . match ( enorme , new RegExp ( ` ^ ${ 2 * * 40 } propositions que l'en-tête annonce manquent ` ) ) ;
assert . ok ( enorme . length < 200 , enorme ) ;
[ADD] application: session, named commands, French label table
The session holds one event, opens it read-only and writes only after
"Modifier" takes the lock (§ 8.4, § 8.8); calls run one after another, so a
double click writes two entries in order. Every change is a pure named
command with a frozen French label (§ 8.2); undo, redo and return write
entries and never truncate the journal (§ 8.3). A failed write — a gesture,
or the lock on a removed key — breaks the medium until the event is written
elsewhere (§ 8.6). The label table covers every code storage, CSV and the
session emit, and its test sweeps the sources so a new code needs a text.
Checked: 1416 node tests; depot.js at 100 % with the identifier lister keeps.
--- FR ---
[ADD] application : séance, commandes nommées, table des libellés
La séance tient un événement, l'ouvre en lecture et n'écrit qu'après que
« Modifier » a pris le verrou (§ 8.4, § 8.8) ; les appels passent l'un après
l'autre : un double clic écrit deux entrées, dans l'ordre. Toute modification
est une commande nommée pure au libellé français figé (§ 8.2) ; défaire,
refaire et revenir écrivent des entrées sans tronquer le journal (§ 8.3). Une
écriture refusée — un geste, ou le verrou sur une clé retirée — rompt le
support jusqu'à l'écriture ailleurs (§ 8.6). La table des libellés couvre
chaque code du stockage, du CSV et de la séance ; son épreuve balaie les
sources, et un code neuf exige un texte.
Vérifié : 1416 épreuves node ; depot.js à 100 %, lister gardant l'identifiant.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 23:17:36 -04:00
} ) ;
test ( "une cause que la table porte se lit par son texte ; une cause du système s'affiche comme il la nomme" , ( ) => {
const avecCause = CODES . filter ( ( code ) => Object . hasOwn ( EXEMPLES . get ( code ) ? ? { } , 'cause' ) ) ;
assert . ok ( avecCause . length > 0 , 'aucun texte ne reçoit de cause' ) ;
for ( const code of avecCause ) {
const texte = libelle ( code , { ... EXEMPLES . get ( code ) , cause : 'RELECTURE_DIFFERENTE' } ) ;
assert . doesNotMatch ( texte , /RELECTURE_DIFFERENTE/ , ` ${ code } : « ${ texte } » ` ) ;
}
const relue = libelle ( 'RELECTURE_DIFFERENTE' ) ;
for ( const code of [ 'ECRITURE' , 'SONDE_ECHOUEE' , 'PORTABLE_NON_INSCRIPTIBLE' , 'DOCUMENTS_NON_INSCRIPTIBLE' ] ) {
const details = EXEMPLES . get ( code ) ;
assert . ok ( libelle ( code , { ... details , cause : 'RELECTURE_DIFFERENTE' } ) . includes ( ` ( ${ relue } ) ` ) , code ) ;
assert . ok ( libelle ( code , { ... details , cause : 'TypeMismatchError' } ) . includes ( '(TypeMismatchError)' ) , code ) ;
}
} ) ;
test ( "le support rompu dit ce que l'écriture refusée laisse sur le disque ; un échec inconnu lève" , ( ) => {
const refus = { chemin : 'Soirée des Lucioles.gtt-journal.jsonl' , dossier : 'E:\\data' , cause : 'EROFS' } ;
const textes = [ 'ECRITURE' , 'ETAT_NON_ECRIT' , 'JALON_NON_ECRIT' , 'VERROU_NON_ECRIT' ] . map ( ( echec ) =>
libelle ( 'SUPPORT_ROMPU' , { echec , ... refus } ) ,
) ;
for ( const texte of textes ) {
assert . ok ( texte . includes ( '« Soirée des Lucioles.gtt-journal.jsonl » dans le dossier « E:\\data » (EROFS)' ) , texte ) ;
assert . match ( texte , /« Écrire ailleurs »/ ) ;
}
assert . equal ( new Set ( textes ) . size , 4 ) ;
const [ ecriture , etat , jalon , verrou ] = textes ;
assert . match ( ecriture , /le dernier geste n'est pas enregistré/ ) ;
assert . match ( etat , /le dernier geste est inscrit à l'historique, mais pas dans l'état/ ) ;
assert . match ( jalon , /le dernier geste est enregistré, mais pas le jalon qui le nomme/i ) ;
for ( const texte of [ jalon , verrou ] ) assert . doesNotMatch ( texte , /n'est pas enregistré|ne se correspondent plus/ ) ;
assert . throws ( ( ) => libelle ( 'SUPPORT_ROMPU' , { echec : 'ECHEC_INVENTE' , ... refus } ) , RangeError ) ;
} ) ;
test ( 'un verrou pris dit depuis quand, et distingue la séance vivante, inconnue ou morte' , ( ) => {
const vivant = libelle ( 'VERROU_PRIS' , { seance : 's' , depuis : '2031-05-01T21:40:00-04:00' , vivant : true } ) ;
assert . match ( vivant , /1er mai 2031 à 21 h 40/ ) ;
const inconnu = libelle ( 'VERROU_PRIS' , { seance : null , depuis : null , vivant : null } ) ;
assert . notEqual ( inconnu , vivant ) ;
assert . notEqual ( libelle ( 'VERROU_PRIS' , { seance : 's' , depuis : '2031-05-01T21:40:00-04:00' , vivant : false } ) , vivant ) ;
} ) ;
test ( 'un état, un champ, un mode inconnus lèvent au lieu de se taire' , ( ) => {
assert . throws ( ( ) => libelle ( 'JALON_ETAT' , { etat : 'archive' } ) , RangeError ) ;
assert . throws ( ( ) => libelle ( 'ENTREE_MODIFIER_PARTICIPANT' , { personne : 'x' , champs : [ 'age' ] , devenu : null } ) , RangeError ) ;
assert . throws ( ( ) => libelle ( 'ENTREE_IMPORTER_PARTICIPANTS' , { mode : 'fusionner' , ajoutes : 1 , misAJour : 0 , retires : 0 } ) , RangeError ) ;
} ) ;
test ( 'les comptes accordent le singulier et le pluriel' , ( ) => {
assert . match ( libelle ( 'JOURNAL_ECARTEES' , { n : 1 } ) , /^1 entrée de l'historique est écartée/ ) ;
assert . match ( libelle ( 'JOURNAL_ECARTEES' , { n : 64 } ) , /^64 entrées de l'historique sont écartées/ ) ;
assert . equal ( libelle ( 'DERIVE' , { ids : [ ] , retenu : true } ) . startsWith ( 'Le placement retenu ne décrit plus' ) , true ) ;
assert . match ( libelle ( 'DERIVE' , { ids : [ 1 , 2 ] , retenu : true } ) , /^Le placement retenu et les propositions n° 1 et 2 ne décrivent plus/ ) ;
assert . match ( libelle ( 'PROPOSITIONS_FAUTIVES' , { ids : [ 3 , null ] } ) , /n° 3/ ) ;
} ) ;
} ) ;
[ADD] application: clock, translation, formatting, displayed name
The screens need one clock, one translation mechanism and one way to
format and name, or each component grows its own. horloge.js is the only
code that reads time and arms timers, injected so tests drive it.
traduction.js keys texts by domain, in two families of tables, raises on a
missing key and never shows a raw key; libelles/ splits the label table by
domain. format.js formats numbers and dates in French, and personnes.js
composes the displayed name the frozen labels use. Nothing persists
between sessions.
Checked: 1773 node and 43 browser tests.
--- FR ---
[ADD] application : horloge, traduction, mise en forme, nom affiché
Les écrans exigent une horloge, un mécanisme de traduction et une façon de
mettre en forme et de nommer, sans quoi chaque composant ferait les siens.
horloge.js est le seul code qui lit l'heure et arme des minuteries, injecté
pour que les épreuves le pilotent. traduction.js range les textes par
domaine, en deux familles de tables, lève sur une clé absente et ne montre
jamais une clé brute ; libelles/ découpe la table des libellés par domaine.
format.js met en forme nombres et dates en français, et personnes.js
compose le nom affiché qu'emploient les libellés figés. Rien ne persiste
d'une séance à l'autre.
Vérifié : 1773 node et 43 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 02:33:39 -04:00
// --- Les tables par domaine ---------------------------------------------------
// Les deux formes de clé du contrat : un code, nu ; un libellé, préfixé du nom
// de sa table, en camelCase.
const FORME _CODE = /^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)*$/ ;
const formeLibelle = ( table ) => new RegExp ( ` ^ ${ table } (?: \\ .[a-z][A-Za-z0-9]*)+ $ ` ) ;
// Les modules de table du dossier, aides et épreuves exceptées, par ordre
// alphabétique, et ce que chacun exporte.
async function tablesDuDossier ( ) {
const noms = readdirSync ( join ( RACINE , DOSSIER _DES _TABLES ) )
. filter ( ( nom ) => nom . endsWith ( '.js' ) && ! nom . endsWith ( '.test.js' ) && nom !== 'aides.js' )
. sort ( ) ;
return Promise . all ( noms . map ( async ( nom ) => ( { table : nom . slice ( 0 , - 3 ) , module : await import ( ` ./libelles/ ${ nom } ` ) } ) ) ) ;
}
describe ( 'libellés : les tables par domaine (§ 14.6)' , ( ) => {
[ADD] badges: sheet model from the retained plan or a chosen proposal
Badges hang on guests' necks: their content must be exact and stable. The
model builds one badge per non-excluded person, from the retained plan or,
on demand, from a proposal, which always prints as a draft. Each round says
table, seat when seats are assigned, and a title only where a reservation
makes the person its holder there; a constant itinerary takes one line.
Names sort by code point, never by locale; namesakes carry their id, and a
reprint gives the same texts. Without any placement it refuses, pointing at
the proposals.
Checked: red first; 3343 node tests green from the index alone.
--- FR ---
[ADD] badges : modèle de planche, du retenu ou d'une proposition choisie
Les badges pendent au cou des invités : leur contenu doit être exact et
stable. Le modèle fait un badge par personne non exclue, depuis le retenu
ou, sur demande, une proposition, toujours imprimée en brouillon. Chaque
tour dit la table, le siège sous attribution, et un titre seulement là où
une réservation en fait la personne titulaire ; un itinéraire constant tient
en une ligne. Les noms se classent par points de code, jamais selon la
locale ; les homonymes portent leur identifiant, et une réédition rend les
mêmes textes. Sans aucun placement, il refuse et renvoie aux propositions.
Vérifié : rouge d'abord ; 3343 node verts depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 13:03:04 -04:00
test ( 'chaque module de libelles/, aides.js excepté, exporte TABLE, une Map ; TABLES_APPLICATION les assemble, douze, par ordre alphabétique' , async ( ) => {
[ADD] application: clock, translation, formatting, displayed name
The screens need one clock, one translation mechanism and one way to
format and name, or each component grows its own. horloge.js is the only
code that reads time and arms timers, injected so tests drive it.
traduction.js keys texts by domain, in two families of tables, raises on a
missing key and never shows a raw key; libelles/ splits the label table by
domain. format.js formats numbers and dates in French, and personnes.js
composes the displayed name the frozen labels use. Nothing persists
between sessions.
Checked: 1773 node and 43 browser tests.
--- FR ---
[ADD] application : horloge, traduction, mise en forme, nom affiché
Les écrans exigent une horloge, un mécanisme de traduction et une façon de
mettre en forme et de nommer, sans quoi chaque composant ferait les siens.
horloge.js est le seul code qui lit l'heure et arme des minuteries, injecté
pour que les épreuves le pilotent. traduction.js range les textes par
domaine, en deux familles de tables, lève sur une clé absente et ne montre
jamais une clé brute ; libelles/ découpe la table des libellés par domaine.
format.js met en forme nombres et dates en français, et personnes.js
compose le nom affiché qu'emploient les libellés figés. Rien ne persiste
d'une séance à l'autre.
Vérifié : 1773 node et 43 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 02:33:39 -04:00
const tables = await tablesDuDossier ( ) ;
[ADD] badges: sheet model from the retained plan or a chosen proposal
Badges hang on guests' necks: their content must be exact and stable. The
model builds one badge per non-excluded person, from the retained plan or,
on demand, from a proposal, which always prints as a draft. Each round says
table, seat when seats are assigned, and a title only where a reservation
makes the person its holder there; a constant itinerary takes one line.
Names sort by code point, never by locale; namesakes carry their id, and a
reprint gives the same texts. Without any placement it refuses, pointing at
the proposals.
Checked: red first; 3343 node tests green from the index alone.
--- FR ---
[ADD] badges : modèle de planche, du retenu ou d'une proposition choisie
Les badges pendent au cou des invités : leur contenu doit être exact et
stable. Le modèle fait un badge par personne non exclue, depuis le retenu
ou, sur demande, une proposition, toujours imprimée en brouillon. Chaque
tour dit la table, le siège sous attribution, et un titre seulement là où
une réservation en fait la personne titulaire ; un itinéraire constant tient
en une ligne. Les noms se classent par points de code, jamais selon la
locale ; les homonymes portent leur identifiant, et une réédition rend les
mêmes textes. Sans aucun placement, il refuse et renvoie aux propositions.
Vérifié : rouge d'abord ; 3343 node verts depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 13:03:04 -04:00
assert . equal ( tables . length , 12 ) ;
[ADD] application: clock, translation, formatting, displayed name
The screens need one clock, one translation mechanism and one way to
format and name, or each component grows its own. horloge.js is the only
code that reads time and arms timers, injected so tests drive it.
traduction.js keys texts by domain, in two families of tables, raises on a
missing key and never shows a raw key; libelles/ splits the label table by
domain. format.js formats numbers and dates in French, and personnes.js
composes the displayed name the frozen labels use. Nothing persists
between sessions.
Checked: 1773 node and 43 browser tests.
--- FR ---
[ADD] application : horloge, traduction, mise en forme, nom affiché
Les écrans exigent une horloge, un mécanisme de traduction et une façon de
mettre en forme et de nommer, sans quoi chaque composant ferait les siens.
horloge.js est le seul code qui lit l'heure et arme des minuteries, injecté
pour que les épreuves le pilotent. traduction.js range les textes par
domaine, en deux familles de tables, lève sur une clé absente et ne montre
jamais une clé brute ; libelles/ découpe la table des libellés par domaine.
format.js met en forme nombres et dates en français, et personnes.js
compose le nom affiché qu'emploient les libellés figés. Rien ne persiste
d'une séance à l'autre.
Vérifié : 1773 node et 43 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 02:33:39 -04:00
assert . deepEqual ( tables . map ( ( { table } ) => table ) , [
[ADD] badges: sheet model from the retained plan or a chosen proposal
Badges hang on guests' necks: their content must be exact and stable. The
model builds one badge per non-excluded person, from the retained plan or,
on demand, from a proposal, which always prints as a draft. Each round says
table, seat when seats are assigned, and a title only where a reservation
makes the person its holder there; a constant itinerary takes one line.
Names sort by code point, never by locale; namesakes carry their id, and a
reprint gives the same texts. Without any placement it refuses, pointing at
the proposals.
Checked: red first; 3343 node tests green from the index alone.
--- FR ---
[ADD] badges : modèle de planche, du retenu ou d'une proposition choisie
Les badges pendent au cou des invités : leur contenu doit être exact et
stable. Le modèle fait un badge par personne non exclue, depuis le retenu
ou, sur demande, une proposition, toujours imprimée en brouillon. Chaque
tour dit la table, le siège sous attribution, et un titre seulement là où
une réservation en fait la personne titulaire ; un itinéraire constant tient
en une ligne. Les noms se classent par points de code, jamais selon la
locale ; les homonymes portent leur identifiant, et une réédition rend les
mêmes textes. Sans aucun placement, il refuse et renvoie aux propositions.
Vérifié : rouge d'abord ; 3343 node verts depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 13:03:04 -04:00
'csv' , 'evenements' , 'format' , 'generation' , 'mobilier' , 'moteur' , 'participants' , 'places' , 'planche' , 'seance' , 'sorties' , 'stockage' ,
[ADD] application: clock, translation, formatting, displayed name
The screens need one clock, one translation mechanism and one way to
format and name, or each component grows its own. horloge.js is the only
code that reads time and arms timers, injected so tests drive it.
traduction.js keys texts by domain, in two families of tables, raises on a
missing key and never shows a raw key; libelles/ splits the label table by
domain. format.js formats numbers and dates in French, and personnes.js
composes the displayed name the frozen labels use. Nothing persists
between sessions.
Checked: 1773 node and 43 browser tests.
--- FR ---
[ADD] application : horloge, traduction, mise en forme, nom affiché
Les écrans exigent une horloge, un mécanisme de traduction et une façon de
mettre en forme et de nommer, sans quoi chaque composant ferait les siens.
horloge.js est le seul code qui lit l'heure et arme des minuteries, injecté
pour que les épreuves le pilotent. traduction.js range les textes par
domaine, en deux familles de tables, lève sur une clé absente et ne montre
jamais une clé brute ; libelles/ découpe la table des libellés par domaine.
format.js met en forme nombres et dates en français, et personnes.js
compose le nom affiché qu'emploient les libellés figés. Rien ne persiste
d'une séance à l'autre.
Vérifié : 1773 node et 43 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 02:33:39 -04:00
] ) ;
for ( const { table , module } of tables ) assert . ok ( module . TABLE instanceof Map , table ) ;
assert . equal ( TABLES _APPLICATION . length , tables . length ) ;
tables . forEach ( ( { table , module } , rang ) => assert . equal ( TABLES _APPLICATION [ rang ] , module . TABLE , table ) ) ;
} ) ;
test ( "chaque clé a l'une des deux formes ; un libellé commence par le nom de sa table ; participants et generation ne portent que des codes" , async ( ) => {
const tables = await tablesDuDossier ( ) ;
let comptees = 0 ;
for ( const { table , module } of tables ) {
for ( const cle of module . TABLE . keys ( ) ) {
comptees += 1 ;
const code = FORME _CODE . test ( cle ) ;
assert . ok ( code || formeLibelle ( table ) . test ( cle ) , ` ${ table } : clé ${ JSON . stringify ( cle ) } ` ) ;
if ( table === 'participants' || table === 'generation' ) assert . ok ( code , ` ${ table } : ${ cle } n'est pas un code ` ) ;
}
}
assert . ok ( comptees >= CODES . length , ` ${ comptees } clés ` ) ;
} ) ;
[FIX] core: bounded geometry, dated lock, empty seat, neighbour lists
Non-finite scale ratios, view translations and handle counts now raise
instead of returning 0, NaN or looping forever. The lock carries the
instant from the single clock, a lock taken back in this session's name is
its own, and quitting releases held locks. An empty chair is null only, at
its exact path. A table grown over a neighbour warns LISTE_SUR_VOISINE once
per pair; successive remedies never aim at the same free cell.
Checked: every new test red first; 2983 node and 617 browser tests green
from the index alone.
--- FR ---
[FIX] cœur : géométrie bornée, verrou daté, chaise vide, listes voisines
Un rapport d'échelle, une translation de vue ou un compte de poignée non
finis lèvent au lieu de rendre 0, NaN ou de boucler sans fin. Le verrou
porte l'instant de l'horloge unique, un verrou repris au nom de la séance
est le sien, et quitter rend les verrous tenus. Une chaise vide est null
seulement, à son chemin exact. Une table agrandie sur sa voisine avertit
LISTE_SUR_VOISINE une fois par paire ; les remèdes successifs ne visent
jamais la même case libre.
Vérifié : chaque épreuve rouge d'abord ; 2983 node et 617 navigateur verts
depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 04:00:22 -04:00
test ( "chaque texte est dans la table du domaine qui l'émet" , async ( ) => {
[ADD] application: clock, translation, formatting, displayed name
The screens need one clock, one translation mechanism and one way to
format and name, or each component grows its own. horloge.js is the only
code that reads time and arms timers, injected so tests drive it.
traduction.js keys texts by domain, in two families of tables, raises on a
missing key and never shows a raw key; libelles/ splits the label table by
domain. format.js formats numbers and dates in French, and personnes.js
composes the displayed name the frozen labels use. Nothing persists
between sessions.
Checked: 1773 node and 43 browser tests.
--- FR ---
[ADD] application : horloge, traduction, mise en forme, nom affiché
Les écrans exigent une horloge, un mécanisme de traduction et une façon de
mettre en forme et de nommer, sans quoi chaque composant ferait les siens.
horloge.js est le seul code qui lit l'heure et arme des minuteries, injecté
pour que les épreuves le pilotent. traduction.js range les textes par
domaine, en deux familles de tables, lève sur une clé absente et ne montre
jamais une clé brute ; libelles/ découpe la table des libellés par domaine.
format.js met en forme nombres et dates en français, et personnes.js
compose le nom affiché qu'emploient les libellés figés. Rien ne persiste
d'une séance à l'autre.
Vérifié : 1773 node et 43 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 02:33:39 -04:00
const parTable = new Map ( ( await tablesDuDossier ( ) ) . map ( ( { table , module } ) => [ table , module . TABLE ] ) ) ;
const ATTENDUS = [
[ 'stockage' , [ 'ABSENT' , 'VERROU_PRIS' , 'LECTURE_SEULE' , 'DERIVE' , 'RELECTURE_DIFFERENTE' , 'COMPTEUR_SATURE' ] ] ,
[ 'csv' , [ 'NOM_ABSENT' , 'UTF8_INVALIDE' , 'ENREGISTRER_EN_UTF8' , 'PLAN_BLOQUE' , 'AMBIGUITE' ] ] ,
[ 'seance' , [ 'MODE_LECTURE' , 'SUPPORT_ROMPU' , 'SANS_EFFET' , 'ENTREE_DEFAIRE' , 'JALON_CREATION' ] ] ,
[ 'participants' , [ 'NOM_REQUIS' , 'PARTICIPANT_INCONNU' , 'RESERVATIONS_SUSPENDUES' , 'IMPORT_EN_RETENU' , 'ENTREE_AJOUTER_PARTICIPANT' ] ] ,
[ 'evenements' , [ 'ENTREE_CREATION' , 'DATE_INVALIDE' , 'AUCUN_RETENU' , 'PLAN_NON_BLOQUE' , 'ENTREE_DEBLOQUER' ] ] ,
[ 'generation' , [ 'PROPOSITION_INCONNUE' , 'ENTREE_ENREGISTRER_GENERATION' , 'ENTREE_EFFACER_PROPOSITIONS' ] ] ,
[ 'format' , [ 'format.vide' , 'format.duree.vingtMinutes' ] ] ,
] ;
assert . equal ( ATTENDUS . length , 7 ) ;
for ( const [ table , cles ] of ATTENDUS ) for ( const cle of cles ) assert . ok ( parTable . get ( table ) . has ( cle ) , ` ${ cle } hors de ${ table } .js ` ) ;
} ) ;
test ( "CODES ne compte que les clés de forme code ; libelle lit aussi un libellé de l'application" , ( ) => {
assert . ok ( CODES . length > 0 ) ;
assert . deepEqual ( CODES . filter ( ( cle ) => ! FORME _CODE . test ( cle ) ) , [ ] ) ;
assert . ok ( ! CODES . includes ( 'format.vide' ) ) ;
assert . equal ( libelle ( 'format.vide' ) , '\u{2014}' ) ;
const toutes = TABLES _APPLICATION . flatMap ( ( table ) => [ ... table . keys ( ) ] ) . filter ( ( cle ) => FORME _CODE . test ( cle ) ) ;
assert . deepEqual ( [ ... CODES ] , [ ... toutes ] . sort ( ( a , b ) => ( a < b ? - 1 : a > b ? 1 : 0 ) ) ) ;
} ) ;
} ) ;
[ADD] application: generation controller, worker, evaluation table
Generating must never block the screen or record a plan that breaks a
rule. The controller refuses before launching, in a fixed order: an
out-of-range setting named by the engine's own check, the session's
refusals, then what makes the run useless. It runs the search in a worker
or in the thread, records only propositions whose invariants hold, skips
plans already present, and keeps the last duration for the session only.
The evaluation table ranks propositions on the minimum of encounters and
names the reservations a proposition does not honour.
Checked: 2112 node and 60 node-long tests; coverage floors kept.
--- FR ---
[ADD] application : génération, travailleur, table d'évaluation
Générer ne doit jamais bloquer l'écran ni enregistrer un plan qui enfreint
une règle. Le contrôleur refuse avant tout lancement, dans un ordre fixe :
un réglage hors de son domaine, nommé par la règle même du moteur, les
refus de la séance, puis ce qui rend le calcul vain. Il lance la recherche
dans un travailleur ou dans le fil, n'enregistre que les propositions dont
les invariants tiennent, écarte les plans déjà présents, et garde la
dernière durée pour la seule séance. La table d'évaluation classe les
propositions sur le minimum de rencontres et nomme les réservations
qu'une proposition ne tient pas.
Vérifié : 2112 node et 60 node-long ; seuils de couverture tenus.
Assisted-by: Claude Opus 5.5
2026-10-07 06:09:45 -04:00
[FIX] core: bounded geometry, dated lock, empty seat, neighbour lists
Non-finite scale ratios, view translations and handle counts now raise
instead of returning 0, NaN or looping forever. The lock carries the
instant from the single clock, a lock taken back in this session's name is
its own, and quitting releases held locks. An empty chair is null only, at
its exact path. A table grown over a neighbour warns LISTE_SUR_VOISINE once
per pair; successive remedies never aim at the same free cell.
Checked: every new test red first; 2983 node and 617 browser tests green
from the index alone.
--- FR ---
[FIX] cœur : géométrie bornée, verrou daté, chaise vide, listes voisines
Un rapport d'échelle, une translation de vue ou un compte de poignée non
finis lèvent au lieu de rendre 0, NaN ou de boucler sans fin. Le verrou
porte l'instant de l'horloge unique, un verrou repris au nom de la séance
est le sien, et quitter rend les verrous tenus. Une chaise vide est null
seulement, à son chemin exact. Une table agrandie sur sa voisine avertit
LISTE_SUR_VOISINE une fois par paire ; les remèdes successifs ne visent
jamais la même case libre.
Vérifié : chaque épreuve rouge d'abord ; 2983 node et 617 navigateur verts
depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 04:00:22 -04:00
// --- Le moteur et la génération ----------------------------------------------
[ADD] application: generation controller, worker, evaluation table
Generating must never block the screen or record a plan that breaks a
rule. The controller refuses before launching, in a fixed order: an
out-of-range setting named by the engine's own check, the session's
refusals, then what makes the run useless. It runs the search in a worker
or in the thread, records only propositions whose invariants hold, skips
plans already present, and keeps the last duration for the session only.
The evaluation table ranks propositions on the minimum of encounters and
names the reservations a proposition does not honour.
Checked: 2112 node and 60 node-long tests; coverage floors kept.
--- FR ---
[ADD] application : génération, travailleur, table d'évaluation
Générer ne doit jamais bloquer l'écran ni enregistrer un plan qui enfreint
une règle. Le contrôleur refuse avant tout lancement, dans un ordre fixe :
un réglage hors de son domaine, nommé par la règle même du moteur, les
refus de la séance, puis ce qui rend le calcul vain. Il lance la recherche
dans un travailleur ou dans le fil, n'enregistre que les propositions dont
les invariants tiennent, écarte les plans déjà présents, et garde la
dernière durée pour la seule séance. La table d'évaluation classe les
propositions sur le minimum de rencontres et nomme les réservations
qu'une proposition ne tient pas.
Vérifié : 2112 node et 60 node-long ; seuils de couverture tenus.
Assisted-by: Claude Opus 5.5
2026-10-07 06:09:45 -04:00
describe ( 'libellés : les codes du moteur et de la génération (§ 14.6, § 5.7)' , ( ) => {
test ( 'le relevé du moteur porte des codes, dans ses quatre modules à codes, et rien des valeurs de domine' , ( ) => {
const { codes , porteurs } = releverMoteur ( ) ;
assert . ok ( codes . size >= 20 , ` ${ codes . size } codes relevés ` ) ;
assert . deepEqual ( porteurs , MODULES _A _CODES ) ;
assert . ok ( ! codes . has ( 'A' ) && ! codes . has ( 'B' ) ) ;
for ( const code of [ 'PLAN_EXCLU_PLACE' , 'PLAN_INCONNU' , 'ENUMERATION_TROP_GRANDE' , 'PLACES_MANQUANTES' , 'RESERVATION' ] ) {
assert . ok ( codes . has ( code ) , code ) ;
}
assert . deepEqual ( litterauxEnCapitales ( "// 'DANS_UN_COMMENTAIRE'\nconst x = a ? 'PREMIER' : 'SECOND';" ) , [ 'PREMIER' , 'SECOND' ] ) ;
} ) ;
test ( "chaque code du moteur a sa clé, une seule ; un code que seul le moteur lève l'a dans libelles/moteur.js, aux exceptions nommées près" , async ( ) => {
const { codes } = releverMoteur ( ) ;
const releve = releverArbre ( ) ;
const ailleurs = new Set ( [ ... releve . tables , ... ARBRES . flatMap ( ( arbre ) => [ ... releve . parArbre . get ( arbre ) ] ) ] ) ;
const { TABLE : MOTEUR _TABLE } = await import ( './libelles/moteur.js' ) ;
const { TABLE : STOCKAGE _TABLE } = await import ( './libelles/stockage.js' ) ;
[FIX] core: bounded geometry, dated lock, empty seat, neighbour lists
Non-finite scale ratios, view translations and handle counts now raise
instead of returning 0, NaN or looping forever. The lock carries the
instant from the single clock, a lock taken back in this session's name is
its own, and quitting releases held locks. An empty chair is null only, at
its exact path. A table grown over a neighbour warns LISTE_SUR_VOISINE once
per pair; successive remedies never aim at the same free cell.
Checked: every new test red first; 2983 node and 617 browser tests green
from the index alone.
--- FR ---
[FIX] cœur : géométrie bornée, verrou daté, chaise vide, listes voisines
Un rapport d'échelle, une translation de vue ou un compte de poignée non
finis lèvent au lieu de rendre 0, NaN ou de boucler sans fin. Le verrou
porte l'instant de l'horloge unique, un verrou repris au nom de la séance
est le sien, et quitter rend les verrous tenus. Une chaise vide est null
seulement, à son chemin exact. Une table agrandie sur sa voisine avertit
LISTE_SUR_VOISINE une fois par paire ; les remèdes successifs ne visent
jamais la même case libre.
Vérifié : chaque épreuve rouge d'abord ; 2983 node et 617 navigateur verts
depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 04:00:22 -04:00
// INCONNU est déjà une clé de la table du stockage, une faute de
// placement aux mêmes détails ; PLACES_MANQUANTES et SURRESERVATION,
// que la séance émet aussi, sont dans libelles/moteur.js, et ne s'y
// redéfinissent pas.
[ADD] application: generation controller, worker, evaluation table
Generating must never block the screen or record a plan that breaks a
rule. The controller refuses before launching, in a fixed order: an
out-of-range setting named by the engine's own check, the session's
refusals, then what makes the run useless. It runs the search in a worker
or in the thread, records only propositions whose invariants hold, skips
plans already present, and keeps the last duration for the session only.
The evaluation table ranks propositions on the minimum of encounters and
names the reservations a proposition does not honour.
Checked: 2112 node and 60 node-long tests; coverage floors kept.
--- FR ---
[ADD] application : génération, travailleur, table d'évaluation
Générer ne doit jamais bloquer l'écran ni enregistrer un plan qui enfreint
une règle. Le contrôleur refuse avant tout lancement, dans un ordre fixe :
un réglage hors de son domaine, nommé par la règle même du moteur, les
refus de la séance, puis ce qui rend le calcul vain. Il lance la recherche
dans un travailleur ou dans le fil, n'enregistre que les propositions dont
les invariants tiennent, écarte les plans déjà présents, et garde la
dernière durée pour la seule séance. La table d'évaluation classe les
propositions sur le minimum de rencontres et nomme les réservations
qu'une proposition ne tient pas.
Vérifié : 2112 node et 60 node-long ; seuils de couverture tenus.
Assisted-by: Claude Opus 5.5
2026-10-07 06:09:45 -04:00
const EXCEPTIONS = new Map ( [ [ 'INCONNU' , STOCKAGE _TABLE ] ] ) ;
let comptes = 0 ;
for ( const code of codes ) {
comptes += 1 ;
const porteuses = TABLES _APPLICATION . filter ( ( table ) => table . has ( code ) ) ;
assert . equal ( porteuses . length , 1 , ` ${ code } : ${ porteuses . length } tables ` ) ;
if ( EXCEPTIONS . has ( code ) ) assert . equal ( porteuses [ 0 ] , EXCEPTIONS . get ( code ) , code ) ;
else if ( ! ailleurs . has ( code ) ) assert . equal ( porteuses [ 0 ] , MOTEUR _TABLE , ` ${ code } hors de libelles/moteur.js ` ) ;
}
assert . ok ( comptes > 0 ) ;
for ( const code of [ 'PLACES_MANQUANTES' , 'SURRESERVATION' ] ) assert . ok ( MOTEUR _TABLE . has ( code ) , code ) ;
} ) ;
test ( 'chaque critère de CRITERES a son libellé moteur.critere.<nom>' , ( ) => {
assert . ok ( CRITERES . length > 0 ) ;
for ( const critere of CRITERES ) {
const texte = libelle ( ` moteur.critere. ${ critere } ` ) ;
assert . ok ( texte . length > 0 , critere ) ;
for ( const trace of TRACES ) assert . doesNotMatch ( texte , trace ) ;
}
assert . equal ( new Set ( CRITERES . map ( ( critere ) => libelle ( ` moteur.critere. ${ critere } ` ) ) ) . size , CRITERES . length ) ;
} ) ;
test ( "les tables de l'application et des écrans s'assemblent sans clé en double" , ( ) => {
assert . ok ( TABLES _INTERFACE . length > 0 ) ;
assert . doesNotThrow ( ( ) => creerTraduction ( [ ... TABLES _APPLICATION , ... TABLES _INTERFACE ] ) ) ;
} ) ;
test ( 'CAPACITE et TOURS, levés sous deux formes, ont un seul texte qui les distingue par leurs détails' , ( ) => {
const configuration = libelle ( 'CAPACITE' , { table : 2 , capacite : 1 } ) ;
const violation = libelle ( 'CAPACITE' , { table : 2 , tour : 3 } ) ;
assert . notEqual ( configuration , violation ) ;
assert . match ( configuration , /\b1\b/ ) ;
assert . match ( violation , /tour 3/ ) ;
const tours = libelle ( 'TOURS' , { tours : 0 } ) ;
const toursViolation = libelle ( 'TOURS' , { } ) ;
assert . notEqual ( tours , toursViolation ) ;
for ( const texte of [ configuration , violation , tours , toursViolation ] ) for ( const trace of TRACES ) assert . doesNotMatch ( texte , trace , texte ) ;
} ) ;
test ( "une violation d'indicateur nomme la personne quand l'appelant la traduit, son rang sinon ; un détail de table inconnu lève" , ( ) => {
assert . match ( libelle ( 'DEPASSEMENT' , { index : 4 , participant : 9 } ) , /n° 9/ ) ;
assert . match ( libelle ( 'DEPASSEMENT' , { index : 4 } ) , /rang 5/ ) ;
for ( const detail of [ 'inconnue' , 'doublon' , 'absente' ] ) assert . match ( libelle ( 'TABLE_INCONNUE' , { table : 9 , detail } ) , /9/ ) ;
assert . match ( libelle ( 'TABLE_INCONNUE' , { tour : 2 , detail : 'listes' } ) , /tour 2/ ) ;
assert . throws ( ( ) => libelle ( 'TABLE_INCONNUE' , { table : 9 , detail : 'inventee' } ) , RangeError ) ;
} ) ;
test ( 'chaque refus de valider et de generer a son texte, dans la table de son domaine' , async ( ) => {
const parTable = new Map ( ( await tablesDuDossier ( ) ) . map ( ( { table , module } ) => [ table , module . TABLE ] ) ) ;
// Les refus que lèvent les épreuves de valider et de generer
// (generation.test.js), et la table qui porte chacun. Ceux de la séance,
// du stockage, du mobilier et du moteur sont relayés tels quels ;
// GENERATION_INVARIANT_VIOLE, propre à la génération, se distingue
// d'INVARIANT_VIOLE, la violation du retenu, que la séance porte.
const REFUS = [
[ 'NON_ENTIER' , 'mobilier' ] ,
[ 'HORS_DOMAINE' , 'generation' ] ,
[ 'AUCUN_EVENEMENT' , 'seance' ] ,
[ 'LECTURE_SEULE' , 'stockage' ] ,
[ 'MODE_LECTURE' , 'seance' ] ,
[ 'SUPPORT_ROMPU' , 'seance' ] ,
[ 'GENERATION_EN_COURS' , 'generation' ] ,
[FIX] iteration 3 review: seat targets, gestures, warnings, settings
The wheel no longer zooms during a gesture, so a drop lands under the
ghost. A seat is a target the size of its pitch. A floor drop refuses to
remove an every-tour reservation. A seat reservation the retained plan
does not hold is flagged at the seat, with its swap as remedy. DERIVE names
the person; capacities have an upper bound; Generate waits for a pending
setting; a worker that fails to load shows a refusal; § 5.3 constraints
get their switches. Commands are not re-entrant and a double click does not
confirm. Checked: 2755 node, 550 browser tests, from the index alone.
--- FR ---
[FIX] revue de l'itération 3 : cibles, gestes, avertissements, réglages
La molette ne zoome plus pendant un geste : le dépôt tombe sous le fantôme.
Un siège est une cible de la taille de son pas. Un dépôt sur le sol refuse
de retirer une réservation de tous les tours. Une réservation à un siège
que le retenu ne tient pas est relevée au siège, l'échange en remède. La
dérive nomme la personne ; les capacités ont une borne ; Générer attend un
réglage en cours ; un travailleur qui ne se charge pas donne un refus ; le
§ 5.3 a ses bascules. Les commandes ne sont pas ré-entrantes et un double
clic ne confirme pas. Vérifié : 2755 node, 550 navigateur, index seul.
Assisted-by: Claude Opus 5.5
2026-10-07 17:50:35 -04:00
[ 'TRAVAILLEUR_EN_ECHEC' , 'generation' ] ,
[ADD] application: generation controller, worker, evaluation table
Generating must never block the screen or record a plan that breaks a
rule. The controller refuses before launching, in a fixed order: an
out-of-range setting named by the engine's own check, the session's
refusals, then what makes the run useless. It runs the search in a worker
or in the thread, records only propositions whose invariants hold, skips
plans already present, and keeps the last duration for the session only.
The evaluation table ranks propositions on the minimum of encounters and
names the reservations a proposition does not honour.
Checked: 2112 node and 60 node-long tests; coverage floors kept.
--- FR ---
[ADD] application : génération, travailleur, table d'évaluation
Générer ne doit jamais bloquer l'écran ni enregistrer un plan qui enfreint
une règle. Le contrôleur refuse avant tout lancement, dans un ordre fixe :
un réglage hors de son domaine, nommé par la règle même du moteur, les
refus de la séance, puis ce qui rend le calcul vain. Il lance la recherche
dans un travailleur ou dans le fil, n'enregistre que les propositions dont
les invariants tiennent, écarte les plans déjà présents, et garde la
dernière durée pour la seule séance. La table d'évaluation classe les
propositions sur le minimum de rencontres et nomme les réservations
qu'une proposition ne tient pas.
Vérifié : 2112 node et 60 node-long ; seuils de couverture tenus.
Assisted-by: Claude Opus 5.5
2026-10-07 06:09:45 -04:00
[ 'AUCUN_PARTICIPANT' , 'generation' ] ,
[ 'AUCUNE_TABLE' , 'generation' ] ,
[ 'SURRESERVATION' , 'moteur' ] ,
[ 'PLACES_MANQUANTES' , 'moteur' ] ,
[ 'CONFIRMATION_REQUISE' , 'generation' ] ,
[ 'REGLAGE_HORS_DOMAINE' , 'generation' ] ,
[ 'GENERATION_INVARIANT_VIOLE' , 'generation' ] ,
[ 'COMPTEUR_SATURE' , 'stockage' ] ,
] ;
[FIX] iteration 3 review: seat targets, gestures, warnings, settings
The wheel no longer zooms during a gesture, so a drop lands under the
ghost. A seat is a target the size of its pitch. A floor drop refuses to
remove an every-tour reservation. A seat reservation the retained plan
does not hold is flagged at the seat, with its swap as remedy. DERIVE names
the person; capacities have an upper bound; Generate waits for a pending
setting; a worker that fails to load shows a refusal; § 5.3 constraints
get their switches. Commands are not re-entrant and a double click does not
confirm. Checked: 2755 node, 550 browser tests, from the index alone.
--- FR ---
[FIX] revue de l'itération 3 : cibles, gestes, avertissements, réglages
La molette ne zoome plus pendant un geste : le dépôt tombe sous le fantôme.
Un siège est une cible de la taille de son pas. Un dépôt sur le sol refuse
de retirer une réservation de tous les tours. Une réservation à un siège
que le retenu ne tient pas est relevée au siège, l'échange en remède. La
dérive nomme la personne ; les capacités ont une borne ; Générer attend un
réglage en cours ; un travailleur qui ne se charge pas donne un refus ; le
§ 5.3 a ses bascules. Les commandes ne sont pas ré-entrantes et un double
clic ne confirme pas. Vérifié : 2755 node, 550 navigateur, index seul.
Assisted-by: Claude Opus 5.5
2026-10-07 17:50:35 -04:00
assert . equal ( REFUS . length , 16 ) ;
[ADD] application: generation controller, worker, evaluation table
Generating must never block the screen or record a plan that breaks a
rule. The controller refuses before launching, in a fixed order: an
out-of-range setting named by the engine's own check, the session's
refusals, then what makes the run useless. It runs the search in a worker
or in the thread, records only propositions whose invariants hold, skips
plans already present, and keeps the last duration for the session only.
The evaluation table ranks propositions on the minimum of encounters and
names the reservations a proposition does not honour.
Checked: 2112 node and 60 node-long tests; coverage floors kept.
--- FR ---
[ADD] application : génération, travailleur, table d'évaluation
Générer ne doit jamais bloquer l'écran ni enregistrer un plan qui enfreint
une règle. Le contrôleur refuse avant tout lancement, dans un ordre fixe :
un réglage hors de son domaine, nommé par la règle même du moteur, les
refus de la séance, puis ce qui rend le calcul vain. Il lance la recherche
dans un travailleur ou dans le fil, n'enregistre que les propositions dont
les invariants tiennent, écarte les plans déjà présents, et garde la
dernière durée pour la seule séance. La table d'évaluation classe les
propositions sur le minimum de rencontres et nomme les réservations
qu'une proposition ne tient pas.
Vérifié : 2112 node et 60 node-long ; seuils de couverture tenus.
Assisted-by: Claude Opus 5.5
2026-10-07 06:09:45 -04:00
for ( const [ code , table ] of REFUS ) {
assert . ok ( parTable . get ( table ) . has ( code ) , ` ${ code } hors de ${ table } .js ` ) ;
assert . ok ( libelle ( code , EXEMPLES . get ( code ) ) . length > 0 , code ) ;
}
} ) ;
test ( "la confirmation d'une génération longue dit l'ordre de grandeur, la mesure, le plafond ; sans mesure, le seuil" , ( ) => {
const mesure = { dureeMs : 2000 , mouvements : 1000000 , participants : 260 , nombre : 5 , arret : 200000 } ;
const longue = libelle ( 'CONFIRMATION_REQUISE' , { estimation : { secondes : 1200 , longue : true , auDelaDuPlafond : false , mesure } , remede : null } ) ;
assert . match ( longue , /environ vingt minutes/ ) ;
assert . match ( longue , /260 participants/ ) ;
assert . doesNotMatch ( longue , /trente minutes/ ) ;
const plafond = libelle ( 'CONFIRMATION_REQUISE' , { estimation : { secondes : 7200 , longue : true , auDelaDuPlafond : true , mesure } , remede : null } ) ;
assert . match ( plafond , /trente minutes/ ) ;
const inconnue = libelle ( 'CONFIRMATION_REQUISE' , { estimation : { inconnue : true } , remede : null } ) ;
assert . match ( inconnue , /dix fois le réglage par défaut/ ) ;
for ( const texte of [ longue , plafond , inconnue ] ) for ( const trace of TRACES ) assert . doesNotMatch ( texte , trace , texte ) ;
} ) ;
test ( "une violation d'invariant rendue par la génération nomme son rang et chaque règle, sans retenu ni dérive" , ( ) => {
const details = {
rang : 2 ,
violations : [
{ code : 'RESERVATION' , participant : 4 , table : 5 , tour : 1 } ,
{ code : 'CAPACITE' , table : 5 , tour : 2 } ,
] ,
remede : null ,
} ;
const texte = libelle ( 'GENERATION_INVARIANT_VIOLE' , details ) ;
assert . match ( texte , /rang 2\b/ ) ;
assert . match ( texte , /2 règles/ ) ;
assert . ok ( texte . includes ( libelle ( 'RESERVATION' , details . violations [ 0 ] ) ) , texte ) ;
assert . ok ( texte . includes ( libelle ( 'CAPACITE' , details . violations [ 1 ] ) ) , texte ) ;
assert . doesNotMatch ( texte , /retenu|dérive|proposition n°/ ) ;
assert . match ( libelle ( 'GENERATION_INVARIANT_VIOLE' , { ... details , violations : details . violations . slice ( 0 , 1 ) } ) , /1 règle\b/ ) ;
} ) ;
test ( 'un domaine borné nomme ses deux bornes, un domaine ouvert son minimum' , ( ) => {
assert . match ( libelle ( 'HORS_DOMAINE' , { min : 0 , max : 4294967295 , remede : null } ) , /0 et 4\u{202F}294\u{202F}967\u{202F}295/u ) ;
assert . match ( libelle ( 'HORS_DOMAINE' , { min : 1 , max : null , remede : null } ) , /au moins 1\b/ ) ;
} ) ;
} ) ;