[ADD] interface: application shell — banner, sections, theme, about
The window showed only a banner; it now holds the application. App mounts
the session, the facade and the generation, settles the working folder
and lists its events before it reports ready, re-reading the list once a
question is answered or a refused determination succeeds. The banner
carries version, mode and the event list command; an open event shows its
sections in their first form; the theme follows the system at each start
and the operator's choice lasts the session; À propos names the build.
Errors are collected for a report, and Electron removes the session's
locks at exit.
Checked: 2189 node, 193 browser, 60 node-long; the Electron start check.
--- FR ---
[ADD] interface : coquille — bandeau, sections, thème, À propos
La fenêtre ne montrait qu'un bandeau ; elle tient désormais l'application.
App monte la séance, la façade et la génération, règle le dossier de
travail et liste ses événements avant de se dire prête, et relit la liste
une fois une question répondue ou une détermination refusée réussie. Le
bandeau porte version, mode et la commande de la liste ; un événement
ouvert montre ses sections dans leur première forme ; le thème suit le
système à chaque démarrage et le choix de l'opérateur vaut pour la séance ;
À propos nomme la construction. Les erreurs se collectent pour un
signalement, et Electron efface à la sortie les verrous de la séance.
Vérifié : 2189 node, 193 navigateur, 60 node-long ; démarrage d'Electron.
Assisted-by: Claude Opus 5.5
2026-10-07 07:22:05 -04:00
// © 2026 TechnoLibre (http://www.technolibre.ca)
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
// L'état de navigation de l'interface : chaque transition, le contrat des
// magasins de Svelte, et l'absence de toute écriture — l'état ne connaît pas
// la séance, et regarder n'écrit jamais (§ 8.2, § 8.4).
import assert from 'node:assert/strict' ;
import { readFileSync } from 'node:fs' ;
import { describe , test } from '../../test/lanceur.js' ;
import { creerEtatUi } from './etat_ui.js' ;
import { SECTION _OUVERTURE , SECTIONS } from './evenement/sections.js' ;
const DEPART = {
section : SECTION _OUVERTURE ,
apropos : false ,
tour : 1 ,
miseEnEvidence : null ,
proposition : null ,
importEnAttente : null ,
listes : true ,
pleinEcran : false ,
selection : null ,
[ADD] interface: event list, open event, participants, import, plan
The working screens of an evening. The home screen lists the events of
the working folder, asks its question, loads demos, copies, renames,
imports and deletes, and shows what the trash holds. An open event frames
its mode, offers Modifier, Débloquer and a confirmed lock takeover, and
shows its warnings. Participants get a grid, a form and a file; an import
goes through a preview and a report; tables, the default and tours have
their screens; the plan draws tables, chairs, empty seats and names.
Checked: 2342 node, 356 browser, 60 node-long; the Electron start check.
--- FR ---
[ADD] interface : liste, événement, participants, import, tables, plan
Les écrans de travail d'une soirée. L'accueil liste les événements du
dossier de travail, pose sa question, charge les démonstrations, copie,
renomme, importe et supprime, et montre ce que tient la corbeille. Un
événement ouvert encadre son mode, offre Modifier, Débloquer et une
reprise de verrou confirmée, et montre ses avertissements. Les
participants ont une grille, un formulaire et une fiche ; un import passe
par un aperçu et un rapport ; tables, défaut et tours ont leurs écrans ;
le plan dessine tables, chaises, sièges vides et noms.
Vérifié : 2342 node, 356 navigateur, 60 node-long ; démarrage d'Electron.
Assisted-by: Claude Opus 5.5
2026-10-07 08:12:27 -04:00
fiche : null ,
[ADD] interface: plan keyboard, tooltip, side panels, person menu, lists
Every gesture the pointer offers on the plan is reachable from the
keyboard (§ 7.3): one stop enters the plan, arrows move between tables and
seats, and the table and place panels choose a seat and keep the focus.
A tooltip names who sits where; a per-person menu moves, assigns or
reserves without dragging; the reserve and the work lists choose a person
to designate on the plan. Escape, a tour change and the executed command
forget the choice, and a click never moves the view.
Checked: 2537 node, 466 browser (one expected failure kept), 60 node-long.
--- FR ---
[ADD] interface : clavier du plan, infobulle, panneaux, menu, listes
Chaque geste que le pointeur offre sur le plan s'atteint au clavier
(§ 7.3) : un seul arrêt entre dans le plan, les flèches vont de table en
table et de siège en siège, et les panneaux de table et de place
choisissent un siège en gardant le focus. Une infobulle nomme qui est
assis où ; un menu par personne déplace, assigne ou réserve sans glisser ;
la réserve et les listes de travail choisissent une personne à désigner
sur le plan. Échap, un changement de tour et la commande exécutée
oublient le choix, et un clic ne déplace jamais la vue.
Vérifié : 2537 node, 466 navigateur (un échec attendu gardé), 60 node-long.
Assisted-by: Claude Opus 5.5
2026-10-07 11:08:59 -04:00
choix : null ,
pose : 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
appartenance : '' ,
[ADD] interface: application shell — banner, sections, theme, about
The window showed only a banner; it now holds the application. App mounts
the session, the facade and the generation, settles the working folder
and lists its events before it reports ready, re-reading the list once a
question is answered or a refused determination succeeds. The banner
carries version, mode and the event list command; an open event shows its
sections in their first form; the theme follows the system at each start
and the operator's choice lasts the session; À propos names the build.
Errors are collected for a report, and Electron removes the session's
locks at exit.
Checked: 2189 node, 193 browser, 60 node-long; the Electron start check.
--- FR ---
[ADD] interface : coquille — bandeau, sections, thème, À propos
La fenêtre ne montrait qu'un bandeau ; elle tient désormais l'application.
App monte la séance, la façade et la génération, règle le dossier de
travail et liste ses événements avant de se dire prête, et relit la liste
une fois une question répondue ou une détermination refusée réussie. Le
bandeau porte version, mode et la commande de la liste ; un événement
ouvert montre ses sections dans leur première forme ; le thème suit le
système à chaque démarrage et le choix de l'opérateur vaut pour la séance ;
À propos nomme la construction. Les erreurs se collectent pour un
signalement, et Electron efface à la sortie les verrous de la séance.
Vérifié : 2189 node, 193 navigateur, 60 node-long ; démarrage d'Electron.
Assisted-by: Claude Opus 5.5
2026-10-07 07:22:05 -04:00
} ;
// Un état et le relevé de ce que reçoit son abonné.
function suivi ( ) {
const ui = creerEtatUi ( ) ;
const recus = [ ] ;
const desabonner = ui . subscribe ( ( etat ) => recus . push ( etat ) ) ;
return { ui , recus , desabonner } ;
}
describe ( 'état de navigation : départ et magasin' , ( ) => {
test ( 'le départ : la section d’ ouverture, le tour 1, rien de choisi, les listes montrées' , ( ) => {
assert . deepEqual ( creerEtatUi ( ) . etat ( ) , DEPART ) ;
} ) ;
test ( 'subscribe rend l’ état tout de suite, puis chaque nouvel état, figé ; désabonné, plus rien' , ( ) => {
const { ui , recus , desabonner } = suivi ( ) ;
assert . deepEqual ( recus , [ DEPART ] ) ;
ui . allerA ( 'tables' ) ;
assert . equal ( recus . length , 2 ) ;
assert . equal ( recus [ 1 ] , ui . etat ( ) ) ;
assert . ok ( Object . isFrozen ( recus [ 1 ] ) ) ;
assert . throws ( ( ) => {
recus [ 1 ] . section = 'plan' ;
} , TypeError ) ;
desabonner ( ) ;
ui . allerA ( 'plan' ) ;
assert . equal ( recus . length , 2 ) ;
} ) ;
test ( 'l’ état ne reçoit rien à sa création : il ne connaît ni la séance, ni la façade, ni un système de fichiers' , ( ) => {
assert . equal ( creerEtatUi . length , 0 ) ;
const source = readFileSync ( new URL ( './etat_ui.js' , import . meta . url ) , 'utf8' ) ;
// Le dernier mot de chaque ligne d'import : le module qu'elle charge.
const imports = source
. split ( '\n' )
. filter ( ( ligne ) => ligne . startsWith ( 'import ' ) )
. map ( ( ligne ) => ligne . slice ( ligne . lastIndexOf ( ' ' ) + 1 ) ) ;
assert . deepEqual ( imports , [ "'./evenement/sections.js';" ] ) ;
} ) ;
} ) ;
describe ( 'état de navigation : chaque transition' , ( ) => {
test ( 'allerA mène à chaque section ; hors de SECTIONS, RangeError sans rien changer' , ( ) => {
const ui = creerEtatUi ( ) ;
assert . ok ( SECTIONS . length > 0 ) ;
for ( const section of SECTIONS ) {
ui . allerA ( section ) ;
assert . deepEqual ( ui . etat ( ) , { ... DEPART , section } ) ;
}
const avant = ui . etat ( ) ;
for ( const faute of [ 'accueil' , 'apropos' , '' , undefined , 'Plan' , 'constructor' ] ) {
assert . throws ( ( ) => ui . allerA ( faute ) , RangeError , String ( faute ) ) ;
}
assert . equal ( ui . etat ( ) , avant ) ;
} ) ;
test ( 'reinitialiser rend l’ état de départ, le panneau « À propos » excepté' , ( ) => {
const ui = creerEtatUi ( ) ;
ui . allerA ( 'generation' ) ;
ui . montrerTour ( 3 ) ;
ui . mettreEnEvidence ( 7 ) ;
ui . montrerProposition ( 2 ) ;
ui . ouvrirImport ( { texte : 'nom\nOmbrelle' , origine : 'liste.csv' } ) ;
ui . basculerListes ( ) ;
ui . basculerPleinEcran ( ) ;
ui . selectionner ( { table : 1 , siege : 2 } ) ;
[ADD] interface: event list, open event, participants, import, plan
The working screens of an evening. The home screen lists the events of
the working folder, asks its question, loads demos, copies, renames,
imports and deletes, and shows what the trash holds. An open event frames
its mode, offers Modifier, Débloquer and a confirmed lock takeover, and
shows its warnings. Participants get a grid, a form and a file; an import
goes through a preview and a report; tables, the default and tours have
their screens; the plan draws tables, chairs, empty seats and names.
Checked: 2342 node, 356 browser, 60 node-long; the Electron start check.
--- FR ---
[ADD] interface : liste, événement, participants, import, tables, plan
Les écrans de travail d'une soirée. L'accueil liste les événements du
dossier de travail, pose sa question, charge les démonstrations, copie,
renomme, importe et supprime, et montre ce que tient la corbeille. Un
événement ouvert encadre son mode, offre Modifier, Débloquer et une
reprise de verrou confirmée, et montre ses avertissements. Les
participants ont une grille, un formulaire et une fiche ; un import passe
par un aperçu et un rapport ; tables, défaut et tours ont leurs écrans ;
le plan dessine tables, chaises, sièges vides et noms.
Vérifié : 2342 node, 356 navigateur, 60 node-long ; démarrage d'Electron.
Assisted-by: Claude Opus 5.5
2026-10-07 08:12:27 -04:00
ui . ouvrirFiche ( 4 ) ;
[ADD] interface: plan keyboard, tooltip, side panels, person menu, lists
Every gesture the pointer offers on the plan is reachable from the
keyboard (§ 7.3): one stop enters the plan, arrows move between tables and
seats, and the table and place panels choose a seat and keep the focus.
A tooltip names who sits where; a per-person menu moves, assigns or
reserves without dragging; the reserve and the work lists choose a person
to designate on the plan. Escape, a tour change and the executed command
forget the choice, and a click never moves the view.
Checked: 2537 node, 466 browser (one expected failure kept), 60 node-long.
--- FR ---
[ADD] interface : clavier du plan, infobulle, panneaux, menu, listes
Chaque geste que le pointeur offre sur le plan s'atteint au clavier
(§ 7.3) : un seul arrêt entre dans le plan, les flèches vont de table en
table et de siège en siège, et les panneaux de table et de place
choisissent un siège en gardant le focus. Une infobulle nomme qui est
assis où ; un menu par personne déplace, assigne ou réserve sans glisser ;
la réserve et les listes de travail choisissent une personne à désigner
sur le plan. Échap, un changement de tour et la commande exécutée
oublient le choix, et un clic ne déplace jamais la vue.
Vérifié : 2537 node, 466 navigateur (un échec attendu gardé), 60 node-long.
Assisted-by: Claude Opus 5.5
2026-10-07 11:08:59 -04:00
ui . choisir ( { genre : 'personne' , participant : 2 , depuis : null } ) ;
ui . poserSurPlace ( { place : { table : 1 , siege : 2 } , refus : { code : 'SANS_EFFET' , details : { } } , avertissements : [ ] } ) ;
[ADD] interface: application shell — banner, sections, theme, about
The window showed only a banner; it now holds the application. App mounts
the session, the facade and the generation, settles the working folder
and lists its events before it reports ready, re-reading the list once a
question is answered or a refused determination succeeds. The banner
carries version, mode and the event list command; an open event shows its
sections in their first form; the theme follows the system at each start
and the operator's choice lasts the session; À propos names the build.
Errors are collected for a report, and Electron removes the session's
locks at exit.
Checked: 2189 node, 193 browser, 60 node-long; the Electron start check.
--- FR ---
[ADD] interface : coquille — bandeau, sections, thème, À propos
La fenêtre ne montrait qu'un bandeau ; elle tient désormais l'application.
App monte la séance, la façade et la génération, règle le dossier de
travail et liste ses événements avant de se dire prête, et relit la liste
une fois une question répondue ou une détermination refusée réussie. Le
bandeau porte version, mode et la commande de la liste ; un événement
ouvert montre ses sections dans leur première forme ; le thème suit le
système à chaque démarrage et le choix de l'opérateur vaut pour la séance ;
À propos nomme la construction. Les erreurs se collectent pour un
signalement, et Electron efface à la sortie les verrous de la séance.
Vérifié : 2189 node, 193 navigateur, 60 node-long ; démarrage d'Electron.
Assisted-by: Claude Opus 5.5
2026-10-07 07:22:05 -04:00
ui . ouvrirApropos ( ) ;
ui . reinitialiser ( ) ;
assert . deepEqual ( ui . etat ( ) , { ... DEPART , apropos : true } ) ;
assert . equal ( ui . etat ( ) . section , SECTION _OUVERTURE ) ;
} ) ;
test ( 'ouvrirApropos et fermerApropos' , ( ) => {
const ui = creerEtatUi ( ) ;
ui . ouvrirApropos ( ) ;
assert . deepEqual ( ui . etat ( ) , { ... DEPART , apropos : true } ) ;
ui . fermerApropos ( ) ;
assert . deepEqual ( ui . etat ( ) , DEPART ) ;
} ) ;
test ( 'montrerTour prend un entier positif ; sinon RangeError' , ( ) => {
const ui = creerEtatUi ( ) ;
ui . montrerTour ( 4 ) ;
assert . equal ( ui . etat ( ) . tour , 4 ) ;
for ( const faute of [ 0 , - 1 , 1.5 , Number . NaN , '2' , null ] ) {
assert . throws ( ( ) => ui . montrerTour ( faute ) , RangeError , String ( faute ) ) ;
}
assert . equal ( ui . etat ( ) . tour , 4 ) ;
} ) ;
test ( 'mettreEnEvidence, montrerProposition et selectionner posent puis retirent leur choix' , ( ) => {
const ui = creerEtatUi ( ) ;
ui . mettreEnEvidence ( 12 ) ;
ui . montrerProposition ( 3 ) ;
ui . selectionner ( { table : 2 , siege : 1 } ) ;
assert . deepEqual ( ui . etat ( ) , { ... DEPART , miseEnEvidence : 12 , proposition : 3 , selection : { table : 2 , siege : 1 } } ) ;
ui . mettreEnEvidence ( null ) ;
ui . montrerProposition ( null ) ;
ui . selectionner ( null ) ;
assert . deepEqual ( ui . etat ( ) , DEPART ) ;
} ) ;
test ( 'ouvrirImport garde le texte en attente et mène à la section import ; prendreImport le rend une fois' , ( ) => {
const ui = creerEtatUi ( ) ;
const enAttente = { texte : 'nom;prénom\nPervenche;Théo' , origine : 'invites.csv' } ;
ui . ouvrirImport ( enAttente ) ;
assert . deepEqual ( ui . etat ( ) , { ... DEPART , section : 'import' , importEnAttente : enAttente } ) ;
assert . deepEqual ( ui . prendreImport ( ) , enAttente ) ;
assert . equal ( ui . etat ( ) . importEnAttente , null ) ;
assert . equal ( ui . prendreImport ( ) , null ) ;
} ) ;
test ( 'basculerListes et basculerPleinEcran alternent' , ( ) => {
const ui = creerEtatUi ( ) ;
ui . basculerListes ( ) ;
ui . basculerPleinEcran ( ) ;
assert . deepEqual ( ui . etat ( ) , { ... DEPART , listes : false , pleinEcran : true } ) ;
ui . basculerListes ( ) ;
ui . basculerPleinEcran ( ) ;
assert . deepEqual ( ui . etat ( ) , DEPART ) ;
} ) ;
[FIX] iteration 3 review: seat targets, gestures, warnings, settings
The wheel no longer zooms during a gesture, so a drop lands under the
ghost. A seat is a target the size of its pitch. A floor drop refuses to
remove an every-tour reservation. A seat reservation the retained plan
does not hold is flagged at the seat, with its swap as remedy. DERIVE names
the person; capacities have an upper bound; Generate waits for a pending
setting; a worker that fails to load shows a refusal; § 5.3 constraints
get their switches. Commands are not re-entrant and a double click does not
confirm. Checked: 2755 node, 550 browser tests, from the index alone.
--- FR ---
[FIX] revue de l'itération 3 : cibles, gestes, avertissements, réglages
La molette ne zoome plus pendant un geste : le dépôt tombe sous le fantôme.
Un siège est une cible de la taille de son pas. Un dépôt sur le sol refuse
de retirer une réservation de tous les tours. Une réservation à un siège
que le retenu ne tient pas est relevée au siège, l'échange en remède. La
dérive nomme la personne ; les capacités ont une borne ; Générer attend un
réglage en cours ; un travailleur qui ne se charge pas donne un refus ; le
§ 5.3 a ses bascules. Les commandes ne sont pas ré-entrantes et un double
clic ne confirme pas. Vérifié : 2755 node, 550 navigateur, index seul.
Assisted-by: Claude Opus 5.5
2026-10-07 17:50:35 -04:00
test ( 'le plein écran ne vaut que pour le plan : quitter le plan le retire, par allerA, ouvrirFiche ou ouvrirImport ; hors du plan, il ne se pose pas' , ( ) => {
const quitters = [
... SECTIONS . filter ( ( section ) => section !== 'plan' ) . map ( ( section ) => [ ` allerA ${ section } ` , ( ui ) => ui . allerA ( section ) ] ) ,
[ 'ouvrirFiche' , ( ui ) => ui . ouvrirFiche ( 4 ) ] ,
[ 'ouvrirImport' , ( ui ) => ui . ouvrirImport ( { texte : 'nom\nOmbrelle' , origine : 'collage' } ) ] ,
] ;
assert . ok ( SECTIONS . includes ( 'plan' ) ) ;
assert . equal ( quitters . length , SECTIONS . length - 1 + 2 ) ;
for ( const [ nom , quitter ] of quitters ) {
const ui = creerEtatUi ( ) ;
ui . allerA ( 'plan' ) ;
ui . basculerPleinEcran ( ) ;
assert . equal ( ui . etat ( ) . pleinEcran , true , nom ) ;
quitter ( ui ) ;
assert . notEqual ( ui . etat ( ) . section , 'plan' , nom ) ;
assert . equal ( ui . etat ( ) . pleinEcran , false , nom ) ;
ui . basculerPleinEcran ( ) ;
assert . equal ( ui . etat ( ) . pleinEcran , false , ` ${ nom } : hors du plan, la bascule ne pose rien ` ) ;
ui . allerA ( 'plan' ) ;
assert . equal ( ui . etat ( ) . pleinEcran , false , ` ${ nom } : revenu au plan, la tête est là ` ) ;
}
} ) ;
[ADD] interface: event list, open event, participants, import, plan
The working screens of an evening. The home screen lists the events of
the working folder, asks its question, loads demos, copies, renames,
imports and deletes, and shows what the trash holds. An open event frames
its mode, offers Modifier, Débloquer and a confirmed lock takeover, and
shows its warnings. Participants get a grid, a form and a file; an import
goes through a preview and a report; tables, the default and tours have
their screens; the plan draws tables, chairs, empty seats and names.
Checked: 2342 node, 356 browser, 60 node-long; the Electron start check.
--- FR ---
[ADD] interface : liste, événement, participants, import, tables, plan
Les écrans de travail d'une soirée. L'accueil liste les événements du
dossier de travail, pose sa question, charge les démonstrations, copie,
renomme, importe et supprime, et montre ce que tient la corbeille. Un
événement ouvert encadre son mode, offre Modifier, Débloquer et une
reprise de verrou confirmée, et montre ses avertissements. Les
participants ont une grille, un formulaire et une fiche ; un import passe
par un aperçu et un rapport ; tables, défaut et tours ont leurs écrans ;
le plan dessine tables, chaises, sièges vides et noms.
Vérifié : 2342 node, 356 navigateur, 60 node-long ; démarrage d'Electron.
Assisted-by: Claude Opus 5.5
2026-10-07 08:12:27 -04:00
test ( 'ouvrirFiche(id) mène à la section participants et ouvre la fiche ; ouvrirFiche(null) la referme, la section reste' , ( ) => {
const ui = creerEtatUi ( ) ;
ui . allerA ( 'plan' ) ;
ui . ouvrirFiche ( 7 ) ;
assert . deepEqual ( ui . etat ( ) , { ... DEPART , section : 'participants' , fiche : 7 } ) ;
ui . allerA ( 'tables' ) ;
ui . ouvrirFiche ( null ) ;
assert . deepEqual ( ui . etat ( ) , { ... DEPART , section : 'tables' , fiche : null } ) ;
} ) ;
test ( 'ouvrirFiche prend un identifiant entier positif ou null ; sinon RangeError sans rien changer' , ( ) => {
const ui = creerEtatUi ( ) ;
ui . ouvrirFiche ( 3 ) ;
const avant = ui . etat ( ) ;
for ( const faute of [ 0 , - 2 , 1.5 , Number . NaN , '3' , undefined ] ) {
assert . throws ( ( ) => ui . ouvrirFiche ( faute ) , RangeError , String ( faute ) ) ;
}
assert . equal ( ui . etat ( ) , avant ) ;
} ) ;
[FIX] iteration 3 review: seat targets, gestures, warnings, settings
The wheel no longer zooms during a gesture, so a drop lands under the
ghost. A seat is a target the size of its pitch. A floor drop refuses to
remove an every-tour reservation. A seat reservation the retained plan
does not hold is flagged at the seat, with its swap as remedy. DERIVE names
the person; capacities have an upper bound; Generate waits for a pending
setting; a worker that fails to load shows a refusal; § 5.3 constraints
get their switches. Commands are not re-entrant and a double click does not
confirm. Checked: 2755 node, 550 browser tests, from the index alone.
--- FR ---
[FIX] revue de l'itération 3 : cibles, gestes, avertissements, réglages
La molette ne zoome plus pendant un geste : le dépôt tombe sous le fantôme.
Un siège est une cible de la taille de son pas. Un dépôt sur le sol refuse
de retirer une réservation de tous les tours. Une réservation à un siège
que le retenu ne tient pas est relevée au siège, l'échange en remède. La
dérive nomme la personne ; les capacités ont une borne ; Générer attend un
réglage en cours ; un travailleur qui ne se charge pas donne un refus ; le
§ 5.3 a ses bascules. Les commandes ne sont pas ré-entrantes et un double
clic ne confirme pas. Vérifié : 2755 node, 550 navigateur, index seul.
Assisted-by: Claude Opus 5.5
2026-10-07 17:50:35 -04:00
test ( 'garderAppartenance tient l’ appartenance collante du formulaire d’ ajout (§ 10.2) : elle survit au changement de section, et un autre événement repart vide' , ( ) => {
const ui = creerEtatUi ( ) ;
ui . allerA ( 'participants' ) ;
ui . garderAppartenance ( 'Club des Merles' ) ;
assert . equal ( ui . etat ( ) . appartenance , 'Club des Merles' ) ;
ui . allerA ( 'plan' ) ;
ui . allerA ( 'participants' ) ;
ui . ouvrirApropos ( ) ;
ui . fermerApropos ( ) ;
assert . equal ( ui . etat ( ) . appartenance , 'Club des Merles' ) ;
ui . reinitialiser ( ) ;
assert . equal ( ui . etat ( ) . appartenance , '' ) ;
for ( const faute of [ null , undefined , 3 , { } ] ) assert . throws ( ( ) => ui . garderAppartenance ( faute ) , RangeError , String ( faute ) ) ;
assert . equal ( ui . etat ( ) . appartenance , '' ) ;
} ) ;
[ADD] interface: event list, open event, participants, import, plan
The working screens of an evening. The home screen lists the events of
the working folder, asks its question, loads demos, copies, renames,
imports and deletes, and shows what the trash holds. An open event frames
its mode, offers Modifier, Débloquer and a confirmed lock takeover, and
shows its warnings. Participants get a grid, a form and a file; an import
goes through a preview and a report; tables, the default and tours have
their screens; the plan draws tables, chairs, empty seats and names.
Checked: 2342 node, 356 browser, 60 node-long; the Electron start check.
--- FR ---
[ADD] interface : liste, événement, participants, import, tables, plan
Les écrans de travail d'une soirée. L'accueil liste les événements du
dossier de travail, pose sa question, charge les démonstrations, copie,
renomme, importe et supprime, et montre ce que tient la corbeille. Un
événement ouvert encadre son mode, offre Modifier, Débloquer et une
reprise de verrou confirmée, et montre ses avertissements. Les
participants ont une grille, un formulaire et une fiche ; un import passe
par un aperçu et un rapport ; tables, défaut et tours ont leurs écrans ;
le plan dessine tables, chaises, sièges vides et noms.
Vérifié : 2342 node, 356 navigateur, 60 node-long ; démarrage d'Electron.
Assisted-by: Claude Opus 5.5
2026-10-07 08:12:27 -04:00
[ADD] interface: application shell — banner, sections, theme, about
The window showed only a banner; it now holds the application. App mounts
the session, the facade and the generation, settles the working folder
and lists its events before it reports ready, re-reading the list once a
question is answered or a refused determination succeeds. The banner
carries version, mode and the event list command; an open event shows its
sections in their first form; the theme follows the system at each start
and the operator's choice lasts the session; À propos names the build.
Errors are collected for a report, and Electron removes the session's
locks at exit.
Checked: 2189 node, 193 browser, 60 node-long; the Electron start check.
--- FR ---
[ADD] interface : coquille — bandeau, sections, thème, À propos
La fenêtre ne montrait qu'un bandeau ; elle tient désormais l'application.
App monte la séance, la façade et la génération, règle le dossier de
travail et liste ses événements avant de se dire prête, et relit la liste
une fois une question répondue ou une détermination refusée réussie. Le
bandeau porte version, mode et la commande de la liste ; un événement
ouvert montre ses sections dans leur première forme ; le thème suit le
système à chaque démarrage et le choix de l'opérateur vaut pour la séance ;
À propos nomme la construction. Les erreurs se collectent pour un
signalement, et Electron efface à la sortie les verrous de la séance.
Vérifié : 2189 node, 193 navigateur, 60 node-long ; démarrage d'Electron.
Assisted-by: Claude Opus 5.5
2026-10-07 07:22:05 -04:00
test ( 'une transition qui ne change rien ne publie pas d’ état neuf' , ( ) => {
const { ui , recus } = suivi ( ) ;
ui . allerA ( SECTION _OUVERTURE ) ;
ui . fermerApropos ( ) ;
ui . mettreEnEvidence ( null ) ;
[ADD] interface: event list, open event, participants, import, plan
The working screens of an evening. The home screen lists the events of
the working folder, asks its question, loads demos, copies, renames,
imports and deletes, and shows what the trash holds. An open event frames
its mode, offers Modifier, Débloquer and a confirmed lock takeover, and
shows its warnings. Participants get a grid, a form and a file; an import
goes through a preview and a report; tables, the default and tours have
their screens; the plan draws tables, chairs, empty seats and names.
Checked: 2342 node, 356 browser, 60 node-long; the Electron start check.
--- FR ---
[ADD] interface : liste, événement, participants, import, tables, plan
Les écrans de travail d'une soirée. L'accueil liste les événements du
dossier de travail, pose sa question, charge les démonstrations, copie,
renomme, importe et supprime, et montre ce que tient la corbeille. Un
événement ouvert encadre son mode, offre Modifier, Débloquer et une
reprise de verrou confirmée, et montre ses avertissements. Les
participants ont une grille, un formulaire et une fiche ; un import passe
par un aperçu et un rapport ; tables, défaut et tours ont leurs écrans ;
le plan dessine tables, chaises, sièges vides et noms.
Vérifié : 2342 node, 356 navigateur, 60 node-long ; démarrage d'Electron.
Assisted-by: Claude Opus 5.5
2026-10-07 08:12:27 -04:00
ui . ouvrirFiche ( null ) ;
[ADD] interface: application shell — banner, sections, theme, about
The window showed only a banner; it now holds the application. App mounts
the session, the facade and the generation, settles the working folder
and lists its events before it reports ready, re-reading the list once a
question is answered or a refused determination succeeds. The banner
carries version, mode and the event list command; an open event shows its
sections in their first form; the theme follows the system at each start
and the operator's choice lasts the session; À propos names the build.
Errors are collected for a report, and Electron removes the session's
locks at exit.
Checked: 2189 node, 193 browser, 60 node-long; the Electron start check.
--- FR ---
[ADD] interface : coquille — bandeau, sections, thème, À propos
La fenêtre ne montrait qu'un bandeau ; elle tient désormais l'application.
App monte la séance, la façade et la génération, règle le dossier de
travail et liste ses événements avant de se dire prête, et relit la liste
une fois une question répondue ou une détermination refusée réussie. Le
bandeau porte version, mode et la commande de la liste ; un événement
ouvert montre ses sections dans leur première forme ; le thème suit le
système à chaque démarrage et le choix de l'opérateur vaut pour la séance ;
À propos nomme la construction. Les erreurs se collectent pour un
signalement, et Electron efface à la sortie les verrous de la séance.
Vérifié : 2189 node, 193 navigateur, 60 node-long ; démarrage d'Electron.
Assisted-by: Claude Opus 5.5
2026-10-07 07:22:05 -04:00
assert . equal ( recus . length , 1 ) ;
} ) ;
} ) ;
[ADD] interface: plan keyboard, tooltip, side panels, person menu, lists
Every gesture the pointer offers on the plan is reachable from the
keyboard (§ 7.3): one stop enters the plan, arrows move between tables and
seats, and the table and place panels choose a seat and keep the focus.
A tooltip names who sits where; a per-person menu moves, assigns or
reserves without dragging; the reserve and the work lists choose a person
to designate on the plan. Escape, a tour change and the executed command
forget the choice, and a click never moves the view.
Checked: 2537 node, 466 browser (one expected failure kept), 60 node-long.
--- FR ---
[ADD] interface : clavier du plan, infobulle, panneaux, menu, listes
Chaque geste que le pointeur offre sur le plan s'atteint au clavier
(§ 7.3) : un seul arrêt entre dans le plan, les flèches vont de table en
table et de siège en siège, et les panneaux de table et de place
choisissent un siège en gardant le focus. Une infobulle nomme qui est
assis où ; un menu par personne déplace, assigne ou réserve sans glisser ;
la réserve et les listes de travail choisissent une personne à désigner
sur le plan. Échap, un changement de tour et la commande exécutée
oublient le choix, et un clic ne déplace jamais la vue.
Vérifié : 2537 node, 466 navigateur (un échec attendu gardé), 60 node-long.
Assisted-by: Claude Opus 5.5
2026-10-07 11:08:59 -04:00
describe ( 'état de navigation : le choix en attente (§ 5.8, § 7.1)' , ( ) => {
const PERSONNE = { genre : 'personne' , participant : 2 , depuis : { table : 4 , siege : 1 } } ;
const DEPUIS _LA _RESERVE = { genre : 'personne' , participant : 3 , depuis : null } ;
const RESERVATION = {
genre : 'reservation' ,
participant : 5 ,
depuis : { table : 3 , siege : 2 } ,
de : { table : 3 , portee : 'tous' , tour : null } ,
} ;
test ( 'choisir pose un choix de chaque genre, figé, puis null l’ oublie' , ( ) => {
const ui = creerEtatUi ( ) ;
for ( const choix of [ PERSONNE , DEPUIS _LA _RESERVE , RESERVATION ] ) {
ui . choisir ( choix ) ;
assert . deepEqual ( ui . etat ( ) , { ... DEPART , choix } ) ;
assert . ok ( Object . isFrozen ( ui . etat ( ) . choix ) ) ;
ui . choisir ( null ) ;
assert . deepEqual ( ui . etat ( ) , DEPART ) ;
}
} ) ;
test ( 'un choix hors du contrat lève RangeError sans rien changer' , ( ) => {
const ui = creerEtatUi ( ) ;
ui . choisir ( PERSONNE ) ;
const avant = ui . etat ( ) ;
const fautes = [
{ genre : 'table' , participant : 2 , depuis : null } ,
{ genre : 'personne' , participant : 0 , depuis : null } ,
{ genre : 'personne' , participant : '2' , depuis : null } ,
{ genre : 'personne' , participant : 2 } ,
{ genre : 'personne' , participant : 2 , depuis : { siege : 1 } } ,
{ genre : 'personne' , participant : 2 , depuis : { table : 1 , siege : 0 } } ,
{ genre : 'reservation' , participant : 5 , depuis : { table : 3 , siege : 2 } } ,
{ genre : 'reservation' , participant : 5 , depuis : null , de : { table : 3 , portee : 'parfois' , tour : null } } ,
undefined ,
] ;
for ( const faute of fautes ) assert . throws ( ( ) => ui . choisir ( faute ) , RangeError , JSON . stringify ( faute ) ) ;
assert . equal ( ui . etat ( ) , avant ) ;
} ) ;
test ( 'montrerTour et selectionner(null) oublient le choix ; selectionner une place le garde' , ( ) => {
const ui = creerEtatUi ( ) ;
ui . choisir ( PERSONNE ) ;
ui . selectionner ( { table : 2 , siege : 3 } ) ;
assert . deepEqual ( ui . etat ( ) . choix , PERSONNE ) ;
ui . selectionner ( null ) ;
assert . equal ( ui . etat ( ) . choix , null ) ;
ui . choisir ( RESERVATION ) ;
ui . montrerTour ( 2 ) ;
assert . equal ( ui . etat ( ) . choix , null ) ;
ui . choisir ( PERSONNE ) ;
ui . montrerTour ( 2 ) ;
assert . equal ( ui . etat ( ) . choix , null , 'le même tour remontré oublie aussi' ) ;
} ) ;
test ( 'poserSurPlace pose un refus ou des avertissements sur une place, puis null les retire ; aucune transition ne reçoit la séance' , ( ) => {
const ui = creerEtatUi ( ) ;
const pose = {
place : { table : 3 , siege : null } ,
refus : { code : 'PERSONNE_EXCLUE' , details : { participant : 1 } } ,
avertissements : [ { code : 'TABLE_GELEE' , details : { table : 3 } } ] ,
} ;
ui . poserSurPlace ( pose ) ;
assert . deepEqual ( ui . etat ( ) , { ... DEPART , pose } ) ;
assert . ok ( Object . isFrozen ( ui . etat ( ) . pose ) ) ;
ui . poserSurPlace ( null ) ;
assert . deepEqual ( ui . etat ( ) , DEPART ) ;
assert . throws ( ( ) => ui . poserSurPlace ( { place : null , refus : null , avertissements : [ ] } ) , RangeError ) ;
assert . equal ( ui . choisir . length , 1 ) ;
assert . equal ( ui . poserSurPlace . length , 1 ) ;
} ) ;
test ( 'montrerTour, montrerProposition et un changement de section retirent la pose ; la section remontrée la garde' , ( ) => {
const ui = creerEtatUi ( ) ;
const pose = { place : { table : 7 , siege : 1 } , refus : { code : 'PLACE_RESERVEE' , details : { participant : 4 } } , avertissements : [ ] } ;
ui . poserSurPlace ( pose ) ;
ui . montrerTour ( 2 ) ;
assert . equal ( ui . etat ( ) . pose , null , 'montrerTour' ) ;
ui . poserSurPlace ( pose ) ;
ui . montrerProposition ( 1 ) ;
assert . equal ( ui . etat ( ) . pose , null , 'montrerProposition' ) ;
ui . allerA ( 'plan' ) ;
ui . poserSurPlace ( pose ) ;
ui . allerA ( 'plan' ) ;
assert . deepEqual ( ui . etat ( ) . pose , pose , 'la même section' ) ;
ui . allerA ( 'tables' ) ;
assert . equal ( ui . etat ( ) . pose , null , 'une autre section' ) ;
} ) ;
} ) ;