[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
|
|
|
// © 2026 TechnoLibre (http://www.technolibre.ca)
|
|
|
|
|
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
|
|
|
|
|
|
|
|
|
// Le modèle de l'événement ouvert, pur : ce que ses composants montrent de
|
|
|
|
|
// l'état observable de la séance (§ 8.4, § 8.5, § 8.8, § 8.9, § 9). Le cadre
|
|
|
|
|
// de mode et sa forme, la commande de la place fixe, les états du plan et
|
|
|
|
|
// leurs clés, la reprise de verrou que l'écran offre, et les avertissements
|
|
|
|
|
// dans l'ordre de la séance, chacun avec son remède. Rien n'y est décidé
|
|
|
|
|
// que la séance ne décide déjà : le refus courant, le mode et l'état du plan
|
|
|
|
|
// se lisent dans EtatSeance, jamais se recalculent.
|
|
|
|
|
import { cleDeMode } from '../mode.js';
|
|
|
|
|
|
|
|
|
|
// La forme du cadre pour chaque clé du libellé de mode : un trait différent
|
|
|
|
|
// par mode, pour qu'aucun ne se distingue par la seule couleur (§ 14.5).
|
|
|
|
|
const FORMES = new Map([
|
|
|
|
|
['coquille.mode.lecture', 'tirets'],
|
|
|
|
|
['coquille.mode.ecriture', 'plein'],
|
|
|
|
|
['coquille.mode.bloque', 'double'],
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Libellé et forme du cadre de mode : cle, celle de cleDeMode (mode.js) — le
|
|
|
|
|
* libellé du bandeau — ; forme : lecture (tirets), écriture (trait plein),
|
|
|
|
|
* bloqué (trait double), quel que soit le mode d'un plan bloqué. null sans
|
|
|
|
|
* événement.
|
|
|
|
|
*
|
|
|
|
|
* @param {Object} etatSeance EtatSeance
|
|
|
|
|
* @returns {null|{cle: string, forme: 'tirets'|'plein'|'double'}}
|
|
|
|
|
*/
|
|
|
|
|
export function cadreDeMode(etatSeance) {
|
|
|
|
|
const cle = cleDeMode(etatSeance);
|
|
|
|
|
if (cle === null) return null;
|
|
|
|
|
return { cle, forme: FORMES.get(cle) };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* La commande de la place fixe (§ 8.4, § 8.5, § 8.8) : 'format' quand le
|
|
|
|
|
* refus courant est celui d'un format plus récent — l'explication remplace
|
|
|
|
|
* alors toute commande, plan bloqué compris ; 'debloquer' sur un plan
|
|
|
|
|
* bloqué ; 'lecture' — « Revenir en lecture » — en écriture ; 'modifier'
|
|
|
|
|
* sinon. null sans événement.
|
|
|
|
|
*
|
|
|
|
|
* @param {Object} etatSeance EtatSeance
|
|
|
|
|
* @returns {null|'format'|'debloquer'|'lecture'|'modifier'}
|
|
|
|
|
*/
|
|
|
|
|
export function commandeFixe(etatSeance) {
|
|
|
|
|
if (etatSeance.evenement === null) return null;
|
|
|
|
|
const { refus } = etatSeance;
|
|
|
|
|
if (refus?.code === 'LECTURE_SEULE' && refus.details?.raison === 'FORMAT') return 'format';
|
|
|
|
|
if (etatSeance.evenement.etat === 'bloque') return 'debloquer';
|
|
|
|
|
return etatSeance.mode === 'ecriture' ? 'lecture' : 'modifier';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Les états d'un plan (§ 9), dans l'ordre du spec. */
|
|
|
|
|
export const ETATS_PLAN = Object.freeze(['brouillon', 'propose', 'retenu', 'bloque']);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* La clé du libellé d'un état du plan, evenement.etat.<etat>.
|
|
|
|
|
*
|
|
|
|
|
* @param {string} etat
|
|
|
|
|
* @returns {string}
|
|
|
|
|
* @throws {RangeError} hors d'ETATS_PLAN
|
|
|
|
|
*/
|
|
|
|
|
export function cleEtat(etat) {
|
|
|
|
|
if (!ETATS_PLAN.includes(etat)) throw new RangeError(`état du plan inconnu : ${JSON.stringify(etat)}`);
|
|
|
|
|
return `evenement.etat.${etat}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Le verrou que l'écran offre de reprendre (§ 8.8) : en lecture, celui qui a
|
|
|
|
|
* refusé le dernier passage en écriture quand sa vie ne se prouve pas
|
|
|
|
|
* (vivant null) — un autre poste, un verrou illisible. Un verrou vivant ne
|
|
|
|
|
* se reprend jamais, et celui d'une séance morte, la séance le reprend
|
|
|
|
|
* seule. null sinon.
|
|
|
|
|
*
|
|
|
|
|
* @param {Object} etatSeance EtatSeance
|
|
|
|
|
* @returns {null|{seance: string|null, depuis: string|null, vivant: null}}
|
|
|
|
|
*/
|
|
|
|
|
export function repriseOfferte(etatSeance) {
|
|
|
|
|
const { evenement, mode, verrouAutre } = etatSeance;
|
|
|
|
|
if (evenement === null || mode !== 'lecture' || verrouAutre === null || verrouAutre.vivant !== null) return null;
|
|
|
|
|
return verrouAutre;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Le remède que l'écran joint à un avertissement : rouvrir la version
|
|
|
|
|
// précédente d'un retenu fautif ; ouvrir les propositions devant une dérive,
|
|
|
|
|
// et les tables devant des places manquantes. Les autres n'en ont pas.
|
|
|
|
|
const VERS_PROPOSITIONS = Object.freeze({ geste: 'ouvrirSection', section: 'propositions' });
|
|
|
|
|
const REMEDES = new Map([
|
|
|
|
|
['RETENU_FAUTIF', Object.freeze({ geste: 'ouvrirSecours', source: 'precedent' })],
|
|
|
|
|
['DERIVE', VERS_PROPOSITIONS],
|
|
|
|
|
['DERIVE_NOUVELLE', VERS_PROPOSITIONS],
|
|
|
|
|
['PLACES_MANQUANTES', Object.freeze({ geste: 'ouvrirSection', section: 'tables' })],
|
|
|
|
|
]);
|
|
|
|
|
|
[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
|
|
|
// Le remède d'un avertissement : celui de REMEDES, sauf une réservation que
|
|
|
|
|
// le retenu ne tient pas à son siège (details.siege), que lève l'échange de
|
|
|
|
|
// sa personne avec l'occupant, ou, chaise vide, son déplacement au siège.
|
|
|
|
|
function remedeDe(code, details) {
|
|
|
|
|
if (code === 'RESERVATION_NON_HONOREE' && details.siege !== undefined) {
|
|
|
|
|
const { tour, participant, table, siege, occupant } = details;
|
|
|
|
|
return occupant === null
|
|
|
|
|
? { commande: 'deplacerDansRetenu', arguments: { tour, participant, table, siege } }
|
|
|
|
|
: { commande: 'echangerDansRetenu', arguments: { tour, a: participant, b: occupant } };
|
|
|
|
|
}
|
|
|
|
|
return REMEDES.get(code) ?? 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
|
|
|
// Ce que l'examen de la séance dit de chaque placement que nomme une dérive
|
|
|
|
|
// {ids, retenu} : le retenu d'abord, quand la dérive le nomme, puis chaque
|
|
|
|
|
// proposition de ids, dans l'ordre de ids, avec ses raisons. Un placement
|
|
|
|
|
// que l'examen ne porte pas n'apparaît pas.
|
|
|
|
|
function derivesNommees({ ids = [], retenu = false }, examen) {
|
|
|
|
|
if (examen === null || examen === undefined) return [];
|
|
|
|
|
const nommees = [];
|
|
|
|
|
if (retenu && examen.retenu.raisons.length > 0) nommees.push({ id: 'retenu', raisons: examen.retenu.raisons });
|
|
|
|
|
for (const id of ids) {
|
|
|
|
|
const derive = examen.derives.find((candidate) => candidate.id === id);
|
|
|
|
|
if (derive !== undefined) nommees.push({ id, raisons: derive.raisons });
|
|
|
|
|
}
|
|
|
|
|
return nommees;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Les avertissements à montrer, dans l'ordre d'EtatSeance.avertissements —
|
|
|
|
|
* celui de la séance : ceux de l'Ouvert courant, INVARIANT_VIOLE, ceux du
|
|
|
|
|
* dernier geste, RETOUR_AUTOMATIQUE —, sans en retirer ni en ajouter. Chacun
|
|
|
|
|
* garde son code et ses détails, et reçoit son remède : RETENU_FAUTIF →
|
|
|
|
|
* { geste: 'ouvrirSecours', source: 'precedent' } ; DERIVE et
|
|
|
|
|
* DERIVE_NOUVELLE → { geste: 'ouvrirSection', section: 'propositions' } ;
|
|
|
|
|
* PLACES_MANQUANTES → { geste: 'ouvrirSection', section: 'tables' } ; null
|
|
|
|
|
* pour les autres. PROPOSITIONS_FAUTIVES porte en plus fautives, celles
|
|
|
|
|
* d'EtatSeance.fautives ; DERIVE, derives, ce que l'examen dit de chaque
|
|
|
|
|
* placement qu'elle nomme ({id: number|'retenu', raisons}). Le texte vient
|
|
|
|
|
* de la traduction, sur le code.
|
|
|
|
|
*
|
|
|
|
|
* @param {Object} etatSeance EtatSeance
|
|
|
|
|
* @returns {Array<{code: string, details: Object, remede: Object|null, fautives?: Object[], derives?: Object[]}>}
|
|
|
|
|
*/
|
|
|
|
|
export function avertissementsOrdonnes(etatSeance) {
|
|
|
|
|
return etatSeance.avertissements.map(({ code, details }) => {
|
[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 rendu = { code, details, remede: remedeDe(code, details) };
|
[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
|
|
|
if (code === 'PROPOSITIONS_FAUTIVES') rendu.fautives = etatSeance.fautives;
|
|
|
|
|
if (code === 'DERIVE') rendu.derives = derivesNommees(details, etatSeance.examen);
|
|
|
|
|
return rendu;
|
|
|
|
|
});
|
|
|
|
|
}
|