[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)
|
|
|
|
|
|
|
|
|
|
// La table des textes de l'application (§ 14.6) : chaque code que lèvent ou
|
|
|
|
|
// rendent src/stockage, src/csv et src/application — refus, avertissements,
|
|
|
|
|
// raisons, causes et remèdes de leurs détails — et chaque libellé figé d'une
|
|
|
|
|
// entrée ou d'un jalon du journal a ici son texte français, qu'une fonction
|
|
|
|
|
// compose de ses détails. Un code sans texte lève : l'écran ne montre jamais
|
[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 code brut. Une valeur codée d'un détail — état, champ, mode, échec —
|
|
|
|
|
// que la table ne connaît pas lève aussi, au lieu de se taire. Une raison
|
|
|
|
|
// qu'un texte ne traite pas à part ne lève jamais : une phrase générique la
|
|
|
|
|
// nomme, par son texte quand la table en porte un, telle quelle sinon. Une
|
[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
|
|
|
// cause que nomme le système, EROFS, ENOENT ou TypeMismatchError, n'est pas
|
|
|
|
|
// un code de l'application : les textes la citent telle quelle, entre
|
|
|
|
|
// parenthèses ; une cause que la table porte s'y lit par son texte.
|
|
|
|
|
//
|
|
|
|
|
// Un libellé d'entrée se compose une fois, à l'écriture du geste, et le
|
|
|
|
|
// journal en garde le texte, jamais le code (§ 8.2) : changer un texte ici ne
|
|
|
|
|
// change aucun libellé déjà écrit.
|
|
|
|
|
//
|
|
|
|
|
// Les textes ne lisent ni l'horloge ni la langue du système : un horodatage
|
|
|
|
|
// se met en mots à partir de sa seule forme, AAAA-MM-JJTHH:MM:SS±HH:MM.
|
|
|
|
|
|
|
|
|
|
// Deux chaînes, comparées unité UTF-16 par unité.
|
|
|
|
|
const comparerTextes = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
|
|
|
|
|
|
|
|
const NOMS_DES_ETATS = new Map([
|
|
|
|
|
['brouillon', 'Brouillon'],
|
|
|
|
|
['propose', 'Proposé'],
|
|
|
|
|
['retenu', 'Retenu'],
|
|
|
|
|
['bloque', 'Bloqué'],
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// Les champs d'un participant qu'une saisie modifie, dans l'ordre du fichier.
|
|
|
|
|
const NOMS_DES_CHAMPS = new Map([
|
|
|
|
|
['nom', 'nom'],
|
|
|
|
|
['prenom', 'prénom'],
|
|
|
|
|
['appartenance', 'appartenance'],
|
|
|
|
|
['courriel', 'courriel'],
|
|
|
|
|
['titrePressenti', 'titre pressenti'],
|
|
|
|
|
['notes', 'notes'],
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
const MOIS = [
|
|
|
|
|
'janvier',
|
|
|
|
|
'février',
|
|
|
|
|
'mars',
|
|
|
|
|
'avril',
|
|
|
|
|
'mai',
|
|
|
|
|
'juin',
|
|
|
|
|
'juillet',
|
|
|
|
|
'août',
|
|
|
|
|
'septembre',
|
|
|
|
|
'octobre',
|
|
|
|
|
'novembre',
|
|
|
|
|
'décembre',
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// Valeur d'une table de noms, ou RangeError qui la nomme.
|
|
|
|
|
function nomDans(table, valeur, quoi) {
|
|
|
|
|
if (!table.has(valeur)) throw new RangeError(`libelle : ${quoi} inconnu ${JSON.stringify(valeur)}`);
|
|
|
|
|
return table.get(valeur);
|
|
|
|
|
}
|
|
|
|
|
const nomEtat = (etat) => nomDans(NOMS_DES_ETATS, etat, 'état');
|
|
|
|
|
const nomChamp = (champ) => nomDans(NOMS_DES_CHAMPS, champ, 'champ');
|
|
|
|
|
|
|
|
|
|
// Un texte cité entre guillemets français.
|
|
|
|
|
const cite = (texte) => `« ${texte} »`;
|
|
|
|
|
// Une cause entre parenthèses, rien quand elle manque : son texte quand la
|
|
|
|
|
// table en porte un, sinon la cause telle que le système la nomme.
|
|
|
|
|
function causeEntreParentheses(cause) {
|
|
|
|
|
if (cause === null || cause === undefined || cause === '') return '';
|
|
|
|
|
return ` (${TEXTES.has(cause) ? texteDe(cause) : cause})`;
|
|
|
|
|
}
|
|
|
|
|
// Le chemin de l'élément fautif, rien quand il manque ou désigne la racine.
|
|
|
|
|
const element = (chemin) => (typeof chemin === 'string' && chemin !== '' ? ` (${chemin})` : '');
|
|
|
|
|
// Un compte et son nom, au singulier jusqu'à un, comme le veut le français.
|
|
|
|
|
const compte = (n, singulier, pluriel) => `${n} ${n <= 1 ? singulier : pluriel}`;
|
|
|
|
|
// Une énumération : « a », « a et b », « a, b et c ».
|
|
|
|
|
function enumeration(termes) {
|
|
|
|
|
if (termes.length <= 1) return termes.join('');
|
|
|
|
|
return `${termes.slice(0, -1).join(', ')} et ${termes.at(-1)}`;
|
|
|
|
|
}
|
|
|
|
|
// Des numéros de proposition, un illisible nommé comme tel.
|
|
|
|
|
const numeros = (ids) => enumeration(ids.map((id) => (id === null ? 'sans numéro lisible' : `n° ${id}`)));
|
|
|
|
|
// La première lettre en capitale.
|
|
|
|
|
const capitale = (texte) => texte.charAt(0).toUpperCase() + texte.slice(1);
|
|
|
|
|
|
|
|
|
|
// Un horodatage AAAA-MM-JJTHH:MM:SS±HH:MM en mots, « le 17 mai 2031 à 8 h 05 »,
|
|
|
|
|
// à l'heure qu'il porte ; une autre forme se cite telle quelle.
|
|
|
|
|
const HORODATAGE = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):\d{2}[+-]\d{2}:\d{2}$/;
|
|
|
|
|
function moment(horodatage) {
|
|
|
|
|
const morceaux = HORODATAGE.exec(String(horodatage));
|
|
|
|
|
if (morceaux === null) return String(horodatage);
|
|
|
|
|
const [annee, mois, jour, heure, minute] = morceaux.slice(1).map(Number);
|
|
|
|
|
if (mois < 1 || mois > 12) return String(horodatage);
|
|
|
|
|
const quantieme = jour === 1 ? '1er' : String(jour);
|
|
|
|
|
return `le ${quantieme} ${MOIS[mois - 1]} ${annee} à ${heure} h ${String(minute).padStart(2, '0')}`;
|
|
|
|
|
}
|
|
|
|
|
const depuisQuand = (depuis) => (depuis === null || depuis === undefined ? '' : ` depuis ${moment(depuis)}`);
|
|
|
|
|
|
|
|
|
|
// Le chemin affichable d'une racine, reçue entière ou par son seul chemin.
|
|
|
|
|
const cheminDe = (racine) => (typeof racine === 'string' ? racine : racine?.chemin);
|
|
|
|
|
|
|
|
|
|
// Le remède commun aux refus d'encodage (§ 10.1).
|
|
|
|
|
const REMEDE_UTF8 = 'Réenregistrez le fichier en UTF-8 depuis le tableur, puis importez-le de nouveau.';
|
|
|
|
|
const refusEncodage = (constat) => () => `${constat} ${REMEDE_UTF8}`;
|
|
|
|
|
|
[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 raison codée en une phrase : son texte quand la table en porte un et
|
|
|
|
|
// que les détails reçus lui suffisent ; sinon une phrase générique qui la
|
|
|
|
|
// cite telle quelle. Un texte qui attend ses propres détails lève ou rend
|
|
|
|
|
// « undefined » sans eux : la phrase générique le remplace alors.
|
|
|
|
|
function phraseDeRaison(raison, details = {}) {
|
|
|
|
|
const generique = `La raison donnée est ${cite(String(raison))}.`;
|
|
|
|
|
if (!TEXTES.has(raison)) return generique;
|
|
|
|
|
try {
|
|
|
|
|
const texte = texteDe(raison, details);
|
|
|
|
|
return typeof texte === 'string' && !texte.includes('undefined') ? texte : generique;
|
|
|
|
|
} catch {
|
|
|
|
|
return generique;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
[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 raison d'un fichier d'état illisible, en une phrase.
|
|
|
|
|
function raisonIllisible({ raison, ...details }) {
|
|
|
|
|
if (raison === 'ABSENT') return "Le fichier d'état manque, à côté de son historique.";
|
[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
|
|
|
return phraseDeRaison(raison, details);
|
[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
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Les secours d'un fichier d'état illisible, quand l'erreur les nomme.
|
|
|
|
|
function secoursDe(secours) {
|
|
|
|
|
if (secours === undefined || secours === null) return '';
|
|
|
|
|
const offerts = [];
|
|
|
|
|
if (secours.precedent !== null) offerts.push(`la version précédente du fichier (révision ${secours.precedent})`);
|
|
|
|
|
if (secours.journal !== null) offerts.push(`le dernier instant de l'historique (révision ${secours.journal})`);
|
|
|
|
|
if (offerts.length === 0) return ' Aucune version de secours ne se lit.';
|
|
|
|
|
return ` ${capitale(enumeration(offerts))} ${offerts.length === 1 ? 'peut' : 'peuvent'} se rouvrir.`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ce qu'une dérive touche, du retenu aux propositions, et l'accord du verbe.
|
|
|
|
|
function touchees({ ids, retenu }) {
|
|
|
|
|
const parties = [];
|
|
|
|
|
if (retenu) parties.push('le placement retenu');
|
|
|
|
|
if (ids.length === 1) parties.push(`la proposition n° ${ids[0]}`);
|
|
|
|
|
if (ids.length > 1) parties.push(`les propositions n° ${enumeration(ids.map(String))}`);
|
|
|
|
|
return { texte: capitale(enumeration(parties)), pluriel: (retenu ? 1 : 0) + ids.length > 1 };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Les titres de place qu'un geste sur une personne touche, quand il y en a.
|
|
|
|
|
const titresDe = (n, singulier, pluriel) =>
|
|
|
|
|
n > 0 ? `, et ${compte(n, 'titre de place', 'titres de place')} ${n <= 1 ? singulier : pluriel}` : '';
|
|
|
|
|
|
|
|
|
|
// Ce qu'un refus d'écriture nomme : le fichier, son dossier, la cause.
|
|
|
|
|
const ouEcrire = ({ chemin, dossier, cause }) =>
|
|
|
|
|
`${cite(chemin)} dans le dossier ${cite(dossier)}${causeEntreParentheses(cause)}`;
|
|
|
|
|
|
|
|
|
|
const TEXTES = new Map([
|
|
|
|
|
// --- Le système de fichiers et le dépôt (src/stockage/types.js) ---------
|
|
|
|
|
[
|
|
|
|
|
'ABSENT',
|
|
|
|
|
({ chemin }) =>
|
|
|
|
|
typeof chemin === 'string' ? `Le fichier ${cite(chemin)} est introuvable.` : "Le fichier d'état manque.",
|
|
|
|
|
],
|
|
|
|
|
['ECRITURE', (details) => `L'écriture de ${ouEcrire(details)} a échoué ; le fichier d'avant reste intact.`],
|
|
|
|
|
['LECTURE', (details) => `La lecture de ${ouEcrire(details)} est refusée.`],
|
|
|
|
|
['EXISTE', ({ chemin }) => `Un fichier ${cite(chemin)} existe déjà.`],
|
|
|
|
|
['CHEMIN_REFUSE', ({ chemin }) => `Le chemin ${cite(String(chemin))} est refusé.`],
|
|
|
|
|
[
|
|
|
|
|
'ETAT_ILLISIBLE',
|
|
|
|
|
(details) => {
|
|
|
|
|
const sujet = typeof details.base === 'string' ? `Le fichier d'état de ${cite(details.base)}` : 'Le fichier';
|
|
|
|
|
return `${sujet} ne s'ouvre pas. ${raisonIllisible(details)}${secoursDe(details.secours)}`;
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
['CORRECTIF', ({ rang }) => `Un correctif de l'historique ne s'applique pas (opération ${rang}).`],
|
|
|
|
|
[
|
|
|
|
|
'CHEMIN_TROP_LONG',
|
|
|
|
|
({ racine }) =>
|
|
|
|
|
`Le dossier ${cite(cheminDe(racine))} est trop profond : aucun nom de fichier n'y tient ` +
|
|
|
|
|
'sous la longueur de chemin que Windows accepte.',
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'IDENTIFIANT_PRESENT',
|
|
|
|
|
({ base }) =>
|
|
|
|
|
`Cet événement est déjà dans ce dossier, sous ${cite(base)} : ` +
|
|
|
|
|
"l'y placer une seconde fois en ferait deux copies de même identifiant.",
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'VERROU_PRIS',
|
|
|
|
|
({ depuis, vivant }) => {
|
|
|
|
|
if (vivant === true) {
|
|
|
|
|
return `Une autre séance modifie cet événement${depuisQuand(depuis)} : il reste en lecture ici.`;
|
|
|
|
|
}
|
|
|
|
|
if (vivant === false) {
|
|
|
|
|
return (
|
|
|
|
|
`Une séance interrompue a laissé son verrou${depuisQuand(depuis)}, et il n'a pas pu se reprendre : ` +
|
|
|
|
|
"l'événement reste en lecture."
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return (
|
|
|
|
|
`Un verrou d'un autre poste, ou illisible, tient cet événement${depuisQuand(depuis)} : ` +
|
|
|
|
|
'il reste en lecture ici, et rien ne le reprend seul.'
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'LECTURE_SEULE',
|
|
|
|
|
({ raison }) => {
|
|
|
|
|
if (raison === 'BLOQUE') {
|
|
|
|
|
return `${texteDe('BLOQUE')} Rien ne peut changer ; « Débloquer » le rend de nouveau modifiable.`;
|
|
|
|
|
}
|
|
|
|
|
if (raison === 'FORMAT' || raison === 'FORMAT_PLUS_RECENT') {
|
|
|
|
|
return `${texteDe(raison)} Il s'ouvre en lecture seule, et aucun geste ne le modifie ici.`;
|
|
|
|
|
}
|
[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 (raison === 'ENCODAGE') {
|
|
|
|
|
return (
|
|
|
|
|
`Le fichier d'état a changé sur le disque. ${texteDe('ENCODAGE')} ` +
|
|
|
|
|
"L'événement reste en lecture seule ; rouvert par une version de secours, son premier geste " +
|
|
|
|
|
"range ce fichier dans la corbeille avant d'écrire l'état."
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return `${phraseDeRaison(raison)} L'événement reste en lecture seule, et aucun geste ne le modifie ici.`;
|
[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
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'ETAT_NON_ECRIT',
|
|
|
|
|
(details) => `Le geste est inscrit à l'historique, mais l'état ${ouEcrire(details)} n'a pas pu s'écrire.`,
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'CORBEILLE_SATUREE',
|
|
|
|
|
({ dossier }) =>
|
|
|
|
|
`La corbeille ${cite(dossier)} a reçu trop de suppressions dans la même seconde : ` +
|
|
|
|
|
'la suppression se refait une seconde plus tard.',
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'COMPTEUR_SATURE',
|
|
|
|
|
({ compteur }) =>
|
|
|
|
|
`Les numéros ${compteur === 'proposition' ? 'de proposition' : `du compteur ${cite(compteur)}`} sont épuisés : ` +
|
|
|
|
|
'rien de neuf ne se numérote plus dans cet événement.',
|
|
|
|
|
],
|
|
|
|
|
['NON_DISPONIBLE', ({ cause }) => `Cette commande n'est pas offerte ici${causeEntreParentheses(cause)}.`],
|
|
|
|
|
|
|
|
|
|
// --- Les raisons d'un fichier illisible ----------------------------------
|
|
|
|
|
['VIDE', () => 'Le fichier est vide.'],
|
|
|
|
|
['JSON', () => "Le texte n'est pas du JSON valide."],
|
|
|
|
|
['FORME', ({ chemin }) => `Une valeur sort de sa règle${element(chemin)}.`],
|
|
|
|
|
['COMPTES', ({ chemin }) => `Un compte de l'en-tête contredit sa liste${element(chemin)}.`],
|
|
|
|
|
['REFERENCE', ({ chemin }) => `Une réservation ou un titre désigne ce qui n'existe pas${element(chemin)}.`],
|
|
|
|
|
['FORMAT_INCONNU', () => "Le format du fichier n'est pas reconnu."],
|
|
|
|
|
[
|
|
|
|
|
'FORMAT_PLUS_RECENT',
|
|
|
|
|
({ format }) => {
|
|
|
|
|
const quel = Number.isInteger(format) ? ` (format ${format})` : '';
|
|
|
|
|
return `Ce fichier vient d'une version plus récente du logiciel${quel}.`;
|
|
|
|
|
},
|
|
|
|
|
],
|
[FIX] storage, session: strict UTF-8, lost journal, lock, exclusions
The whole-iteration review found ways to lose or corrupt data. A state
saved in another encoding opened with its accents turned to U+FFFD and was
then overwritten; decoding is now strict, the state opens read-only, and a
gesture from its secours first moves it to the trash. A journal decodes line
by line, so one bad byte drops only the lines after it. A vanished journal
is rewritten whole, never appended to. Deleting refuses an event another
session holds; switching to writing rereads the disk; an import that
changes exclu says which reservations it suspends.
Checked: 1504 node, 35 browser, 29 node-long; depot.js, journal.js 100 %.
--- FR ---
[FIX] stockage, séance : UTF-8 strict, journal perdu, verrou, exclusions
La revue d'ensemble a trouvé des façons de perdre ou d'abîmer des données.
Un état enregistré dans un autre encodage s'ouvrait, ses accents changés en
U+FFFD, puis s'écrasait ; le décodage est strict, l'état s'ouvre en lecture
seule, et un geste depuis son secours le range d'abord à la corbeille. Le
journal se décode ligne par ligne : un octet fautif n'écarte que la suite.
Un journal disparu se réécrit en entier, jamais par ajout. Supprimer refuse
un événement qu'une autre séance tient ; passer en écriture relit le disque ;
un import qui change exclu dit quelles réservations il suspend.
Vérifié : 1504 node, 35 navigateur, 29 node-long ; depot.js, journal.js 100 %.
Assisted-by: Claude Opus 5.5
2026-10-07 01:20:42 -04:00
|
|
|
[
|
|
|
|
|
'ENCODAGE',
|
|
|
|
|
() => "Ses octets ne sont pas de l'UTF-8 : un éditeur l'a sans doute réenregistré dans un autre encodage.",
|
|
|
|
|
],
|
[ADD] application: session, named commands, French label table
The session holds one event, opens it read-only and writes only after
"Modifier" takes the lock (§ 8.4, § 8.8); calls run one after another, so a
double click writes two entries in order. Every change is a pure named
command with a frozen French label (§ 8.2); undo, redo and return write
entries and never truncate the journal (§ 8.3). A failed write — a gesture,
or the lock on a removed key — breaks the medium until the event is written
elsewhere (§ 8.6). The label table covers every code storage, CSV and the
session emit, and its test sweeps the sources so a new code needs a text.
Checked: 1416 node tests; depot.js at 100 % with the identifier lister keeps.
--- FR ---
[ADD] application : séance, commandes nommées, table des libellés
La séance tient un événement, l'ouvre en lecture et n'écrit qu'après que
« Modifier » a pris le verrou (§ 8.4, § 8.8) ; les appels passent l'un après
l'autre : un double clic écrit deux entrées, dans l'ordre. Toute modification
est une commande nommée pure au libellé français figé (§ 8.2) ; défaire,
refaire et revenir écrivent des entrées sans tronquer le journal (§ 8.3). Une
écriture refusée — un geste, ou le verrou sur une clé retirée — rompt le
support jusqu'à l'écriture ailleurs (§ 8.6). La table des libellés couvre
chaque code du stockage, du CSV et de la séance ; son épreuve balaie les
sources, et un code neuf exige un texte.
Vérifié : 1416 épreuves node ; depot.js à 100 %, lister gardant l'identifiant.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 23:17:36 -04:00
|
|
|
|
|
|
|
|
// --- Les fautes d'un placement, et ses raisons de dérive (§ 8.9, § 9) ----
|
|
|
|
|
[
|
|
|
|
|
'LONGUEUR',
|
|
|
|
|
({ tour, table, declare, ecrit }) => {
|
|
|
|
|
if (tour === null) return `Le placement déclare ${compte(declare, 'tour', 'tours')} et en porte ${ecrit}.`;
|
|
|
|
|
if (table === null) {
|
|
|
|
|
return `Au tour ${tour}, le placement déclare ${compte(declare, 'table', 'tables')} et en porte ${ecrit}.`;
|
|
|
|
|
}
|
|
|
|
|
return (
|
|
|
|
|
`Au tour ${tour}, la table ${table} reçoit ${compte(ecrit, 'personne', 'personnes')} ` +
|
|
|
|
|
`pour ${compte(declare, 'place', 'places')}.`
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
['DOUBLON', ({ tour, participant }) => `Au tour ${tour}, la personne n° ${participant} est placée plus d'une fois.`],
|
|
|
|
|
[
|
|
|
|
|
'INCONNU',
|
|
|
|
|
({ tour, participant }) => `Au tour ${tour}, la personne n° ${participant} est placée sans être déclarée.`,
|
|
|
|
|
],
|
|
|
|
|
['MANQUANT', ({ tour, participant }) => `Au tour ${tour}, la personne n° ${participant} n'est placée nulle part.`],
|
|
|
|
|
['IDENTIFIANT_REPETE', () => 'Une autre proposition porte déjà ce numéro.'],
|
|
|
|
|
['IDENTIFIANT_HORS_COMPTEUR', () => 'Ce numéro de proposition dépasse le compteur du fichier.'],
|
|
|
|
|
['PARTICIPANT_EXCLU', ({ participant }) => `La personne n° ${participant} est exclue depuis.`],
|
|
|
|
|
['PARTICIPANT_SUPPRIME', ({ participant }) => `La personne n° ${participant} a été supprimée depuis.`],
|
|
|
|
|
[
|
|
|
|
|
'PARTICIPANT_NON_PLACE',
|
|
|
|
|
({ participant }) => `La personne n° ${participant}, ajoutée ou réintégrée depuis, n'y est pas placée.`,
|
|
|
|
|
],
|
|
|
|
|
['TABLE_SUPPRIMEE', ({ table }) => `La table d'identifiant ${table} a été supprimée depuis.`],
|
|
|
|
|
['TABLE_AJOUTEE', ({ table }) => `La table d'identifiant ${table} a été ajoutée depuis.`],
|
|
|
|
|
[
|
|
|
|
|
'CAPACITE_CHANGEE',
|
|
|
|
|
({ table, avant, maintenant }) =>
|
|
|
|
|
`La table d'identifiant ${table} est passée de ${avant} à ${compte(maintenant, 'place', 'places')}.`,
|
|
|
|
|
],
|
|
|
|
|
['TOURS_CHANGES', ({ avant, maintenant }) => `Le nombre de tours est passé de ${avant} à ${maintenant}.`],
|
|
|
|
|
|
|
|
|
|
// --- Ce que l'ouverture annonce (§ 8.6, § 8.8, § 8.9, § 9) ---------------
|
|
|
|
|
['RETENU_FAUTIF', () => 'Le placement retenu se contredit : la version précédente du fichier peut le reprendre.'],
|
|
|
|
|
['JOURNAL_REPRIS', () => "Le dernier geste, inscrit à l'historique mais pas dans l'état, est repris."],
|
|
|
|
|
[
|
|
|
|
|
'JOURNAL_EN_AVANCE_DE',
|
|
|
|
|
({ n }) =>
|
|
|
|
|
`L'historique compte ${compte(n, 'geste', 'gestes')} de plus que l'état : ` +
|
|
|
|
|
"l'état du disque est affiché, et le prochain geste inscrit une reprise.",
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'JOURNAL_INCOMPLET',
|
|
|
|
|
({ etat, journal }) =>
|
|
|
|
|
`L'historique s'arrête à la révision ${journal}, l'état est à la révision ${etat} : ` +
|
|
|
|
|
'le prochain geste inscrit une reprise.',
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'JOURNAL_ECARTEES',
|
|
|
|
|
({ n }) =>
|
|
|
|
|
`${compte(n, 'entrée', 'entrées')} de l'historique ${n <= 1 ? 'est écartée' : 'sont écartées'}, ` +
|
|
|
|
|
'à partir de la première ligne illisible.',
|
|
|
|
|
],
|
[FIX] storage, session: strict UTF-8, lost journal, lock, exclusions
The whole-iteration review found ways to lose or corrupt data. A state
saved in another encoding opened with its accents turned to U+FFFD and was
then overwritten; decoding is now strict, the state opens read-only, and a
gesture from its secours first moves it to the trash. A journal decodes line
by line, so one bad byte drops only the lines after it. A vanished journal
is rewritten whole, never appended to. Deleting refuses an event another
session holds; switching to writing rereads the disk; an import that
changes exclu says which reservations it suspends.
Checked: 1504 node, 35 browser, 29 node-long; depot.js, journal.js 100 %.
--- FR ---
[FIX] stockage, séance : UTF-8 strict, journal perdu, verrou, exclusions
La revue d'ensemble a trouvé des façons de perdre ou d'abîmer des données.
Un état enregistré dans un autre encodage s'ouvrait, ses accents changés en
U+FFFD, puis s'écrasait ; le décodage est strict, l'état s'ouvre en lecture
seule, et un geste depuis son secours le range d'abord à la corbeille. Le
journal se décode ligne par ligne : un octet fautif n'écarte que la suite.
Un journal disparu se réécrit en entier, jamais par ajout. Supprimer refuse
un événement qu'une autre séance tient ; passer en écriture relit le disque ;
un import qui change exclu dit quelles réservations il suspend.
Vérifié : 1504 node, 35 navigateur, 29 node-long ; depot.js, journal.js 100 %.
Assisted-by: Claude Opus 5.5
2026-10-07 01:20:42 -04:00
|
|
|
[
|
|
|
|
|
'JOURNAL_RECREE',
|
|
|
|
|
() =>
|
|
|
|
|
"Le fichier de l'historique avait disparu du dossier : il est réécrit en entier, " +
|
|
|
|
|
'tel que la séance le connaissait.',
|
|
|
|
|
],
|
[ADD] application: session, named commands, French label table
The session holds one event, opens it read-only and writes only after
"Modifier" takes the lock (§ 8.4, § 8.8); calls run one after another, so a
double click writes two entries in order. Every change is a pure named
command with a frozen French label (§ 8.2); undo, redo and return write
entries and never truncate the journal (§ 8.3). A failed write — a gesture,
or the lock on a removed key — breaks the medium until the event is written
elsewhere (§ 8.6). The label table covers every code storage, CSV and the
session emit, and its test sweeps the sources so a new code needs a text.
Checked: 1416 node tests; depot.js at 100 % with the identifier lister keeps.
--- FR ---
[ADD] application : séance, commandes nommées, table des libellés
La séance tient un événement, l'ouvre en lecture et n'écrit qu'après que
« Modifier » a pris le verrou (§ 8.4, § 8.8) ; les appels passent l'un après
l'autre : un double clic écrit deux entrées, dans l'ordre. Toute modification
est une commande nommée pure au libellé français figé (§ 8.2) ; défaire,
refaire et revenir écrivent des entrées sans tronquer le journal (§ 8.3). Une
écriture refusée — un geste, ou le verrou sur une clé retirée — rompt le
support jusqu'à l'écriture ailleurs (§ 8.6). La table des libellés couvre
chaque code du stockage, du CSV et de la séance ; son épreuve balaie les
sources, et un code neuf exige un texte.
Vérifié : 1416 épreuves node ; depot.js à 100 %, lister gardant l'identifiant.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 23:17:36 -04:00
|
|
|
[
|
|
|
|
|
'JOURNAL_FORMAT_PLUS_RECENT',
|
|
|
|
|
({ format }) =>
|
|
|
|
|
`L'historique vient d'une version plus récente du logiciel (format ${format}) : ` +
|
|
|
|
|
"il ne se lit pas, et l'événement reste en lecture seule.",
|
|
|
|
|
],
|
[FIX] storage, session: strict UTF-8, lost journal, lock, exclusions
The whole-iteration review found ways to lose or corrupt data. A state
saved in another encoding opened with its accents turned to U+FFFD and was
then overwritten; decoding is now strict, the state opens read-only, and a
gesture from its secours first moves it to the trash. A journal decodes line
by line, so one bad byte drops only the lines after it. A vanished journal
is rewritten whole, never appended to. Deleting refuses an event another
session holds; switching to writing rereads the disk; an import that
changes exclu says which reservations it suspends.
Checked: 1504 node, 35 browser, 29 node-long; depot.js, journal.js 100 %.
--- FR ---
[FIX] stockage, séance : UTF-8 strict, journal perdu, verrou, exclusions
La revue d'ensemble a trouvé des façons de perdre ou d'abîmer des données.
Un état enregistré dans un autre encodage s'ouvrait, ses accents changés en
U+FFFD, puis s'écrasait ; le décodage est strict, l'état s'ouvre en lecture
seule, et un geste depuis son secours le range d'abord à la corbeille. Le
journal se décode ligne par ligne : un octet fautif n'écarte que la suite.
Un journal disparu se réécrit en entier, jamais par ajout. Supprimer refuse
un événement qu'une autre séance tient ; passer en écriture relit le disque ;
un import qui change exclu dit quelles réservations il suspend.
Vérifié : 1504 node, 35 navigateur, 29 node-long ; depot.js, journal.js 100 %.
Assisted-by: Claude Opus 5.5
2026-10-07 01:20:42 -04:00
|
|
|
[
|
|
|
|
|
'JOURNAL_ILLISIBLE',
|
|
|
|
|
({ raison }) =>
|
|
|
|
|
`L'historique à côté de ce fichier ne se lit pas. ${phraseDeRaison(raison)} ` +
|
|
|
|
|
"Il est laissé tel quel, et le prochain geste commence l'historique dans un nouveau fichier.",
|
|
|
|
|
],
|
[ADD] application: session, named commands, French label table
The session holds one event, opens it read-only and writes only after
"Modifier" takes the lock (§ 8.4, § 8.8); calls run one after another, so a
double click writes two entries in order. Every change is a pure named
command with a frozen French label (§ 8.2); undo, redo and return write
entries and never truncate the journal (§ 8.3). A failed write — a gesture,
or the lock on a removed key — breaks the medium until the event is written
elsewhere (§ 8.6). The label table covers every code storage, CSV and the
session emit, and its test sweeps the sources so a new code needs a text.
Checked: 1416 node tests; depot.js at 100 % with the identifier lister keeps.
--- FR ---
[ADD] application : séance, commandes nommées, table des libellés
La séance tient un événement, l'ouvre en lecture et n'écrit qu'après que
« Modifier » a pris le verrou (§ 8.4, § 8.8) ; les appels passent l'un après
l'autre : un double clic écrit deux entrées, dans l'ordre. Toute modification
est une commande nommée pure au libellé français figé (§ 8.2) ; défaire,
refaire et revenir écrivent des entrées sans tronquer le journal (§ 8.3). Une
écriture refusée — un geste, ou le verrou sur une clé retirée — rompt le
support jusqu'à l'écriture ailleurs (§ 8.6). La table des libellés couvre
chaque code du stockage, du CSV et de la séance ; son épreuve balaie les
sources, et un code neuf exige un texte.
Vérifié : 1416 épreuves node ; depot.js à 100 %, lister gardant l'identifiant.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 23:17:36 -04:00
|
|
|
[
|
|
|
|
|
'JOURNAL_ETRANGER',
|
|
|
|
|
({ evenement }) =>
|
|
|
|
|
`L'historique à côté de ce fichier appartient à un autre événement (${evenement}) : il est ignoré.`,
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'PROPOSITIONS_FAUTIVES',
|
|
|
|
|
({ ids }) =>
|
|
|
|
|
ids.length <= 1
|
|
|
|
|
? `Une proposition incohérente est écartée (${numeros(ids)}).`
|
|
|
|
|
: `${ids.length} propositions incohérentes sont écartées (${numeros(ids)}).`,
|
|
|
|
|
],
|
[FIX] storage, session: strict UTF-8, lost journal, lock, exclusions
The whole-iteration review found ways to lose or corrupt data. A state
saved in another encoding opened with its accents turned to U+FFFD and was
then overwritten; decoding is now strict, the state opens read-only, and a
gesture from its secours first moves it to the trash. A journal decodes line
by line, so one bad byte drops only the lines after it. A vanished journal
is rewritten whole, never appended to. Deleting refuses an event another
session holds; switching to writing rereads the disk; an import that
changes exclu says which reservations it suspends.
Checked: 1504 node, 35 browser, 29 node-long; depot.js, journal.js 100 %.
--- FR ---
[FIX] stockage, séance : UTF-8 strict, journal perdu, verrou, exclusions
La revue d'ensemble a trouvé des façons de perdre ou d'abîmer des données.
Un état enregistré dans un autre encodage s'ouvrait, ses accents changés en
U+FFFD, puis s'écrasait ; le décodage est strict, l'état s'ouvre en lecture
seule, et un geste depuis son secours le range d'abord à la corbeille. Le
journal se décode ligne par ligne : un octet fautif n'écarte que la suite.
Un journal disparu se réécrit en entier, jamais par ajout. Supprimer refuse
un événement qu'une autre séance tient ; passer en écriture relit le disque ;
un import qui change exclu dit quelles réservations il suspend.
Vérifié : 1504 node, 35 navigateur, 29 node-long ; depot.js, journal.js 100 %.
Assisted-by: Claude Opus 5.5
2026-10-07 01:20:42 -04:00
|
|
|
[
|
|
|
|
|
'PROPOSITIONS_MANQUANTES',
|
|
|
|
|
({ n }) =>
|
|
|
|
|
`${compte(n, "proposition que l'en-tête annonce manque", "propositions que l'en-tête annonce manquent")} ` +
|
|
|
|
|
'dans le fichier : celles qu\'il porte sont gardées.',
|
|
|
|
|
],
|
[ADD] application: session, named commands, French label table
The session holds one event, opens it read-only and writes only after
"Modifier" takes the lock (§ 8.4, § 8.8); calls run one after another, so a
double click writes two entries in order. Every change is a pure named
command with a frozen French label (§ 8.2); undo, redo and return write
entries and never truncate the journal (§ 8.3). A failed write — a gesture,
or the lock on a removed key — breaks the medium until the event is written
elsewhere (§ 8.6). The label table covers every code storage, CSV and the
session emit, and its test sweeps the sources so a new code needs a text.
Checked: 1416 node tests; depot.js at 100 % with the identifier lister keeps.
--- FR ---
[ADD] application : séance, commandes nommées, table des libellés
La séance tient un événement, l'ouvre en lecture et n'écrit qu'après que
« Modifier » a pris le verrou (§ 8.4, § 8.8) ; les appels passent l'un après
l'autre : un double clic écrit deux entrées, dans l'ordre. Toute modification
est une commande nommée pure au libellé français figé (§ 8.2) ; défaire,
refaire et revenir écrivent des entrées sans tronquer le journal (§ 8.3). Une
écriture refusée — un geste, ou le verrou sur une clé retirée — rompt le
support jusqu'à l'écriture ailleurs (§ 8.6). La table des libellés couvre
chaque code du stockage, du CSV et de la séance ; son épreuve balaie les
sources, et un code neuf exige un texte.
Vérifié : 1416 épreuves node ; depot.js à 100 %, lister gardant l'identifiant.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 23:17:36 -04:00
|
|
|
[
|
|
|
|
|
'DERIVE',
|
|
|
|
|
(details) => {
|
|
|
|
|
const { texte, pluriel } = touchees(details);
|
|
|
|
|
return pluriel
|
|
|
|
|
? `${texte} ne décrivent plus la liste ou les tables actuelles : ils sont gardés, et rien n'est effacé.`
|
|
|
|
|
: `${texte} ne décrit plus la liste ou les tables actuelles : il est gardé, et rien n'est effacé.`;
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
|
|
|
|
|
// --- Le dossier de travail, les réglages locaux (§ 8.5, § 8.6, § 8.8) ----
|
|
|
|
|
['NON_PUBLIE', () => "Le dossier de l'exécutable n'est pas connu : les événements se rangent dans les Documents."],
|
|
|
|
|
[
|
|
|
|
|
'DONNEES_APPLICATIVES',
|
|
|
|
|
() => "L'exécutable est rangé sous les données applicatives : les événements se rangent dans les Documents.",
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'SONDE_ECHOUEE',
|
|
|
|
|
({ cause }) =>
|
|
|
|
|
`Le dossier data à côté de l'exécutable refuse l'écriture${causeEntreParentheses(cause)} : ` +
|
|
|
|
|
'les événements se rangent dans les Documents.',
|
|
|
|
|
],
|
|
|
|
|
// Une cause de la sonde, que les textes citent entre parenthèses.
|
|
|
|
|
['RELECTURE_DIFFERENTE', () => "le fichier d'essai relu ne porte pas ce qui vient d'y être écrit"],
|
|
|
|
|
[
|
|
|
|
|
'PORTABLE_NON_INSCRIPTIBLE',
|
|
|
|
|
({ racine, evenements, cause }) => {
|
|
|
|
|
const porte =
|
|
|
|
|
evenements === null
|
|
|
|
|
? 'porte peut-être des événements'
|
|
|
|
|
: `porte ${compte(evenements, 'événement', 'événements')}`;
|
|
|
|
|
return `Le dossier ${cite(cheminDe(racine))} ${porte}, mais refuse l'écriture${causeEntreParentheses(cause)}.`;
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'DOCUMENTS_NON_INSCRIPTIBLE',
|
|
|
|
|
({ racine, cause }) =>
|
|
|
|
|
`Le dossier de travail ${cite(cheminDe(racine))} refuse l'écriture${causeEntreParentheses(cause)} : ` +
|
|
|
|
|
"aucun geste ne s'y enregistrerait.",
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'PLATEFORME_WEB',
|
|
|
|
|
() =>
|
|
|
|
|
"Sous la plateforme web, ni le renommage atomique ni le verrou n'existent : " +
|
|
|
|
|
'une panne peut laisser un fichier incomplet, et deux onglets peuvent écrire le même événement.',
|
|
|
|
|
],
|
[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
|
|
|
// dossier, quand il est donné, nomme le dossier où la séance écrit
|
|
|
|
|
// désormais (ecrireAilleurs) ; sans lui, le dossier de travail.
|
[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
|
|
|
[
|
|
|
|
|
'SUPPORT_AMOVIBLE',
|
[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
|
|
|
({ dossier }) =>
|
|
|
|
|
`${typeof dossier === 'string' ? `Le dossier ${cite(dossier)}` : 'Le dossier de travail'} ` +
|
|
|
|
|
'est sur un support amovible : retirer le support pendant une écriture peut la laisser incomplète.',
|
[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
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'REGLAGES_ILLISIBLES',
|
|
|
|
|
({ chemin, raison }) => {
|
|
|
|
|
const pourquoi =
|
|
|
|
|
raison === 'JSON' || raison === 'FORME' ? texteDe(raison) : `La lecture est refusée (${raison}).`;
|
|
|
|
|
return `Les réglages locaux ${cite(chemin)} ne se lisent pas. ${pourquoi} Les valeurs par défaut s'appliquent.`;
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
|
|
|
|
|
// --- Le CSV (§ 10.1) -------------------------------------------------------
|
|
|
|
|
['UTF16_SANS_MARQUE', refusEncodage("Le fichier semble encodé en UTF-16, sans la marque qui l'annonce.")],
|
|
|
|
|
['UTF16_INVALIDE', refusEncodage("Le fichier s'annonce en UTF-16, mais ses octets ne le sont pas.")],
|
|
|
|
|
['UTF8_INVALIDE', refusEncodage("Le fichier s'annonce en UTF-8, mais ses octets ne le sont pas.")],
|
|
|
|
|
['CARACTERE_NUL', refusEncodage("Le texte porte un caractère nul, qu'aucune liste ne contient.")],
|
|
|
|
|
['ENREGISTRER_EN_UTF8', () => REMEDE_UTF8],
|
|
|
|
|
[
|
|
|
|
|
'SEPARATEUR_INTROUVABLE',
|
|
|
|
|
({ ligne, cause }) => {
|
|
|
|
|
const constat = 'Aucun séparateur ne convient : ni le point-virgule, ni la virgule, ni la tabulation.';
|
|
|
|
|
if (ligne === null || ligne === undefined) return constat;
|
|
|
|
|
const ecart = texteDe(cause === 'GUILLEMET_OUVERT' ? 'GUILLEMET_OUVERT' : 'NOMBRE_DE_CHAMPS', { ligne });
|
|
|
|
|
return `${constat} ${ecart}`;
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
['NOMBRE_DE_CHAMPS', ({ ligne }) => `Avec chacun, la ligne ${ligne} n'a pas le nombre de champs de l'en-tête.`],
|
|
|
|
|
[
|
|
|
|
|
'GUILLEMET_OUVERT',
|
|
|
|
|
({ ligne }) =>
|
|
|
|
|
`Un guillemet ouvert${Number.isInteger(ligne) ? ` à la ligne ${ligne}` : ''} ne se referme pas : ` +
|
|
|
|
|
'tout ce qui le suit tiendrait dans un seul champ.',
|
|
|
|
|
],
|
|
|
|
|
['AUCUN_ENTETE', () => "Aucun en-tête n'est reconnu : la première ligne nomme les colonnes, « nom » au moins."],
|
|
|
|
|
['NOM_NON_ASSOCIE', () => "Aucune colonne ne porte le nom : l'en-tête doit compter une colonne « nom »."],
|
|
|
|
|
['AUCUNE_LIGNE_VALIDE', () => "Aucune ligne n'est valide : rien n'est importé."],
|
|
|
|
|
['CHAMPS_EN_TROP', () => "La ligne compte plus de champs que l'en-tête."],
|
|
|
|
|
['NOM_ABSENT', () => 'Le nom manque.'],
|
|
|
|
|
[
|
|
|
|
|
'EXCLU_INCONNU',
|
|
|
|
|
({ valeur }) =>
|
|
|
|
|
`La valeur d'« exclu » ${cite(valeur)} n'est pas reconnue : ` +
|
|
|
|
|
'oui, o, vrai, 1 ou x pour exclure ; non, n, faux, 0 ou vide sinon.',
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'PLUSIEURS_CORRESPONDENT',
|
|
|
|
|
({ participants }) =>
|
|
|
|
|
'Plusieurs personnes de la liste correspondent à cette ligne ' +
|
|
|
|
|
`(${enumeration(participants.map((id) => `n° ${id}`))}) : rien n'est fusionné.`,
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'DEJA_DESIGNE',
|
|
|
|
|
({ premiereLigne }) => `La ligne ${premiereLigne} désigne déjà cette personne : rien n'est fusionné.`,
|
|
|
|
|
],
|
|
|
|
|
['PLAN_BLOQUE', () => 'Le plan est bloqué : « Débloquer » le rend de nouveau modifiable.'],
|
|
|
|
|
[
|
|
|
|
|
'AMBIGUITE',
|
|
|
|
|
({ champs }) =>
|
|
|
|
|
`Plusieurs colonnes répondent au même champ (${enumeration(champs)}) : choisissez celle qui s'importe.`,
|
|
|
|
|
],
|
|
|
|
|
|
|
|
|
|
// --- La séance (§ 8.3, § 8.4, § 8.5, § 8.6, § 8.8) -------------------------
|
|
|
|
|
['AUCUN_EVENEMENT', () => "Aucun événement n'est ouvert."],
|
|
|
|
|
['MODE_LECTURE', () => "L'événement est ouvert en lecture : « Modifier » permet de le changer."],
|
|
|
|
|
['FORMAT', (details) => texteDe('FORMAT_PLUS_RECENT', details)],
|
|
|
|
|
['BLOQUE', () => 'Le plan est bloqué.'],
|
|
|
|
|
// echec nomme ce que le support n'a pas reçu : la ligne d'un geste
|
|
|
|
|
// (ECRITURE), l'état d'un geste inscrit au journal (ETAT_NON_ECRIT), le
|
|
|
|
|
// jalon d'un geste enregistré (JALON_NON_ECRIT), ou le verrou d'un passage
|
|
|
|
|
// en écriture (VERROU_NON_ECRIT), qui ne perd rien.
|
|
|
|
|
[
|
|
|
|
|
'SUPPORT_ROMPU',
|
|
|
|
|
({ echec, ...details }) => {
|
|
|
|
|
const refus = `Le support ne prend plus les écritures : ${ouEcrire(details)} n'a pas pu s'écrire`;
|
|
|
|
|
const ailleurs = "« Écrire ailleurs » enregistre l'événement dans un autre dossier.";
|
|
|
|
|
if (echec === 'ECRITURE' || echec === 'ETAT_NON_ECRIT') {
|
|
|
|
|
const perte =
|
|
|
|
|
echec === 'ETAT_NON_ECRIT'
|
|
|
|
|
? "le dernier geste est inscrit à l'historique, mais pas dans l'état"
|
|
|
|
|
: "le dernier geste n'est pas enregistré";
|
|
|
|
|
return `${refus}, et ${perte}. Sur ce support, l'historique et l'état ne se correspondent plus ; ${ailleurs}`;
|
|
|
|
|
}
|
|
|
|
|
if (echec === 'JALON_NON_ECRIT') {
|
|
|
|
|
return (
|
|
|
|
|
`${refus}. Le dernier geste est enregistré, mais pas le jalon qui le nomme, ` +
|
|
|
|
|
`et aucun geste ne s'y enregistrerait plus ; ${ailleurs}`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
if (echec === 'VERROU_NON_ECRIT') {
|
|
|
|
|
return (
|
|
|
|
|
`${refus}. L'événement y reste tel qu'il s'affiche, ` +
|
|
|
|
|
`mais aucun geste ne s'y enregistrerait ; ${ailleurs}`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
throw new RangeError(`libelle : échec du support inconnu ${JSON.stringify(echec)}`);
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
['SUPPORT_INTACT', () => "Le support prend encore les écritures : l'événement reste où il est."],
|
[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
|
|
|
// Quitter l'événement d'un support rompu : la séance tient la seule copie
|
|
|
|
|
// que l'écriture ailleurs peut encore sauver ; ETAT_NON_ECRIT dit que son
|
|
|
|
|
// dernier geste n'est qu'à l'historique de ce support.
|
|
|
|
|
[
|
|
|
|
|
'SUPPORT_ROMPU_NON_SAUVE',
|
|
|
|
|
({ echec, dossier }) => {
|
|
|
|
|
const geste =
|
|
|
|
|
echec === 'ETAT_NON_ECRIT' ? ", et son dernier geste n'y est inscrit qu'à l'historique" : '';
|
|
|
|
|
return (
|
|
|
|
|
`Le support ${cite(dossier)} ne prend plus les écritures${geste} : ` +
|
|
|
|
|
"l'événement ouvert n'est sauvé dans aucun autre dossier. " +
|
|
|
|
|
"« Écrire ailleurs » l'enregistre d'abord ; le quitter maintenant l'abandonne à ce support."
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'EVENEMENT_CHANGE',
|
|
|
|
|
({ revision }) =>
|
|
|
|
|
`Une autre séance a modifié cet événement depuis son ouverture : il s'affiche tel qu'elle l'a laissé, ` +
|
|
|
|
|
`à la révision ${revision}, et les gestes suivants s'y ajoutent.`,
|
|
|
|
|
],
|
[ADD] application: session, named commands, French label table
The session holds one event, opens it read-only and writes only after
"Modifier" takes the lock (§ 8.4, § 8.8); calls run one after another, so a
double click writes two entries in order. Every change is a pure named
command with a frozen French label (§ 8.2); undo, redo and return write
entries and never truncate the journal (§ 8.3). A failed write — a gesture,
or the lock on a removed key — breaks the medium until the event is written
elsewhere (§ 8.6). The label table covers every code storage, CSV and the
session emit, and its test sweeps the sources so a new code needs a text.
Checked: 1416 node tests; depot.js at 100 % with the identifier lister keeps.
--- FR ---
[ADD] application : séance, commandes nommées, table des libellés
La séance tient un événement, l'ouvre en lecture et n'écrit qu'après que
« Modifier » a pris le verrou (§ 8.4, § 8.8) ; les appels passent l'un après
l'autre : un double clic écrit deux entrées, dans l'ordre. Toute modification
est une commande nommée pure au libellé français figé (§ 8.2) ; défaire,
refaire et revenir écrivent des entrées sans tronquer le journal (§ 8.3). Une
écriture refusée — un geste, ou le verrou sur une clé retirée — rompt le
support jusqu'à l'écriture ailleurs (§ 8.6). La table des libellés couvre
chaque code du stockage, du CSV et de la séance ; son épreuve balaie les
sources, et un code neuf exige un texte.
Vérifié : 1416 épreuves node ; depot.js à 100 %, lister gardant l'identifiant.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 23:17:36 -04:00
|
|
|
[
|
|
|
|
|
'VERROU_REPRIS',
|
|
|
|
|
({ depuis }) => `Le verrou qu'une séance interrompue de ce poste avait laissé${depuisQuand(depuis)} est repris.`,
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'VERROU_NON_RENDU',
|
|
|
|
|
({ cause }) =>
|
|
|
|
|
`Le verrou de l'événement n'a pas pu être rendu${causeEntreParentheses(cause)} : ` +
|
|
|
|
|
'une autre séance de ce poste le reprendra une fois ce logiciel fermé.',
|
|
|
|
|
],
|
|
|
|
|
['RIEN_A_DEFAIRE', () => "Rien à défaire : l'historique commence à cet instant."],
|
|
|
|
|
['RIEN_A_REFAIRE', () => 'Rien à refaire : seul un retour se refait, tant que nul geste ne le suit.'],
|
|
|
|
|
['INSTANT_NON_RESTITUABLE', ({ revision }) => `L'historique ne restitue plus l'instant ${revision}.`],
|
|
|
|
|
['PLAN_NON_BLOQUE', () => "Le plan n'est pas bloqué : il n'y a rien à débloquer."],
|
|
|
|
|
[
|
|
|
|
|
'JALON_NON_ECRIT',
|
|
|
|
|
({ dossier, cause }) =>
|
|
|
|
|
`Le geste est enregistré, mais le jalon qui le nomme n'a pas pu s'écrire ` +
|
|
|
|
|
`dans le dossier ${cite(dossier)}${causeEntreParentheses(cause)}.`,
|
|
|
|
|
],
|
|
|
|
|
['SANS_EFFET', () => "Ce geste ne change rien : rien n'est enregistré."],
|
|
|
|
|
|
|
|
|
|
// --- Les refus et les avertissements des commandes (§ 4.4, § 9, § 10.2) ---
|
|
|
|
|
['NOM_REQUIS', () => 'Le nom est obligatoire.'],
|
|
|
|
|
['DATE_INVALIDE', ({ date }) => `La date ${cite(String(date))} n'est pas un jour du calendrier, écrit AAAA-MM-JJ.`],
|
|
|
|
|
['SIEGES_INVALIDES', () => 'Une table compte au moins deux sièges.'],
|
|
|
|
|
['TOURS_INVALIDES', () => 'La soirée compte au moins un tour.'],
|
|
|
|
|
['PARTICIPANT_INCONNU', ({ id }) => `La personne n° ${id} n'est pas dans cet événement.`],
|
|
|
|
|
['PROPOSITION_INCONNUE', ({ id }) => `La proposition n° ${id} n'est pas dans cet événement.`],
|
|
|
|
|
['AUCUNE_PROPOSITION', () => "Aucune proposition n'existe : l'état « Proposé » suit une génération."],
|
|
|
|
|
['AUCUN_RETENU', () => "Aucun placement n'est retenu : l'état « Retenu » suit le choix d'une proposition."],
|
|
|
|
|
[
|
|
|
|
|
'RESERVATIONS_SUSPENDUES',
|
|
|
|
|
({ personne, reservations, titres }) =>
|
|
|
|
|
`Exclusion de ${personne} : ${compte(reservations, 'réservation suspendue', 'réservations suspendues')}, ` +
|
|
|
|
|
`ses places reviennent aux autres${titresDe(titres, "n'est plus pourvu", 'ne sont plus pourvus')}.`,
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'RESERVATIONS_RETABLIES',
|
|
|
|
|
({ personne, reservations, titres }) =>
|
|
|
|
|
`Réintégration de ${personne} : ${compte(reservations, 'réservation rétablie', 'réservations rétablies')}` +
|
|
|
|
|
`${titresDe(titres, 'de nouveau pourvu', 'de nouveau pourvus')}.`,
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'RESERVATIONS_RETIREES',
|
|
|
|
|
({ personne, reservations, titres }) =>
|
|
|
|
|
`Suppression de ${personne} : ${compte(reservations, 'réservation retirée', 'réservations retirées')}` +
|
|
|
|
|
`${titresDe(titres, "n'est plus pourvu", 'ne sont plus pourvus')}.`,
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'IMPORT_EN_RETENU',
|
|
|
|
|
() =>
|
|
|
|
|
"L'import change la liste après le choix d'un placement : le placement retenu est gardé tel quel, " +
|
|
|
|
|
"et rien n'est effacé ; à vous de juger s'il convient encore.",
|
|
|
|
|
],
|
|
|
|
|
|
|
|
|
|
// --- Les libellés figés du journal (§ 8.2, § 8.3) --------------------------
|
|
|
|
|
['ENTREE_CREATION', ({ nom }) => `Création de l'événement ${cite(nom)}`],
|
|
|
|
|
['JALON_CREATION', () => 'Création'],
|
|
|
|
|
['JALON_ETAT', ({ etat }) => `État ${cite(nomEtat(etat))}`],
|
|
|
|
|
['JALON_AVANT_GENERATION', () => 'Avant la génération'],
|
|
|
|
|
['ENTREE_REPRISE', () => "Reprise de l'état trouvé sur le disque"],
|
|
|
|
|
['ENTREE_DEFAIRE', ({ libelle: instant }) => `Défait : ${cite(instant)}`],
|
|
|
|
|
['ENTREE_REFAIRE', ({ libelle: instant }) => `Refait : ${cite(instant)}`],
|
|
|
|
|
['ENTREE_REVENIR', ({ libelle: instant }) => `Retour à ${cite(instant)}`],
|
|
|
|
|
['ENTREE_AJOUTER_PARTICIPANT', ({ personne }) => `Ajouté ${personne}`],
|
|
|
|
|
[
|
|
|
|
|
'ENTREE_MODIFIER_PARTICIPANT',
|
|
|
|
|
({ personne, champs, devenu }) => {
|
|
|
|
|
const nouveau = devenu === null || devenu === undefined ? '' : ` (désormais ${devenu})`;
|
|
|
|
|
return `Modifié ${personne} : ${champs.map(nomChamp).join(', ')}${nouveau}`;
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
['ENTREE_EXCLURE_PARTICIPANT', ({ personne }) => `Exclu ${personne}`],
|
|
|
|
|
['ENTREE_REINTEGRER_PARTICIPANT', ({ personne }) => `Réintégré ${personne}`],
|
|
|
|
|
['ENTREE_SUPPRIMER_PARTICIPANT', ({ personne }) => `Supprimé ${personne}`],
|
|
|
|
|
[
|
|
|
|
|
'ENTREE_IMPORTER_PARTICIPANTS',
|
|
|
|
|
({ mode, ajoutes, misAJour, retires }) => {
|
|
|
|
|
const ajoutesTexte = compte(ajoutes, 'ajouté', 'ajoutés');
|
|
|
|
|
if (mode === 'ajouter') return `Importé ${compte(ajoutes, 'participant', 'participants')}`;
|
|
|
|
|
if (mode === 'mettreAJour') {
|
|
|
|
|
return `Mis à jour la liste par import : ${ajoutesTexte}, ${compte(misAJour, 'modifié', 'modifiés')}`;
|
|
|
|
|
}
|
|
|
|
|
if (mode === 'remplacer') {
|
|
|
|
|
return `Remplacé la liste par import : ${compte(retires, 'retiré', 'retirés')}, ${ajoutesTexte}`;
|
|
|
|
|
}
|
|
|
|
|
throw new RangeError(`libelle : mode d'import inconnu ${JSON.stringify(mode)}`);
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
['ENTREE_CHANGER_ETAT', ({ etat }) => `Plan passé à l'état ${cite(nomEtat(etat))}`],
|
|
|
|
|
['ENTREE_DEBLOQUER', ({ etat }) => `Plan débloqué, état ${cite(nomEtat(etat))}`],
|
|
|
|
|
[
|
|
|
|
|
'ENTREE_ENREGISTRER_GENERATION',
|
|
|
|
|
({ n, premier, dernier }) =>
|
|
|
|
|
`Généré ${compte(n, 'proposition', 'propositions')} ` +
|
|
|
|
|
`(n° ${premier === dernier ? premier : `${premier} à ${dernier}`})`,
|
|
|
|
|
],
|
|
|
|
|
['ENTREE_RETENIR_PROPOSITION', ({ id }) => `Retenu la proposition n° ${id}`],
|
|
|
|
|
[
|
|
|
|
|
'ENTREE_EFFACER_PROPOSITIONS',
|
|
|
|
|
({ n, gardee }) =>
|
|
|
|
|
`Effacé ${compte(n, 'proposition', 'propositions')}` +
|
|
|
|
|
`${gardee === null || gardee === undefined ? '' : `, gardé la retenue (n° ${gardee})`}`,
|
|
|
|
|
],
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// Le texte d'un code de la table, ou RangeError qui le nomme.
|
|
|
|
|
function texteDe(code, details = {}) {
|
|
|
|
|
const texte = TEXTES.get(code);
|
|
|
|
|
if (texte === undefined) throw new RangeError(`libelle : code inconnu ${JSON.stringify(code)}`);
|
|
|
|
|
return texte(details ?? {});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Les codes qui ont un texte, triés par unités UTF-16 croissantes. */
|
|
|
|
|
export const CODES = Object.freeze([...TEXTES.keys()].sort(comparerTextes));
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Le texte français d'un code et de ses détails (§ 14.6) : un refus, un
|
|
|
|
|
* avertissement, une raison, ou le libellé figé d'une entrée ou d'un jalon.
|
|
|
|
|
*
|
|
|
|
|
* @param {string} code
|
|
|
|
|
* @param {Object} [details]
|
|
|
|
|
* @returns {string}
|
|
|
|
|
* @throws {RangeError} un code sans texte, ou une valeur codée de ses détails
|
[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
|
|
|
* — état, champ, mode, échec — que la table ne connaît pas ; jamais
|
|
|
|
|
* une raison, qu'une phrase générique nomme
|
[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
|
|
|
*/
|
|
|
|
|
export function libelle(code, details = {}) {
|
|
|
|
|
return texteDe(code, details);
|
|
|
|
|
}
|