[ADD] application: observable session, events facade, places and seats
The screens of iteration 3 need state they can subscribe to and commands
that keep the plan sound. The session follows the Svelte store contract,
unblocks a plan, cancels a gesture, keeps the framing, and orders the
warnings of a gesture. The facade lists events, settles the working folder
and runs copy, rename, import and delete. Places cover reservations, titles,
designation and the retained plan: each gesture reads seats as the display
shows them, reserved people keep their table, and a person removed frees
her chair while every other seat stays.
Checked: 1973 node, 127 browser, 60 node-long tests; coverage floors kept.
--- FR ---
[ADD] application : séance observable, façade des événements, places
Les écrans de l'itération 3 exigent un état auquel s'abonner et des
commandes qui gardent le plan sain. La séance suit le contrat des magasins
de Svelte, débloque un plan, annule un geste, garde le cadrage et ordonne
les avertissements d'un geste. La façade liste les événements, règle le
dossier de travail et mène copie, renommage, import et suppression. Les
places couvrent réservations, titres, désignation et plan retenu : chaque
geste lit les sièges comme l'écran les montre, une personne réservée garde
sa table, et une personne retirée libère sa chaise sans déplacer les autres.
Vérifié : 1973 node, 127 navigateur, 60 node-long ; seuils de couverture.
Assisted-by: Claude Opus 5.5
2026-10-07 04:14:24 -04:00
// © 2026 TechnoLibre (http://www.technolibre.ca)
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
// Épreuves des avertissements d'un geste (§ 5.9, § 9, § 14.12), sur des
// charges construites à la main : le manque de places qui croît, la
// configuration qui ne se normalise plus, la dérive qu'un geste ajoute, la
// réservation que le retenu n'honore plus, et les violations du retenu qui
// subsistent hors de la dérive. La valeur attendue du manque vient du moteur
// lui-même, sur la configuration rendue. Les noms d'épreuve sont inventés.
import assert from 'node:assert/strict' ;
import { describe , test } from '../../test/lanceur.js' ;
import { nombrePlacesManquantes , normaliser } from '../moteur/configuration.js' ;
import { configurationDepuisCharge , creerCharge } from '../stockage/document.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 { examiner } from '../stockage/placements.js' ;
[ADD] application: observable session, events facade, places and seats
The screens of iteration 3 need state they can subscribe to and commands
that keep the plan sound. The session follows the Svelte store contract,
unblocks a plan, cancels a gesture, keeps the framing, and orders the
warnings of a gesture. The facade lists events, settles the working folder
and runs copy, rename, import and delete. Places cover reservations, titles,
designation and the retained plan: each gesture reads seats as the display
shows them, reserved people keep their table, and a person removed frees
her chair while every other seat stays.
Checked: 1973 node, 127 browser, 60 node-long tests; coverage floors kept.
--- FR ---
[ADD] application : séance observable, façade des événements, places
Les écrans de l'itération 3 exigent un état auquel s'abonner et des
commandes qui gardent le plan sain. La séance suit le contrat des magasins
de Svelte, débloque un plan, annule un geste, garde le cadrage et ordonne
les avertissements d'un geste. La façade liste les événements, règle le
dossier de travail et mène copie, renommage, import et suppression. Les
places couvrent réservations, titres, désignation et plan retenu : chaque
geste lit les sièges comme l'écran les montre, une personne réservée garde
sa table, et une personne retirée libère sa chaise sans déplacer les autres.
Vérifié : 1973 node, 127 navigateur, 60 node-long ; seuils de couverture.
Assisted-by: Claude Opus 5.5
2026-10-07 04:14:24 -04:00
import { VERSION } from '../version.genere.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 { avertissementsApres , examenNomme , invariantViole } from './avertissements.js' ;
[ADD] application: observable session, events facade, places and seats
The screens of iteration 3 need state they can subscribe to and commands
that keep the plan sound. The session follows the Svelte store contract,
unblocks a plan, cancels a gesture, keeps the framing, and orders the
warnings of a gesture. The facade lists events, settles the working folder
and runs copy, rename, import and delete. Places cover reservations, titles,
designation and the retained plan: each gesture reads seats as the display
shows them, reserved people keep their table, and a person removed frees
her chair while every other seat stays.
Checked: 1973 node, 127 browser, 60 node-long tests; coverage floors kept.
--- FR ---
[ADD] application : séance observable, façade des événements, places
Les écrans de l'itération 3 exigent un état auquel s'abonner et des
commandes qui gardent le plan sain. La séance suit le contrat des magasins
de Svelte, débloque un plan, annule un geste, garde le cadrage et ordonne
les avertissements d'un geste. La façade liste les événements, règle le
dossier de travail et mène copie, renommage, import et suppression. Les
places couvrent réservations, titres, désignation et plan retenu : chaque
geste lit les sièges comme l'écran les montre, une personne réservée garde
sa table, et une personne retirée libère sa chaise sans déplacer les autres.
Vérifié : 1973 node, 127 navigateur, 60 node-long ; seuils de couverture.
Assisted-by: Claude Opus 5.5
2026-10-07 04:14:24 -04:00
const personne = ( id , nom , exclu = false ) => ( {
id ,
nom ,
prenom : null ,
appartenance : null ,
courriel : null ,
titrePressenti : null ,
notes : null ,
exclu ,
} ) ;
const table = ( id , sieges ) => ( { id , numero : id , sieges , forme : 'ronde' , position : { x : 300 * ( id - 1 ) , y : 0 } } ) ;
// Une copie modifiée : celle reçue ne change pas.
function avec ( charge , modifier ) {
const copie = structuredClone ( charge ) ;
modifier ( copie ) ;
return copie ;
}
// Ajoute à la charge les convives de rang premier à dernier, inclus.
function ajouterConvives ( charge , premier , dernier ) {
for ( let rang = premier ; rang <= dernier ; rang += 1 ) charge . participants . push ( personne ( rang , ` Convive ${ rang } ` ) ) ;
charge . prochainsIds . participant = dernier + 1 ;
}
// Deux tables de huit et seize convives : la salle est pleine.
function sallePleine ( ) {
const charge = creerCharge ( { id : 'evt-merles' , nom : 'Soirée des Merles' , siegesParDefaut : 8 , tours : 2 } ) ;
charge . tables = [ table ( 1 , 8 ) , table ( 2 , 8 ) ] ;
charge . prochainsIds = { participant : 1 , table : 3 , proposition : 1 } ;
ajouterConvives ( charge , 1 , 16 ) ;
return charge ;
}
const manqueDu = ( charge ) => nombrePlacesManquantes ( normaliser ( configurationDepuisCharge ( charge ) ) ) ;
// Quatre personnes, deux tables de deux, deux tours ; Iris Ombrelle réservée
// à la table 1 pour tous les tours. Le placement de la proposition 1 : Iris
// et Grisaille à la table 1 au premier tour, Iris et Pervenche au second.
const PLACEMENT = [
{ sieges : [ [ 1 , 2 ] , [ 3 , 4 ] ] , reserve : [ ] } ,
{ sieges : [ [ 1 , 3 ] , [ 2 , 4 ] ] , reserve : [ ] } ,
] ;
const plan = ( ) => ( { tables : [ 1 , 2 ] , capacites : [ 2 , 2 ] , tours : 2 , participants : [ 1 , 2 , 3 , 4 ] , placement : structuredClone ( PLACEMENT ) } ) ;
const proposition = ( id ) => ( {
id ,
graine : id ,
arret : 40 ,
historique : 1 ,
produitVersion : VERSION . affichee ,
siegesAttribues : false ,
... plan ( ) ,
} ) ;
function petiteSoiree ( { retenu = true } = { } ) {
const charge = creerCharge ( { id : 'evt-lucioles' , nom : 'Soirée des Lucioles' , siegesParDefaut : 2 , tours : 2 } ) ;
charge . participants = [
personne ( 1 , 'Ombrelle' ) ,
personne ( 2 , 'Grisaille' ) ,
personne ( 3 , 'Pervenche' ) ,
personne ( 4 , 'Lacasse' ) ,
] ;
charge . tables = [ table ( 1 , null ) , table ( 2 , null ) ] ;
charge . reservations = [ { participant : 1 , table : 1 , siege : null , portee : 'tous' , tour : null } ] ;
charge . propositions = [ proposition ( 1 ) , proposition ( 2 ) ] ;
charge . retenu = retenu ? { proposition : 1 , siegesAttribues : false , ... plan ( ) } : null ;
charge . prochainsIds = { participant : 5 , table : 3 , proposition : 3 } ;
charge . evenement . etat = retenu ? 'retenu' : 'propose' ;
return charge ;
}
// Grisaille réservée à la table 2 au premier tour : le placement l'assoit à
// la table 1 à ce tour.
const reserverGrisaille = ( charge ) =>
charge . reservations . push ( { participant : 2 , table : 2 , siege : null , portee : 'tour' , tour : 1 } ) ;
// Le retenu se contredit : la personne 2 en réserve au premier tour, alors
// qu'elle y est déjà assise (DOUBLON).
const rendreFautif = ( charge ) => {
charge . retenu . placement [ 0 ] . reserve = [ 2 ] ;
} ;
const codes = ( avertissements ) => avertissements . map ( ( { code } ) => code ) ;
describe ( 'avertissements : le manque de places (§ 5.9)' , ( ) => {
test ( 'la dix-septième personne : PLACES_MANQUANTES {placesManquantes: 1}, le nombre du moteur' , ( ) => {
const avant = sallePleine ( ) ;
const apres = avec ( avant , ( charge ) => ajouterConvives ( charge , 17 , 17 ) ) ;
assert . equal ( manqueDu ( avant ) , 0 ) ;
const rendu = avertissementsApres ( avant , apres ) ;
assert . deepEqual ( rendu , [ { code : 'PLACES_MANQUANTES' , details : { placesManquantes : 1 } } ] ) ;
assert . equal ( rendu [ 0 ] . details . placesManquantes , manqueDu ( apres ) ) ;
} ) ;
test ( 'trois de plus : {placesManquantes: 4}, le compte de la configuration rendue' , ( ) => {
const avant = avec ( sallePleine ( ) , ( charge ) => ajouterConvives ( charge , 17 , 17 ) ) ;
const apres = avec ( avant , ( charge ) => ajouterConvives ( charge , 18 , 20 ) ) ;
const rendu = avertissementsApres ( avant , apres ) ;
assert . deepEqual ( rendu , [ { code : 'PLACES_MANQUANTES' , details : { placesManquantes : 4 } } ] ) ;
assert . equal ( manqueDu ( apres ) , 4 ) ;
} ) ;
test ( "un geste qui ne fait pas croître le manque n'en dit rien" , ( ) => {
const pleine = sallePleine ( ) ;
const manque = avec ( pleine , ( charge ) => ajouterConvives ( charge , 17 , 18 ) ) ;
const cas = [
[ pleine , avec ( pleine , ( charge ) => ( charge . participants [ 0 ] . nom = 'Ancolie' ) ) ] ,
[ manque , avec ( manque , ( charge ) => ( charge . participants [ 0 ] . nom = 'Ancolie' ) ) ] ,
[ manque , avec ( manque , ( charge ) => charge . participants . pop ( ) ) ] ,
[ manque , avec ( manque , ( charge ) => ( charge . tables [ 0 ] . sieges = 10 ) ) ] ,
[ manque , avec ( manque , ( charge ) => ( charge . participants [ 16 ] . exclu = true ) ) ] ,
] ;
assert . equal ( cas . length , 5 ) ;
for ( const [ avant , apres ] of cas ) assert . deepEqual ( avertissementsApres ( avant , apres ) , [ ] ) ;
} ) ;
test ( 'ôter une table, ou baisser une capacité, fait croître le manque et le dit' , ( ) => {
const pleine = sallePleine ( ) ;
assert . deepEqual ( avertissementsApres ( pleine , avec ( pleine , ( charge ) => charge . tables . pop ( ) ) ) , [
{ code : 'PLACES_MANQUANTES' , details : { placesManquantes : 8 } } ,
] ) ;
assert . deepEqual ( avertissementsApres ( pleine , avec ( pleine , ( charge ) => ( charge . tables [ 1 ] . sieges = 6 ) ) ) , [
{ code : 'PLACES_MANQUANTES' , details : { placesManquantes : 2 } } ,
] ) ;
} ) ;
} ) ;
describe ( 'avertissements : une configuration qui ne se normalise plus (§ 5.9)' , ( ) => {
// Iris réservée aussi à la table 2 au premier tour : deux tables au même
// tour, RESERVATION_CONFLIT.
const enConflit = ( charge ) =>
charge . reservations . push ( { participant : 1 , table : 2 , siege : null , portee : 'tour' , tour : 1 } ) ;
test ( "la configuration rendue lève : CONFIGURATION_INCOHERENTE porte le code et les détails du moteur, et le manque ne se calcule pas" , ( ) => {
const avant = sallePleine ( ) ;
avant . reservations = [ { participant : 1 , table : 1 , siege : null , portee : 'tous' , tour : null } ] ;
const apres = avec ( avant , ( charge ) => {
enConflit ( charge ) ;
ajouterConvives ( charge , 17 , 19 ) ;
} ) ;
assert . deepEqual ( avertissementsApres ( avant , apres ) , [
{
code : 'CONFIGURATION_INCOHERENTE' ,
details : { code : 'RESERVATION_CONFLIT' , details : { participant : 1 , tour : 1 , tables : [ 1 , 2 ] } } ,
} ,
] ) ;
} ) ;
test ( "déjà incohérente avant le geste : rien ne se répète ; redevenue cohérente, le manque se compte depuis zéro" , ( ) => {
const avant = sallePleine ( ) ;
avant . reservations = [ { participant : 1 , table : 1 , siege : null , portee : 'tous' , tour : null } ] ;
enConflit ( avant ) ;
const encore = avec ( avant , ( charge ) => ajouterConvives ( charge , 17 , 17 ) ) ;
assert . deepEqual ( avertissementsApres ( avant , encore ) , [ ] ) ;
const reparee = avec ( encore , ( charge ) => charge . reservations . pop ( ) ) ;
assert . deepEqual ( avertissementsApres ( encore , reparee ) , [ { code : 'PLACES_MANQUANTES' , details : { placesManquantes : 1 } } ] ) ;
} ) ;
test ( "une configuration hors de sa forme n'est pas une incohérence : la TypeError du moteur remonte" , ( ) => {
const avant = sallePleine ( ) ;
const apres = avec ( avant , ( charge ) => ( charge . participants [ 0 ] . id = 'un' ) ) ;
assert . throws ( ( ) => avertissementsApres ( avant , apres ) , TypeError ) ;
} ) ;
} ) ;
describe ( 'avertissements : la dérive que le geste ajoute (§ 9, § 8.9)' , ( ) => {
test ( 'exclure une personne : DERIVE_NOUVELLE nomme chaque proposition par identifiant, puis le retenu, et la raison' , ( ) => {
const avant = petiteSoiree ( ) ;
const apres = avec ( avant , ( charge ) => ( charge . participants [ 2 ] . exclu = true ) ) ;
[FIX] iteration 3 review: seat targets, gestures, warnings, settings
The wheel no longer zooms during a gesture, so a drop lands under the
ghost. A seat is a target the size of its pitch. A floor drop refuses to
remove an every-tour reservation. A seat reservation the retained plan
does not hold is flagged at the seat, with its swap as remedy. DERIVE names
the person; capacities have an upper bound; Generate waits for a pending
setting; a worker that fails to load shows a refusal; § 5.3 constraints
get their switches. Commands are not re-entrant and a double click does not
confirm. Checked: 2755 node, 550 browser tests, from the index alone.
--- FR ---
[FIX] revue de l'itération 3 : cibles, gestes, avertissements, réglages
La molette ne zoome plus pendant un geste : le dépôt tombe sous le fantôme.
Un siège est une cible de la taille de son pas. Un dépôt sur le sol refuse
de retirer une réservation de tous les tours. Une réservation à un siège
que le retenu ne tient pas est relevée au siège, l'échange en remède. La
dérive nomme la personne ; les capacités ont une borne ; Générer attend un
réglage en cours ; un travailleur qui ne se charge pas donne un refus ; le
§ 5.3 a ses bascules. Les commandes ne sont pas ré-entrantes et un double
clic ne confirme pas. Vérifié : 2755 node, 550 navigateur, index seul.
Assisted-by: Claude Opus 5.5
2026-10-07 17:50:35 -04:00
const raisons = [ { code : 'PARTICIPANT_EXCLU' , participant : 3 , personne : 'Pervenche' } ] ;
[ADD] application: observable session, events facade, places and seats
The screens of iteration 3 need state they can subscribe to and commands
that keep the plan sound. The session follows the Svelte store contract,
unblocks a plan, cancels a gesture, keeps the framing, and orders the
warnings of a gesture. The facade lists events, settles the working folder
and runs copy, rename, import and delete. Places cover reservations, titles,
designation and the retained plan: each gesture reads seats as the display
shows them, reserved people keep their table, and a person removed frees
her chair while every other seat stays.
Checked: 1973 node, 127 browser, 60 node-long tests; coverage floors kept.
--- FR ---
[ADD] application : séance observable, façade des événements, places
Les écrans de l'itération 3 exigent un état auquel s'abonner et des
commandes qui gardent le plan sain. La séance suit le contrat des magasins
de Svelte, débloque un plan, annule un geste, garde le cadrage et ordonne
les avertissements d'un geste. La façade liste les événements, règle le
dossier de travail et mène copie, renommage, import et suppression. Les
places couvrent réservations, titres, désignation et plan retenu : chaque
geste lit les sièges comme l'écran les montre, une personne réservée garde
sa table, et une personne retirée libère sa chaise sans déplacer les autres.
Vérifié : 1973 node, 127 navigateur, 60 node-long ; seuils de couverture.
Assisted-by: Claude Opus 5.5
2026-10-07 04:14:24 -04:00
assert . deepEqual ( avertissementsApres ( avant , apres ) , [
{
code : 'DERIVE_NOUVELLE' ,
details : { touchees : [ { id : 1 , raisons } , { id : 2 , raisons } , { id : 'retenu' , raisons } ] } ,
} ,
] ) ;
} ) ;
test ( "une raison que le placement portait déjà ne se redit pas ; une raison au champ changé est nouvelle" , ( ) => {
const une = avec ( petiteSoiree ( ) , ( charge ) => ( charge . participants [ 2 ] . exclu = true ) ) ;
const deux = avec ( une , ( charge ) => ( charge . participants [ 3 ] . exclu = true ) ) ;
[FIX] iteration 3 review: seat targets, gestures, warnings, settings
The wheel no longer zooms during a gesture, so a drop lands under the
ghost. A seat is a target the size of its pitch. A floor drop refuses to
remove an every-tour reservation. A seat reservation the retained plan
does not hold is flagged at the seat, with its swap as remedy. DERIVE names
the person; capacities have an upper bound; Generate waits for a pending
setting; a worker that fails to load shows a refusal; § 5.3 constraints
get their switches. Commands are not re-entrant and a double click does not
confirm. Checked: 2755 node, 550 browser tests, from the index alone.
--- FR ---
[FIX] revue de l'itération 3 : cibles, gestes, avertissements, réglages
La molette ne zoome plus pendant un geste : le dépôt tombe sous le fantôme.
Un siège est une cible de la taille de son pas. Un dépôt sur le sol refuse
de retirer une réservation de tous les tours. Une réservation à un siège
que le retenu ne tient pas est relevée au siège, l'échange en remède. La
dérive nomme la personne ; les capacités ont une borne ; Générer attend un
réglage en cours ; un travailleur qui ne se charge pas donne un refus ; le
§ 5.3 a ses bascules. Les commandes ne sont pas ré-entrantes et un double
clic ne confirme pas. Vérifié : 2755 node, 550 navigateur, index seul.
Assisted-by: Claude Opus 5.5
2026-10-07 17:50:35 -04:00
const raisons = [ { code : 'PARTICIPANT_EXCLU' , participant : 4 , personne : 'Lacasse' } ] ;
[ADD] application: observable session, events facade, places and seats
The screens of iteration 3 need state they can subscribe to and commands
that keep the plan sound. The session follows the Svelte store contract,
unblocks a plan, cancels a gesture, keeps the framing, and orders the
warnings of a gesture. The facade lists events, settles the working folder
and runs copy, rename, import and delete. Places cover reservations, titles,
designation and the retained plan: each gesture reads seats as the display
shows them, reserved people keep their table, and a person removed frees
her chair while every other seat stays.
Checked: 1973 node, 127 browser, 60 node-long tests; coverage floors kept.
--- FR ---
[ADD] application : séance observable, façade des événements, places
Les écrans de l'itération 3 exigent un état auquel s'abonner et des
commandes qui gardent le plan sain. La séance suit le contrat des magasins
de Svelte, débloque un plan, annule un geste, garde le cadrage et ordonne
les avertissements d'un geste. La façade liste les événements, règle le
dossier de travail et mène copie, renommage, import et suppression. Les
places couvrent réservations, titres, désignation et plan retenu : chaque
geste lit les sièges comme l'écran les montre, une personne réservée garde
sa table, et une personne retirée libère sa chaise sans déplacer les autres.
Vérifié : 1973 node, 127 navigateur, 60 node-long ; seuils de couverture.
Assisted-by: Claude Opus 5.5
2026-10-07 04:14:24 -04:00
assert . deepEqual ( avertissementsApres ( une , deux ) , [
{ code : 'DERIVE_NOUVELLE' , details : { touchees : [ { id : 1 , raisons } , { id : 2 , raisons } , { id : 'retenu' , raisons } ] } } ,
] ) ;
assert . deepEqual ( avertissementsApres ( deux , avec ( deux , ( charge ) => ( charge . participants [ 0 ] . nom = 'Parasol' ) ) ) , [ ] ) ;
const trois = avec ( petiteSoiree ( ) , ( charge ) => ( charge . tables [ 0 ] . sieges = 3 ) ) ;
const quatre = avec ( trois , ( charge ) => ( charge . tables [ 0 ] . sieges = 4 ) ) ;
[FIX] iteration 3 review: seat targets, gestures, warnings, settings
The wheel no longer zooms during a gesture, so a drop lands under the
ghost. A seat is a target the size of its pitch. A floor drop refuses to
remove an every-tour reservation. A seat reservation the retained plan
does not hold is flagged at the seat, with its swap as remedy. DERIVE names
the person; capacities have an upper bound; Generate waits for a pending
setting; a worker that fails to load shows a refusal; § 5.3 constraints
get their switches. Commands are not re-entrant and a double click does not
confirm. Checked: 2755 node, 550 browser tests, from the index alone.
--- FR ---
[FIX] revue de l'itération 3 : cibles, gestes, avertissements, réglages
La molette ne zoome plus pendant un geste : le dépôt tombe sous le fantôme.
Un siège est une cible de la taille de son pas. Un dépôt sur le sol refuse
de retirer une réservation de tous les tours. Une réservation à un siège
que le retenu ne tient pas est relevée au siège, l'échange en remède. La
dérive nomme la personne ; les capacités ont une borne ; Générer attend un
réglage en cours ; un travailleur qui ne se charge pas donne un refus ; le
§ 5.3 a ses bascules. Les commandes ne sont pas ré-entrantes et un double
clic ne confirme pas. Vérifié : 2755 node, 550 navigateur, index seul.
Assisted-by: Claude Opus 5.5
2026-10-07 17:50:35 -04:00
const changee = [ { code : 'CAPACITE_CHANGEE' , table : 1 , avant : 2 , maintenant : 4 , numero : 1 } ] ;
[ADD] application: observable session, events facade, places and seats
The screens of iteration 3 need state they can subscribe to and commands
that keep the plan sound. The session follows the Svelte store contract,
unblocks a plan, cancels a gesture, keeps the framing, and orders the
warnings of a gesture. The facade lists events, settles the working folder
and runs copy, rename, import and delete. Places cover reservations, titles,
designation and the retained plan: each gesture reads seats as the display
shows them, reserved people keep their table, and a person removed frees
her chair while every other seat stays.
Checked: 1973 node, 127 browser, 60 node-long tests; coverage floors kept.
--- FR ---
[ADD] application : séance observable, façade des événements, places
Les écrans de l'itération 3 exigent un état auquel s'abonner et des
commandes qui gardent le plan sain. La séance suit le contrat des magasins
de Svelte, débloque un plan, annule un geste, garde le cadrage et ordonne
les avertissements d'un geste. La façade liste les événements, règle le
dossier de travail et mène copie, renommage, import et suppression. Les
places couvrent réservations, titres, désignation et plan retenu : chaque
geste lit les sièges comme l'écran les montre, une personne réservée garde
sa table, et une personne retirée libère sa chaise sans déplacer les autres.
Vérifié : 1973 node, 127 navigateur, 60 node-long ; seuils de couverture.
Assisted-by: Claude Opus 5.5
2026-10-07 04:14:24 -04:00
assert . deepEqual ( avertissementsApres ( trois , quatre ) , [
{
code : 'DERIVE_NOUVELLE' ,
details : { touchees : [ { id : 1 , raisons : changee } , { id : 2 , raisons : changee } , { id : 'retenu' , raisons : changee } ] } ,
} ,
] ) ;
} ) ;
test ( "un retenu fautif ne dit rien de lui : ni dérive, ni réservation non honorée, et rien ne lève" , ( ) => {
const avant = avec ( petiteSoiree ( ) , rendreFautif ) ;
const apres = avec ( avant , ( charge ) => {
charge . participants [ 2 ] . exclu = true ;
reserverGrisaille ( charge ) ;
} ) ;
[FIX] iteration 3 review: seat targets, gestures, warnings, settings
The wheel no longer zooms during a gesture, so a drop lands under the
ghost. A seat is a target the size of its pitch. A floor drop refuses to
remove an every-tour reservation. A seat reservation the retained plan
does not hold is flagged at the seat, with its swap as remedy. DERIVE names
the person; capacities have an upper bound; Generate waits for a pending
setting; a worker that fails to load shows a refusal; § 5.3 constraints
get their switches. Commands are not re-entrant and a double click does not
confirm. Checked: 2755 node, 550 browser tests, from the index alone.
--- FR ---
[FIX] revue de l'itération 3 : cibles, gestes, avertissements, réglages
La molette ne zoome plus pendant un geste : le dépôt tombe sous le fantôme.
Un siège est une cible de la taille de son pas. Un dépôt sur le sol refuse
de retirer une réservation de tous les tours. Une réservation à un siège
que le retenu ne tient pas est relevée au siège, l'échange en remède. La
dérive nomme la personne ; les capacités ont une borne ; Générer attend un
réglage en cours ; un travailleur qui ne se charge pas donne un refus ; le
§ 5.3 a ses bascules. Les commandes ne sont pas ré-entrantes et un double
clic ne confirme pas. Vérifié : 2755 node, 550 navigateur, index seul.
Assisted-by: Claude Opus 5.5
2026-10-07 17:50:35 -04:00
const raisons = [ { code : 'PARTICIPANT_EXCLU' , participant : 3 , personne : 'Pervenche' } ] ;
[ADD] application: observable session, events facade, places and seats
The screens of iteration 3 need state they can subscribe to and commands
that keep the plan sound. The session follows the Svelte store contract,
unblocks a plan, cancels a gesture, keeps the framing, and orders the
warnings of a gesture. The facade lists events, settles the working folder
and runs copy, rename, import and delete. Places cover reservations, titles,
designation and the retained plan: each gesture reads seats as the display
shows them, reserved people keep their table, and a person removed frees
her chair while every other seat stays.
Checked: 1973 node, 127 browser, 60 node-long tests; coverage floors kept.
--- FR ---
[ADD] application : séance observable, façade des événements, places
Les écrans de l'itération 3 exigent un état auquel s'abonner et des
commandes qui gardent le plan sain. La séance suit le contrat des magasins
de Svelte, débloque un plan, annule un geste, garde le cadrage et ordonne
les avertissements d'un geste. La façade liste les événements, règle le
dossier de travail et mène copie, renommage, import et suppression. Les
places couvrent réservations, titres, désignation et plan retenu : chaque
geste lit les sièges comme l'écran les montre, une personne réservée garde
sa table, et une personne retirée libère sa chaise sans déplacer les autres.
Vérifié : 1973 node, 127 navigateur, 60 node-long ; seuils de couverture.
Assisted-by: Claude Opus 5.5
2026-10-07 04:14:24 -04:00
assert . deepEqual ( avertissementsApres ( avant , apres ) , [
{ code : 'DERIVE_NOUVELLE' , details : { touchees : [ { id : 1 , raisons } , { id : 2 , raisons } ] } } ,
] ) ;
} ) ;
} ) ;
[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
describe ( 'avertissements : la dérive nomme les personnes et les tables (§ 9)' , ( ) => {
test ( "une personne se nomme par son nom affiché, une table par son numéro ; celles que le geste retire, par la charge d'avant" , ( ) => {
const avant = avec ( petiteSoiree ( ) , ( charge ) => {
charge . tables [ 1 ] . numero = 7 ;
charge . participants [ 1 ] . prenom = 'Aubépine' ;
} ) ;
const supprimees = avec ( avant , ( charge ) => {
charge . participants = charge . participants . filter ( ( { id } ) => id !== 2 ) ;
charge . reservations = charge . reservations . filter ( ( { participant } ) => participant !== 2 ) ;
charge . tables = charge . tables . filter ( ( { id } ) => id !== 2 ) ;
} ) ;
const raisons = [
{ code : 'PARTICIPANT_SUPPRIME' , participant : 2 , personne : 'Aubépine Grisaille' } ,
{ code : 'TABLE_SUPPRIMEE' , table : 2 , numero : 7 } ,
] ;
const [ derive ] = avertissementsApres ( avant , supprimees ) . filter ( ( { code } ) => code === 'DERIVE_NOUVELLE' ) ;
assert . deepEqual ( derive . details . touchees , [ { id : 1 , raisons } , { id : 2 , raisons } , { id : 'retenu' , raisons } ] ) ;
const ajoutees = avec ( avant , ( charge ) => {
charge . participants . push ( personne ( 5 , 'Ancolie' ) ) ;
charge . tables . push ( { ... table ( 3 , null ) , numero : 9 } ) ;
charge . prochainsIds . participant = 6 ;
charge . prochainsIds . table = 4 ;
} ) ;
const [ nouvelle ] = avertissementsApres ( avant , ajoutees ) . filter ( ( { code } ) => code === 'DERIVE_NOUVELLE' ) ;
assert . deepEqual ( nouvelle . details . touchees [ 0 ] . raisons , [
{ code : 'PARTICIPANT_NON_PLACE' , participant : 5 , personne : 'Ancolie' } ,
{ code : 'TABLE_AJOUTEE' , table : 3 , numero : 9 } ,
] ) ;
} ) ;
test ( "examenNomme nomme chaque raison de l'examen sur la charge, sans toucher l'examen reçu ; une personne ou une table que la charge n'a plus garde son identifiant" , ( ) => {
const charge = avec ( petiteSoiree ( ) , ( copie ) => {
copie . participants [ 2 ] . exclu = true ;
copie . participants . pop ( ) ;
} ) ;
const examen = examiner ( charge ) ;
const copie = structuredClone ( examen ) ;
const nomme = examenNomme ( examen , charge ) ;
assert . deepEqual ( examen , copie ) ;
const attendues = [
{ code : 'PARTICIPANT_EXCLU' , participant : 3 , personne : 'Pervenche' } ,
{ code : 'PARTICIPANT_SUPPRIME' , participant : 4 } ,
] ;
assert . deepEqual ( nomme . retenu . raisons , attendues ) ;
assert . deepEqual ( nomme . derives . map ( ( { raisons } ) => raisons ) , [ attendues , attendues ] ) ;
assert . deepEqual ( nomme . fautives , examen . fautives ) ;
} ) ;
} ) ;
[ADD] application: observable session, events facade, places and seats
The screens of iteration 3 need state they can subscribe to and commands
that keep the plan sound. The session follows the Svelte store contract,
unblocks a plan, cancels a gesture, keeps the framing, and orders the
warnings of a gesture. The facade lists events, settles the working folder
and runs copy, rename, import and delete. Places cover reservations, titles,
designation and the retained plan: each gesture reads seats as the display
shows them, reserved people keep their table, and a person removed frees
her chair while every other seat stays.
Checked: 1973 node, 127 browser, 60 node-long tests; coverage floors kept.
--- FR ---
[ADD] application : séance observable, façade des événements, places
Les écrans de l'itération 3 exigent un état auquel s'abonner et des
commandes qui gardent le plan sain. La séance suit le contrat des magasins
de Svelte, débloque un plan, annule un geste, garde le cadrage et ordonne
les avertissements d'un geste. La façade liste les événements, règle le
dossier de travail et mène copie, renommage, import et suppression. Les
places couvrent réservations, titres, désignation et plan retenu : chaque
geste lit les sièges comme l'écran les montre, une personne réservée garde
sa table, et une personne retirée libère sa chaise sans déplacer les autres.
Vérifié : 1973 node, 127 navigateur, 60 node-long ; seuils de couverture.
Assisted-by: Claude Opus 5.5
2026-10-07 04:14:24 -04:00
describe ( "avertissements : la réservation que le retenu n'honore plus (§ 5.8, § 14.12)" , ( ) => {
[FIX] plan: named warning, one places state, highlight, table spacing
The unheld-reservation warning names the person and the table number
instead of their identifiers. The places state is computed once per change
and handed to the side panels, not once per panel. A person can be
highlighted from their menu or from the grid, and the view bar names them.
A created table takes a cell a full pitch away from every table, its own
capacity included, so no name list runs over a neighbour's chairs. The
rendering guards fail on an empty walk and also check focus and selection.
Checked: 2692 node, 496 browser, 60 long tests, from the index alone.
--- FR ---
[FIX] plan : avertissement nommé, état des places unique, évidence, pas
L'avertissement de réservation non honorée nomme la personne et le numéro
de la table plutôt que leurs identifiants. L'état des places se calcule une
fois par changement et passe aux panneaux, non une fois par panneau. Une
personne se met en évidence par son menu ou depuis la grille, et la barre
de vue la nomme. Une table créée prend une case à un pas plein de chaque
table, sa capacité comprise : aucune liste ne court sur les chaises
voisines. Les gardes du rendu échouent sur un parcours vide et éprouvent
aussi le focus et la sélection. Vérifié : 2692 node, 496 navigateur, 60.
Assisted-by: Claude Opus 5.5
2026-10-07 15:05:01 -04:00
test ( 'une réservation posée après la retenue, que le retenu ne tient pas : RESERVATION_NON_HONOREE {participant, table, tour, personne, numero}' , ( ) => {
[ADD] application: observable session, events facade, places and seats
The screens of iteration 3 need state they can subscribe to and commands
that keep the plan sound. The session follows the Svelte store contract,
unblocks a plan, cancels a gesture, keeps the framing, and orders the
warnings of a gesture. The facade lists events, settles the working folder
and runs copy, rename, import and delete. Places cover reservations, titles,
designation and the retained plan: each gesture reads seats as the display
shows them, reserved people keep their table, and a person removed frees
her chair while every other seat stays.
Checked: 1973 node, 127 browser, 60 node-long tests; coverage floors kept.
--- FR ---
[ADD] application : séance observable, façade des événements, places
Les écrans de l'itération 3 exigent un état auquel s'abonner et des
commandes qui gardent le plan sain. La séance suit le contrat des magasins
de Svelte, débloque un plan, annule un geste, garde le cadrage et ordonne
les avertissements d'un geste. La façade liste les événements, règle le
dossier de travail et mène copie, renommage, import et suppression. Les
places couvrent réservations, titres, désignation et plan retenu : chaque
geste lit les sièges comme l'écran les montre, une personne réservée garde
sa table, et une personne retirée libère sa chaise sans déplacer les autres.
Vérifié : 1973 node, 127 navigateur, 60 node-long ; seuils de couverture.
Assisted-by: Claude Opus 5.5
2026-10-07 04:14:24 -04:00
const avant = petiteSoiree ( ) ;
const apres = avec ( avant , reserverGrisaille ) ;
assert . deepEqual ( avertissementsApres ( avant , apres ) , [
[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
{ code : 'RESERVATION_NON_HONOREE' , details : { participant : 2 , table : 2 , tour : 1 , personne : 'Grisaille' , numero : 2 } } ,
[ADD] application: observable session, events facade, places and seats
The screens of iteration 3 need state they can subscribe to and commands
that keep the plan sound. The session follows the Svelte store contract,
unblocks a plan, cancels a gesture, keeps the framing, and orders the
warnings of a gesture. The facade lists events, settles the working folder
and runs copy, rename, import and delete. Places cover reservations, titles,
designation and the retained plan: each gesture reads seats as the display
shows them, reserved people keep their table, and a person removed frees
her chair while every other seat stays.
Checked: 1973 node, 127 browser, 60 node-long tests; coverage floors kept.
--- FR ---
[ADD] application : séance observable, façade des événements, places
Les écrans de l'itération 3 exigent un état auquel s'abonner et des
commandes qui gardent le plan sain. La séance suit le contrat des magasins
de Svelte, débloque un plan, annule un geste, garde le cadrage et ordonne
les avertissements d'un geste. La façade liste les événements, règle le
dossier de travail et mène copie, renommage, import et suppression. Les
places couvrent réservations, titres, désignation et plan retenu : chaque
geste lit les sièges comme l'écran les montre, une personne réservée garde
sa table, et une personne retirée libère sa chaise sans déplacer les autres.
Vérifié : 1973 node, 127 navigateur, 60 node-long ; seuils de couverture.
Assisted-by: Claude Opus 5.5
2026-10-07 04:14:24 -04:00
] ) ;
assert . deepEqual ( avertissementsApres ( apres , avec ( apres , ( charge ) => ( charge . participants [ 0 ] . nom = 'Parasol' ) ) ) , [ ] ) ;
} ) ;
[FIX] plan: named warning, one places state, highlight, table spacing
The unheld-reservation warning names the person and the table number
instead of their identifiers. The places state is computed once per change
and handed to the side panels, not once per panel. A person can be
highlighted from their menu or from the grid, and the view bar names them.
A created table takes a cell a full pitch away from every table, its own
capacity included, so no name list runs over a neighbour's chairs. The
rendering guards fail on an empty walk and also check focus and selection.
Checked: 2692 node, 496 browser, 60 long tests, from the index alone.
--- FR ---
[FIX] plan : avertissement nommé, état des places unique, évidence, pas
L'avertissement de réservation non honorée nomme la personne et le numéro
de la table plutôt que leurs identifiants. L'état des places se calcule une
fois par changement et passe aux panneaux, non une fois par panneau. Une
personne se met en évidence par son menu ou depuis la grille, et la barre
de vue la nomme. Une table créée prend une case à un pas plein de chaque
table, sa capacité comprise : aucune liste ne court sur les chaises
voisines. Les gardes du rendu échouent sur un parcours vide et éprouvent
aussi le focus et la sélection. Vérifié : 2692 node, 496 navigateur, 60.
Assisted-by: Claude Opus 5.5
2026-10-07 15:05:01 -04:00
test ( 'la personne se nomme par son nom affiché, et la table par son numéro, distinct de son identifiant' , ( ) => {
const avant = avec ( petiteSoiree ( ) , ( charge ) => {
charge . tables [ 1 ] . numero = 7 ;
charge . participants [ 1 ] . prenom = 'Aubépine' ;
} ) ;
const apres = avec ( avant , reserverGrisaille ) ;
assert . deepEqual ( avertissementsApres ( avant , apres ) , [
{ code : 'RESERVATION_NON_HONOREE' , details : { participant : 2 , table : 2 , tour : 1 , personne : 'Aubépine Grisaille' , numero : 7 } } ,
] ) ;
} ) ;
[ADD] application: observable session, events facade, places and seats
The screens of iteration 3 need state they can subscribe to and commands
that keep the plan sound. The session follows the Svelte store contract,
unblocks a plan, cancels a gesture, keeps the framing, and orders the
warnings of a gesture. The facade lists events, settles the working folder
and runs copy, rename, import and delete. Places cover reservations, titles,
designation and the retained plan: each gesture reads seats as the display
shows them, reserved people keep their table, and a person removed frees
her chair while every other seat stays.
Checked: 1973 node, 127 browser, 60 node-long tests; coverage floors kept.
--- FR ---
[ADD] application : séance observable, façade des événements, places
Les écrans de l'itération 3 exigent un état auquel s'abonner et des
commandes qui gardent le plan sain. La séance suit le contrat des magasins
de Svelte, débloque un plan, annule un geste, garde le cadrage et ordonne
les avertissements d'un geste. La façade liste les événements, règle le
dossier de travail et mène copie, renommage, import et suppression. Les
places couvrent réservations, titres, désignation et plan retenu : chaque
geste lit les sièges comme l'écran les montre, une personne réservée garde
sa table, et une personne retirée libère sa chaise sans déplacer les autres.
Vérifié : 1973 node, 127 navigateur, 60 node-long ; seuils de couverture.
Assisted-by: Claude Opus 5.5
2026-10-07 04:14:24 -04:00
test ( "retenir une proposition qui ne tient pas une réservation : le retenu nul d'avant n'en comptait aucune" , ( ) => {
const avant = avec ( petiteSoiree ( { retenu : false } ) , reserverGrisaille ) ;
const apres = avec ( avant , ( charge ) => {
charge . retenu = { proposition : 1 , siegesAttribues : false , ... plan ( ) } ;
charge . evenement . etat = 'retenu' ;
} ) ;
assert . deepEqual ( avertissementsApres ( avant , apres ) , [
[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
{ code : 'RESERVATION_NON_HONOREE' , details : { participant : 2 , table : 2 , tour : 1 , personne : 'Grisaille' , numero : 2 } } ,
[ADD] application: observable session, events facade, places and seats
The screens of iteration 3 need state they can subscribe to and commands
that keep the plan sound. The session follows the Svelte store contract,
unblocks a plan, cancels a gesture, keeps the framing, and orders the
warnings of a gesture. The facade lists events, settles the working folder
and runs copy, rename, import and delete. Places cover reservations, titles,
designation and the retained plan: each gesture reads seats as the display
shows them, reserved people keep their table, and a person removed frees
her chair while every other seat stays.
Checked: 1973 node, 127 browser, 60 node-long tests; coverage floors kept.
--- FR ---
[ADD] application : séance observable, façade des événements, places
Les écrans de l'itération 3 exigent un état auquel s'abonner et des
commandes qui gardent le plan sain. La séance suit le contrat des magasins
de Svelte, débloque un plan, annule un geste, garde le cadrage et ordonne
les avertissements d'un geste. La façade liste les événements, règle le
dossier de travail et mène copie, renommage, import et suppression. Les
places couvrent réservations, titres, désignation et plan retenu : chaque
geste lit les sièges comme l'écran les montre, une personne réservée garde
sa table, et une personne retirée libère sa chaise sans déplacer les autres.
Vérifié : 1973 node, 127 navigateur, 60 node-long ; seuils de couverture.
Assisted-by: Claude Opus 5.5
2026-10-07 04:14:24 -04:00
] ) ;
} ) ;
test ( "le retenu redevenu cohérent compte ses violations contre un avant fautif, qui n'en comptait aucune" , ( ) => {
const avant = avec ( avec ( petiteSoiree ( ) , reserverGrisaille ) , rendreFautif ) ;
const apres = avec ( avant , ( charge ) => ( charge . retenu . placement [ 0 ] . reserve = [ ] ) ) ;
assert . deepEqual ( avertissementsApres ( avant , apres ) , [
[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
{ code : 'RESERVATION_NON_HONOREE' , details : { participant : 2 , table : 2 , tour : 1 , personne : 'Grisaille' , numero : 2 } } ,
[ADD] application: observable session, events facade, places and seats
The screens of iteration 3 need state they can subscribe to and commands
that keep the plan sound. The session follows the Svelte store contract,
unblocks a plan, cancels a gesture, keeps the framing, and orders the
warnings of a gesture. The facade lists events, settles the working folder
and runs copy, rename, import and delete. Places cover reservations, titles,
designation and the retained plan: each gesture reads seats as the display
shows them, reserved people keep their table, and a person removed frees
her chair while every other seat stays.
Checked: 1973 node, 127 browser, 60 node-long tests; coverage floors kept.
--- FR ---
[ADD] application : séance observable, façade des événements, places
Les écrans de l'itération 3 exigent un état auquel s'abonner et des
commandes qui gardent le plan sain. La séance suit le contrat des magasins
de Svelte, débloque un plan, annule un geste, garde le cadrage et ordonne
les avertissements d'un geste. La façade liste les événements, règle le
dossier de travail et mène copie, renommage, import et suppression. Les
places couvrent réservations, titres, désignation et plan retenu : chaque
geste lit les sièges comme l'écran les montre, une personne réservée garde
sa table, et une personne retirée libère sa chaise sans déplacer les autres.
Vérifié : 1973 node, 127 navigateur, 60 node-long ; seuils de couverture.
Assisted-by: Claude Opus 5.5
2026-10-07 04:14:24 -04:00
] ) ;
} ) ;
test ( "l'ordre du contrat : le manque, puis la dérive, puis la réservation non honorée" , ( ) => {
const avant = petiteSoiree ( ) ;
const apres = avec ( avant , ( charge ) => {
charge . participants . push ( personne ( 5 , 'Ancolie' ) ) ;
charge . prochainsIds . participant = 6 ;
reserverGrisaille ( charge ) ;
} ) ;
assert . deepEqual ( codes ( avertissementsApres ( avant , apres ) ) , [ 'PLACES_MANQUANTES' , 'DERIVE_NOUVELLE' , 'RESERVATION_NON_HONOREE' ] ) ;
} ) ;
} ) ;
describe ( 'avertissements : les violations du retenu hors de la dérive (§ 14.12, § 14.15)' , ( ) => {
test ( 'une réservation non honorée : INVARIANT_VIOLE {proposition, violations}, sans les violations que la dérive nomme' , ( ) => {
const charge = avec ( petiteSoiree ( ) , ( copie ) => {
reserverGrisaille ( copie ) ;
copie . participants [ 3 ] . exclu = true ;
} ) ;
assert . deepEqual ( invariantViole ( charge ) , {
code : 'INVARIANT_VIOLE' ,
details : { proposition : 1 , violations : [ { code : 'RESERVATION' , participant : 2 , table : 2 , tour : 1 } ] } ,
} ) ;
} ) ;
test ( "chaque violation que la dérive nomme se tait : exclus, supprimés, non placés, tables, capacités, tours" , ( ) => {
const cas = [
( charge ) => ( charge . participants [ 3 ] . exclu = true ) ,
( charge ) => charge . participants . pop ( ) ,
( charge ) => {
charge . participants . push ( personne ( 5 , 'Ancolie' ) ) ;
charge . prochainsIds . participant = 6 ;
} ,
( charge ) => {
charge . tables . push ( table ( 3 , null ) ) ;
charge . prochainsIds . table = 4 ;
} ,
( charge ) => {
charge . tables [ 1 ] . sieges = 2 ;
charge . retenu . capacites = [ 2 , 3 ] ;
for ( const tour of charge . retenu . placement ) tour . sieges = [ [ 1 ] , [ 2 , 3 , 4 ] ] ;
} ,
( charge ) => {
charge . tables [ 1 ] . id = 3 ;
charge . prochainsIds . table = 4 ;
} ,
( charge ) => ( charge . evenement . tours = 3 ) ,
] ;
assert . equal ( cas . length , 7 ) ;
for ( const modifier of cas ) assert . equal ( invariantViole ( avec ( petiteSoiree ( ) , modifier ) ) , null , String ( modifier ) ) ;
} ) ;
test ( 'un retenu aux surnuméraires versés en réserve est sans violation ; sans retenu, ou fautif, ou sur une configuration incohérente, rien' , ( ) => {
const reserve = avec ( petiteSoiree ( ) , ( charge ) => {
charge . participants . push ( personne ( 5 , 'Ancolie' ) ) ;
charge . prochainsIds . participant = 6 ;
charge . retenu . participants = [ 1 , 2 , 3 , 4 , 5 ] ;
for ( const tour of charge . retenu . placement ) tour . reserve = [ 5 ] ;
} ) ;
assert . equal ( invariantViole ( reserve ) , null ) ;
assert . equal ( invariantViole ( petiteSoiree ( { retenu : false } ) ) , null ) ;
assert . equal ( invariantViole ( avec ( avec ( petiteSoiree ( ) , reserverGrisaille ) , rendreFautif ) ) , null ) ;
const incoherente = avec ( petiteSoiree ( ) , ( charge ) => {
reserverGrisaille ( charge ) ;
charge . reservations . push ( { participant : 2 , table : 1 , siege : null , portee : 'tour' , tour : 1 } ) ;
} ) ;
assert . equal ( invariantViole ( incoherente ) , null ) ;
} ) ;
} ) ;
[FIX] iteration 3 review: seat targets, gestures, warnings, settings
The wheel no longer zooms during a gesture, so a drop lands under the
ghost. A seat is a target the size of its pitch. A floor drop refuses to
remove an every-tour reservation. A seat reservation the retained plan
does not hold is flagged at the seat, with its swap as remedy. DERIVE names
the person; capacities have an upper bound; Generate waits for a pending
setting; a worker that fails to load shows a refusal; § 5.3 constraints
get their switches. Commands are not re-entrant and a double click does not
confirm. Checked: 2755 node, 550 browser tests, from the index alone.
--- FR ---
[FIX] revue de l'itération 3 : cibles, gestes, avertissements, réglages
La molette ne zoome plus pendant un geste : le dépôt tombe sous le fantôme.
Un siège est une cible de la taille de son pas. Un dépôt sur le sol refuse
de retirer une réservation de tous les tours. Une réservation à un siège
que le retenu ne tient pas est relevée au siège, l'échange en remède. La
dérive nomme la personne ; les capacités ont une borne ; Générer attend un
réglage en cours ; un travailleur qui ne se charge pas donne un refus ; le
§ 5.3 a ses bascules. Les commandes ne sont pas ré-entrantes et un double
clic ne confirme pas. Vérifié : 2755 node, 550 navigateur, index seul.
Assisted-by: Claude Opus 5.5
2026-10-07 17:50:35 -04:00
// Huit personnes, deux tables de quatre, un tour, sièges attribués : le
// retenu assied 1 à 4 aux sièges 1 à 4 de la table 1, et 5 à 8 à la table 2.
function soireeAuxSieges ( ) {
const charge = creerCharge ( { id : 'evt-sieges' , nom : 'Soirée des Chaises' , siegesParDefaut : 4 , tours : 1 } ) ;
const ids = [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ] ;
charge . participants = ids . map ( ( id ) => personne ( id , ` Convive ${ id } ` ) ) ;
charge . tables = [ table ( 1 , null ) , table ( 2 , null ) ] ;
charge . reglages . attribuerSieges = true ;
const plan = { tables : [ 1 , 2 ] , capacites : [ 4 , 4 ] , tours : 1 , participants : ids , placement : [ { sieges : [ [ 1 , 2 , 3 , 4 ] , [ 5 , 6 , 7 , 8 ] ] , reserve : [ ] } ] } ;
charge . propositions = [
{ id : 1 , graine : 1 , arret : 40 , historique : 1 , produitVersion : VERSION . affichee , siegesAttribues : true , ... structuredClone ( plan ) } ,
] ;
charge . retenu = { proposition : 1 , siegesAttribues : true , ... structuredClone ( plan ) } ;
charge . prochainsIds = { participant : 9 , table : 3 , proposition : 2 } ;
charge . evenement . etat = 'retenu' ;
return charge ;
}
const reserverAuSiege = ( participant , idTable , siege ) => ( charge ) =>
charge . reservations . push ( { participant , table : idTable , siege , portee : 'tous' , tour : null } ) ;
const deCode = ( code ) => ( avertissements ) => avertissements . filter ( ( avertissement ) => avertissement . code === code ) ;
describe ( "avertissements : la réservation que le retenu n'honore pas au siège (§ 5.8, § 14.12)" , ( ) => {
test ( 'sièges attribués, une réservation au siège 3 que le retenu donne à un autre, la personne assise à la même table : RESERVATION_NON_HONOREE au siège, avec son occupant' , ( ) => {
const avant = soireeAuxSieges ( ) ;
const apres = avec ( avant , reserverAuSiege ( 2 , 1 , 3 ) ) ;
assert . deepEqual ( avertissementsApres ( avant , apres ) , [
{
code : 'RESERVATION_NON_HONOREE' ,
details : { participant : 2 , table : 1 , tour : 1 , personne : 'Convive 2' , numero : 1 , siege : 3 , occupant : 3 } ,
} ,
] ) ;
assert . deepEqual ( invariantViole ( apres ) , {
code : 'INVARIANT_VIOLE' ,
details : { proposition : 1 , violations : [ { code : 'RESERVATION' , participant : 2 , table : 1 , tour : 1 , siege : 3 , occupant : 3 } ] } ,
} ) ;
assert . deepEqual ( avertissementsApres ( apres , avec ( apres , ( charge ) => ( charge . participants [ 0 ] . nom = 'Parasol' ) ) ) , [ ] ) ;
} ) ;
test ( "la chaise réservée vide : l'occupant est null" , ( ) => {
const avant = avec ( soireeAuxSieges ( ) , ( charge ) => {
charge . retenu . placement [ 0 ] . sieges [ 0 ] = [ 1 , 2 , null , 4 ] ;
charge . retenu . placement [ 0 ] . reserve = [ 3 ] ;
} ) ;
const apres = avec ( avant , reserverAuSiege ( 2 , 1 , 3 ) ) ;
assert . deepEqual ( deCode ( 'RESERVATION_NON_HONOREE' ) ( avertissementsApres ( avant , apres ) ) , [
{
code : 'RESERVATION_NON_HONOREE' ,
details : { participant : 2 , table : 1 , tour : 1 , personne : 'Convive 2' , numero : 1 , siege : 3 , occupant : null } ,
} ,
] ) ;
} ) ;
test ( "tenue à son siège, réservée sans siège, ou sans attribution des sièges : rien au siège" , ( ) => {
const avant = soireeAuxSieges ( ) ;
assert . deepEqual ( avertissementsApres ( avant , avec ( avant , reserverAuSiege ( 3 , 1 , 3 ) ) ) , [ ] ) ;
assert . deepEqual ( avertissementsApres ( avant , avec ( avant , reserverAuSiege ( 2 , 1 , null ) ) ) , [ ] ) ;
const sansAttribution = avec ( avant , ( charge ) => {
charge . reglages . attribuerSieges = false ;
charge . retenu . siegesAttribues = false ;
charge . propositions [ 0 ] . siegesAttribues = false ;
} ) ;
assert . deepEqual ( avertissementsApres ( sansAttribution , avec ( sansAttribution , reserverAuSiege ( 2 , 1 , 3 ) ) ) , [ ] ) ;
assert . equal ( invariantViole ( avec ( sansAttribution , reserverAuSiege ( 2 , 1 , 3 ) ) ) , null ) ;
} ) ;
test ( 'deux réservations au même siège : la première dans l’ ordre canonique le tient, la seconde se lit sans siège' , ( ) => {
const avant = soireeAuxSieges ( ) ;
const apres = avec ( avant , ( charge ) => {
reserverAuSiege ( 3 , 1 , 3 ) ( charge ) ;
reserverAuSiege ( 4 , 1 , 3 ) ( charge ) ;
} ) ;
assert . deepEqual ( avertissementsApres ( avant , apres ) , [ ] ) ;
} ) ;
} ) ;
describe ( "avertissements : la réservation d'une personne que le retenu ne place pas (§ 9, § 14.12)" , ( ) => {
test ( "une personne ajoutée après la retenue et réservée : RESERVATION_NON_HONOREE, en plus de la dérive qui la dit non placée ; INVARIANT_VIOLE la garde" , ( ) => {
const avant = soireeAuxSieges ( ) ;
const apres = avec ( avant , ( charge ) => {
charge . participants . push ( personne ( 9 , 'Ancolie' ) ) ;
charge . prochainsIds . participant = 10 ;
reserverAuSiege ( 9 , 2 , null ) ( charge ) ;
} ) ;
const rendus = avertissementsApres ( avant , apres ) ;
assert . deepEqual ( codes ( rendus ) , [ 'PLACES_MANQUANTES' , 'DERIVE_NOUVELLE' , 'RESERVATION_NON_HONOREE' ] ) ;
assert . deepEqual ( deCode ( 'RESERVATION_NON_HONOREE' ) ( rendus ) [ 0 ] . details , {
participant : 9 ,
table : 2 ,
tour : 1 ,
personne : 'Ancolie' ,
numero : 2 ,
} ) ;
assert . deepEqual ( invariantViole ( apres ) . details . violations , [ { code : 'RESERVATION' , participant : 9 , table : 2 , tour : 1 } ] ) ;
} ) ;
test ( "non réservée, la personne ajoutée ne relève que de la dérive" , ( ) => {
const avant = soireeAuxSieges ( ) ;
const apres = avec ( avant , ( charge ) => {
charge . participants . push ( personne ( 9 , 'Ancolie' ) ) ;
charge . prochainsIds . participant = 10 ;
} ) ;
assert . deepEqual ( deCode ( 'RESERVATION_NON_HONOREE' ) ( avertissementsApres ( avant , apres ) ) , [ ] ) ;
assert . equal ( invariantViole ( apres ) , null ) ;
} ) ;
} ) ;