[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)
|
|
|
|
|
|
|
|
|
|
|
|
// Les commandes nommées (§ 8.2) : toute modification d'un événement passe par
|
|
|
|
|
|
// l'une d'elles. Une commande est une fonction pure (charge, arguments) →
|
|
|
|
|
|
// { charge, libelle, avertissements } : la charge rendue est neuve, celle
|
|
|
|
|
|
// reçue ne change jamais ; le libellé est le texte français que le journal
|
|
|
|
|
|
// fige (libelles.js) ; les avertissements, { code, details }, disent ce que le
|
|
|
|
|
|
// geste emporte de plus que son effet, sans rien empêcher. Un refus lève
|
|
|
|
|
|
// ErreurCommande(code, details), où details.remede nomme le geste qui le lève,
|
|
|
|
|
|
// null quand il n'y en a pas ; une faute du code — un argument de forme
|
|
|
|
|
|
// fausse, un état ou un champ inconnus — lève TypeError. Aucune commande
|
|
|
|
|
|
// n'écrit, ne lit l'horloge ni ne tire un aléa.
|
|
|
|
|
|
//
|
|
|
|
|
|
// La saisie entre en NFC, ses blancs de bord retirés, une chaîne vide valant
|
|
|
|
|
|
// null (§ 10.2) ; la règle d'un champ est celle du schéma du fichier d'état,
|
|
|
|
|
|
// que document.js tient seul. Les identifiants viennent de prochainsIds, qui
|
|
|
|
|
|
// ne recule jamais : un identifiant retiré ne revient pas (§ 4, § 5.7). Un
|
|
|
|
|
|
// plan bloqué refuse toute commande, débloquer excepté (§ 9).
|
|
|
|
|
|
//
|
|
|
|
|
|
// Les états du plan (§ 9) : brouillon, proposé et retenu se posent par
|
|
|
|
|
|
// changerEtat sans rien détruire, proposé quand une proposition existe,
|
|
|
|
|
|
// retenu quand un placement est retenu ; bloqué se pose de chacun d'eux, et
|
|
|
|
|
|
// seul debloquer le lève, vers l'état que le contenu impose (etatDeduit). Une
|
|
|
|
|
|
// génération fait passer un brouillon à proposé ; retenir pose retenu ;
|
|
|
|
|
|
// effacer les propositions ramène un plan proposé sans retenu à brouillon.
|
|
|
|
|
|
import { ErreurCsv } from '../csv/erreurs.js';
|
|
|
|
|
|
import { appliquerImport, bilanRemplacement } from '../csv/import.js';
|
|
|
|
|
|
import { serialiserCharge } from '../stockage/canonique.js';
|
|
|
|
|
|
import { SCHEMA, clesRangees, creerCharge, etatDeduit, premiereFaute } from '../stockage/document.js';
|
|
|
|
|
|
import { ErreurStockage } from '../stockage/erreurs.js';
|
[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
|
|
|
|
import { fautes, formeNommee, versFichier } from '../stockage/placements.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 { COMMANDES_MOBILIER } from './commandes_mobilier.js';
|
|
|
|
|
|
import { COMMANDES_PLACES } from './commandes_places.js';
|
|
|
|
|
|
import { ErreurCommande, refus } from './erreurs.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
|
|
|
|
import { libelle } from './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
|
|
|
|
import { nomAffiche } from './personnes.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
|
|
|
|
|
[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
|
|
|
|
// Le refus d'une commande vit dans erreurs.js, le nom affiché d'une personne
|
|
|
|
|
|
// dans personnes.js : les importateurs de ce module les y trouvent encore.
|
|
|
|
|
|
export { ErreurCommande, nomAffiche };
|
[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 avertissement : il n'empêche rien.
|
|
|
|
|
|
const avertissement = (code, details) => ({ code, details });
|
|
|
|
|
|
|
|
|
|
|
|
// Règles du schéma : celle d'un champ d'objet, puis celles de l'événement et
|
|
|
|
|
|
// d'un participant.
|
|
|
|
|
|
const regleDuChamp = (regle, cle) => regle.champs.find(([nom]) => nom === cle)[1];
|
|
|
|
|
|
const CHARGE = regleDuChamp(SCHEMA, 'charge');
|
|
|
|
|
|
const EVENEMENT = regleDuChamp(CHARGE, 'evenement');
|
|
|
|
|
|
const PARTICIPANT = regleDuChamp(CHARGE, 'participants').element;
|
|
|
|
|
|
// Le plus grand identifiant qu'admet la règle : le compteur, qui la suit,
|
|
|
|
|
|
// n'aurait plus de valeur au-delà de lui.
|
|
|
|
|
|
const IDENTIFIANT_MAX = regleDuChamp(PARTICIPANT, 'id').max;
|
|
|
|
|
|
|
|
|
|
|
|
const ETATS = ['brouillon', 'propose', 'retenu', 'bloque'];
|
|
|
|
|
|
|
|
|
|
|
|
// Les champs de texte d'un participant que la saisie pose, dans l'ordre du
|
|
|
|
|
|
// fichier.
|
|
|
|
|
|
const CHAMPS_DE_SAISIE = ['nom', 'prenom', 'appartenance', 'courriel', 'titrePressenti', 'notes'];
|
|
|
|
|
|
|
|
|
|
|
|
// Une saisie de texte : en NFC, ses blancs de bord retirés, null quand il ne
|
|
|
|
|
|
// reste rien ; null et l'absence valent null.
|
|
|
|
|
|
function saisie(valeur, champ) {
|
|
|
|
|
|
if (valeur === undefined || valeur === null) return null;
|
|
|
|
|
|
if (typeof valeur !== 'string') {
|
|
|
|
|
|
throw new TypeError(`${champ} : chaîne ou null attendue, reçu ${JSON.stringify(valeur)}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
const texte = valeur.normalize('NFC').trim();
|
|
|
|
|
|
return texte === '' ? null : texte;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Les champs d'une saisie de participant : chaque champ de texte présent, lu
|
|
|
|
|
|
// par saisie, et exclu quand admisExclu, un booléen. Un champ inconnu, ou
|
|
|
|
|
|
// d'une autre sorte, est une faute du code.
|
|
|
|
|
|
function lireChamps(champs, { admisExclu }) {
|
|
|
|
|
|
if (champs === null || typeof champs !== 'object' || Array.isArray(champs)) {
|
|
|
|
|
|
throw new TypeError(`champs : objet attendu, reçu ${JSON.stringify(champs)}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
const permis = admisExclu ? [...CHAMPS_DE_SAISIE, 'exclu'] : CHAMPS_DE_SAISIE;
|
|
|
|
|
|
const inconnu = clesRangees(champs).find((cle) => !permis.includes(cle));
|
|
|
|
|
|
if (inconnu !== undefined) throw new TypeError(`champs : ${inconnu} n'est pas un champ de cette saisie`);
|
|
|
|
|
|
const lus = {};
|
|
|
|
|
|
for (const champ of CHAMPS_DE_SAISIE) if (Object.hasOwn(champs, champ)) lus[champ] = saisie(champs[champ], champ);
|
|
|
|
|
|
if (Object.hasOwn(champs, 'exclu')) {
|
|
|
|
|
|
if (typeof champs.exclu !== 'boolean') {
|
|
|
|
|
|
throw new TypeError(`exclu : booléen attendu, reçu ${JSON.stringify(champs.exclu)}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
lus.exclu = champs.exclu;
|
|
|
|
|
|
}
|
|
|
|
|
|
return lus;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Un participant tel que la saisie le pose : nom requis (NOM_REQUIS) ; toute
|
|
|
|
|
|
// autre valeur hors de sa règle est une faute du code.
|
|
|
|
|
|
function exigerParticipant(participant) {
|
|
|
|
|
|
if (premiereFaute(participant.nom, regleDuChamp(PARTICIPANT, 'nom')) !== null) throw refus('NOM_REQUIS');
|
|
|
|
|
|
const faute = premiereFaute(participant, PARTICIPANT);
|
|
|
|
|
|
if (faute !== null) throw new TypeError(`participant : ${faute} hors de sa règle`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Le participant d'identifiant id, ou PARTICIPANT_INCONNU.
|
|
|
|
|
|
function participantDe(charge, id) {
|
|
|
|
|
|
if (!Number.isSafeInteger(id)) throw new TypeError(`id : entier attendu, reçu ${JSON.stringify(id)}`);
|
|
|
|
|
|
const participant = charge.participants.find((candidat) => candidat.id === id);
|
|
|
|
|
|
if (participant === undefined) throw refus('PARTICIPANT_INCONNU', { id });
|
|
|
|
|
|
return participant;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Un plan bloqué refuse toute modification ; débloquer seul le lève (§ 9).
|
|
|
|
|
|
function exigerModifiable(charge) {
|
|
|
|
|
|
if (charge.evenement.etat === 'bloque') throw refus('PLAN_BLOQUE', {}, { geste: 'debloquer' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Titres de place pourvus (§ 4.1, § 4.4) : le compte que bilanRemplacement
|
|
|
|
|
|
// tient pour l'import, seule implémentation de la règle (§ 13.2).
|
|
|
|
|
|
const titresPourvus = (charge) => bilanRemplacement(charge).titresPourvus;
|
|
|
|
|
|
|
|
|
|
|
|
// La charge reçue, copiée, son participant id remplacé par ce que rend
|
|
|
|
|
|
// modifier de sa copie.
|
|
|
|
|
|
function avecParticipant(charge, id, modifier) {
|
|
|
|
|
|
const suivante = structuredClone(charge);
|
|
|
|
|
|
suivante.participants = suivante.participants.map((participant) =>
|
|
|
|
|
|
participant.id === id ? modifier(participant) : participant,
|
|
|
|
|
|
);
|
|
|
|
|
|
return suivante;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// La charge reçue, copiée, à l'état donné.
|
|
|
|
|
|
function avecEtat(charge, etat) {
|
|
|
|
|
|
const suivante = structuredClone(charge);
|
|
|
|
|
|
suivante.evenement.etat = etat;
|
|
|
|
|
|
return suivante;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** ajouterParticipant {champs} : seul le nom est requis ; exclu vaut faux par défaut. */
|
|
|
|
|
|
function ajouterParticipant(charge, { champs }) {
|
|
|
|
|
|
exigerModifiable(charge);
|
|
|
|
|
|
const lus = lireChamps(champs, { admisExclu: true });
|
|
|
|
|
|
const id = charge.prochainsIds.participant;
|
|
|
|
|
|
const participant = {
|
|
|
|
|
|
id,
|
|
|
|
|
|
nom: lus.nom ?? null,
|
|
|
|
|
|
prenom: lus.prenom ?? null,
|
|
|
|
|
|
appartenance: lus.appartenance ?? null,
|
|
|
|
|
|
courriel: lus.courriel ?? null,
|
|
|
|
|
|
titrePressenti: lus.titrePressenti ?? null,
|
|
|
|
|
|
notes: lus.notes ?? null,
|
|
|
|
|
|
exclu: lus.exclu ?? false,
|
|
|
|
|
|
};
|
|
|
|
|
|
exigerParticipant(participant);
|
|
|
|
|
|
if (id >= IDENTIFIANT_MAX) throw refus('COMPTEUR_SATURE', { compteur: 'participant' });
|
|
|
|
|
|
const suivante = structuredClone(charge);
|
|
|
|
|
|
suivante.participants.push(participant);
|
|
|
|
|
|
suivante.prochainsIds.participant = id + 1;
|
|
|
|
|
|
return {
|
|
|
|
|
|
charge: suivante,
|
[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
|
|
|
|
libelle: libelle('ENTREE_AJOUTER_PARTICIPANT', { personne: nomAffiche(participant) }),
|
[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
|
|
|
|
avertissements: [],
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* modifierParticipant {id, champs} : les champs de texte donnés ; l'exclusion
|
|
|
|
|
|
* a ses deux commandes. Le libellé nomme la personne d'avant et chaque champ
|
|
|
|
|
|
* changé, puis le nouveau nom quand il change.
|
|
|
|
|
|
*/
|
|
|
|
|
|
function modifierParticipant(charge, { id, champs }) {
|
|
|
|
|
|
exigerModifiable(charge);
|
|
|
|
|
|
const avant = participantDe(charge, id);
|
|
|
|
|
|
const apres = { ...avant, ...lireChamps(champs, { admisExclu: false }) };
|
|
|
|
|
|
exigerParticipant(apres);
|
|
|
|
|
|
const changes = CHAMPS_DE_SAISIE.filter((champ) => apres[champ] !== avant[champ]);
|
|
|
|
|
|
if (changes.length === 0) throw refus('SANS_EFFET', { commande: 'modifierParticipant' });
|
|
|
|
|
|
const renomme = apres.nom !== avant.nom || apres.prenom !== avant.prenom;
|
|
|
|
|
|
return {
|
|
|
|
|
|
charge: avecParticipant(charge, id, () => apres),
|
|
|
|
|
|
libelle: libelle('ENTREE_MODIFIER_PARTICIPANT', {
|
[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
|
|
|
|
personne: nomAffiche(avant),
|
[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
|
|
|
|
champs: changes,
|
[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
|
|
|
|
devenu: renomme ? nomAffiche(apres) : 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
|
|
|
|
}),
|
|
|
|
|
|
avertissements: [],
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Les comptes d'une exclusion ou d'une réintégration (§ 4.4) : les
|
|
|
|
|
|
// réservations de la personne, que l'exclusion suspend sans les retirer, et
|
|
|
|
|
|
// les titres que le geste cesse de pourvoir, ou pourvoit de nouveau.
|
|
|
|
|
|
function bilanExclusion(avant, apres, participant) {
|
|
|
|
|
|
const reservations = avant.reservations.filter((reservation) => reservation.participant === participant.id).length;
|
|
|
|
|
|
const titres = Math.abs(titresPourvus(avant) - titresPourvus(apres));
|
[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
|
|
|
|
return { participant: participant.id, personne: nomAffiche(participant), reservations, titres };
|
[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
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* exclureParticipant {id} (§ 4.4) : ses réservations restent, suspendues ; ses
|
|
|
|
|
|
* places reviennent aux autres et ses titres ne sont plus pourvus, ce que
|
|
|
|
|
|
* RESERVATIONS_SUSPENDUES dit quand il en porte.
|
|
|
|
|
|
*/
|
|
|
|
|
|
function exclureParticipant(charge, { id }) {
|
|
|
|
|
|
exigerModifiable(charge);
|
|
|
|
|
|
const participant = participantDe(charge, id);
|
|
|
|
|
|
if (participant.exclu) throw refus('SANS_EFFET', { commande: 'exclureParticipant' });
|
|
|
|
|
|
const suivante = avecParticipant(charge, id, (copie) => ({ ...copie, exclu: true }));
|
|
|
|
|
|
const bilan = bilanExclusion(charge, suivante, participant);
|
|
|
|
|
|
return {
|
|
|
|
|
|
charge: suivante,
|
|
|
|
|
|
libelle: libelle('ENTREE_EXCLURE_PARTICIPANT', { personne: bilan.personne }),
|
|
|
|
|
|
avertissements: bilan.reservations > 0 ? [avertissement('RESERVATIONS_SUSPENDUES', bilan)] : [],
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** reintegrerParticipant {id} (§ 4.4) : ses réservations et ses titres reviennent, RESERVATIONS_RETABLIES. */
|
|
|
|
|
|
function reintegrerParticipant(charge, { id }) {
|
|
|
|
|
|
exigerModifiable(charge);
|
|
|
|
|
|
const participant = participantDe(charge, id);
|
|
|
|
|
|
if (!participant.exclu) throw refus('SANS_EFFET', { commande: 'reintegrerParticipant' });
|
|
|
|
|
|
const suivante = avecParticipant(charge, id, (copie) => ({ ...copie, exclu: false }));
|
|
|
|
|
|
const bilan = bilanExclusion(charge, suivante, participant);
|
|
|
|
|
|
return {
|
|
|
|
|
|
charge: suivante,
|
|
|
|
|
|
libelle: libelle('ENTREE_REINTEGRER_PARTICIPANT', { personne: bilan.personne }),
|
|
|
|
|
|
avertissements: bilan.reservations > 0 ? [avertissement('RESERVATIONS_RETABLIES', bilan)] : [],
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* supprimerParticipant {id} : la personne et ses réservations partent dans la
|
|
|
|
|
|
* même entrée ; les titres restent à leurs places, les propositions et le
|
|
|
|
|
|
* retenu restent, en dérive (§ 9) ; le compteur ne recule pas.
|
|
|
|
|
|
*/
|
|
|
|
|
|
function supprimerParticipant(charge, { id }) {
|
|
|
|
|
|
exigerModifiable(charge);
|
|
|
|
|
|
const participant = participantDe(charge, id);
|
|
|
|
|
|
const suivante = structuredClone(charge);
|
|
|
|
|
|
suivante.participants = suivante.participants.filter((candidat) => candidat.id !== id);
|
|
|
|
|
|
suivante.reservations = suivante.reservations.filter((reservation) => reservation.participant !== id);
|
|
|
|
|
|
const reservations = charge.reservations.length - suivante.reservations.length;
|
|
|
|
|
|
const details = {
|
|
|
|
|
|
participant: id,
|
[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
|
|
|
|
personne: nomAffiche(participant),
|
[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
|
|
|
|
reservations,
|
|
|
|
|
|
titres: titresPourvus(charge) - titresPourvus(suivante),
|
|
|
|
|
|
};
|
|
|
|
|
|
return {
|
|
|
|
|
|
charge: suivante,
|
|
|
|
|
|
libelle: libelle('ENTREE_SUPPRIMER_PARTICIPANT', { personne: details.personne }),
|
|
|
|
|
|
avertissements: reservations > 0 ? [avertissement('RESERVATIONS_RETIREES', details)] : [],
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
[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
|
|
|
|
// Les avertissements de chaque personne qu'un import exclut ou réintègre
|
|
|
|
|
|
// (§ 4.4) : celle que la charge suivante porte sous le même identifiant,
|
|
|
|
|
|
// exclu changé, et qui tient une réservation. Chacune a le bilan que
|
|
|
|
|
|
// donnerait sa commande seule, exclureParticipant ou reintegrerParticipant,
|
|
|
|
|
|
// appliquée à la charge d'avant : RESERVATIONS_SUSPENDUES ou
|
|
|
|
|
|
// RESERVATIONS_RETABLIES, par identifiant croissant.
|
|
|
|
|
|
function avertissementsDExclusion(avant, suivante) {
|
|
|
|
|
|
const exclusDApres = new Map(suivante.participants.map(({ id, exclu }) => [id, exclu]));
|
|
|
|
|
|
return avant.participants
|
|
|
|
|
|
.filter(({ id, exclu }) => exclusDApres.has(id) && exclusDApres.get(id) !== exclu)
|
|
|
|
|
|
.sort((a, b) => a.id - b.id)
|
|
|
|
|
|
.map((participant) => {
|
|
|
|
|
|
const seule = avecParticipant(avant, participant.id, (copie) => ({ ...copie, exclu: !copie.exclu }));
|
|
|
|
|
|
const code = participant.exclu ? 'RESERVATIONS_RETABLIES' : 'RESERVATIONS_SUSPENDUES';
|
|
|
|
|
|
return avertissement(code, bilanExclusion(avant, seule, participant));
|
|
|
|
|
|
})
|
|
|
|
|
|
.filter(({ details }) => details.reservations > 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
[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
|
|
|
|
/**
|
|
|
|
|
|
* importerParticipants {apercu, mode} (§ 10.1) : l'aperçu appliqué selon le
|
|
|
|
|
|
* mode (appliquerImport), en une seule entrée. Le refus du CSV — un aperçu
|
|
|
|
|
|
* refusé ou ambigu — devient le refus de la commande, sous le même code. Le
|
|
|
|
|
|
* résumé suit le rendu, pour le rapport d'import et le CSV des refus. Selon
|
|
|
|
|
|
* l'état : sans avertissement en brouillon et proposé ; en retenu,
|
|
|
|
|
|
* l'avertissement de dérive IMPORT_EN_RETENU, quel que soit le mode, sur le
|
|
|
|
|
|
* signal resume.derive d'appliquerImport ; refusé en bloqué, PLAN_BLOQUE.
|
[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
|
|
|
|
* Une personne que l'import exclut ou réintègre le dit ensuite comme sa
|
|
|
|
|
|
* commande le dirait (avertissementsDExclusion).
|
|
|
|
|
|
*
|
|
|
|
|
|
* Un import dont la charge ne change pas, mais qui refuse des lignes, n'est
|
|
|
|
|
|
* pas sans effet : ses refus sont à lire et à réexporter. Il rend la charge
|
|
|
|
|
|
* reçue, un libellé null — aucune entrée ne s'écrit —, aucun avertissement,
|
|
|
|
|
|
* et son résumé.
|
[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 importerParticipants(charge, { apercu, mode }) {
|
|
|
|
|
|
exigerModifiable(charge);
|
|
|
|
|
|
let resultat;
|
|
|
|
|
|
try {
|
|
|
|
|
|
resultat = appliquerImport(charge, apercu, mode);
|
|
|
|
|
|
} catch (erreur) {
|
|
|
|
|
|
if (erreur instanceof ErreurCsv) throw new ErreurCommande(erreur.code, { ...erreur.details, remede: null });
|
|
|
|
|
|
throw erreur;
|
|
|
|
|
|
}
|
[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 { ajoutes, misAJour, retires, derive, refusees } = resultat.resume;
|
|
|
|
|
|
if (refusees.length > 0 && serialiserCharge(resultat.charge) === serialiserCharge(charge)) {
|
|
|
|
|
|
return { charge, libelle: null, avertissements: [], resume: resultat.resume };
|
|
|
|
|
|
}
|
[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 {
|
|
|
|
|
|
charge: resultat.charge,
|
|
|
|
|
|
libelle: libelle('ENTREE_IMPORTER_PARTICIPANTS', { mode, ajoutes, misAJour, retires }),
|
[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
|
|
|
|
avertissements: [
|
|
|
|
|
|
...(derive ? [avertissement('IMPORT_EN_RETENU', {})] : []),
|
|
|
|
|
|
...avertissementsDExclusion(charge, resultat.charge),
|
|
|
|
|
|
],
|
[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
|
|
|
|
resume: resultat.resume,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** changerEtat {etat} (§ 9) : un état du plan, sans rien détruire ; bloque se pose de chacun. */
|
|
|
|
|
|
function changerEtat(charge, { etat }) {
|
|
|
|
|
|
if (!ETATS.includes(etat)) throw new TypeError(`etat : ${ETATS.join(', ')} attendu, reçu ${JSON.stringify(etat)}`);
|
|
|
|
|
|
exigerModifiable(charge);
|
|
|
|
|
|
if (etat === charge.evenement.etat) throw refus('SANS_EFFET', { commande: 'changerEtat' });
|
|
|
|
|
|
if (etat === 'propose' && charge.propositions.length === 0) {
|
|
|
|
|
|
throw refus('AUCUNE_PROPOSITION', {}, { geste: 'ouvrirSection', section: 'generation' });
|
|
|
|
|
|
}
|
|
|
|
|
|
if (etat === 'retenu' && charge.retenu === null) {
|
|
|
|
|
|
throw refus('AUCUN_RETENU', {}, { geste: 'ouvrirSection', section: 'propositions' });
|
|
|
|
|
|
}
|
|
|
|
|
|
return { charge: avecEtat(charge, etat), libelle: libelle('ENTREE_CHANGER_ETAT', { etat }), avertissements: [] };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** debloquer {} (§ 8.5, § 9) : l'état revient à celui que le contenu impose. */
|
|
|
|
|
|
function debloquer(charge) {
|
|
|
|
|
|
if (charge.evenement.etat !== 'bloque') throw refus('PLAN_NON_BLOQUE');
|
|
|
|
|
|
const etat = etatDeduit(charge);
|
|
|
|
|
|
return { charge: avecEtat(charge, etat), libelle: libelle('ENTREE_DEBLOQUER', { etat }), avertissements: [] };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* enregistrerGeneration {propositions, configuration, produitVersion} (§ 5.7,
|
|
|
|
|
|
* § 8.9) : les propositions du moteur, mises en forme de fichier
|
|
|
|
|
|
* (versFichier) sous les identifiants qui suivent prochainsIds.proposition —
|
|
|
|
|
|
* la proposition d'identifiant k du moteur prend prochainsIds.proposition +
|
|
|
|
|
|
* k − 1 —, s'ajoutent aux précédentes, et le compteur passe au-delà de la
|
|
|
|
|
|
* dernière. Un brouillon passe à proposé.
|
|
|
|
|
|
*/
|
|
|
|
|
|
function enregistrerGeneration(charge, { propositions, configuration, produitVersion }) {
|
|
|
|
|
|
exigerModifiable(charge);
|
|
|
|
|
|
if (!Array.isArray(propositions)) throw new TypeError('propositions : liste attendue');
|
|
|
|
|
|
if (propositions.length === 0) throw refus('SANS_EFFET', { commande: 'enregistrerGeneration' });
|
|
|
|
|
|
const rangs = propositions.map(({ id }) => id);
|
|
|
|
|
|
if (!rangs.every((id) => Number.isSafeInteger(id) && id >= 1) || new Set(rangs).size !== rangs.length) {
|
|
|
|
|
|
throw new TypeError('propositions : identifiants du moteur entiers, ≥ 1 et distincts attendus');
|
|
|
|
|
|
}
|
|
|
|
|
|
const decalage = charge.prochainsIds.proposition - 1;
|
|
|
|
|
|
const options = { produitVersion, attribuerSieges: charge.reglages.attribuerSieges, decalage };
|
|
|
|
|
|
let fichiers;
|
|
|
|
|
|
try {
|
|
|
|
|
|
fichiers = propositions.map((proposition) => versFichier(proposition, configuration, options));
|
|
|
|
|
|
} catch (erreur) {
|
|
|
|
|
|
if (erreur instanceof ErreurStockage && erreur.code === 'COMPTEUR_SATURE') {
|
|
|
|
|
|
throw refus('COMPTEUR_SATURE', erreur.details);
|
|
|
|
|
|
}
|
|
|
|
|
|
throw erreur;
|
|
|
|
|
|
}
|
|
|
|
|
|
const ids = fichiers.map(({ id }) => id);
|
|
|
|
|
|
const premier = Math.min(...ids);
|
|
|
|
|
|
const dernier = Math.max(...ids);
|
|
|
|
|
|
const suivante = structuredClone(charge);
|
|
|
|
|
|
suivante.propositions.push(...fichiers);
|
|
|
|
|
|
suivante.prochainsIds.proposition = dernier + 1;
|
|
|
|
|
|
if (suivante.evenement.etat === 'brouillon') suivante.evenement.etat = 'propose';
|
|
|
|
|
|
return {
|
|
|
|
|
|
charge: suivante,
|
|
|
|
|
|
libelle: libelle('ENTREE_ENREGISTRER_GENERATION', { n: fichiers.length, premier, dernier }),
|
|
|
|
|
|
avertissements: [],
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* retenirProposition {id} (§ 5.7, § 9) : le placement de la proposition,
|
|
|
|
|
|
* copié, devient le retenu, que les retouches modifieront sans toucher la
|
|
|
|
|
|
* proposition ; le plan passe à retenu.
|
|
|
|
|
|
*/
|
|
|
|
|
|
function retenirProposition(charge, { id }) {
|
|
|
|
|
|
exigerModifiable(charge);
|
|
|
|
|
|
const proposition = charge.propositions.find((candidate) => candidate.id === id);
|
|
|
|
|
|
if (proposition === undefined) throw refus('PROPOSITION_INCONNUE', { id });
|
|
|
|
|
|
const { siegesAttribues, tables, capacites, tours, participants, placement } = structuredClone(proposition);
|
|
|
|
|
|
const suivante = avecEtat(charge, 'retenu');
|
|
|
|
|
|
suivante.retenu = { proposition: id, siegesAttribues, tables, capacites, tours, participants, placement };
|
|
|
|
|
|
return { charge: suivante, libelle: libelle('ENTREE_RETENIR_PROPOSITION', { id }), avertissements: [] };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* effacerPropositions {} (§ 5.7) : la liste se vide, la proposition d'où vient
|
|
|
|
|
|
* le retenu exceptée ; le compteur ne recule pas. Un plan proposé sans retenu
|
|
|
|
|
|
* revient à brouillon.
|
|
|
|
|
|
*/
|
|
|
|
|
|
function effacerPropositions(charge) {
|
|
|
|
|
|
exigerModifiable(charge);
|
|
|
|
|
|
const origine = charge.retenu?.proposition ?? null;
|
|
|
|
|
|
const gardees = charge.propositions.filter(({ id }) => id === origine);
|
|
|
|
|
|
const n = charge.propositions.length - gardees.length;
|
|
|
|
|
|
if (n === 0) throw refus('SANS_EFFET', { commande: 'effacerPropositions' });
|
|
|
|
|
|
const suivante = structuredClone(charge);
|
|
|
|
|
|
suivante.propositions = suivante.propositions.filter(({ id }) => id === origine);
|
|
|
|
|
|
if (suivante.retenu === null && suivante.evenement.etat === 'propose') suivante.evenement.etat = 'brouillon';
|
|
|
|
|
|
return {
|
|
|
|
|
|
charge: suivante,
|
|
|
|
|
|
libelle: libelle('ENTREE_EFFACER_PROPOSITIONS', { n, gardee: gardees.length > 0 ? origine : null }),
|
|
|
|
|
|
avertissements: [],
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
[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
|
|
|
|
/**
|
|
|
|
|
|
* Un registre réuni de plusieurs : chaque nom une fois, dans l'ordre des
|
|
|
|
|
|
* registres reçus, puis de chacun.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {...Map<string, Function>} registres
|
|
|
|
|
|
* @returns {Map<string, Function>}
|
|
|
|
|
|
* @throws {RangeError} un nom porté par deux registres, qu'il nomme
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function reunirCommandes(...registres) {
|
|
|
|
|
|
const reunies = new Map();
|
|
|
|
|
|
for (const registre of registres) {
|
|
|
|
|
|
for (const [nom, commande] of registre) {
|
|
|
|
|
|
if (reunies.has(nom)) throw new RangeError(`commandes : ${JSON.stringify(nom)} est dans deux registres`);
|
|
|
|
|
|
reunies.set(nom, commande);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return reunies;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
[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 registre des commandes, par nom : chacune (charge, arguments) →
|
|
|
|
|
|
* { charge, libelle, avertissements }, et importerParticipants y ajoute le
|
[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
|
|
|
|
* résumé de l'import. Il réunit les commandes de ce module, celles du
|
|
|
|
|
|
* mobilier et celles 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
|
|
|
|
*
|
|
|
|
|
|
* @typedef {{code: string, details: Object}} Avertissement
|
|
|
|
|
|
* @typedef {{charge: Object, libelle: string, avertissements: Avertissement[]}} Rendu
|
|
|
|
|
|
* @type {Map<string, (charge: Object, args: Object) => Rendu>}
|
|
|
|
|
|
*/
|
[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
|
|
|
|
export const COMMANDES = reunirCommandes(
|
|
|
|
|
|
new Map([
|
|
|
|
|
|
['ajouterParticipant', ajouterParticipant],
|
|
|
|
|
|
['modifierParticipant', modifierParticipant],
|
|
|
|
|
|
['exclureParticipant', exclureParticipant],
|
|
|
|
|
|
['reintegrerParticipant', reintegrerParticipant],
|
|
|
|
|
|
['supprimerParticipant', supprimerParticipant],
|
|
|
|
|
|
['importerParticipants', importerParticipants],
|
|
|
|
|
|
['changerEtat', changerEtat],
|
|
|
|
|
|
['debloquer', debloquer],
|
|
|
|
|
|
['enregistrerGeneration', enregistrerGeneration],
|
|
|
|
|
|
['retenirProposition', retenirProposition],
|
|
|
|
|
|
['effacerPropositions', effacerPropositions],
|
|
|
|
|
|
]),
|
|
|
|
|
|
COMMANDES_MOBILIER,
|
|
|
|
|
|
COMMANDES_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
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Applique la commande nom à la charge : ce qu'elle rend, avertissements []
|
|
|
|
|
|
* quand elle n'en donne pas. Un geste dont la charge rendue s'écrit comme la
|
|
|
|
|
|
* charge reçue ne change rien, et n'est pas une entrée (§ 8.2) : SANS_EFFET.
|
[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
|
|
|
|
* Un rendu au libellé null ne change rien non plus, mais porte ce qu'il y a
|
|
|
|
|
|
* à lire — l'import dont seules des lignes refusées auraient changé la
|
|
|
|
|
|
* liste : il passe tel quel, et rien ne s'écrit.
|
[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
|
|
|
|
*
|
|
|
|
|
|
* @param {string} nom
|
|
|
|
|
|
* @param {Object} charge
|
|
|
|
|
|
* @param {Object} [args]
|
[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
|
|
|
|
* @returns {{charge: Object, libelle: string|null, avertissements: Array<{code: string, details: Object}>}}
|
[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
|
|
|
|
* @throws {ErreurCommande} le refus de la commande, ou SANS_EFFET
|
|
|
|
|
|
* @throws {TypeError} une commande inconnue, un argument de forme fausse
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function appliquerCommande(nom, charge, args = {}) {
|
|
|
|
|
|
const commande = COMMANDES.get(nom);
|
|
|
|
|
|
if (commande === undefined) throw new TypeError(`commande inconnue : ${JSON.stringify(nom)}`);
|
|
|
|
|
|
const rendu = commande(charge, args ?? {});
|
[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
|
|
|
|
if (rendu.libelle === null) return { ...rendu, avertissements: rendu.avertissements ?? [] };
|
[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
|
|
|
|
if (serialiserCharge(rendu.charge) === serialiserCharge(charge)) throw refus('SANS_EFFET', { commande: nom });
|
|
|
|
|
|
return { ...rendu, avertissements: rendu.avertissements ?? [] };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
[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
|
|
|
|
/**
|
|
|
|
|
|
* La forme nommée d'un placement de la charge (§ 8.9, point 4), pour la
|
|
|
|
|
|
* lecture et le signalement d'un défaut : une lecture, ni commande ni
|
|
|
|
|
|
* entrée. cible désigne { proposition: id } ou { retenu: true }. Rend
|
|
|
|
|
|
* { lignes, fautes } : fautes, celles que le placement porte (fautes) ;
|
|
|
|
|
|
* lignes, sa forme nommée (formeNommee), une ligne par identifiant écrit,
|
|
|
|
|
|
* ou null quand sa forme est fausse (faute FORME), qui ne se lit pas en
|
|
|
|
|
|
* lignes. Une proposition fautive n'est plus dans la charge qu'examiner
|
|
|
|
|
|
* rend ; le retenu y reste, fautif ou non. La charge n'est pas modifiée.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {Object} charge
|
|
|
|
|
|
* @param {{proposition: number}|{retenu: true}} cible
|
|
|
|
|
|
* @returns {{lignes: Array<Object>|null, fautes: Array<Object>}}
|
|
|
|
|
|
* @throws {ErreurCommande} PROPOSITION_INCONNUE {id} ; AUCUN_RETENU, remède
|
|
|
|
|
|
* la section des propositions
|
|
|
|
|
|
* @throws {TypeError} une cible d'une autre forme
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function formeNommeeDe(charge, cible) {
|
|
|
|
|
|
const cles = cible !== null && typeof cible === 'object' ? clesRangees(cible) : null;
|
|
|
|
|
|
let placement;
|
|
|
|
|
|
if (cles?.length === 1 && cles[0] === 'proposition' && Number.isSafeInteger(cible.proposition)) {
|
|
|
|
|
|
placement = charge.propositions.find(({ id }) => id === cible.proposition);
|
|
|
|
|
|
if (placement === undefined) throw refus('PROPOSITION_INCONNUE', { id: cible.proposition });
|
|
|
|
|
|
} else if (cles?.length === 1 && cles[0] === 'retenu' && cible.retenu === true) {
|
|
|
|
|
|
placement = charge.retenu;
|
|
|
|
|
|
if (placement === null) throw refus('AUCUN_RETENU', {}, { geste: 'ouvrirSection', section: 'propositions' });
|
|
|
|
|
|
} else {
|
|
|
|
|
|
throw new TypeError(`cible : {proposition: id} ou {retenu: true} attendu, reçu ${JSON.stringify(cible)}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
const lues = fautes(placement);
|
|
|
|
|
|
const lisible = !lues.some(({ code }) => code === 'FORME');
|
|
|
|
|
|
return { lignes: lisible ? formeNommee(placement, charge) : null, fautes: lues };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
[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
|
|
|
|
/**
|
|
|
|
|
|
* La charge d'un événement neuf (§ 8.1) et le libellé de sa création. Chaque
|
|
|
|
|
|
* champ se contrôle selon sa règle avant que l'identifiant ne se tire :
|
|
|
|
|
|
* NOM_REQUIS, DATE_INVALIDE {date}, SIEGES_INVALIDES {sieges},
|
|
|
|
|
|
* TOURS_INVALIDES {tours}. Le nom entre en NFC, ses blancs de bord retirés ;
|
|
|
|
|
|
* une date vide vaut null.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {{nom: string, date?: string|null, siegesParDefaut: number, tours: number}} evenement
|
|
|
|
|
|
* @param {() => string} identifiant tiré une fois, après les contrôles
|
|
|
|
|
|
* @returns {{charge: Object, libelle: string}}
|
|
|
|
|
|
* @throws {ErreurCommande}
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function chargeNeuve({ nom, date = null, siegesParDefaut, tours }, identifiant) {
|
|
|
|
|
|
const regle = (cle) => regleDuChamp(EVENEMENT, cle);
|
|
|
|
|
|
const nomLu = saisie(nom, 'nom');
|
|
|
|
|
|
const dateLue = saisie(date, 'date');
|
|
|
|
|
|
if (premiereFaute(nomLu, regle('nom')) !== null) throw refus('NOM_REQUIS');
|
|
|
|
|
|
if (premiereFaute(dateLue, regle('date')) !== null) throw refus('DATE_INVALIDE', { date });
|
|
|
|
|
|
if (premiereFaute(siegesParDefaut, regle('siegesParDefaut')) !== null) {
|
|
|
|
|
|
throw refus('SIEGES_INVALIDES', { sieges: siegesParDefaut });
|
|
|
|
|
|
}
|
|
|
|
|
|
if (premiereFaute(tours, regle('tours')) !== null) throw refus('TOURS_INVALIDES', { tours });
|
|
|
|
|
|
const charge = creerCharge({ id: identifiant(), nom: nomLu, date: dateLue, siegesParDefaut, tours });
|
|
|
|
|
|
return { charge, libelle: libelle('ENTREE_CREATION', { nom: nomLu }) };
|
|
|
|
|
|
}
|