[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
|
|
|
|
// © 2026 TechnoLibre (http://www.technolibre.ca)
|
|
|
|
|
|
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
|
|
|
|
|
|
[ADD] application: furniture — tables, default, tours, capacity checks
Changing a table, the default seat count or the number of tours reshapes
a plan the operator may already have retained. Eight commands create,
remove and resize tables and set the default and the tours; every change
of capacity first passes controlerCapacite, which refuses to cut a reserved
or titled seat, states who goes to the reserve, and only brings back a
reserved person the display showed seated (§ 6.1, § 6.2). bilans.js states
the figures of a change before it applies.
Checked: 2033 node and 127 browser tests.
--- FR ---
[ADD] application : mobilier — tables, défaut, tours, contrôles
Changer une table, le défaut de sièges ou le nombre de tours remodèle un
plan que l'opérateur a peut-être retenu. Huit commandes créent, retirent et
redimensionnent les tables, et règlent le défaut et les tours ; tout
changement de capacité passe d'abord par controlerCapacite, qui refuse de
couper un siège réservé ou titré, dit qui part en réserve, et ne ramène une
personne réservée que si l'écran la montrait assise (§ 6.1, § 6.2).
bilans.js énonce les chiffres d'un changement avant qu'il s'applique.
Vérifié : 2033 node et 127 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 05:12:43 -04:00
|
|
|
|
// Le registre des commandes du mobilier — tables, sièges, tours, réglages
|
|
|
|
|
|
// (§ 4, § 6, § 7.1, § 8.2, § 9), que commandes.js réunit aux autres. Ses
|
|
|
|
|
|
// imports n'atteignent jamais commandes.js : le registre réuni dépend de lui,
|
|
|
|
|
|
// non l'inverse.
|
|
|
|
|
|
//
|
|
|
|
|
|
// Chaque commande suit le contrat de commandes.js : une fonction pure
|
|
|
|
|
|
// (charge, arguments) → { charge, libelle, avertissements }, la charge reçue
|
|
|
|
|
|
// intacte ; un refus lève ErreurCommande(code, details), details.remede
|
|
|
|
|
|
// compris, calculé avant toute modification ; un argument de forme fausse ou
|
|
|
|
|
|
// une table inconnue lèvent TypeError, que l'interface n'offre jamais. Un
|
|
|
|
|
|
// plan bloqué refuse tout (PLAN_BLOQUE). Une saisie de places ou de tours se
|
|
|
|
|
|
// lit par lireEntier, la seule lecture d'un entier saisi.
|
|
|
|
|
|
//
|
|
|
|
|
|
// Une table a un identifiant, tiré de prochainsIds.table, qui ne revient
|
|
|
|
|
|
// jamais, et un numéro affiché, le plus grand plus un, qui ne se renumérote
|
|
|
|
|
|
// pas (§ 4). Les capacités passent par controlerCapacite (capacite.js) ; le
|
|
|
|
|
|
// retenu les suit, ses surnuméraires versés en réserve, et une table créée y
|
|
|
|
|
|
// entre, vide à chaque tour : il ne gagne aucune raison de dérive. Une
|
|
|
|
|
|
// proposition ne se réécrit jamais : elle dérive (§ 8.9, § 9). Supprimer une
|
|
|
|
|
|
// table et changer les tours laissent propositions et retenu en dérive.
|
|
|
|
|
|
import { caseLibre } from '../geometrie/tables.js';
|
[FIX] iteration 3 review: seat targets, gestures, warnings, settings
The wheel no longer zooms during a gesture, so a drop lands under the
ghost. A seat is a target the size of its pitch. A floor drop refuses to
remove an every-tour reservation. A seat reservation the retained plan
does not hold is flagged at the seat, with its swap as remedy. DERIVE names
the person; capacities have an upper bound; Generate waits for a pending
setting; a worker that fails to load shows a refusal; § 5.3 constraints
get their switches. Commands are not re-entrant and a double click does not
confirm. Checked: 2755 node, 550 browser tests, from the index alone.
--- FR ---
[FIX] revue de l'itération 3 : cibles, gestes, avertissements, réglages
La molette ne zoome plus pendant un geste : le dépôt tombe sous le fantôme.
Un siège est une cible de la taille de son pas. Un dépôt sur le sol refuse
de retirer une réservation de tous les tours. Une réservation à un siège
que le retenu ne tient pas est relevée au siège, l'échange en remède. La
dérive nomme la personne ; les capacités ont une borne ; Générer attend un
réglage en cours ; un travailleur qui ne se charge pas donne un refus ; le
§ 5.3 a ses bascules. Les commandes ne sont pas ré-entrantes et un double
clic ne confirme pas. Vérifié : 2755 node, 550 navigateur, index seul.
Assisted-by: Claude Opus 5.5
2026-10-07 17:50:35 -04:00
|
|
|
|
import { SCHEMA, TOURS_MAX, capacite } from '../stockage/document.js';
|
[ADD] application: furniture — tables, default, tours, capacity checks
Changing a table, the default seat count or the number of tours reshapes
a plan the operator may already have retained. Eight commands create,
remove and resize tables and set the default and the tours; every change
of capacity first passes controlerCapacite, which refuses to cut a reserved
or titled seat, states who goes to the reserve, and only brings back a
reserved person the display showed seated (§ 6.1, § 6.2). bilans.js states
the figures of a change before it applies.
Checked: 2033 node and 127 browser tests.
--- FR ---
[ADD] application : mobilier — tables, défaut, tours, contrôles
Changer une table, le défaut de sièges ou le nombre de tours remodèle un
plan que l'opérateur a peut-être retenu. Huit commandes créent, retirent et
redimensionnent les tables, et règlent le défaut et les tours ; tout
changement de capacité passe d'abord par controlerCapacite, qui refuse de
couper un siège réservé ou titré, dit qui part en réserve, et ne ramène une
personne réservée que si l'écran la montrait assise (§ 6.1, § 6.2).
bilans.js énonce les chiffres d'un changement avant qu'il s'applique.
Vérifié : 2033 node et 127 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 05:12:43 -04:00
|
|
|
|
import { fautes } from '../stockage/placements.js';
|
|
|
|
|
|
import { bilanChangementDefaut } from './bilans.js';
|
|
|
|
|
|
import { controlerCapacite, lireEntier, retenuAuxCapacites } from './capacite.js';
|
|
|
|
|
|
import { ErreurCommande, refus } from './erreurs.js';
|
|
|
|
|
|
import { libelle } from './libelles.js';
|
|
|
|
|
|
import { leverRefus } from './pose.js';
|
|
|
|
|
|
|
|
|
|
|
|
const FORMES = new Set(['ronde', 'carree']);
|
|
|
|
|
|
const ORIGINES = new Set(['champ', 'poignee']);
|
|
|
|
|
|
|
|
|
|
|
|
// Le plus grand identifiant de table qu'admet la règle du schéma : le
|
|
|
|
|
|
// compteur, qui la suit, n'aurait plus de valeur au-delà de lui.
|
|
|
|
|
|
const regleDuChamp = (regle, cle) => regle.champs.find(([nom]) => nom === cle)[1];
|
|
|
|
|
|
const IDENTIFIANT_MAX = regleDuChamp(regleDuChamp(regleDuChamp(SCHEMA, 'charge'), 'tables').element, 'id').max;
|
|
|
|
|
|
|
|
|
|
|
|
// Les réglages que modifierReglage change : un interrupteur, ou un entier ≥ 1
|
|
|
|
|
|
// de la génération (§ 5.3, § 5.10).
|
|
|
|
|
|
const INTERRUPTEURS = new Set(['separerAppartenances', 'nouveauxVoisins', 'nouvelleTable', 'varierAppartenances', 'attribuerSieges']);
|
|
|
|
|
|
const ENTIERS_DE_GENERATION = new Set(['generation.nombre', 'generation.arret', 'generation.historique']);
|
|
|
|
|
|
|
|
|
|
|
|
// 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' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// L'enregistrement de la table d'identifiant id : une table inconnue est une
|
|
|
|
|
|
// faute de l'appelant.
|
|
|
|
|
|
function tableDe(charge, id) {
|
|
|
|
|
|
const table = charge.tables.find((candidate) => candidate.id === id);
|
|
|
|
|
|
if (table === undefined) throw new TypeError(`table inconnue : ${JSON.stringify(id)}`);
|
|
|
|
|
|
return table;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// La valeur d'un entier saisi, ou le refus NON_ENTIER levé.
|
|
|
|
|
|
function entierSaisi(saisie) {
|
|
|
|
|
|
const lu = lireEntier(saisie);
|
|
|
|
|
|
if (lu.refus) throw new ErreurCommande(lu.refus.code, lu.refus.details);
|
|
|
|
|
|
return lu.valeur;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// La charge reçue, copiée, chaque table de changements (Map identifiant →
|
|
|
|
|
|
// sieges) à son nombre de sièges, null pour une table qui suit le défaut, et
|
|
|
|
|
|
// le retenu aux capacités qui en résultent.
|
|
|
|
|
|
function avecSieges(charge, changements, defaut = charge.evenement.siegesParDefaut) {
|
|
|
|
|
|
const suivante = structuredClone(charge);
|
|
|
|
|
|
suivante.evenement.siegesParDefaut = defaut;
|
|
|
|
|
|
for (const table of suivante.tables) if (changements.has(table.id)) table.sieges = changements.get(table.id);
|
|
|
|
|
|
const capacites = new Map(suivante.tables.map((table) => [table.id, capacite(suivante, table)]));
|
|
|
|
|
|
const touchees = new Map(
|
|
|
|
|
|
charge.tables
|
|
|
|
|
|
.filter((table) => changements.has(table.id) || capacites.get(table.id) !== capacite(charge, table))
|
|
|
|
|
|
.map(({ id }) => [id, capacites.get(id)]),
|
|
|
|
|
|
);
|
|
|
|
|
|
suivante.retenu = structuredClone(retenuAuxCapacites(charge, touchees));
|
|
|
|
|
|
return suivante;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Le rendu d'un contrôle de capacité accepté : ses avertissements, ou son
|
|
|
|
|
|
// refus levé.
|
|
|
|
|
|
function exigerCapacite(charge, id, sieges, origine) {
|
|
|
|
|
|
const { refus: refuse, avertissements } = controlerCapacite(charge, id, sieges, { origine });
|
|
|
|
|
|
if (refuse !== null) throw new ErreurCommande(refuse.code, refuse.details);
|
|
|
|
|
|
return avertissements;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* creerTable {forme} (§ 4, § 6.1) : une table qui suit le défaut, d'identifiant
|
|
|
|
|
|
* prochainsIds.table, numérotée après la plus grande, posée sur la première
|
[FIX] plan: named warning, one places state, highlight, table spacing
The unheld-reservation warning names the person and the table number
instead of their identifiers. The places state is computed once per change
and handed to the side panels, not once per panel. A person can be
highlighted from their menu or from the grid, and the view bar names them.
A created table takes a cell a full pitch away from every table, its own
capacity included, so no name list runs over a neighbour's chairs. The
rendering guards fail on an empty walk and also check focus and selection.
Checked: 2692 node, 496 browser, 60 long tests, from the index alone.
--- FR ---
[FIX] plan : avertissement nommé, état des places unique, évidence, pas
L'avertissement de réservation non honorée nomme la personne et le numéro
de la table plutôt que leurs identifiants. L'état des places se calcule une
fois par changement et passe aux panneaux, non une fois par panneau. Une
personne se met en évidence par son menu ou depuis la grille, et la barre
de vue la nomme. Une table créée prend une case à un pas plein de chaque
table, sa capacité comprise : aucune liste ne court sur les chaises
voisines. Les gardes du rendu échouent sur un parcours vide et éprouvent
aussi le focus et la sélection. Vérifié : 2692 node, 496 navigateur, 60.
Assisted-by: Claude Opus 5.5
2026-10-07 15:05:01 -04:00
|
|
|
|
* case libre (caseLibre) de la grille des démonstrations, au pas que donnent
|
|
|
|
|
|
* les tables posées, chacune à sa capacité, et la nouvelle. Sous un retenu
|
|
|
|
|
|
* lisible, elle y entre avec sa capacité et une liste vide à chaque tour.
|
[ADD] application: furniture — tables, default, tours, capacity checks
Changing a table, the default seat count or the number of tours reshapes
a plan the operator may already have retained. Eight commands create,
remove and resize tables and set the default and the tours; every change
of capacity first passes controlerCapacite, which refuses to cut a reserved
or titled seat, states who goes to the reserve, and only brings back a
reserved person the display showed seated (§ 6.1, § 6.2). bilans.js states
the figures of a change before it applies.
Checked: 2033 node and 127 browser tests.
--- FR ---
[ADD] application : mobilier — tables, défaut, tours, contrôles
Changer une table, le défaut de sièges ou le nombre de tours remodèle un
plan que l'opérateur a peut-être retenu. Huit commandes créent, retirent et
redimensionnent les tables, et règlent le défaut et les tours ; tout
changement de capacité passe d'abord par controlerCapacite, qui refuse de
couper un siège réservé ou titré, dit qui part en réserve, et ne ramène une
personne réservée que si l'écran la montrait assise (§ 6.1, § 6.2).
bilans.js énonce les chiffres d'un changement avant qu'il s'applique.
Vérifié : 2033 node et 127 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 05:12:43 -04:00
|
|
|
|
*/
|
|
|
|
|
|
function creerTable(charge, { forme } = {}) {
|
|
|
|
|
|
exigerModifiable(charge);
|
|
|
|
|
|
if (!FORMES.has(forme)) throw new TypeError(`forme : 'ronde' ou 'carree' attendue, reçu ${JSON.stringify(forme)}`);
|
|
|
|
|
|
const id = charge.prochainsIds.table;
|
|
|
|
|
|
if (id >= IDENTIFIANT_MAX) throw refus('COMPTEUR_SATURE', { compteur: 'table' });
|
|
|
|
|
|
const numero = Math.max(0, ...charge.tables.map((table) => table.numero)) + 1;
|
[FIX] plan: named warning, one places state, highlight, table spacing
The unheld-reservation warning names the person and the table number
instead of their identifiers. The places state is computed once per change
and handed to the side panels, not once per panel. A person can be
highlighted from their menu or from the grid, and the view bar names them.
A created table takes a cell a full pitch away from every table, its own
capacity included, so no name list runs over a neighbour's chairs. The
rendering guards fail on an empty walk and also check focus and selection.
Checked: 2692 node, 496 browser, 60 long tests, from the index alone.
--- FR ---
[FIX] plan : avertissement nommé, état des places unique, évidence, pas
L'avertissement de réservation non honorée nomme la personne et le numéro
de la table plutôt que leurs identifiants. L'état des places se calcule une
fois par changement et passe aux panneaux, non une fois par panneau. Une
personne se met en évidence par son menu ou depuis la grille, et la barre
de vue la nomme. Une table créée prend une case à un pas plein de chaque
table, sa capacité comprise : aucune liste ne court sur les chaises
voisines. Les gardes du rendu échouent sur un parcours vide et éprouvent
aussi le focus et la sélection. Vérifié : 2692 node, 496 navigateur, 60.
Assisted-by: Claude Opus 5.5
2026-10-07 15:05:01 -04:00
|
|
|
|
const posees = charge.tables.map((table) => ({ position: table.position, forme: table.forme, places: capacite(charge, table) }));
|
|
|
|
|
|
const position = caseLibre(posees, { forme, places: charge.evenement.siegesParDefaut });
|
[ADD] application: furniture — tables, default, tours, capacity checks
Changing a table, the default seat count or the number of tours reshapes
a plan the operator may already have retained. Eight commands create,
remove and resize tables and set the default and the tours; every change
of capacity first passes controlerCapacite, which refuses to cut a reserved
or titled seat, states who goes to the reserve, and only brings back a
reserved person the display showed seated (§ 6.1, § 6.2). bilans.js states
the figures of a change before it applies.
Checked: 2033 node and 127 browser tests.
--- FR ---
[ADD] application : mobilier — tables, défaut, tours, contrôles
Changer une table, le défaut de sièges ou le nombre de tours remodèle un
plan que l'opérateur a peut-être retenu. Huit commandes créent, retirent et
redimensionnent les tables, et règlent le défaut et les tours ; tout
changement de capacité passe d'abord par controlerCapacite, qui refuse de
couper un siège réservé ou titré, dit qui part en réserve, et ne ramène une
personne réservée que si l'écran la montrait assise (§ 6.1, § 6.2).
bilans.js énonce les chiffres d'un changement avant qu'il s'applique.
Vérifié : 2033 node et 127 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 05:12:43 -04:00
|
|
|
|
const suivante = structuredClone(charge);
|
|
|
|
|
|
suivante.tables.push({ id, numero, sieges: null, forme, position });
|
|
|
|
|
|
suivante.prochainsIds.table = id + 1;
|
|
|
|
|
|
const { retenu } = suivante;
|
|
|
|
|
|
if (retenu !== null && fautes(retenu).length === 0) {
|
|
|
|
|
|
retenu.tables.push(id);
|
|
|
|
|
|
retenu.capacites.push(charge.evenement.siegesParDefaut);
|
|
|
|
|
|
for (const etage of retenu.placement) etage.sieges.push([]);
|
|
|
|
|
|
}
|
|
|
|
|
|
return { charge: suivante, libelle: libelle('ENTREE_CREER_TABLE', { numero }), avertissements: [] };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* supprimerTable {id} (§ 6.2, § 9) : la table part, et avec elle, dans la
|
|
|
|
|
|
* même entrée, ses réservations — suspendues comprises — et ses titres ; les
|
|
|
|
|
|
* autres tables gardent leur numéro, propositions et retenu restent, en
|
|
|
|
|
|
* dérive TABLE_SUPPRIMEE.
|
|
|
|
|
|
*/
|
|
|
|
|
|
function supprimerTable(charge, { id } = {}) {
|
|
|
|
|
|
exigerModifiable(charge);
|
|
|
|
|
|
const table = tableDe(charge, id);
|
|
|
|
|
|
const suivante = structuredClone(charge);
|
|
|
|
|
|
suivante.tables = suivante.tables.filter((candidate) => candidate.id !== id);
|
|
|
|
|
|
suivante.reservations = suivante.reservations.filter((reservation) => reservation.table !== id);
|
|
|
|
|
|
suivante.titres = suivante.titres.filter((titre) => titre.table !== id);
|
|
|
|
|
|
const details = {
|
|
|
|
|
|
numero: table.numero,
|
|
|
|
|
|
reservations: charge.reservations.length - suivante.reservations.length,
|
|
|
|
|
|
titres: charge.titres.length - suivante.titres.length,
|
|
|
|
|
|
};
|
|
|
|
|
|
return { charge: suivante, libelle: libelle('ENTREE_SUPPRIMER_TABLE', details), avertissements: [] };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** deplacerTable {id, position: {x, y}} (§ 7.1) : la position du centre, arrondie au centimètre. */
|
|
|
|
|
|
function deplacerTable(charge, { id, position } = {}) {
|
|
|
|
|
|
exigerModifiable(charge);
|
|
|
|
|
|
const table = tableDe(charge, id);
|
|
|
|
|
|
if (!Number.isFinite(position?.x) || !Number.isFinite(position?.y)) {
|
|
|
|
|
|
throw new TypeError(`position : {x, y} finis attendus, reçu ${JSON.stringify(position)}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
const suivante = structuredClone(charge);
|
|
|
|
|
|
// + 0 écrit 0 là où l'arrondi d'une petite valeur négative rend −0.
|
|
|
|
|
|
suivante.tables.find((candidate) => candidate.id === id).position = { x: Math.round(position.x) + 0, y: Math.round(position.y) + 0 };
|
|
|
|
|
|
return { charge: suivante, libelle: libelle('ENTREE_DEPLACER_TABLE', { numero: table.numero }), avertissements: [] };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* changerPlacesTable {id, sieges, origine: 'champ'|'poignee'} (§ 6.2, § 7.1) :
|
|
|
|
|
|
* la table est surchargée à sieges places, lues par lireEntier, contrôlées
|
|
|
|
|
|
* par controlerCapacite selon leur origine ; le retenu suit.
|
|
|
|
|
|
*/
|
|
|
|
|
|
function changerPlacesTable(charge, { id, sieges: saisie, origine } = {}) {
|
|
|
|
|
|
exigerModifiable(charge);
|
|
|
|
|
|
const table = tableDe(charge, id);
|
|
|
|
|
|
if (!ORIGINES.has(origine)) throw new TypeError(`origine : 'champ' ou 'poignee' attendue, reçu ${JSON.stringify(origine)}`);
|
|
|
|
|
|
const sieges = entierSaisi(saisie);
|
|
|
|
|
|
const avertissements = exigerCapacite(charge, id, sieges, origine);
|
|
|
|
|
|
return {
|
|
|
|
|
|
charge: avecSieges(charge, new Map([[id, sieges]])),
|
|
|
|
|
|
libelle: libelle('ENTREE_CHANGER_PLACES_TABLE', { numero: table.numero, sieges }),
|
|
|
|
|
|
avertissements,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* realignerTable {id} (§ 6.1) : la table suit de nouveau le défaut ; une
|
|
|
|
|
|
* table surchargée au-dessus de lui subit une réduction, sous la règle du
|
|
|
|
|
|
* champ (§ 6.2). Le retenu suit.
|
|
|
|
|
|
*/
|
|
|
|
|
|
function realignerTable(charge, { id } = {}) {
|
|
|
|
|
|
exigerModifiable(charge);
|
|
|
|
|
|
const table = tableDe(charge, id);
|
|
|
|
|
|
const sieges = charge.evenement.siegesParDefaut;
|
|
|
|
|
|
const avertissements = exigerCapacite(charge, id, sieges, 'champ');
|
|
|
|
|
|
return {
|
|
|
|
|
|
charge: avecSieges(charge, new Map([[id, null]])),
|
|
|
|
|
|
libelle: libelle('ENTREE_REALIGNER_TABLE', { numero: table.numero, sieges }),
|
|
|
|
|
|
avertissements,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* changerDefaut {sieges, realigner: boolean} (§ 6.1) : le défaut change, et
|
|
|
|
|
|
* avec lui chaque table qui le suit ; realigner, les surchargées le suivent
|
|
|
|
|
|
* aussi. Une seule entrée. Chaque table touchée se contrôle avant
|
|
|
|
|
|
* d'appliquer (bilanChangementDefaut) : un seul refus refuse le tout, le
|
|
|
|
|
|
* premier levé, les suivants dans details.autres. Les avertissements sont
|
|
|
|
|
|
* ceux du bilan ; AU_DELA_LISIBLE, de mêmes détails à chaque table, n'est
|
|
|
|
|
|
* dit qu'une fois. Le retenu suit.
|
|
|
|
|
|
*/
|
|
|
|
|
|
function changerDefaut(charge, { sieges: saisie, realigner } = {}) {
|
|
|
|
|
|
exigerModifiable(charge);
|
|
|
|
|
|
if (typeof realigner !== 'boolean') throw new TypeError(`realigner : booléen attendu, reçu ${JSON.stringify(realigner)}`);
|
|
|
|
|
|
const sieges = entierSaisi(saisie);
|
|
|
|
|
|
const bilan = bilanChangementDefaut(charge, sieges, { realigner });
|
|
|
|
|
|
leverRefus(bilan.refus.map(({ code, details }) => ({ code, details })));
|
|
|
|
|
|
const avertissements = [];
|
|
|
|
|
|
for (const { code, details } of bilan.avertissements) {
|
|
|
|
|
|
if (code === 'AU_DELA_LISIBLE' && avertissements.some((deja) => deja.code === code)) continue;
|
|
|
|
|
|
avertissements.push({ code, details });
|
|
|
|
|
|
}
|
|
|
|
|
|
const realignees = realigner ? charge.tables.filter((table) => table.sieges !== null) : [];
|
|
|
|
|
|
return {
|
|
|
|
|
|
charge: avecSieges(charge, new Map(realignees.map((table) => [table.id, null])), sieges),
|
|
|
|
|
|
libelle: libelle('ENTREE_CHANGER_DEFAUT', { sieges, realignees: realignees.length }),
|
|
|
|
|
|
avertissements,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* changerTours {tours} (§ 2.1 étape 5, § 4.3) : le nombre de tours, lu par
|
[FIX] iteration 3 review: seat targets, gestures, warnings, settings
The wheel no longer zooms during a gesture, so a drop lands under the
ghost. A seat is a target the size of its pitch. A floor drop refuses to
remove an every-tour reservation. A seat reservation the retained plan
does not hold is flagged at the seat, with its swap as remedy. DERIVE names
the person; capacities have an upper bound; Generate waits for a pending
setting; a worker that fails to load shows a refusal; § 5.3 constraints
get their switches. Commands are not re-entrant and a double click does not
confirm. Checked: 2755 node, 550 browser tests, from the index alone.
--- FR ---
[FIX] revue de l'itération 3 : cibles, gestes, avertissements, réglages
La molette ne zoome plus pendant un geste : le dépôt tombe sous le fantôme.
Un siège est une cible de la taille de son pas. Un dépôt sur le sol refuse
de retirer une réservation de tous les tours. Une réservation à un siège
que le retenu ne tient pas est relevée au siège, l'échange en remède. La
dérive nomme la personne ; les capacités ont une borne ; Générer attend un
réglage en cours ; un travailleur qui ne se charge pas donne un refus ; le
§ 5.3 a ses bascules. Les commandes ne sont pas ré-entrantes et un double
clic ne confirme pas. Vérifié : 2755 node, 550 navigateur, index seul.
Assisted-by: Claude Opus 5.5
2026-10-07 17:50:35 -04:00
|
|
|
|
* lireEntier ; MOINS_D_UN_TOUR {tours} sous 1, TROP_DE_TOURS {max} au-delà
|
|
|
|
|
|
* de TOURS_MAX, la borne que l'analyse pose aussi, remède null. Retirer des tours
|
[ADD] application: furniture — tables, default, tours, capacity checks
Changing a table, the default seat count or the number of tours reshapes
a plan the operator may already have retained. Eight commands create,
remove and resize tables and set the default and the tours; every change
of capacity first passes controlerCapacite, which refuses to cut a reserved
or titled seat, states who goes to the reserve, and only brings back a
reserved person the display showed seated (§ 6.1, § 6.2). bilans.js states
the figures of a change before it applies.
Checked: 2033 node and 127 browser tests.
--- FR ---
[ADD] application : mobilier — tables, défaut, tours, contrôles
Changer une table, le défaut de sièges ou le nombre de tours remodèle un
plan que l'opérateur a peut-être retenu. Huit commandes créent, retirent et
redimensionnent les tables, et règlent le défaut et les tours ; tout
changement de capacité passe d'abord par controlerCapacite, qui refuse de
couper un siège réservé ou titré, dit qui part en réserve, et ne ramène une
personne réservée que si l'écran la montrait assise (§ 6.1, § 6.2).
bilans.js énonce les chiffres d'un changement avant qu'il s'applique.
Vérifié : 2033 node et 127 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 05:12:43 -04:00
|
|
|
|
* détruit les réservations « tour désigné » au-delà, dans la même entrée ;
|
|
|
|
|
|
* la portée « tous » reste, symbolique. Propositions et retenu restent, en
|
|
|
|
|
|
* dérive TOURS_CHANGES, jamais tronqués.
|
|
|
|
|
|
*/
|
|
|
|
|
|
function changerTours(charge, { tours: saisie } = {}) {
|
|
|
|
|
|
exigerModifiable(charge);
|
|
|
|
|
|
const tours = entierSaisi(saisie);
|
|
|
|
|
|
if (tours < 1) throw refus('MOINS_D_UN_TOUR', { tours });
|
[FIX] iteration 3 review: seat targets, gestures, warnings, settings
The wheel no longer zooms during a gesture, so a drop lands under the
ghost. A seat is a target the size of its pitch. A floor drop refuses to
remove an every-tour reservation. A seat reservation the retained plan
does not hold is flagged at the seat, with its swap as remedy. DERIVE names
the person; capacities have an upper bound; Generate waits for a pending
setting; a worker that fails to load shows a refusal; § 5.3 constraints
get their switches. Commands are not re-entrant and a double click does not
confirm. Checked: 2755 node, 550 browser tests, from the index alone.
--- FR ---
[FIX] revue de l'itération 3 : cibles, gestes, avertissements, réglages
La molette ne zoome plus pendant un geste : le dépôt tombe sous le fantôme.
Un siège est une cible de la taille de son pas. Un dépôt sur le sol refuse
de retirer une réservation de tous les tours. Une réservation à un siège
que le retenu ne tient pas est relevée au siège, l'échange en remède. La
dérive nomme la personne ; les capacités ont une borne ; Générer attend un
réglage en cours ; un travailleur qui ne se charge pas donne un refus ; le
§ 5.3 a ses bascules. Les commandes ne sont pas ré-entrantes et un double
clic ne confirme pas. Vérifié : 2755 node, 550 navigateur, index seul.
Assisted-by: Claude Opus 5.5
2026-10-07 17:50:35 -04:00
|
|
|
|
if (tours > TOURS_MAX) throw refus('TROP_DE_TOURS', { max: TOURS_MAX });
|
[ADD] application: furniture — tables, default, tours, capacity checks
Changing a table, the default seat count or the number of tours reshapes
a plan the operator may already have retained. Eight commands create,
remove and resize tables and set the default and the tours; every change
of capacity first passes controlerCapacite, which refuses to cut a reserved
or titled seat, states who goes to the reserve, and only brings back a
reserved person the display showed seated (§ 6.1, § 6.2). bilans.js states
the figures of a change before it applies.
Checked: 2033 node and 127 browser tests.
--- FR ---
[ADD] application : mobilier — tables, défaut, tours, contrôles
Changer une table, le défaut de sièges ou le nombre de tours remodèle un
plan que l'opérateur a peut-être retenu. Huit commandes créent, retirent et
redimensionnent les tables, et règlent le défaut et les tours ; tout
changement de capacité passe d'abord par controlerCapacite, qui refuse de
couper un siège réservé ou titré, dit qui part en réserve, et ne ramène une
personne réservée que si l'écran la montrait assise (§ 6.1, § 6.2).
bilans.js énonce les chiffres d'un changement avant qu'il s'applique.
Vérifié : 2033 node et 127 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 05:12:43 -04:00
|
|
|
|
const suivante = structuredClone(charge);
|
|
|
|
|
|
suivante.evenement.tours = tours;
|
|
|
|
|
|
suivante.reservations = suivante.reservations.filter(({ portee, tour }) => portee === 'tous' || tour <= tours);
|
|
|
|
|
|
return {
|
|
|
|
|
|
charge: suivante,
|
|
|
|
|
|
libelle: libelle('ENTREE_CHANGER_TOURS', { tours, retirees: charge.reservations.length - suivante.reservations.length }),
|
|
|
|
|
|
avertissements: [],
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* modifierReglage {cle, valeur} (§ 5.3, § 15.4) : un interrupteur des
|
|
|
|
|
|
* contraintes ou de l'attribution des sièges, un booléen ; ou generation.nombre,
|
|
|
|
|
|
* generation.arret, generation.historique, un entier ≥ 1, que le contrôleur
|
|
|
|
|
|
* de génération a lu et borné. Une autre clé, ou une autre valeur, est une
|
|
|
|
|
|
* faute du code.
|
|
|
|
|
|
*/
|
|
|
|
|
|
function modifierReglage(charge, { cle, valeur } = {}) {
|
|
|
|
|
|
exigerModifiable(charge);
|
|
|
|
|
|
const suivante = structuredClone(charge);
|
|
|
|
|
|
if (INTERRUPTEURS.has(cle)) {
|
|
|
|
|
|
if (typeof valeur !== 'boolean') throw new TypeError(`${cle} : booléen attendu, reçu ${JSON.stringify(valeur)}`);
|
|
|
|
|
|
suivante.reglages[cle] = valeur;
|
|
|
|
|
|
} else if (ENTIERS_DE_GENERATION.has(cle)) {
|
|
|
|
|
|
if (!Number.isSafeInteger(valeur) || valeur < 1) throw new TypeError(`${cle} : entier ≥ 1 attendu, reçu ${JSON.stringify(valeur)}`);
|
|
|
|
|
|
suivante.reglages.generation[cle.slice('generation.'.length)] = valeur;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
throw new TypeError(`réglage inconnu : ${JSON.stringify(cle)}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
return { charge: suivante, libelle: libelle('ENTREE_MODIFIER_REGLAGE', { cle, valeur }), 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
|
|
|
|
|
|
|
|
|
|
/** @type {Map<string, (charge: Object, args: Object) => Object>} */
|
[ADD] application: furniture — tables, default, tours, capacity checks
Changing a table, the default seat count or the number of tours reshapes
a plan the operator may already have retained. Eight commands create,
remove and resize tables and set the default and the tours; every change
of capacity first passes controlerCapacite, which refuses to cut a reserved
or titled seat, states who goes to the reserve, and only brings back a
reserved person the display showed seated (§ 6.1, § 6.2). bilans.js states
the figures of a change before it applies.
Checked: 2033 node and 127 browser tests.
--- FR ---
[ADD] application : mobilier — tables, défaut, tours, contrôles
Changer une table, le défaut de sièges ou le nombre de tours remodèle un
plan que l'opérateur a peut-être retenu. Huit commandes créent, retirent et
redimensionnent les tables, et règlent le défaut et les tours ; tout
changement de capacité passe d'abord par controlerCapacite, qui refuse de
couper un siège réservé ou titré, dit qui part en réserve, et ne ramène une
personne réservée que si l'écran la montrait assise (§ 6.1, § 6.2).
bilans.js énonce les chiffres d'un changement avant qu'il s'applique.
Vérifié : 2033 node et 127 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 05:12:43 -04:00
|
|
|
|
export const COMMANDES_MOBILIER = new Map([
|
|
|
|
|
|
['creerTable', creerTable],
|
|
|
|
|
|
['supprimerTable', supprimerTable],
|
|
|
|
|
|
['deplacerTable', deplacerTable],
|
|
|
|
|
|
['changerPlacesTable', changerPlacesTable],
|
|
|
|
|
|
['realignerTable', realignerTable],
|
|
|
|
|
|
['changerDefaut', changerDefaut],
|
|
|
|
|
|
['changerTours', changerTours],
|
|
|
|
|
|
['modifierReglage', modifierReglage],
|
|
|
|
|
|
]);
|