diff --git a/GUIDE-USAGE.md b/GUIDE-USAGE.md index a6f8ba0..e606c56 100644 --- a/GUIDE-USAGE.md +++ b/GUIDE-USAGE.md @@ -148,8 +148,8 @@ nulle part. ![La fiche d’une personne : ses étiquettes, le champ d’ajout, une orthographe reçue sous celle de l’événement](doc/captures/04a-etiquettes.png) -*Chaque étiquette est une pastille grise, suivie de sa commande « Retirer ». -Tapez une ou plusieurs étiquettes séparées par une virgule, puis « Ajouter » +*Chaque étiquette est une pastille grise ; en écriture, une petite croix +rouge cerclée la suit, qui la retire. Tapez une ou plusieurs étiquettes séparées par une virgule, puis « Ajouter » ou Entrée. Ici, « FINANCE » s’enregistre sous « Finance », l’orthographe que l’événement porte déjà, et le logiciel le dit sous le champ : une même étiquette ne s’écrit jamais de deux façons. La légende, au-dessus de la diff --git a/README.md b/README.md index 210a8f8..a157b39 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,7 @@ sur les captures. | `--couleur-pastille-texte` | texte | 4,5 | — | — | 14,51 | 19-ajustement | | `--couleur-poignee` | marque | 3 | 7,44 | 07-plan-tables | 11,32 | 07-plan-tables | | `--couleur-refus` | texte | 4,5 | 8,34 | 01b-passer-en-ecriture | 8,14 | 01b-passer-en-ecriture | +| `--couleur-retrait` | marque | 3 | 5,62 | 04a-etiquettes | 5,96 | 04a-etiquettes | | `--couleur-selection` | marque | 3 | 4,57 | 08-animateurs-designes | 4,18 | 08-animateurs-designes | | `--couleur-table-trait` | marque | 3 | 7,44 | 07-plan-tables | 5,59 | 07-plan-tables | | `rgb(24, 33, 47)` | texte | 4,5 | — | — | 16,19 | 22-plan-brouillon | diff --git a/doc/captures/04a-etiquettes.png b/doc/captures/04a-etiquettes.png index fc7e9eb..2091214 100644 Binary files a/doc/captures/04a-etiquettes.png and b/doc/captures/04a-etiquettes.png differ diff --git a/doc/captures/10a-ordre-priorites.png b/doc/captures/10a-ordre-priorites.png index c3f9fa1..61c51fe 100644 Binary files a/doc/captures/10a-ordre-priorites.png and b/doc/captures/10a-ordre-priorites.png differ diff --git a/doc/captures/manifeste.json b/doc/captures/manifeste.json index 67b3249..77d180c 100644 --- a/doc/captures/manifeste.json +++ b/doc/captures/manifeste.json @@ -5,7 +5,7 @@ {"cadre":{"hauteur":700,"largeur":1366},"empreinte":"8d1cd5dd73bd16239e76e7a38c6a227c1a283c5377a6b02ee72a2f28f5f42b9c","etape":"2","etat":"4acf3230b0733a35d33c6baf84b7d4f312e98a5b799147dcea6b3bb5d4f082d5","facteurEchelle":1,"fichier":"02b-import-cp1252","graine":null,"image":{"hauteur":700,"largeur":1366,"sha256":"173368cf8cf2bf154e6ddba1fd46d7ae149dd6e436e2bd000a2e7630042112c5"},"instant":"2026-10-06T19:00:00-04:00","mode":"chromium-headless-shell","pdf":null,"source":"navigateur","styles":{"clair":[{"jeton":"--couleur-bandeau-fond","ratio":15.94,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":6.09,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":9.05,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte","ratio":14.16,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":5.81,"role":"texte","seuil":4.5}],"sombre":[{"jeton":"--couleur-action-texte","ratio":8.45,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-fond","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":7.6,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte","ratio":11.07,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":6.72,"role":"texte","seuil":4.5}]},"texte":["Gestion table tournante Libre","construction de documentation","documentation","Écriture","Liste des événements","Clair","Sombre","À propos","Écriture","Chaque geste s’enregistre aussitôt ; « Revenir en lecture » arrête l’écriture.","Revenir en lecture","Écriture","Défaire","État du plan","Brouillon","Proposé","Retenu","Bloqué","Imprimer…","Soirée d’essai","0 participant","1 table · 8 sièges","4 tours","Préparer","Import","Participants","Tables et tours","Placer","Plan","Diagnostic","Génération","Propositions","Produire","Badges","Import","Choisissez un fichier CSV ou déposez-le sur la fenêtre ; une plage collée dans la grille des participants arrive ici. Rien ne s'importe avant l'aperçu.","Choisir un fichier CSV…","Contenu du fichier","Participants","Objectifs de table","Aperçu","Mode de l'import","Ajouter","Mettre à jour","Remplacer","Fichier","participants_cas_limites_cp1252.csv","Encodage","windows-1252","Séparateur","virgule","Colonnes","En-tête","Devient","Nom","nom","Prénom","prénom","Organisation","appartenance","Courriel","courriel","Rôle","titre pressenti","Exclu","exclu","Notes","notes","Étiquettes","étiquettes","Téléphone","non reconnue : ignorée","13","lignes valides","Ligne","nom","prénom","appartenance","courriel","titre pressenti","exclu","notes","étiquettes","2","Lanterneau","Basile","Chœur de l’Anse","basile.lanterneau@exemple.test","animation","Finance","Montréal","3","Pommerel","Capucine","Société des Hérons","capucine.pommerel@exemple.test","bénévole","ressources humaines","4","Marbrelle","Gaspard","Cercle des Saules","animation","Finance","5","Sablonnet","Ysaline","Chœur de l’Anse","6","Givrecœur","Anselme","anselme.givrecoeur@exemple.test","7","Tourterel","Héloïse","Cercle des Saules","oui","S’est désistée la veille","8","Fenouillet","Côme","Société des Hérons","végétarien; sans noix, ni arachides","9","Coudraie","Apolline","Chœur de l’Anse","Arrive après le premier tour repart avant le dessert","10","Lanterneau","Basile","Chœur de l’Anse","11","Brunœil","Cercle des Saules","brunoeil@exemple.test","12","Ormelin","Faustine","Société des Hérons","14","Ardoisier","Edgar","Société des Hérons","15","Cressonnier","Bertille","Cercle des Saules","Refus","Lignes refusées","Ligne","Motif","13","Le nom manque.","Information","Orthographes fondues en une appartenance","affichée :","Société des Hérons","Société des Hérons","3","lignes","Societe des herons","1","ligne","Information","Orthographes fondues en une étiquette","retenue :","Finance","orthographes :","Finance",",","finance","—","1","ligne réécrite","Avis","Doublons signalés, jamais fusionnés","lignes","2","10","Importer","Enregistrer les lignes refusées","Fermer l'aperçu"],"theme":"clair","versionReelle":"2026.10.05.01"}, {"cadre":{"hauteur":700,"largeur":1366},"empreinte":"9a4259c93a4224fa2a394b4d3a4b748eae52bc126689428d8b8aca8b7e4ef8de","etape":"2","etat":"bb0e3ac780cce888c1e69ea95d1a96a7b77bbd49e2ce6e815ddbcb8e4bdfc65f","facteurEchelle":1,"fichier":"03-import-rapport","graine":null,"image":{"hauteur":700,"largeur":1366,"sha256":"59a799f28e555e3571f62ea55379902f62f225c5ed8c9ef926d6a05611fc0698"},"instant":"2026-10-06T19:00:00-04:00","mode":"chromium-headless-shell","pdf":null,"source":"navigateur","styles":{"clair":[{"jeton":"--couleur-avis","ratio":6.58,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-fond","ratio":15.94,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":6.09,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":9.05,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte","ratio":14.16,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":5.81,"role":"texte","seuil":4.5}],"sombre":[{"jeton":"--couleur-action-texte","ratio":8.45,"role":"texte","seuil":4.5},{"jeton":"--couleur-avis","ratio":8.93,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-fond","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":7.6,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte","ratio":11.07,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":6.72,"role":"texte","seuil":4.5}]},"texte":["Gestion table tournante Libre","construction de documentation","documentation","Écriture","Liste des événements","Clair","Sombre","À propos","Écriture","Chaque geste s’enregistre aussitôt ; « Revenir en lecture » arrête l’écriture.","Revenir en lecture","Écriture","Défaire","État du plan","Brouillon","Proposé","Retenu","Bloqué","Imprimer…","Soirée d’essai","13 participants","1 table · 8 sièges","4 tours","Préparer","Import","Participants","Tables et tours","Placer","Plan","Diagnostic","Génération","Propositions","Produire","Badges","Il manque 4 places à chaque tour pour asseoir toutes les personnes : ajouter des tables ou des sièges, ou exclure des personnes, rend la génération possible.","Ouvrir les tables","Import","Choisissez un fichier CSV ou déposez-le sur la fenêtre ; une plage collée dans la grille des participants arrive ici. Rien ne s'importe avant l'aperçu.","Choisir un fichier CSV…","Contenu du fichier","Participants","Objectifs de table","Rapport d'import","13","personnes ajoutées","0","personne mise à jour","0","personne inchangée","0","personne retirée","0","réservation retirée","Fichier","participants_cas_limites.csv","Encodage","UTF-8 avec marque","Séparateur","point-virgule","Colonnes","En-tête","Devient","Nom","nom","Prénom","prénom","Organisation","appartenance","Courriel","courriel","Rôle","titre pressenti","Exclu","exclu","Notes","notes","Étiquettes","étiquettes","Téléphone","non reconnue : ignorée","13","lignes valides","Ligne","nom","prénom","appartenance","courriel","titre pressenti","exclu","notes","étiquettes","2","Lanterneau","Basile","Chœur de l’Anse","basile.lanterneau@exemple.test","animation","Finance","Montréal","3","Pommerel","Capucine","Société des Hérons","capucine.pommerel@exemple.test","bénévole","ressources humaines","4","Marbrelle","Gaspard","Cercle des Saules","animation","Finance","5","Sablonnet","Ysaline","Chœur de l’Anse","6","Givrecœur","Anselme","anselme.givrecoeur@exemple.test","7","Tourterel","Héloïse","Cercle des Saules","oui","S’est désistée la veille","8","Fenouillet","Côme","Société des Hérons","végétarien; sans noix, ni arachides","9","Coudraie","Apolline","Chœur de l’Anse","Arrive après le premier tour repart avant le dessert","10","Lanterneau","Basile","Chœur de l’Anse","11","Brunœil","Cercle des Saules","brunoeil@exemple.test","12","Ormelin","Faustine","Société des Hérons","14","Ardoisier","Edgar","Société des Hérons","15","Cressonnier","Bertille","Cercle des Saules","Refus","Lignes refusées","Ligne","Motif","13","Le nom manque.","Information","Orthographes fondues en une appartenance","affichée :","Société des Hérons","Société des Hérons","3","lignes","Societe des herons","1","ligne","Information","Orthographes fondues en une étiquette","retenue :","Finance","orthographes :","Finance",",","finance","—","1","ligne réécrite","Avis","Doublons signalés, jamais fusionnés","lignes","2","10","Enregistrer les lignes refusées","Fermer le rapport"],"theme":"clair","versionReelle":"2026.10.05.01"}, {"cadre":{"hauteur":700,"largeur":1366},"empreinte":"2bd426f6ae2820694168b8af62ef8241d6d81e72859318e004ddbfb828d4ff18","etape":"3","etat":"c7c7f05d94c8fcfe6d779172d546d5c73cb2077e9d94ef1348542678c9383c6d","facteurEchelle":1,"fichier":"04-grille-participants","graine":null,"image":{"hauteur":700,"largeur":1366,"sha256":"374cf28f7d60cb013b481367d079fd67511696aa5d1f42eb3929134144664b79"},"instant":"2026-10-06T19:00:00-04:00","mode":"chromium-headless-shell","pdf":null,"source":"navigateur","styles":{"clair":[{"jeton":"--couleur-avis","ratio":6.58,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-fond","ratio":15.94,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":6.09,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":9.05,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte","ratio":13.81,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":5.81,"role":"texte","seuil":4.5}],"sombre":[{"jeton":"--couleur-action-texte","ratio":8.45,"role":"texte","seuil":4.5},{"jeton":"--couleur-avis","ratio":8.93,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-fond","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":7.6,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte","ratio":11.07,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":6.72,"role":"texte","seuil":4.5}]},"texte":["Gestion table tournante Libre","construction de documentation","documentation","Écriture","Liste des événements","Clair","Sombre","À propos","Écriture","Chaque geste s’enregistre aussitôt ; « Revenir en lecture » arrête l’écriture.","Revenir en lecture","Écriture","Défaire","État du plan","Brouillon","Proposé","Retenu","Bloqué","Imprimer…","Soirée d’essai","13 participants","1 table · 8 sièges","4 tours","Préparer","Import","Participants","Tables et tours","Placer","Plan","Diagnostic","Génération","Propositions","Produire","Badges","Il manque 4 places à chaque tour pour asseoir toutes les personnes : ajouter des tables ou des sièges, ou exclure des personnes, rend la génération possible.","Ouvrir les tables","Participants","13 personnes · 12 à placer · 1 exclue · 3 appartenances","Exporter la liste","Ajouter une personne","Nom","Prénom","Appartenance","Courriel","Titre pressenti","Notes","Ajouter","Filtrer","Grouper par","Aucun groupement","Nom","Prénom","Appartenance","Courriel","Titre pressenti","Exclusion","Notes","Statut","13 participants","Cercle des Saules · 4","Chœur de l’Anse · 4","Société des Hérons · 4","bénévole · 1","Finance · 2","Montréal · 1","ressources humaines · 1","Nom","(décroissant)","Prénom","Appartenance","Courriel","Titre pressenti","Exclusion","Notes","Actions","Tourterel","Héloïse","Cercle des Saules","exclue","S’est désistée la veille","Fiche","Voir sur le plan","Réintégrer","Supprimer","Sablonnet","Ysaline","Chœur de l’Anse","présente","Fiche","Voir sur le plan","Exclure","Supprimer","Pommerel","Capucine","Société des Hérons","capucine.pommerel@exemple.test","présente","Fiche","Voir sur le plan","Exclure","Supprimer","Ormelin","Faustine","Société des Hérons","présente","Fiche","Voir sur le plan","Exclure","Supprimer","Marbrelle","Gaspard","Cercle des Saules","ondine@exemple.test","animation","présente","Fiche","Voir sur le plan","Exclure","Supprimer","Lanterneau","Basile","Chœur de l’Anse","basile.lanterneau@exemple.test","animation","présente","Fiche","Voir sur le plan","Exclure","Supprimer","Lanterneau","Basile","Chœur de l’Anse","présente","Fiche","Voir sur le plan","Exclure","Supprimer","Givrecœur","Anselme","anselme.givrecoeur@exemple.test","présente","Fiche","Voir sur le plan","Exclure","Supprimer","Fenouillet","Côme","Société des Hérons","présente","végétarien; sans noix, ni arachides","Fiche","Voir sur le plan","Exclure","Supprimer","Cressonnier","Bertille","Cercle des Saules","présente","Fiche","Voir sur le plan","Exclure","Supprimer","Coudraie","Apolline","Chœur de l’Anse","présente","Arrive après le premier tour repart avant le dessert","Fiche","Voir sur le plan","Exclure","Supprimer","Brunœil","Cercle des Saules","brunoeil@exemple.test","présente","Fiche","Voir sur le plan","Exclure","Supprimer","Ardoisier","Edgar","Société des Hérons","présente","Fiche","Voir sur le plan","Exclure","Supprimer"],"theme":"clair","versionReelle":"2026.10.05.01"}, -{"cadre":{"hauteur":700,"largeur":1366},"empreinte":"f76f0fa068e802fcefb04ba014493d6749be4d527ac530a56985d298889a9868","etape":"3","etat":"dcbae0e085e28a315ba04e33f0c0bac0d9ca860235827a034a184eddad032f1c","facteurEchelle":1,"fichier":"04a-etiquettes","graine":null,"image":{"hauteur":700,"largeur":1366,"sha256":"205a66717913e4e97c4917d22b2f8b782beb0a15741b50cdfa2e6b72bd29c2a8"},"instant":"2026-10-06T19:00:00-04:00","mode":"chromium-headless-shell","pdf":null,"source":"navigateur","styles":{"clair":[{"jeton":"--couleur-avis","ratio":6.58,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-fond","ratio":15.94,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":6.09,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":9.05,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte","ratio":14.16,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":5.81,"role":"texte","seuil":4.5}],"sombre":[{"jeton":"--couleur-action-texte","ratio":8.45,"role":"texte","seuil":4.5},{"jeton":"--couleur-avis","ratio":8.93,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-fond","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":7.6,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte","ratio":11.07,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":6.72,"role":"texte","seuil":4.5}]},"texte":["Gestion table tournante Libre","construction de documentation","documentation","Écriture","Liste des événements","Clair","Sombre","À propos","Écriture","Chaque geste s’enregistre aussitôt ; « Revenir en lecture » arrête l’écriture.","Revenir en lecture","Écriture","Défaire","État du plan","Brouillon","Proposé","Retenu","Bloqué","Imprimer…","Soirée d’essai","13 participants","1 table · 8 sièges","4 tours","Préparer","Import","Participants","Tables et tours","Placer","Plan","Diagnostic","Génération","Propositions","Produire","Badges","« FINANCE » est enregistrée sous l'orthographe que l'événement porte déjà : « Finance ».","Participants","13 personnes · 12 à placer · 1 exclue · 3 appartenances","Exporter la liste","Ajouter une personne","Nom","Prénom","Appartenance","Courriel","Titre pressenti","Notes","Ajouter","Filtrer","Grouper par","Aucun groupement","Nom","Prénom","Appartenance","Courriel","Titre pressenti","Exclusion","Notes","Statut","13 participants","Cercle des Saules · 4","Chœur de l’Anse · 4","Société des Hérons · 4","bénévole · 1","Finance · 3","Montréal · 1","ressources humaines · 1","Nom","(décroissant)","Prénom","Appartenance","Courriel","Titre pressenti","Exclusion","Notes","Actions","Tourterel","Héloïse","Cercle des Saules","exclue","S’est désistée la veille","Fiche","Voir sur le plan","Réintégrer","Supprimer","Sablonnet","Ysaline","Chœur de l’Anse","présente","Fiche","Voir sur le plan","Exclure","Supprimer","Pommerel","Capucine","Société des Hérons","capucine.pommerel@exemple.test","présente","Fiche","Voir sur le plan","Exclure","Supprimer","Ormelin","Faustine","Société des Hérons","présente","Fiche","Voir sur le plan","Exclure","Supprimer","Marbrelle","Gaspard","Cercle des Saules","ondine@exemple.test","animation","présente","Fiche","Voir sur le plan","Exclure","Supprimer","Lanterneau","Basile","Chœur de l’Anse","basile.lanterneau@exemple.test","animation","présente","Fiche","Voir sur le plan","Exclure","Supprimer","Lanterneau","Basile","Chœur de l’Anse","présente","Fiche","Voir sur le plan","Exclure","Supprimer","Givrecœur","Anselme","anselme.givrecoeur@exemple.test","présente","Fiche","Voir sur le plan","Exclure","Supprimer","Fenouillet","Côme","Société des Hérons","présente","végétarien; sans noix, ni arachides","Fiche","Voir sur le plan","Exclure","Supprimer","Cressonnier","Bertille","Cercle des Saules","présente","Fiche","Voir sur le plan","Exclure","Supprimer","Coudraie","Apolline","Chœur de l’Anse","présente","Arrive après le premier tour repart avant le dessert","Fiche","Voir sur le plan","Exclure","Supprimer","Brunœil","Cercle des Saules","brunoeil@exemple.test","présente","Fiche","Voir sur le plan","Exclure","Supprimer","Ardoisier","Edgar","Société des Hérons","présente","Fiche","Voir sur le plan","Exclure","Supprimer","Capucine Pommerel","Fermer la fiche","Nom","Pommerel","Prénom","Capucine","Appartenance","Société des Hérons","Courriel","capucine.pommerel@exemple.test","Titre pressenti","—","Notes","—","Statut","mobile","Étiquettes","Finance","Retirer « Finance »","bénévole","Retirer « bénévole »","ressources humaines","Retirer « ressources humaines »","Ajouter des étiquettes, séparées par une virgule","Ajouter","« FINANCE » est enregistrée sous l'orthographe que l'événement porte déjà : « Finance ».","Réservations","Aucune réservation.","Aucun placement retenu ne la nomme."],"theme":"clair","versionReelle":"2026.10.05.01"}, +{"cadre":{"hauteur":700,"largeur":1366},"empreinte":"4cc57ba3dc46cb364c04e755994f0cbc51b85c4937b6e01e2011d0560e2ec25b","etape":"3","etat":"dcbae0e085e28a315ba04e33f0c0bac0d9ca860235827a034a184eddad032f1c","facteurEchelle":1,"fichier":"04a-etiquettes","graine":null,"image":{"hauteur":700,"largeur":1366,"sha256":"1458da2ac38bd2a9651525ca7eeb413e288d2a220a5febeb504ee28534729e24"},"instant":"2026-10-06T19:00:00-04:00","mode":"chromium-headless-shell","pdf":null,"source":"navigateur","styles":{"clair":[{"jeton":"--couleur-avis","ratio":6.58,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-fond","ratio":15.94,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":6.09,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":9.05,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte","ratio":14.16,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":5.81,"role":"texte","seuil":4.5},{"jeton":"--couleur-retrait","ratio":5.62,"role":"marque","seuil":3}],"sombre":[{"jeton":"--couleur-action-texte","ratio":8.45,"role":"texte","seuil":4.5},{"jeton":"--couleur-avis","ratio":8.93,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-fond","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":7.6,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte","ratio":11.07,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":6.72,"role":"texte","seuil":4.5},{"jeton":"--couleur-retrait","ratio":5.96,"role":"marque","seuil":3}]},"texte":["Gestion table tournante Libre","construction de documentation","documentation","Écriture","Liste des événements","Clair","Sombre","À propos","Écriture","Chaque geste s’enregistre aussitôt ; « Revenir en lecture » arrête l’écriture.","Revenir en lecture","Écriture","Défaire","État du plan","Brouillon","Proposé","Retenu","Bloqué","Imprimer…","Soirée d’essai","13 participants","1 table · 8 sièges","4 tours","Préparer","Import","Participants","Tables et tours","Placer","Plan","Diagnostic","Génération","Propositions","Produire","Badges","« FINANCE » est enregistrée sous l'orthographe que l'événement porte déjà : « Finance ».","Participants","13 personnes · 12 à placer · 1 exclue · 3 appartenances","Exporter la liste","Ajouter une personne","Nom","Prénom","Appartenance","Courriel","Titre pressenti","Notes","Ajouter","Filtrer","Grouper par","Aucun groupement","Nom","Prénom","Appartenance","Courriel","Titre pressenti","Exclusion","Notes","Statut","13 participants","Cercle des Saules · 4","Chœur de l’Anse · 4","Société des Hérons · 4","bénévole · 1","Finance · 3","Montréal · 1","ressources humaines · 1","Nom","(décroissant)","Prénom","Appartenance","Courriel","Titre pressenti","Exclusion","Notes","Actions","Tourterel","Héloïse","Cercle des Saules","exclue","S’est désistée la veille","Fiche","Voir sur le plan","Réintégrer","Supprimer","Sablonnet","Ysaline","Chœur de l’Anse","présente","Fiche","Voir sur le plan","Exclure","Supprimer","Pommerel","Capucine","Société des Hérons","capucine.pommerel@exemple.test","présente","Fiche","Voir sur le plan","Exclure","Supprimer","Ormelin","Faustine","Société des Hérons","présente","Fiche","Voir sur le plan","Exclure","Supprimer","Marbrelle","Gaspard","Cercle des Saules","ondine@exemple.test","animation","présente","Fiche","Voir sur le plan","Exclure","Supprimer","Lanterneau","Basile","Chœur de l’Anse","basile.lanterneau@exemple.test","animation","présente","Fiche","Voir sur le plan","Exclure","Supprimer","Lanterneau","Basile","Chœur de l’Anse","présente","Fiche","Voir sur le plan","Exclure","Supprimer","Givrecœur","Anselme","anselme.givrecoeur@exemple.test","présente","Fiche","Voir sur le plan","Exclure","Supprimer","Fenouillet","Côme","Société des Hérons","présente","végétarien; sans noix, ni arachides","Fiche","Voir sur le plan","Exclure","Supprimer","Cressonnier","Bertille","Cercle des Saules","présente","Fiche","Voir sur le plan","Exclure","Supprimer","Coudraie","Apolline","Chœur de l’Anse","présente","Arrive après le premier tour repart avant le dessert","Fiche","Voir sur le plan","Exclure","Supprimer","Brunœil","Cercle des Saules","brunoeil@exemple.test","présente","Fiche","Voir sur le plan","Exclure","Supprimer","Ardoisier","Edgar","Société des Hérons","présente","Fiche","Voir sur le plan","Exclure","Supprimer","Capucine Pommerel","Fermer la fiche","Nom","Pommerel","Prénom","Capucine","Appartenance","Société des Hérons","Courriel","capucine.pommerel@exemple.test","Titre pressenti","—","Notes","—","Statut","mobile","Étiquettes","Finance","bénévole","ressources humaines","Ajouter des étiquettes, séparées par une virgule","Ajouter","« FINANCE » est enregistrée sous l'orthographe que l'événement porte déjà : « Finance ».","Réservations","Aucune réservation.","Aucun placement retenu ne la nomme."],"theme":"clair","versionReelle":"2026.10.05.01"}, {"cadre":{"hauteur":700,"largeur":1366},"empreinte":"68a9cd24e436c634c0b1eaa36cac48b353489149f23eabe85771d6df3c0ffbb3","etape":"4","etat":"9066550bdb9c9829148a7a50e177a784cd907a0ad7c8b18460a8c911589c71d8","facteurEchelle":1,"fichier":"05-tables-etat","graine":null,"image":{"hauteur":700,"largeur":1366,"sha256":"b5e53613327467ae7600cdb222ac18078ece3d70f01f189973cba1fa385693a0"},"instant":"2026-10-06T19:00:00-04:00","mode":"chromium-headless-shell","pdf":null,"source":"navigateur","styles":{"clair":[{"jeton":"--couleur-bandeau-fond","ratio":15.94,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":6.09,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":9.05,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte","ratio":14.1,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":5.81,"role":"texte","seuil":4.5}],"sombre":[{"jeton":"--couleur-action-texte","ratio":8.45,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-fond","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":7.6,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte","ratio":11.07,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":6.72,"role":"texte","seuil":4.5}]},"texte":["Gestion table tournante Libre","construction de documentation","documentation","Écriture","Liste des événements","Clair","Sombre","À propos","Écriture","Chaque geste s’enregistre aussitôt ; « Revenir en lecture » arrête l’écriture.","Revenir en lecture","Écriture","Défaire","État du plan","Brouillon","Proposé","Retenu","Bloqué","Imprimer…","Soirée d’essai","13 participants","2 tables · 16 sièges","4 tours","Préparer","Import","Participants","Tables et tours","Placer","Plan","Diagnostic","Génération","Propositions","Produire","Badges","Tables et tours","Places par défaut","Nombre de tours","Règle par défaut des objectifs","au moins une (OU)","toutes (ET)","Tables","2 tables · 16 sièges","Ajouter une table ronde","Ajouter une table carrée","Exporter les objectifs","Table","1","Ronde","Suit le défaut","Places","Supprimer","Objectif de la soirée","Règle de l'objectif","selon le réglage général (au moins une)","au moins une (OU)","toutes (ET)","Objectifs par tour","Table","2","Ronde","Surchargée","Places","Réaligner","Supprimer","Objectif de la soirée","Règle de l'objectif","selon le réglage général (au moins une)","au moins une (OU)","toutes (ET)","Objectifs par tour"],"theme":"clair","versionReelle":"2026.10.05.01"}, {"cadre":{"hauteur":700,"largeur":1366},"empreinte":"9bc27323c549026db9d7d4f8b45518bd265146e236b38b32f8f08120c2f38d9f","etape":"4","etat":"aec49e481eef9af563b3ed2e8772b1f03cb81ccc36d1633a6591fb8da44017de","facteurEchelle":1,"fichier":"05a-objectifs-tables","graine":null,"image":{"hauteur":700,"largeur":1366,"sha256":"020580868339aff7d7df7eed96e81dd5ff33d0b45c3db9ab4edc485b026b09d6"},"instant":"2026-10-06T19:00:00-04:00","mode":"chromium-headless-shell","pdf":null,"source":"navigateur","styles":{"clair":[{"jeton":"--couleur-avis","ratio":6.58,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-fond","ratio":15.94,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":6.09,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":9.05,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte","ratio":14.1,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":5.81,"role":"texte","seuil":4.5}],"sombre":[{"jeton":"--couleur-action-texte","ratio":8.45,"role":"texte","seuil":4.5},{"jeton":"--couleur-avis","ratio":8.93,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-fond","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":7.6,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte","ratio":11.07,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":6.72,"role":"texte","seuil":4.5}]},"texte":["Gestion table tournante Libre","construction de documentation","documentation","Écriture","Liste des événements","Clair","Sombre","À propos","Écriture","Chaque geste s’enregistre aussitôt ; « Revenir en lecture » arrête l’écriture.","Revenir en lecture","Écriture","Défaire","État du plan","Brouillon","Proposé","Retenu","Bloqué","Imprimer…","Soirée d’essai","13 participants","2 tables · 16 sièges","4 tours","Préparer","Import","Participants","Tables et tours","Placer","Plan","Diagnostic","Génération","Propositions","Produire","Badges","Aucune personne présente ne correspond à l'objectif au tour 1 : la table ne servira personne à ce titre.","Tables et tours","Places par défaut","Nombre de tours","Règle par défaut des objectifs","au moins une (OU)","toutes (ET)","Tables","2 tables · 16 sièges","Ajouter une table ronde","Ajouter une table carrée","Exporter les objectifs","Table","1","Ronde","Suit le défaut","Places","Supprimer","Objectif de la soirée","Règle de l'objectif","selon le réglage général (au moins une)","au moins une (OU)","toutes (ET)","Suit la règle par défaut","Retirer l'objectif","Objectifs par tour","Table","2","Ronde","Surchargée","Places","Réaligner","Supprimer","Objectif de la soirée","Règle de l'objectif","selon le réglage général (au moins une)","au moins une (OU)","toutes (ET)","Objectifs par tour (1)","Objectif du tour 1","Règle de l'objectif","selon le réglage général (au moins une)","au moins une (OU)","toutes (ET)","Règle propre","Retirer l'objectif","Aucune personne présente ne correspond à l'objectif au tour 1 : la table ne servira personne à ce titre.","Objectif du tour 2","Règle de l'objectif","selon le réglage général (au moins une)","au moins une (OU)","toutes (ET)","Objectif du tour 3","Règle de l'objectif","selon le réglage général (au moins une)","au moins une (OU)","toutes (ET)","Objectif du tour 4","Règle de l'objectif","selon le réglage général (au moins une)","au moins une (OU)","toutes (ET)"],"theme":"clair","versionReelle":"2026.10.05.01"}, {"cadre":{"hauteur":700,"largeur":1366},"empreinte":"b95dd5be1eebff8135cb65f4ebbb40fb235dfdc69cf1f656955b9556932f4121","etape":"4","etat":"aec49e481eef9af563b3ed2e8772b1f03cb81ccc36d1633a6591fb8da44017de","facteurEchelle":1,"fichier":"05b-import-objectifs","graine":null,"image":{"hauteur":700,"largeur":1366,"sha256":"ee69807c700d5ec6cd211f8fc299290a0f35ca98964d68668a20d0cffbe6f724"},"instant":"2026-10-06T19:00:00-04:00","mode":"chromium-headless-shell","pdf":null,"source":"navigateur","styles":{"clair":[{"jeton":"--couleur-avis","ratio":6.58,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-fond","ratio":15.94,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":6.09,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":9.05,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte","ratio":14.1,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":5.81,"role":"texte","seuil":4.5}],"sombre":[{"jeton":"--couleur-action-texte","ratio":8.45,"role":"texte","seuil":4.5},{"jeton":"--couleur-avis","ratio":8.93,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-fond","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":7.6,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte","ratio":11.07,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":6.72,"role":"texte","seuil":4.5}]},"texte":["Gestion table tournante Libre","construction de documentation","documentation","Écriture","Liste des événements","Clair","Sombre","À propos","Écriture","Chaque geste s’enregistre aussitôt ; « Revenir en lecture » arrête l’écriture.","Revenir en lecture","Écriture","Défaire","État du plan","Brouillon","Proposé","Retenu","Bloqué","Imprimer…","Soirée d’essai","13 participants","2 tables · 16 sièges","4 tours","Préparer","Import","Participants","Tables et tours","Placer","Plan","Diagnostic","Génération","Propositions","Produire","Badges","Aucune personne présente ne correspond à l'objectif au tour 1 : la table ne servira personne à ce titre.","Import","Choisissez un fichier CSV ou déposez-le sur la fenêtre ; une plage collée dans la grille des participants arrive ici. Rien ne s'importe avant l'aperçu.","Choisir un fichier CSV…","Contenu du fichier","Participants","Objectifs de table","Aperçu","Mode de l'import","Mettre à jour","Remplacer","Fichier","objectifs_cas_limites.csv","Encodage","UTF-8 avec marque","Séparateur","point-virgule","Colonnes","En-tête","Devient","Table","table","Tour","tour","Objectif","objectif","Règle","règle","3","lignes valides","Ligne","table","tour","objectif","règle","2","1","soirée","Finance","bénévole","règle par défaut","3","1","2","Montréal","toutes","4","2","soirée","Finance","au moins une","Refus","Lignes refusées","Ligne","Motif","5","Aucune table ne porte le numéro 9.","6","La soirée compte 4 tours : le tour 9 n'en est pas un.","7","La règle « peut-être » n'est pas reconnue : ou, une, au moins une ; et, toutes ; vide pour suivre la règle par défaut.","8","La ligne 3 donne déjà l'objectif de la table 1 au tour 2 : rien n'est fusionné.","Information","Orthographes fondues en une étiquette","retenue :","Finance","orthographes :","FINANCE",",","Finance",",","finance","—","2","lignes réécrites","Importer","Enregistrer les lignes refusées","Fermer l'aperçu"],"theme":"clair","versionReelle":"2026.10.05.01"}, @@ -17,7 +17,7 @@ {"cadre":{"hauteur":700,"largeur":1366},"empreinte":"a4d55a7a4ff633fddbc816170e1c6497c75ad91e7639d7019d51ace6608c1670","etape":"6","etat":"0229d0d8208ef262e3b3485bc2f606cad900fe487d038276d3733a5423cb6a5b","facteurEchelle":1,"fichier":"08-animateurs-designes","graine":null,"image":{"hauteur":700,"largeur":1366,"sha256":"8631b3356260b7d77c13b40e2d73b0847454600b3471baabe4de5ceb1fceadf1"},"instant":"2026-10-06T19:00:00-04:00","mode":"chromium-headless-shell","pdf":null,"source":"navigateur","styles":{"clair":[{"jeton":"--couleur-bandeau-fond","ratio":15.94,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":6.09,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":9.05,"role":"texte","seuil":4.5},{"jeton":"--couleur-chaise-pleine","ratio":15.09,"role":"marque","seuil":3},{"jeton":"--couleur-chaise-trait","ratio":7.44,"role":"marque","seuil":3},{"jeton":"--couleur-echelle","ratio":14.82,"role":"marque","seuil":3},{"jeton":"--couleur-hachure","ratio":16.48,"role":"marque","seuil":3},{"jeton":"--couleur-liste-texte","ratio":14.82,"role":"texte","seuil":4.5},{"jeton":"--couleur-marque","ratio":3.63,"role":"marque","seuil":3},{"jeton":"--couleur-page-texte","ratio":13.81,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":5.81,"role":"texte","seuil":4.5},{"jeton":"--couleur-poignee","ratio":7.44,"role":"marque","seuil":3},{"jeton":"--couleur-selection","ratio":4.57,"role":"marque","seuil":3},{"jeton":"--couleur-table-trait","ratio":7.44,"role":"marque","seuil":3}],"sombre":[{"jeton":"--couleur-action-texte","ratio":8.45,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-fond","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":7.6,"role":"texte","seuil":4.5},{"jeton":"--couleur-chaise-pleine","ratio":14.94,"role":"marque","seuil":3},{"jeton":"--couleur-chaise-trait","ratio":8.89,"role":"marque","seuil":3},{"jeton":"--couleur-echelle","ratio":14.62,"role":"marque","seuil":3},{"jeton":"--couleur-hachure","ratio":15.21,"role":"marque","seuil":3},{"jeton":"--couleur-marque","ratio":3.5,"role":"marque","seuil":3},{"jeton":"--couleur-page-texte","ratio":11.07,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":6.72,"role":"texte","seuil":4.5},{"jeton":"--couleur-poignee","ratio":11.32,"role":"marque","seuil":3},{"jeton":"--couleur-selection","ratio":4.18,"role":"marque","seuil":3},{"jeton":"--couleur-table-trait","ratio":5.59,"role":"marque","seuil":3}]},"texte":["Gestion table tournante Libre","construction de documentation","documentation","Écriture","Liste des événements","Clair","Sombre","À propos","Écriture","Chaque geste s’enregistre aussitôt ; « Revenir en lecture » arrête l’écriture.","Revenir en lecture","Écriture","Défaire","État du plan","Brouillon","Proposé","Retenu","Bloqué","Imprimer…","Soirée d’essai","13 participants","2 tables · 18 sièges","3 tours","Préparer","Import","Participants","Tables et tours","Placer","Plan","Diagnostic","Génération","Propositions","Produire","Badges","Plan","Réservations","Tour affiché","1","2","3","Zoom +","Zoom −","Ajuster","Listes de noms","Légende","Plein écran","1. (réservée, tous les tours)","Basile Lanterneau","—","animation","2. (libre)","3. (libre)","4. (libre)","5. (libre)","6. (libre)","7. (libre)","8. (libre)","9. (libre)","10. (libre)","1. (libre)","— titre","animation","non pourvu","2. (réservée, tour 1)","Capucine Pommerel","3. (libre)","4. (libre)","5. (libre)","6. (libre)","7. (libre)","8. (libre)","1","m","disposition relative — l'encombrement dans la salle n'est pas vérifié","Diagnostic","Chœur de l’Anse","4 personnes : au moins 6 collisions cumulées sur les 3 tours, portées par au moins 2 paires distinctes","Société des Hérons","4 personnes : au moins 6 collisions cumulées sur les 3 tours, portées par au moins 2 paires distinctes","Cercle des Saules","3 personnes : au moins 3 collisions cumulées sur les 3 tours, portées par au moins 1 paire distincte","Tout l'événement","au moins 8 appartenances recroisées par personne","plafond a priori 11 rencontres pour 1 ancré et 11 mobiles","2 retours imposés par 1 ancrage","Listes de travail","Titres de place non pourvus","table 2 : titre","animation","non pourvu","Titres pressentis sans réservation","Gaspard Marbrelle","— titre pressenti","animation","Réserve","Personnes en attente au tour 1","Gaspard Marbrelle","Ysaline Sablonnet","Anselme Givrecœur","Côme Fenouillet","Apolline Coudraie","Basile Lanterneau","Brunœil","Faustine Ormelin","Edgar Ardoisier","Bertille Cressonnier","Table 2, siège 1","1. (libre)","— titre","animation","non pourvu","Choisissez d'abord une personne : dans la réserve, dans la liste des pressentis, ou par « Déplacer » dans son menu.","Portée","Tour 1","Tous les tours","Libellé du titre","Assigner","Réserver","Désigner","Retirer le titre"],"theme":"clair","versionReelle":"2026.10.05.01"}, {"cadre":{"hauteur":700,"largeur":1366},"empreinte":"127826d1d2ae53997f9d467736f45d0e9ab663730f23ba53271fc87ce1f7fe73","etape":"7","etat":"0229d0d8208ef262e3b3485bc2f606cad900fe487d038276d3733a5423cb6a5b","facteurEchelle":1,"fichier":"09-refus-sur-place","graine":null,"image":{"hauteur":700,"largeur":1366,"sha256":"db6e14d4a0a151e4440a515f0638de0e62676e2049b59f44672815283aa47b4e"},"instant":"2026-10-06T19:00:00-04:00","mode":"chromium-headless-shell","pdf":null,"source":"navigateur","styles":{"clair":[{"jeton":"--couleur-bandeau-fond","ratio":15.94,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":6.09,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":9.05,"role":"texte","seuil":4.5},{"jeton":"--couleur-chaise-pleine","ratio":15.09,"role":"marque","seuil":3},{"jeton":"--couleur-chaise-trait","ratio":7.44,"role":"marque","seuil":3},{"jeton":"--couleur-echelle","ratio":14.82,"role":"marque","seuil":3},{"jeton":"--couleur-hachure","ratio":16.48,"role":"marque","seuil":3},{"jeton":"--couleur-liste-texte","ratio":14.82,"role":"texte","seuil":4.5},{"jeton":"--couleur-marque","ratio":3.63,"role":"marque","seuil":3},{"jeton":"--couleur-page-texte","ratio":13.81,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":5.81,"role":"texte","seuil":4.5},{"jeton":"--couleur-poignee","ratio":7.44,"role":"marque","seuil":3},{"jeton":"--couleur-refus","ratio":8.34,"role":"texte","seuil":4.5},{"jeton":"--couleur-selection","ratio":4.57,"role":"marque","seuil":3},{"jeton":"--couleur-table-trait","ratio":7.44,"role":"marque","seuil":3}],"sombre":[{"jeton":"--couleur-action-texte","ratio":8.45,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-fond","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":7.6,"role":"texte","seuil":4.5},{"jeton":"--couleur-chaise-pleine","ratio":14.94,"role":"marque","seuil":3},{"jeton":"--couleur-chaise-trait","ratio":8.89,"role":"marque","seuil":3},{"jeton":"--couleur-echelle","ratio":14.62,"role":"marque","seuil":3},{"jeton":"--couleur-hachure","ratio":15.21,"role":"marque","seuil":3},{"jeton":"--couleur-marque","ratio":3.5,"role":"marque","seuil":3},{"jeton":"--couleur-page-texte","ratio":11.07,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":6.72,"role":"texte","seuil":4.5},{"jeton":"--couleur-poignee","ratio":11.32,"role":"marque","seuil":3},{"jeton":"--couleur-refus","ratio":8.14,"role":"texte","seuil":4.5},{"jeton":"--couleur-selection","ratio":4.18,"role":"marque","seuil":3},{"jeton":"--couleur-table-trait","ratio":5.59,"role":"marque","seuil":3}]},"texte":["Gestion table tournante Libre","construction de documentation","documentation","Écriture","Liste des événements","Clair","Sombre","À propos","Écriture","Chaque geste s’enregistre aussitôt ; « Revenir en lecture » arrête l’écriture.","Revenir en lecture","Écriture","Défaire","État du plan","Brouillon","Proposé","Retenu","Bloqué","Imprimer…","Soirée d’essai","13 participants","2 tables · 18 sièges","3 tours","Préparer","Import","Participants","Tables et tours","Placer","Plan","Diagnostic","Génération","Propositions","Produire","Badges","Plan","Réservations","Tour affiché","1","2","3","Zoom +","Zoom −","Ajuster","Listes de noms","Légende","Plein écran","1. (réservée, tous les tours)","Basile Lanterneau","—","animation","2. (libre)","3. (libre)","4. (libre)","5. (libre)","6. (libre)","7. (libre)","8. (libre)","9. (libre)","10. (libre)","1. (libre)","— titre","animation","non pourvu","2. (réservée, tour 1)","Capucine Pommerel","3. (libre)","4. (libre)","5. (libre)","6. (libre)","7. (libre)","8. (libre)","Table 1, siège 1","Le siège 1 est réservé à une autre personne à tous les tours : déplacer sa réservation libère la place.","Choisir une place","Fermer","1","m","disposition relative — l'encombrement dans la salle n'est pas vérifié","Diagnostic","Chœur de l’Anse","4 personnes : au moins 6 collisions cumulées sur les 3 tours, portées par au moins 2 paires distinctes","Société des Hérons","4 personnes : au moins 6 collisions cumulées sur les 3 tours, portées par au moins 2 paires distinctes","Cercle des Saules","3 personnes : au moins 3 collisions cumulées sur les 3 tours, portées par au moins 1 paire distincte","Tout l'événement","au moins 8 appartenances recroisées par personne","plafond a priori 11 rencontres pour 1 ancré et 11 mobiles","2 retours imposés par 1 ancrage","Listes de travail","Titres de place non pourvus","table 2 : titre","animation","non pourvu","Titres pressentis sans réservation","Gaspard Marbrelle","— titre pressenti","animation","Réserve","Personnes en attente au tour 1","Gaspard Marbrelle","Ysaline Sablonnet","Anselme Givrecœur","Côme Fenouillet","Apolline Coudraie","Basile Lanterneau","Brunœil","Faustine Ormelin","Edgar Ardoisier","Bertille Cressonnier","Table 1, siège 1","1. (réservée, tous les tours)","Basile Lanterneau","—","animation","Personne choisie :","Gaspard Marbrelle","Assigner","Retirer la réservation","Retirer le titre","Oublier le choix","Menu de","Basile Lanterneau","Déplacer","Échanger","Retirer du tour","Déplacer sa réservation","Exclure","Mettre en évidence","Ouvrir la fiche"],"theme":"clair","versionReelle":"2026.10.05.01"}, {"cadre":{"hauteur":700,"largeur":1366},"empreinte":"c35f4720e6dac6dc98262aba6615905ba8f6ab28d6c897551cdf07ed5c473d2c","etape":"8","etat":"4d37d30748068354162752743c26aa7e0ab00d6e9b2aa1996b6f9fb410848280","facteurEchelle":1,"fichier":"10-diagnostic","graine":null,"image":{"hauteur":700,"largeur":1366,"sha256":"6d9e4c48b101c40a4ef97fef2dc2241ab37bf5106aa362255b1d3357aecaef3d"},"instant":"2026-10-06T19:00:00-04:00","mode":"chromium-headless-shell","pdf":null,"source":"navigateur","styles":{"clair":[{"jeton":"--couleur-bandeau-fond","ratio":15.94,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":6.09,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":9.05,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte","ratio":13.78,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":5.81,"role":"texte","seuil":4.5}],"sombre":[{"jeton":"--couleur-action-texte","ratio":8.45,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-fond","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":7.6,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte","ratio":11.07,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":6.72,"role":"texte","seuil":4.5}]},"texte":["Gestion table tournante Libre","construction de documentation","documentation","Lecture","Liste des événements","Clair","Sombre","À propos","Lecture","Rien ne change ; « Modifier » passe en écriture.","Modifier","Lecture","Défaire","État du plan","Brouillon","Proposé","Retenu","Bloqué","Imprimer…","Grande démonstration 1","260 participants","33 tables · 260 sièges","4 tours","Préparer","Import","Participants","Tables et tours","Placer","Plan","Diagnostic","Génération","Propositions","Produire","Badges","Diagnostic","Tout l'événement","écart d'itinéraire maximal : plancher 1, au moins 24 mobiles finissent sous leur plafond a priori","aucune relance ne le réduit : il tient aux tables pleines dont les capacités ou les ancrages diffèrent","plafond a priori 24 rencontres pour 4 ancrés","seules les capacités et les réservations le changent : aucune relance ne le dépasse","plafond a priori 28 rencontres pour 29 ancrés et 227 mobiles","seules les capacités et les réservations le changent : aucune relance ne le dépasse","99 retours imposés par 33 ancrages","les réservations les imposent : ils ne comptent pas parmi les retours que choisit le moteur"],"theme":"clair","versionReelle":"2026.10.05.01"}, -{"cadre":{"hauteur":700,"largeur":1366},"empreinte":"16eaaf9da585d8305ecb5975ddbb4b6f97ee2800ba9680389b55a1438c941efe","etape":"9","etat":"1025fe8c346e3a503f00abcaf78cf004f33fe2c01a46bebd87da1bbf4d0b3f53","facteurEchelle":1,"fichier":"10a-ordre-priorites","graine":null,"image":{"hauteur":700,"largeur":1366,"sha256":"097cbf2794eb21f94d10ca51df746ab4008a5bf17cba7aff2b4184670e70c7b6"},"instant":"2026-10-06T19:00:00-04:00","mode":"chromium-headless-shell","pdf":null,"source":"navigateur","styles":{"clair":[{"jeton":"--couleur-bandeau-fond","ratio":15.94,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":6.09,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":9.05,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte","ratio":14.16,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":5.81,"role":"texte","seuil":4.5}],"sombre":[{"jeton":"--couleur-action-texte","ratio":8.45,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-fond","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":7.6,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte","ratio":11.07,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":6.72,"role":"texte","seuil":4.5}]},"texte":["Gestion table tournante Libre","construction de documentation","documentation","Écriture","Liste des événements","Clair","Sombre","À propos","Écriture","Chaque geste s’enregistre aussitôt ; « Revenir en lecture » arrête l’écriture.","Revenir en lecture","Écriture","Défaire","État du plan","Brouillon","Proposé","Retenu","Bloqué","Imprimer…","Soirée d’essai","13 participants","2 tables · 18 sièges","3 tours","Préparer","Import","Participants","Tables et tours","Placer","Plan","Diagnostic","Génération","Propositions","Produire","Badges","Génération","Meilleure proposition du classement","Aucune proposition classée : sans proposition comparable, le classement n'en désigne aucune.","Réglages de la génération","Graine (nombre entier, de 0 à 4 294 967 295)","Propositions par génération (nombre de propositions)","Compte d'arrêt (mouvements évalués par proposition)","Historique d'acceptation (longueur, en mouvements)","Contraintes de la génération","Séparer les appartenances","Nouveaux voisins à chaque tour","Nouvelle table à chaque tour","Varier les appartenances rencontrées","Respecter les objectifs des tables","Objectifs des tables : chercher d'abord","le plus de places servies","Le plus grand total de tours servis : le critère compare les tours hors objectif.","chacun au moins une fois","D'abord le plus de personnes servies à un tour au moins, puis le plus grand total : le critère compare les personnes jamais servies, puis les tours hors objectif.","l'équité d'abord","Le minimum d'abord, puis le total : d'abord la personne la plus mal servie, mesurée contre ses propres tours possibles, puis le plus grand total. Le critère compare le plus de tours hors objectif d'une personne, puis les tours hors objectif.","Rencontrer le plus d'animateurs différents","Attribuer les sièges","Ordre des priorités","Deux propositions se comparent palier par palier : un palier ne départage que des propositions égales sur ceux qui le précèdent. Dans un palier de plusieurs critères, chacun compte selon son poids.","Ordre réglé pour l'événement.","Palier 1 sur 6","écart au plafond a priori","Descendre « écart au plafond a priori » d'un palier","Palier 2 sur 6","excédent de collisions","Poids de « excédent de collisions » (nombre entier)","Monter le palier de « excédent de collisions »","Descendre le palier de « excédent de collisions »","Séparer « excédent de collisions » en palier propre","tours hors objectif","Poids de « tours hors objectif » (nombre entier)","Monter le palier de « tours hors objectif »","Descendre le palier de « tours hors objectif »","Séparer « tours hors objectif » en palier propre","Palier 3 sur 6","collisions cumulées","Monter « collisions cumulées » d'un palier","Descendre « collisions cumulées » d'un palier","Joindre « collisions cumulées » au palier du dessus","Palier 4 sur 6","rencontres répétées et retours","Monter « rencontres répétées et retours » d'un palier","Descendre « rencontres répétées et retours » d'un palier","Palier 5 sur 6","animateurs non rencontrés","décoché","Monter « animateurs non rencontrés » d'un palier","Descendre « animateurs non rencontrés » d'un palier","Palier 6 sur 6","redondance d'appartenance","Monter « redondance d'appartenance » d'un palier","Joindre « redondance d'appartenance » au palier du dessus","« tours hors objectif » est au palier 2 sur 6, avec « excédent de collisions ».","Rétablir l'ordre par défaut","Cœurs de calcul","Cœurs automatiques (un par processeur logique du poste)","Valeur automatique sur ce poste : 4 cœurs, un par processeur logique, hyperthreads compris.","Une génération n'emploie jamais plus de cœurs que de propositions demandées. Ce réglage vaut pour cette séance du logiciel sur ce poste : il ne s'enregistre pas dans l'événement.","Aucun calcul n'est encore mesuré sur ce poste pendant cette séance : la durée d'une génération est inconnue.","Générer"],"theme":"clair","versionReelle":"2026.10.05.01"}, +{"cadre":{"hauteur":700,"largeur":1366},"empreinte":"16eaaf9da585d8305ecb5975ddbb4b6f97ee2800ba9680389b55a1438c941efe","etape":"9","etat":"1025fe8c346e3a503f00abcaf78cf004f33fe2c01a46bebd87da1bbf4d0b3f53","facteurEchelle":1,"fichier":"10a-ordre-priorites","graine":null,"image":{"hauteur":700,"largeur":1366,"sha256":"9d19aae397b3509ebdada1f8ff40f1a1d56f53e3c5bc50c63e358fc68ce5b194"},"instant":"2026-10-06T19:00:00-04:00","mode":"chromium-headless-shell","pdf":null,"source":"navigateur","styles":{"clair":[{"jeton":"--couleur-bandeau-fond","ratio":15.94,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":6.09,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":9.05,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte","ratio":14.16,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":5.81,"role":"texte","seuil":4.5}],"sombre":[{"jeton":"--couleur-action-texte","ratio":8.45,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-fond","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":7.6,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte","ratio":11.07,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":6.72,"role":"texte","seuil":4.5}]},"texte":["Gestion table tournante Libre","construction de documentation","documentation","Écriture","Liste des événements","Clair","Sombre","À propos","Écriture","Chaque geste s’enregistre aussitôt ; « Revenir en lecture » arrête l’écriture.","Revenir en lecture","Écriture","Défaire","État du plan","Brouillon","Proposé","Retenu","Bloqué","Imprimer…","Soirée d’essai","13 participants","2 tables · 18 sièges","3 tours","Préparer","Import","Participants","Tables et tours","Placer","Plan","Diagnostic","Génération","Propositions","Produire","Badges","Génération","Meilleure proposition du classement","Aucune proposition classée : sans proposition comparable, le classement n'en désigne aucune.","Réglages de la génération","Graine (nombre entier, de 0 à 4 294 967 295)","Propositions par génération (nombre de propositions)","Compte d'arrêt (mouvements évalués par proposition)","Historique d'acceptation (longueur, en mouvements)","Contraintes de la génération","Séparer les appartenances","Nouveaux voisins à chaque tour","Nouvelle table à chaque tour","Varier les appartenances rencontrées","Respecter les objectifs des tables","Objectifs des tables : chercher d'abord","le plus de places servies","Le plus grand total de tours servis : le critère compare les tours hors objectif.","chacun au moins une fois","D'abord le plus de personnes servies à un tour au moins, puis le plus grand total : le critère compare les personnes jamais servies, puis les tours hors objectif.","l'équité d'abord","Le minimum d'abord, puis le total : d'abord la personne la plus mal servie, mesurée contre ses propres tours possibles, puis le plus grand total. Le critère compare le plus de tours hors objectif d'une personne, puis les tours hors objectif.","Rencontrer le plus d'animateurs différents","Attribuer les sièges","Ordre des priorités","Deux propositions se comparent palier par palier : un palier ne départage que des propositions égales sur ceux qui le précèdent. Dans un palier de plusieurs critères, chacun compte selon son poids.","Ordre réglé pour l'événement.","Palier 1 sur 6","écart au plafond a priori","Descendre « écart au plafond a priori » d'un palier","Palier 2 sur 6","excédent de collisions","Poids de « excédent de collisions » (nombre entier)","Monter le palier de « excédent de collisions »","Descendre le palier de « excédent de collisions »","Séparer « excédent de collisions » en palier propre","tours hors objectif","Poids de « tours hors objectif » (nombre entier)","Monter le palier de « tours hors objectif »","Descendre le palier de « tours hors objectif »","Séparer « tours hors objectif » en palier propre","Palier 3 sur 6","collisions cumulées","Monter « collisions cumulées » d'un palier","Descendre « collisions cumulées » d'un palier","Joindre « collisions cumulées » au palier du dessus","Palier 4 sur 6","rencontres répétées et retours","Monter « rencontres répétées et retours » d'un palier","Descendre « rencontres répétées et retours » d'un palier","Palier 5 sur 6","animateurs non rencontrés","décoché","Monter « animateurs non rencontrés » d'un palier","Descendre « animateurs non rencontrés » d'un palier","Palier 6 sur 6","redondance d'appartenance","Monter « redondance d'appartenance » d'un palier","Joindre « redondance d'appartenance » au palier du dessus","« tours hors objectif » est au palier 2 sur 6, avec « excédent de collisions ».","Rétablir l'ordre par défaut","Cœurs de calcul","Cœurs automatiques (un par processeur logique du poste)","Valeur automatique sur ce poste : 4 cœurs, un par processeur logique, hyperthreads compris.","Une génération n'emploie jamais plus de cœurs que de propositions demandées. Ce réglage vaut pour cette séance du logiciel sur ce poste : il ne s'enregistre pas dans l'événement.","Aucun calcul n'est encore mesuré sur ce poste pendant cette séance : la durée d'une génération est inconnue.","Générer"],"theme":"clair","versionReelle":"2026.10.05.01"}, {"cadre":{"hauteur":700,"largeur":1366},"empreinte":"df0e5d4dbc1191c7b5ae715c276a366d61b185c4f724009fc508d3c499b06c8a","etape":"9","etat":"b42451443701c80d433d632788edaab7dd1afea7a57add55a37fcedfdaee98ff","facteurEchelle":1,"fichier":"11-propositions","graine":4242,"image":{"hauteur":700,"largeur":1366,"sha256":"047dedcd90dd84347dc8637bccde6d56f8fbd990bdfc6c74eb8e3b3a5e08065f"},"instant":"2026-10-06T19:02:00-04:00","mode":"chromium-headless-shell","pdf":null,"source":"navigateur","styles":{"clair":[{"jeton":"--couleur-bandeau-fond","ratio":15.94,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":6.09,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":9.05,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte","ratio":14.05,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":5.81,"role":"texte","seuil":4.5}],"sombre":[{"jeton":"--couleur-action-texte","ratio":8.45,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-fond","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":7.6,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte","ratio":11.02,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":6.72,"role":"texte","seuil":4.5}]},"texte":["Gestion table tournante Libre","construction de documentation","documentation","Écriture","Liste des événements","Clair","Sombre","À propos","Écriture","Chaque geste s’enregistre aussitôt ; « Revenir en lecture » arrête l’écriture.","Revenir en lecture","Écriture","Défaire","État du plan","Brouillon","Proposé","Retenu","Bloqué","Imprimer…","Soirée d’essai","13 participants","2 tables · 18 sièges","3 tours","Préparer","Import","Participants","Tables et tours","Placer","Plan","Diagnostic","Génération","Propositions","Produire","Badges","Propositions","Propositions classées","Effacer les propositions","Ordre du classement, chaque critère au plus faible d'abord :","écart au plafond a priori",", puis","ensemble,","excédent de collisions","(poids 1)",",","tours hors objectif","(poids 3)",", puis","collisions cumulées",", puis","rencontres répétées",", puis","redondance d'appartenance",", puis","l'identifiant croissant",".","Personnes rencontrées","Collisions d'appartenance","Rencontres répétées et retours","Diversité d'appartenance","Manque et plafond a priori","Objectifs des tables et animateurs","Rang","Proposition","Personnes distinctes rencontrées, minimum : tous","Personnes distinctes rencontrées, minimum : mobiles","Personnes distinctes rencontrées, minimum : ancrés","Collisions cumulées (paire et tour)","Paires distinctes de même appartenance","Excédent de collisions (retrouvailles au-delà de la première)","Rencontres répétées choisies (paires)","Rencontres d'une même paire, au plus","Retours choisis à une table déjà occupée","Appartenances rencontrées A(p), minimum","Redondance d'appartenance, somme Σ r(p)","Taux de diversité d(p), minimum","Plus grand manque (personnes)","Personnes à qui il en manque","Écart d'itinéraire maximal (personnes)","Écart au plafond a priori maximal (personnes)","Tours hors objectif (personne et tour)","Tours selon l'objectif s(p), minimum","Certificat","Imposé par les réservations","Commandes","Placement retenu","3","Tel que la proposition n° 3.","11","11","11","26","15","11","30","3","11","3","85","0,27","0","0","0","0","1","1","minimum atteint","2 retours imposés par 1 ancrage.","Aucune rencontre répétée imposée.","Voir sur le plan","1","3","retenue","11","11","11","26","15","11","30","3","11","3","85","0,27","0","0","0","0","1","1","minimum atteint","2 retours imposés par 1 ancrage.","Aucune rencontre répétée imposée.","Voir sur le plan","Retenir","2","2","11","11","11","33","15","18","54","3","16","3","85","0,27","0","0","0","0","2","1","minimum atteint","2 retours imposés par 1 ancrage.","Aucune rencontre répétée imposée.","Voir sur le plan","Retenir","3","5","10","10","10","20","15","5","22","3","12","3","83","0,27","1","1","1","1","1","1","atteignabilité inconnue","2 retours imposés par 1 ancrage.","Aucune rencontre répétée imposée.","Voir sur le plan","Retenir","4","1","10","10","11","29","15","14","46","3","13","3","82","0,30","1","4","0","1","0","2","atteignabilité inconnue","2 retours imposés par 1 ancrage.","Aucune rencontre répétée imposée.","Voir sur le plan","Retenir","5","4","10","10","11","31","14","17","46","3","17","3","82","0,30","1","4","0","1","0","2","atteignabilité inconnue","2 retours imposés par 1 ancrage.","Aucune rencontre répétée imposée.","Voir sur le plan","Retenir","Cible de la somme de redondance","La liste des événements montre, pour le placement retenu, la différence sans signe entre cette cible et la somme Σ r(p) de la colonne « Redondance d'appartenance ». Un nombre entier, 0 ou plus."],"theme":"clair","versionReelle":"2026.10.05.01"}, {"cadre":{"hauteur":700,"largeur":1366},"empreinte":"bcf0f6b2bd8f8f5118ba7afeaf82157ab445f462cac9177c9746b2da9638955f","etape":"11","etat":"b42451443701c80d433d632788edaab7dd1afea7a57add55a37fcedfdaee98ff","facteurEchelle":1,"fichier":"19-ajustement","graine":4242,"image":{"hauteur":700,"largeur":1366,"sha256":"628d4679190e73a8c50590eb755d688902318b43b2ee14c2aab0ec2ece724877"},"instant":"2026-10-06T19:02:00-04:00","mode":"chromium-headless-shell","pdf":null,"source":"navigateur","styles":{"clair":[{"jeton":"--couleur-bandeau-fond","ratio":15.94,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":6.09,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":9.05,"role":"texte","seuil":4.5},{"jeton":"--couleur-chaise-pleine","ratio":15.09,"role":"marque","seuil":3},{"jeton":"--couleur-chaise-trait","ratio":7.44,"role":"marque","seuil":3},{"jeton":"--couleur-echelle","ratio":14.82,"role":"marque","seuil":3},{"jeton":"--couleur-fantome","ratio":4.34,"role":"marque","seuil":3},{"jeton":"--couleur-hachure","ratio":16.48,"role":"marque","seuil":3},{"jeton":"--couleur-liste-texte","ratio":14.82,"role":"texte","seuil":4.5},{"jeton":"--couleur-marque","ratio":3.63,"role":"marque","seuil":3},{"jeton":"--couleur-page-texte","ratio":13.81,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":5.81,"role":"texte","seuil":4.5},{"jeton":"--couleur-pastille-fond","ratio":13.44,"role":"marque","seuil":3},{"jeton":"--couleur-poignee","ratio":7.44,"role":"marque","seuil":3},{"jeton":"--couleur-table-trait","ratio":7.44,"role":"marque","seuil":3}],"sombre":[{"jeton":"--couleur-action-texte","ratio":8.45,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-fond","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":7.6,"role":"texte","seuil":4.5},{"jeton":"--couleur-chaise-pleine","ratio":14.94,"role":"marque","seuil":3},{"jeton":"--couleur-chaise-trait","ratio":8.89,"role":"marque","seuil":3},{"jeton":"--couleur-echelle","ratio":14.62,"role":"marque","seuil":3},{"jeton":"--couleur-fantome","ratio":4.1,"role":"marque","seuil":3},{"jeton":"--couleur-hachure","ratio":15.21,"role":"marque","seuil":3},{"jeton":"--couleur-marque","ratio":3.5,"role":"marque","seuil":3},{"jeton":"--couleur-page-texte","ratio":11.02,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":6.72,"role":"texte","seuil":4.5},{"jeton":"--couleur-pastille-fond","ratio":11.62,"role":"marque","seuil":3},{"jeton":"--couleur-pastille-texte","ratio":14.51,"role":"texte","seuil":4.5},{"jeton":"--couleur-poignee","ratio":11.32,"role":"marque","seuil":3},{"jeton":"--couleur-table-trait","ratio":5.59,"role":"marque","seuil":3}]},"texte":["Gestion table tournante Libre","construction de documentation","documentation","Écriture","Liste des événements","Clair","Sombre","À propos","Écriture","Chaque geste s’enregistre aussitôt ; « Revenir en lecture » arrête l’écriture.","Revenir en lecture","Écriture","Défaire","État du plan","Brouillon","Proposé","Retenu","Bloqué","Imprimer…","Soirée d’essai","13 participants","2 tables · 18 sièges","3 tours","Préparer","Import","Participants","Tables et tours","Placer","Plan","Diagnostic","Génération","Propositions","Produire","Badges","Plan","Placement retenu","Tour affiché","1","2","3","Zoom +","Zoom −","Ajuster","Listes de noms","Légende","Plein écran","9","1. (réservée, tous les tours)","Basile Lanterneau","—","animation","2.","Gaspard Marbrelle","3.","Ysaline Sablonnet","4.","Anselme Givrecœur","5.","Côme Fenouillet","6.","Apolline Coudraie","7.","Brunœil","8.","Faustine Ormelin","9.","Edgar Ardoisier","10.","Bertille Cressonnier","1.","Basile Lanterneau","— titre","animation","non pourvu","2. (réservée, tour 1)","Capucine Pommerel","3. (libre)","4. (libre)","5. (libre)","6. (libre)","7. (libre)","8. (libre)","Gaspard Marbrelle","déplace dans le placement retenu","1","m","disposition relative — l'encombrement dans la salle n'est pas vérifié","Diagnostic","Chœur de l’Anse","4 personnes : au moins 6 collisions cumulées sur les 3 tours, portées par au moins 2 paires distinctes","Société des Hérons","4 personnes : au moins 6 collisions cumulées sur les 3 tours, portées par au moins 2 paires distinctes","Cercle des Saules","3 personnes : au moins 3 collisions cumulées sur les 3 tours, portées par au moins 1 paire distincte","Tout l'événement","au moins 8 appartenances recroisées par personne","plafond a priori 11 rencontres pour 1 ancré et 11 mobiles","2 retours imposés par 1 ancrage","Listes de travail","Titres de place non pourvus","table 2 : titre","animation","non pourvu","Titres pressentis sans réservation","Gaspard Marbrelle","— titre pressenti","animation","Réserve","Personnes en attente au tour 1","Personne en attente à ce tour."],"theme":"clair","versionReelle":"2026.10.05.01"}, {"cadre":{"hauteur":700,"largeur":1366},"empreinte":"1ba00a6086f6d2f1b695469f1a26ec19092805835305fd30c07310a1911992d0","etape":"11","etat":"e60a6d4dbb76287b287857b2175d4a784996dcdb3cf6df8e8e30cc9d4d01c14d","facteurEchelle":1,"fichier":"20-mode-lecture","graine":4242,"image":{"hauteur":700,"largeur":1366,"sha256":"4f4cad699bed04e20bd932ff8f11dada090e01de4389928914d415b555e097df"},"instant":"2026-10-06T19:02:00-04:00","mode":"chromium-headless-shell","pdf":null,"source":"navigateur","styles":{"clair":[{"jeton":"--couleur-bandeau-fond","ratio":15.94,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":6.09,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":9.05,"role":"texte","seuil":4.5},{"jeton":"--couleur-chaise-pleine","ratio":15.09,"role":"marque","seuil":3},{"jeton":"--couleur-chaise-trait","ratio":7.44,"role":"marque","seuil":3},{"jeton":"--couleur-conflit-appartenance","ratio":8.92,"role":"marque","seuil":3},{"jeton":"--couleur-contour-fort","ratio":12.3,"role":"marque","seuil":3},{"jeton":"--couleur-echelle","ratio":14.82,"role":"marque","seuil":3},{"jeton":"--couleur-hachure","ratio":16.48,"role":"marque","seuil":3},{"jeton":"--couleur-liste-texte","ratio":14.82,"role":"texte","seuil":4.5},{"jeton":"--couleur-marque","ratio":3.63,"role":"marque","seuil":3},{"jeton":"--couleur-page-texte","ratio":13.78,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":5.81,"role":"texte","seuil":4.5},{"jeton":"--couleur-pastille-fond","ratio":13.44,"role":"marque","seuil":3},{"jeton":"--couleur-poignee","ratio":7.44,"role":"marque","seuil":3},{"jeton":"--couleur-refus","ratio":8.34,"role":"texte","seuil":4.5},{"jeton":"--couleur-selection","ratio":4.57,"role":"marque","seuil":3},{"jeton":"--couleur-table-trait","ratio":7.44,"role":"marque","seuil":3}],"sombre":[{"jeton":"--couleur-action-texte","ratio":8.45,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-fond","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":7.6,"role":"texte","seuil":4.5},{"jeton":"--couleur-chaise-pleine","ratio":14.94,"role":"marque","seuil":3},{"jeton":"--couleur-chaise-trait","ratio":8.89,"role":"marque","seuil":3},{"jeton":"--couleur-conflit-appartenance","ratio":6.81,"role":"marque","seuil":3},{"jeton":"--couleur-contour-fort","ratio":11.62,"role":"marque","seuil":3},{"jeton":"--couleur-echelle","ratio":14.62,"role":"marque","seuil":3},{"jeton":"--couleur-hachure","ratio":15.21,"role":"marque","seuil":3},{"jeton":"--couleur-marque","ratio":3.5,"role":"marque","seuil":3},{"jeton":"--couleur-page-texte","ratio":11.02,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":6.72,"role":"texte","seuil":4.5},{"jeton":"--couleur-pastille-fond","ratio":11.62,"role":"marque","seuil":3},{"jeton":"--couleur-pastille-texte","ratio":14.51,"role":"texte","seuil":4.5},{"jeton":"--couleur-poignee","ratio":11.32,"role":"marque","seuil":3},{"jeton":"--couleur-refus","ratio":8.14,"role":"texte","seuil":4.5},{"jeton":"--couleur-selection","ratio":4.18,"role":"marque","seuil":3},{"jeton":"--couleur-table-trait","ratio":5.59,"role":"marque","seuil":3}]},"texte":["Gestion table tournante Libre","construction de documentation","documentation","Lecture","Liste des événements","Clair","Sombre","À propos","Lecture","Rien ne change ; « Modifier » passe en écriture.","Modifier","Lecture","Défaire","État du plan","Brouillon","Proposé","Retenu","Bloqué","Imprimer…","Soirée d’essai","13 participants","2 tables · 18 sièges","3 tours","Préparer","Import","Participants","Tables et tours","Placer","Plan","Diagnostic","Génération","Propositions","Produire","Badges","Plan","Placement retenu","Tour affiché","1","2","3","Zoom +","Zoom −","Ajuster","Listes de noms","Légende","Plein écran","7","1. (réservée, tous les tours)","Basile Lanterneau","—","animation","2.","Ysaline Sablonnet","3.","Anselme Givrecœur","4.","Côme Fenouillet","5.","Apolline Coudraie","6.","Brunœil","7.","Faustine Ormelin","8.","Edgar Ardoisier","9.","Bertille Cressonnier","10. (libre)","1.","Gaspard Marbrelle","— titre","animation","non pourvu","2. (réservée, tour 1)","Capucine Pommerel","3.","Basile Lanterneau","4. (libre)","5. (libre)","6. (libre)","7. (libre)","8. (libre)","Table 2, siège 4","L'événement est ouvert en lecture : « Modifier » permet de le changer.","Modifier","Fermer","1","m","disposition relative — l'encombrement dans la salle n'est pas vérifié","Diagnostic","Chœur de l’Anse","4 personnes : au moins 6 collisions cumulées sur les 3 tours, portées par au moins 2 paires distinctes","Société des Hérons","4 personnes : au moins 6 collisions cumulées sur les 3 tours, portées par au moins 2 paires distinctes","Cercle des Saules","3 personnes : au moins 3 collisions cumulées sur les 3 tours, portées par au moins 1 paire distincte","Tout l'événement","au moins 8 appartenances recroisées par personne","plafond a priori 11 rencontres pour 1 ancré et 11 mobiles","2 retours imposés par 1 ancrage","Listes de travail","Titres de place non pourvus","table 2 : titre","animation","non pourvu","Titres pressentis sans réservation","Gaspard Marbrelle","— titre pressenti","animation","Réserve","Personnes en attente au tour 1","Personne en attente à ce tour.","Table 2, siège 4","4. (libre)","Choisissez d'abord une personne : dans la réserve, dans la liste des pressentis, ou par « Déplacer » dans son menu.","Portée","Tour 1","Tous les tours","Libellé du titre","Assigner","Réserver","Donner un titre","Désigner"],"theme":"clair","versionReelle":"2026.10.05.01"}, diff --git a/spec.md b/spec.md index b49f925..a2c1f84 100644 --- a/spec.md +++ b/spec.md @@ -2751,9 +2751,10 @@ l'appartenance s'affiche en pastille de sa teinte (§ 12.7), le titre pressenti et l'exclusion en pastilles à icône. La fiche d'une personne porte ses étiquettes, en pastilles **neutres** — la -teinte d'appartenance ne sert qu'à l'appartenance (§ 12.7) —, chacune avec sa -commande « Retirer », et un champ qui en ajoute une ou plusieurs, séparées comme -dans une cellule CSV ; la saisie passe par la même lecture que l'import (§ 4.5, +teinte d'appartenance ne sert qu'à l'appartenance (§ 12.7) —, chacune suivie, +en mode écriture seulement, d'une petite croix cerclée rouge qui la retire, +nommée « Retirer « étiquette » » pour les technologies d'assistance, et un +champ qui en ajoute une ou plusieurs, séparées comme dans une cellule CSV ; la saisie passe par la même lecture que l'import (§ 4.5, § 10.1). Quand l'événement porte des étiquettes, une légende des étiquettes suit celle des appartenances, chacune avec le nombre de personnes qui la portent, exclues comprises. @@ -3622,6 +3623,9 @@ Une page de statistiques se casse par ses bords, et chacun se produira. le plan de salle, et ne servent à rien d'autre ; chacune ne peint que les marques de sa règle. Elles se distinguent entre elles et des autres marques du plan **une fois les trois dichromaties simulées**. +- **Le rouge du retrait** (`--couleur-retrait`) ne peint que la croix cerclée + d'une commande qui retire, sur une carte, jamais sur le plan : c'est le seul + jeton chromatique admis à moins de 30° d'une teinte de conflit. - **Les couleurs d'appartenance ne vont que sur les listes**, jamais sur le plan, dont le canal chromatique appartient aux conflits : au plus six teintes catégorielles, chacune à 30° au moins des teintes de conflit dans chaque @@ -4023,7 +4027,7 @@ des fonctions pures du module de géométrie. | tenir hors ligne | se compile en JavaScript ordinaire ; Vite est une dépendance de développement qui ne survit pas à la construction | | rester rapide | compile des mises à jour fines, sans arbre virtuel ni mémoïsation à poser à la main — à une exception près : la table d'évaluation des propositions, gardée dans le modèle pur sous l'identité de la charge figée de la séance, qu'une écriture remplace | | se tester sans navigateur | n'impose d'écrire aucun calcul dans un composant ; le composant ne contient que le câblage | -| être joli sans y passer des semaines | un fichier de jetons — couleurs, rayons, ombres, échelle typographique, cible principale, largeur de page — décliné en deux thèmes ; ses familles de couleur : la page, le bandeau, la carte, l'action principale, les quatre états du plan, le neutre, les appartenances, la bande de mode, les messages (avis, refus, information), le plan, le papier | +| être joli sans y passer des semaines | un fichier de jetons — couleurs, rayons, ombres, échelle typographique, cible principale, largeur de page — décliné en deux thèmes ; ses familles de couleur : la page, le bandeau, la carte, l'action principale, les quatre états du plan, le neutre, les appartenances, la bande de mode, les messages (avis, refus, information), le retrait, le plan, le papier | **Les candidates écartées.** Vue 3 est le second choix défendable, écarté de peu sur le poids et sur la discipline qu'exige sa réactivité. React est écarté sur la @@ -4243,7 +4247,8 @@ l'étage intermédiaire et laisse partir les défauts de coordonnées. ### 14.5 Accessibilité et thèmes Le texte tient **4,5:1** de contraste, les marques porteuses d'information -**3:1**. Chaque couleur est accompagnée du ratio **mesuré sur la couleur relue du +**3:1**. Une commande sans texte, à icône seule, offre une cible de 24 × 24 px +au moins et porte son libellé en nom accessible. Chaque couleur est accompagnée du ratio **mesuré sur la couleur relue du rendu**, non sur le jeton source. Les deux thèmes sont éprouvés **par rendu**, jamais seulement par calcul, et le tableau des ratios est **engendré pour les deux** à partir des styles relevés au pilotage (§ 19.5), jamais recopié. Le diff --git a/src/application/commandes_mobilier.js b/src/application/commandes_mobilier.js index 8f32d96..83e67da 100644 --- a/src/application/commandes_mobilier.js +++ b/src/application/commandes_mobilier.js @@ -543,6 +543,10 @@ function lirePaliers(paliers) { }); } +/** Le plus grand poids d'un critère (§ 5.7), celui du moteur : l'écran en + * borne le champ du poids, que changerPriorites contrôle. */ +export { POIDS_MAX }; + // Vrai pour un poids du domaine : un entier de 1 à POIDS_MAX (§ 5.7). const poidsAdmis = (poids) => Number.isInteger(poids) && poids >= 1 && poids <= POIDS_MAX; diff --git a/src/interface/generation/Priorites.svelte b/src/interface/generation/Priorites.svelte index ef0ca31..fc8db21 100644 --- a/src/interface/generation/Priorites.svelte +++ b/src/interface/generation/Priorites.svelte @@ -30,11 +30,14 @@ // contre la liste, avec son remède, et décrit le groupe de l'éditeur // (aria-describedby), focalisable par programme (tabindex -1) : après le // remède, le focus revient au groupe ; rien ne s'écrit. Le poids d'un - // critère d'un palier partagé se saisit dans un Champ entier (poids.), - // écrit à la sortie du champ par changerPriorites ; son refus, un poids - // hors de son domaine compris, se pose contre le champ. + // critère d'un palier partagé se saisit dans un Champ nombre, étroit, de + // 1 à POIDS_MAX par pas de 1 (poids.), lu par lireEntier et écrit à + // la sortie du champ par changerPriorites ; son refus, un poids hors de + // son domaine compris, se pose contre le champ : les bornes de l'entrée + // guident ses flèches et ne refusent rien. import { getContext, tick } from 'svelte'; import { lireEntier } from '../../application/capacite.js'; + import { POIDS_MAX } from '../../application/commandes_mobilier.js'; import Champ from '../primitives/Champ.svelte'; import Commande from '../primitives/Commande.svelte'; import Refus from '../primitives/Refus.svelte'; @@ -134,6 +137,10 @@ valeur={poids} lire={lireEntier} executer={ecrirePoids(critere)} + type="number" + min={1} + max={POIDS_MAX} + pas={1} /> {/if}
diff --git a/src/interface/generation/Reglages.navigateur.test.js b/src/interface/generation/Reglages.navigateur.test.js index 1949710..c793faa 100644 --- a/src/interface/generation/Reglages.navigateur.test.js +++ b/src/interface/generation/Reglages.navigateur.test.js @@ -392,11 +392,22 @@ describe('l’ordre des priorités : l’éditeur (§ 5.7, A11, A12)', () => { expect(annonce(m)).toBe(t('generation.priorites.annonce', { critere: CUMULEES, palier: 3, paliers: 7, avec: [] })); }); - test('le poids d’un critère d’un palier partagé : 0 refusé contre son champ, rien d’écrit ; 5 écrit, une entrée', async () => { + test('le poids d’un critère d’un palier partagé : un champ nombre étroit de 1 à 100 ; 0 refusé contre son champ, rien d’écrit ; 5 écrit, une entrée', async () => { const m = await demonstration(); await cliquer(m, bouton(m, REDONDANCE, 'joindrePalier')); const champ = () => editeur(m).querySelector(`input[data-champ="poids.${REDONDANCE}"]`); expect(editeur(m).querySelector(`label[for="${champ().id}"]`).textContent.trim()).toBe(t('generation.priorites.poids', { critere: REDONDANCE })); + expect([champ().type, champ().min, champ().max, champ().step]).toEqual(['number', '1', '100', '1']); + // Étroit : six caractères de large, le contenu de la boîte, flèches + // comprises. + const sonde = document.createElement('span'); + sonde.style.cssText = 'position:absolute;font:inherit;visibility:hidden'; + sonde.textContent = '0'.repeat(6); + champ().parentElement.append(sonde); + const sixCaracteres = sonde.getBoundingClientRect().width; + sonde.remove(); + const style = getComputedStyle(champ()); + expect(Number.parseFloat(style.width)).toBeCloseTo(sixCaracteres, 0); const e0 = journal(m); await cliquer(m, champ()); champ().select(); diff --git a/src/interface/jetons.css b/src/interface/jetons.css index e095abd..61d2030 100644 --- a/src/interface/jetons.css +++ b/src/interface/jetons.css @@ -24,11 +24,18 @@ appartenances), --couleur-conflit-voisins (nouveaux voisins) et --couleur-conflit-table (nouvelle table). Chacune ne peint que la trame de sa règle ; tout autre jeton chromatique se tient à 30° au moins de chacune, - et aucun ne porte la valeur de l'une. Elles se distinguent entre elles et - des autres marques du plan sous les trois dichromaties (SEPARATIONS, - marques.js), et tiennent 3:1 sur le fond du plateau, qui borde leurs - traits. Le contour épaissi d'une table en conflit, commun aux trois, est - de l'encre forte du plan, --couleur-contour-fort. + sauf le rouge du retrait, et aucun ne porte la valeur de l'une. Elles se + distinguent entre elles et des autres marques du plan sous les trois + dichromaties (SEPARATIONS, marques.js), et tiennent 3:1 sur le fond du + plateau, qui borde leurs traits. Le contour épaissi d'une table en + conflit, commun aux trois, est de l'encre forte du plan, + --couleur-contour-fort. + + Le rouge du retrait, --couleur-retrait, peint la croix cerclée d'une + commande qui retire (Commande, variante retrait), sur la carte et son + fond sourd, jamais sur le plan : c'est le seul jeton chromatique admis à + moins de 30° d'une teinte de conflit (JETON_RETRAIT, marques.js), le + canal chromatique que les conflits se réservent étant celui du plan. La hachure d'une place à pourvoir peint des traits --couleur-hachure sur le fond de la chaise, --couleur-chaise-fond : ce sont ses deux couleurs. @@ -72,7 +79,8 @@ le neutre et les appartenances, chacun un fond teinté et une marque, que lisent les pastilles ; la bande de mode, un fond, un filet et une teinte par mode, celle de son icône ; les messages — avis, refus, - information —, chacun son encre, un fond, un filet et une marque. + information —, chacun son encre, un fond, un filet et une marque ; le + retrait, la teinte de sa croix. Le texte d'une pastille ou de la bande de mode garde l'encre de la page (§ 12.7) : la teinte se porte par le fond et la marque. Les marques des @@ -134,9 +142,11 @@ textarea { --rayon-3: 0.75rem; --rayon-pilule: 62.5rem; - /* La hauteur d'une cible principale (44 px), et la largeur au-delà de - laquelle le contenu d'un écran se centre. */ + /* La hauteur d'une cible principale (44 px), le côté minimal d'une cible + (24 px, § 14.5), et la largeur au-delà de laquelle le contenu d'un + écran se centre. */ --hauteur-cible: 2.75rem; + --cible-minimale: 1.5rem; --largeur-page: 87rem; /* Ombres : une couleur opaque par thème, --couleur-ombre ; celle d'une @@ -229,6 +239,9 @@ textarea { --couleur-information-filet: #b7cef0; --couleur-information-marque: var(--couleur-information); + /* Retrait : la croix cerclée d'une commande qui retire. */ + --couleur-retrait: #c62828; + /* Désignation. Le focus, la sélection, la mise en évidence et le fantôme passent sur une chaise vide comme sur une chaise pleine : leur clarté se tient entre le fond et le plein de la chaise, à 3:1 de chacun. */ @@ -334,6 +347,8 @@ textarea { --couleur-information-filet: #34507a; --couleur-information-marque: var(--couleur-information); + --couleur-retrait: #ff6b6b; + --couleur-focus: #8764e1; --couleur-selection: #4070f0; --couleur-mise-en-evidence: #00897b; diff --git a/src/interface/marques.js b/src/interface/marques.js index 48cc1c3..ab75d45 100644 --- a/src/interface/marques.js +++ b/src/interface/marques.js @@ -52,6 +52,14 @@ export const CONFLITS = Object.freeze( /** Les jetons des teintes de conflit, dans l'ordre de CONFLITS. */ export const JETONS_CONFLIT = Object.freeze(CONFLITS.map(({ jeton }) => jeton)); +/** Le jeton du rouge du retrait (§ 12.7) : la croix cerclée d'une commande + * qui retire (Commande, variante retrait), sur la carte et son fond sourd, + * jamais sur le plan. C'est le seul jeton chromatique admis à moins de 30° + * d'une teinte de conflit (verifierTeintesReservees, palette.js) : le + * canal chromatique que les conflits se réservent est celui du plan, où il + * ne se pose pas. */ +export const JETON_RETRAIT = c('retrait'); + /** Écarts perçus minimaux d'une teinte de conflit, en distance OKLab × 100 * mesurée en couleurs d'origine et sous chaque dichromatie (ecartPercu, * palette.js) : entreConflits, d'une teinte de conflit à une autre — la @@ -152,6 +160,9 @@ export const MARQUES = Object.freeze([ // contenu d'une feuille imprimée, mesuré sur le papier, clair dans les // deux thèmes. marque('filigrane', 'filigrane', ['papier']), + // La croix cerclée d'un retrait, seul contenu de son bouton : sur la + // carte, et sur son fond sourd, celui du survol. + marque('retrait', 'retrait', ['carte-fond', 'carte-fond-sourd']), ]); // Les fonds teintés d'une pastille : ceux de ses teintes, puis ceux des diff --git a/src/interface/marques.test.js b/src/interface/marques.test.js index 1c40195..e820ea4 100644 --- a/src/interface/marques.test.js +++ b/src/interface/marques.test.js @@ -18,7 +18,7 @@ const NOMS_MARQUES = [ 'cadenasFerme', 'cadenasOuvert', 'anneau', 'hachure', 'remplissagePlein', 'contourConflit', 'trameAppartenance', 'trameVoisins', 'trameTable', 'pastille', 'poignee', 'gel', 'miseEnEvidence', 'selection', 'focus', - 'contourTable', 'contourChaise', 'echelle', 'fantome', 'chargement', 'filigrane', + 'contourTable', 'contourChaise', 'echelle', 'fantome', 'chargement', 'filigrane', 'retrait', ]; // Les remplissages d'une chaise : fond, pleine, et les deux couleurs de la @@ -39,7 +39,7 @@ const FONDS_REQUIS = { const parNom = (registre, nom) => registre.find((e) => e.nom === nom); describe('registre des marques', () => { - test('les vingt et une marques du contrat, noms uniques, seuil 3, au moins un fond chacune', () => { + test('les vingt-deux marques du contrat, noms uniques, seuil 3, au moins un fond chacune', () => { assert.equal(MARQUES.length, NOMS_MARQUES.length); assert.deepEqual(MARQUES.map((m) => m.nom), NOMS_MARQUES); assert.equal(new Set(MARQUES.map((m) => m.nom)).size, MARQUES.length); diff --git a/src/interface/palette.js b/src/interface/palette.js index 328b774..6ee8fbd 100644 --- a/src/interface/palette.js +++ b/src/interface/palette.js @@ -273,14 +273,14 @@ export function teinte(couleur) { /** § 12.7 : chaque teinte réservée — celles des conflits — est chromatique, * et la teinte de chaque autre jeton chromatique se tient à ECART_TEINTE - * degrés au moins de chacune, dans chaque thème. Les teintes réservées ne - * se mesurent pas entre elles : verifierSeparation les distingue. Rend les - * fautes [{theme, reservee, jeton, ecart}], ecart en degrés sur le cercle, - * dans l'ordre thème, teinte réservée, jeton ; une teinte réservée - * achromatique est elle-même une faute, jeton égal à reservee et ecart - * null — aucune teinte n'est alors réservée. Un jeton réservé absent d'un - * thème lève. */ -export function verifierTeintesReservees(texteCss, reservees) { + * degrés au moins de chacune, dans chaque thème, sauf celle d'un jeton + * d'admis, qui ne se mesure pas. Les teintes réservées ne se mesurent pas + * entre elles : verifierSeparation les distingue. Rend les fautes + * [{theme, reservee, jeton, ecart}], ecart en degrés sur le cercle, dans + * l'ordre thème, teinte réservée, jeton ; une teinte réservée achromatique + * est elle-même une faute, jeton égal à reservee et ecart null — aucune + * teinte n'est alors réservée. Un jeton réservé absent d'un thème lève. */ +export function verifierTeintesReservees(texteCss, reservees, admis = []) { const fautes = []; for (const theme of THEMES) { const jetons = lireJetons(texteCss, theme); @@ -292,7 +292,7 @@ export function verifierTeintesReservees(texteCss, reservees) { continue; } for (const [nom, couleur] of jetons) { - if (reservees.includes(nom)) continue; + if (reservees.includes(nom) || admis.includes(nom)) continue; const h = teinte(couleur); if (h === null) continue; const brut = Math.abs(h - reference); diff --git a/src/interface/palette.test.js b/src/interface/palette.test.js index b058d77..d8a1f87 100644 --- a/src/interface/palette.test.js +++ b/src/interface/palette.test.js @@ -11,7 +11,7 @@ import { readdirSync, readFileSync } from 'node:fs'; import { join } from 'node:path'; import { fileURLToPath } from 'node:url'; import { describe, test } from '../../test/lanceur.js'; -import { JETONS_CONFLIT } from './marques.js'; +import { JETONS_CONFLIT, JETON_RETRAIT, MARQUES } from './marques.js'; import { SIMULATIONS, contraste, @@ -320,6 +320,9 @@ describe('teinte et verifierTeintesReservees (§ 12.7)', () => { assert.deepEqual(fautes.map((f) => `${f.theme}:${f.jeton}`), [ 'clair:--couleur-b', 'clair:--couleur-d', 'sombre:--couleur-b', 'sombre:--couleur-d', ]); + // Un jeton admis ne se mesure pas ; les autres, si. + const admis = verifierTeintesReservees(css, ['--couleur-conflit'], ['--couleur-b']); + assert.deepEqual(admis.map((f) => `${f.theme}:${f.jeton}`), ['clair:--couleur-d', 'sombre:--couleur-d']); }); }); @@ -496,6 +499,21 @@ describe('jetons.css', () => { // Aucun autre jeton ne porte le préfixe des teintes de conflit. assert.deepEqual([...jetons.keys()].filter((n) => n.startsWith('--couleur-conflit')), JETONS_CONFLIT, theme); } - assert.deepEqual(verifierTeintesReservees(JETONS, JETONS_CONFLIT), []); + assert.deepEqual(verifierTeintesReservees(JETONS, JETONS_CONFLIT, [JETON_RETRAIT]), []); + }); + + test('le rouge du retrait est le seul jeton admis près d’une teinte de conflit : chromatique, sa valeur à lui, peint sur la carte seulement, jamais sur le plan', () => { + assert.equal(JETON_RETRAIT, '--couleur-retrait'); + // Sans l'admission, le retrait est la seule faute. + assert.deepEqual([...new Set(verifierTeintesReservees(JETONS, JETONS_CONFLIT).map((f) => f.jeton))], [JETON_RETRAIT]); + for (const theme of ['clair', 'sombre']) { + const jetons = lireJetons(JETONS, theme); + const couleur = jetons.get(JETON_RETRAIT); + assert.notEqual(teinte(couleur), null, theme); + const memeValeur = [...jetons].filter(([n, c]) => n !== JETON_RETRAIT && String(c) === String(couleur)); + assert.deepEqual(memeValeur.map(([n]) => n), [], theme); + } + const peintes = MARQUES.filter((m) => m.jeton === JETON_RETRAIT || m.fonds.includes(JETON_RETRAIT)); + assert.deepEqual(peintes.map((m) => [m.nom, [...m.fonds]]), [['retrait', ['--couleur-carte-fond', '--couleur-carte-fond-sourd']]]); }); }); diff --git a/src/interface/participants/Fiche.svelte b/src/interface/participants/Fiche.svelte index 12bd172..ca138bf 100644 --- a/src/interface/participants/Fiche.svelte +++ b/src/interface/participants/Fiche.svelte @@ -18,10 +18,11 @@ // // Les étiquettes (etiquettes.js) : une pastille neutre à icône par // étiquette, dans l'ordre de la charge — la teinte d'appartenance ne sert - // qu'à l'appartenance (§ 12.7) —, chacune suivie de sa commande - // « Retirer », nommée par elle ; puis un formulaire d'ajout, dont le - // champ reçoit une ou plusieurs étiquettes séparées comme dans une - // cellule CSV. Entrée, ou « Ajouter », envoie la saisie entière à + // qu'à l'appartenance (§ 12.7) —, chacune suivie, en écriture seulement, + // de sa commande de retrait : une croix cerclée rouge (Commande, variante + // retrait), nommée « Retirer « <étiquette> » » ; puis un formulaire + // d'ajout, dont le champ reçoit une ou plusieurs étiquettes séparées + // comme dans une cellule CSV. Entrée, ou « Ajouter », envoie la saisie entière à // changerEtiquettes, qui la découpe, la met en forme et la réconcilie : la // fiche ne lit rien elle-même. Un refus de l'ajout — étiquette mal formée, // trop d'étiquettes, séance en lecture, plan bloqué — se pose contre le @@ -31,14 +32,15 @@ // (ORTHOGRAPHE_RETENUE), tant que la révision qui l'a produit est la // courante et la fiche celle de la personne. Le refus d'un retrait se // pose à côté de sa commande (Commande). En lecture comme sur un plan - // bloqué, pastilles et commandes restent : la séance refuse le geste, et + // bloqué (cleDeMode, mode.js), aucune commande de retrait ne se montre ; + // pastilles, champ et « Ajouter » restent : la séance refuse l'ajout, et // le refus porte son remède. Une saisie non envoyée est un geste ouvert // (geste.js), qui suspend le compte d'inactivité ; l'envoi n'est pas // ré-entrant. Passer à une autre personne vide le champ et son refus ; le // refus d'un envoi qui revient après ce passage n'est posé nulle part. - // Un retrait abouti rend le focus au « Retirer » qui prend la place du - // sien — le suivant, sinon le précédent (ligneVoisine) —, ou au champ - // quand il n'en reste aucun. + // Un retrait abouti rend le focus à la commande de retrait qui prend la + // place de la sienne — la suivante, sinon la précédente (ligneVoisine) —, + // ou au champ quand il n'en reste aucune. // // La fiche est une carte : fond et filet de la carte, rayon de 12 px, // ombre légère ; ses étiquettes et légendes de tableau en petites @@ -50,6 +52,7 @@ import { getContext, onDestroy, tick, untrack } from 'svelte'; import { nomAffiche } from '../../application/personnes.js'; import { etatPlaces, statuts } from '../../application/places.js'; + import { cleDeMode } from '../mode.js'; import Commande from '../primitives/Commande.svelte'; import Icone from '../primitives/Icone.svelte'; import Pastille from '../primitives/Pastille.svelte'; @@ -79,7 +82,7 @@ const etapes = $derived(itineraire(participant.id, charge.evenement.tours, (tour) => etatPlaces(charge, { tour }))); // La fiche et son champ d'ajout, montés : le défilement vise la fiche, - // le focus d'après un retrait ses « Retirer » ou le champ. + // le focus d'après un retrait ses commandes de retrait ou le champ. let element; let champ; $effect(() => { @@ -97,6 +100,9 @@ const idSignal = `${uid}-orthographe`; const pastilles = $derived(etiquettesDeLaFiche(participant)); + // Les commandes de retrait ne se montrent qu'en écriture, hors d'un plan + // bloqué. + const retraitOffert = $derived(cleDeMode($seance) === 'coquille.mode.ecriture'); const signaux = $derived(signauxOrthographe(dernier, { revision: $seance.evenement?.revision ?? null, participant: participant.id })); const decrit = $derived(refusChamp !== null ? idRefus : signaux.length > 0 ? idSignal : undefined); @@ -142,8 +148,8 @@ } // Retire motCle ; aboutie, quand le bouton détruit a laissé le focus au - // document, le porte au « Retirer » voisin ou au champ. Un refus remonte - // à Commande, qui le pose à côté du bouton resté en place. + // document, le porte à la commande de retrait voisine ou au champ. Un + // refus remonte à Commande, qui le pose à côté du bouton resté en place. async function retirer(motCle) { const id = participant.id; const rang = pastilles.indexOf(motCle); @@ -190,13 +196,15 @@ {#each pastilles as motCle (motCle)}
  • - retirer(motCle)} - /> + {#if retraitOffert} + retirer(motCle)} + /> + {/if}
  • {/each} @@ -399,8 +407,9 @@ } /* Les étiquettes : l'intitulé en petites capitales, les pastilles en - rangée qui se replie, chacune suivie de son « Retirer » ; puis le champ - d'ajout et son bouton sur une ligne, le refus ou le signal dessous. */ + rangée qui se replie, chacune suivie, en écriture, de sa croix de + retrait ; puis le champ d'ajout et son bouton sur une ligne, le refus + ou le signal dessous. */ .etiquettes { display: flex; flex-direction: column; diff --git a/src/interface/participants/Participants.navigateur.test.js b/src/interface/participants/Participants.navigateur.test.js index e5f4249..7cc08a9 100644 --- a/src/interface/participants/Participants.navigateur.test.js +++ b/src/interface/participants/Participants.navigateur.test.js @@ -935,10 +935,11 @@ describe('les étiquettes : la fiche et la légende (§ 4.5, § 10.2, § 5.9)', const rubrique = (m) => fiche(m).querySelector('[data-etiquettes]'); const champEtiquettes = (m) => fiche(m).querySelector('[data-champ="etiquettes"]'); const ajoutEtiquettes = (m) => fiche(m).querySelector('[data-commande="ajouterEtiquettes"]'); + // La commande de retrait d'une étiquette : une croix cerclée sans texte, + // nommée par son aria-label. + const retraits = (m) => [...fiche(m).querySelectorAll('[data-commande="retirerEtiquette"]')]; const retrait = (m, motCle) => - [...fiche(m).querySelectorAll('[data-commande="retirerEtiquette"]')].find( - (bouton) => bouton.textContent.trim() === t('participants.etiquettes.retirer', { motCle }), - ) ?? null; + retraits(m).find((bouton) => bouton.getAttribute('aria-label') === t('participants.etiquettes.retirer', { motCle })) ?? null; const legendeEtiquettes = (m) => section(m).querySelector('[data-legende-etiquettes]'); const etiquettesDe = (m, nom) => participants(m).find((p) => p.nom === nom).etiquettes; @@ -964,7 +965,7 @@ describe('les étiquettes : la fiche et la légende (§ 4.5, § 10.2, § 5.9)', await attendreSeance(m); } - test('la fiche montre chaque étiquette en pastille neutre à icône, sa commande « Retirer » nommée par elle ; sans étiquette, elle le dit', async () => { + test('la fiche montre chaque étiquette en pastille neutre à icône, sa croix de retrait rouge nommée par elle ; sans étiquette, elle le dit', async () => { const m = await evenementOuvert({ personnes: TROIS_ETIQUETEES }); await m.seance.executer('changerEtiquettes', { id: idDe(m, 'Ombrelle'), ajouter: 'finance, Montréal' }); await ouvrirFiche(m, 'Ombrelle'); @@ -980,10 +981,33 @@ describe('les étiquettes : la fiche et la légende (§ 4.5, § 10.2, § 5.9)', // La teinte d'appartenance ne sert qu'à l'appartenance (§ 12.7). const teintes = new Set([1, 2, 3, 4, 5, 6].map((rang) => JSON.stringify(jeton(`--couleur-appartenance-${rang}-fond`)))); for (const li of elements) expect(teintes.has(JSON.stringify(fondDe(pastilleDans(li))))).toBe(false); - for (const motCle of ['Montréal', 'finance']) expect(retrait(m, motCle), motCle).not.toBeNull(); - // En sombre, la pastille prend le neutre sombre. + // La croix de retrait : une icône seule, sans texte, la croix cerclée + // de la teinte du retrait, marquée « retrait », dans une cible de + // 24 px de côté au moins, et un anneau de focus au clavier. + for (const motCle of ['Montréal', 'finance']) { + const bouton = retrait(m, motCle); + expect(bouton, motCle).not.toBeNull(); + expect(bouton.textContent.trim(), motCle).toBe(''); + const traces = [...bouton.querySelectorAll('svg path')]; + expect(traces.length, motCle).toBeGreaterThan(0); + for (const trace of traces) { + expect(trace.dataset.marque).toBe('retrait'); + expect(composantes(getComputedStyle(trace).stroke)).toEqual(jeton('--couleur-retrait')); + } + const { width, height } = bouton.getBoundingClientRect(); + expect(Math.min(width, height), motCle).toBeGreaterThanOrEqual(24); + } + // Tab mène d'une croix à la suivante, qui porte l'anneau de focus. + retrait(m, 'Montréal').focus(); + await touche('Tab'); + expect(document.activeElement).toBe(retrait(m, 'finance')); + expect(retrait(m, 'finance').matches(':focus-visible')).toBe(true); + expect(getComputedStyle(retrait(m, 'finance')).outlineStyle).toBe('solid'); + expect(composantes(getComputedStyle(retrait(m, 'finance')).outlineColor)).toEqual(jeton('--couleur-focus')); + // En sombre, la pastille prend le neutre sombre, la croix le rouge sombre. await sousTheme('sombre', async () => { for (const li of elements) expect(fondDe(pastilleDans(li))).toEqual(jeton('--couleur-neutre-fond')); + expect(composantes(getComputedStyle(retrait(m, 'finance').querySelector('path')).stroke)).toEqual(jeton('--couleur-retrait')); }); await ouvrirFiche(m, 'Pervenche'); @@ -992,7 +1016,7 @@ describe('les étiquettes : la fiche et la légende (§ 4.5, § 10.2, § 5.9)', expect(fiche(m).querySelectorAll('[data-commande="retirerEtiquette"]')).toHaveLength(0); }); - test('au clavier : une saisie de trois étiquettes, Entrée, une entrée au journal ; le champ se vide et garde le focus ; « Retirer » ôte la sienne et rend le focus à sa voisine', async () => { + test('au clavier : une saisie de trois étiquettes, Entrée, une entrée au journal ; le champ se vide et garde le focus ; la croix de retrait ôte la sienne et rend le focus à sa voisine', async () => { const m = await evenementOuvert({ personnes: TROIS_ETIQUETEES }); await ouvrirFiche(m, 'Pervenche'); const champ = champEtiquettes(m); @@ -1017,8 +1041,8 @@ describe('les étiquettes : la fiche et la légende (§ 4.5, § 10.2, § 5.9)', expect(document.activeElement).toBe(champEtiquettes(m)); expect([...rubrique(m).querySelectorAll('li')].map((li) => texteDe(pastilleDans(li)))).toEqual(['bénévole', 'ressources humaines', 'voile']); - // « Retirer » activé par Entrée : le focus passe au « Retirer » suivant, - // sinon au précédent, sinon au champ ; jamais au document. + // La croix de retrait activée par Entrée : le focus passe à la croix + // suivante, sinon à la précédente, sinon au champ ; jamais au document. const retirerAuClavier = async (motCle) => { retrait(m, motCle).focus(); await touche('Enter'); @@ -1197,29 +1221,31 @@ describe('les étiquettes : la fiche et la légende (§ 4.5, § 10.2, § 5.9)', expect(legendeEtiquettes(m)).toBeNull(); }); - test('en lecture : les pastilles restent ; « Ajouter » et « Retirer » sont refusés, chacun avec son remède, et rien ne s’écrit', async () => { + test('en lecture : les pastilles restent, sans croix de retrait ; « Ajouter » est refusé avec son remède, rien ne s’écrit ; de retour en écriture, les croix reviennent', async () => { const m = await evenementOuvert({ personnes: TROIS_ETIQUETEES }); await m.seance.executer('changerEtiquettes', { id: idDe(m, 'Ombrelle'), ajouter: 'finance' }); await m.seance.passerEnLecture(); await ouvrirFiche(m, 'Ombrelle'); const avant = texteJournal(m); expect(texteDe(pastilleDans(rubrique(m).querySelector('li')))).toBe('finance'); + expect(retraits(m)).toHaveLength(0); await ajouterParClavier(m, 'bénévole'); const message = document.getElementById(champEtiquettes(m).getAttribute('aria-describedby')); expect(message.dataset.refus).toBe('MODE_LECTURE'); expect(message.querySelector('[data-commande]')).not.toBeNull(); - - await cliquer(retrait(m, 'finance')); - await attendreSeance(m); - const refusRetrait = retrait(m, 'finance').parentElement.querySelector('[data-refus]'); - expect(refusRetrait.dataset.refus).toBe('MODE_LECTURE'); - expect(refusRetrait.querySelector('[data-commande]')).not.toBeNull(); expect(etiquettesDe(m, 'Ombrelle')).toEqual(['finance']); expect(texteJournal(m)).toBe(avant); + + await m.seance.passerEnEcriture(); + await rendu(); + expect(retrait(m, 'finance')).not.toBeNull(); + await m.seance.passerEnLecture(); + await rendu(); + expect(retraits(m)).toHaveLength(0); }); - test('plan bloqué, en écriture : « Ajouter » et « Retirer » sont refusés avec « Débloquer » pour remède', async () => { + test('plan bloqué : aucune croix de retrait ; « Ajouter » est refusé avec « Débloquer » pour remède', async () => { const m = await evenementOuvert({ sieges: 4, tours: 2, tables: 1, personnes: TROIS_ETIQUETEES }); await m.seance.executer('changerEtiquettes', { id: idDe(m, 'Ombrelle'), ajouter: 'finance' }); await m.app.generation().generer({ graine: 1, nombre: 1, arret: 50, historique: 1000 }); @@ -1229,13 +1255,12 @@ describe('les étiquettes : la fiche et la légende (§ 4.5, § 10.2, § 5.9)', await ouvrirFiche(m, 'Ombrelle'); const avant = texteJournal(m); + expect(texteDe(pastilleDans(rubrique(m).querySelector('li')))).toBe('finance'); + expect(retraits(m)).toHaveLength(0); + await ajouterParClavier(m, 'bénévole'); const message = document.getElementById(champEtiquettes(m).getAttribute('aria-describedby')); expect(message.querySelector('[data-commande="debloquer"]')).not.toBeNull(); - await cliquer(retrait(m, 'finance')); - await attendreSeance(m); - const refusRetrait = retrait(m, 'finance').parentElement.querySelector('[data-refus]'); - expect(refusRetrait.querySelector('[data-commande="debloquer"]')).not.toBeNull(); expect(texteJournal(m)).toBe(avant); }); }); diff --git a/src/interface/primitives/Champ.svelte b/src/interface/primitives/Champ.svelte index 8d2db3d..8fe8de5 100644 --- a/src/interface/primitives/Champ.svelte +++ b/src/interface/primitives/Champ.svelte @@ -36,13 +36,32 @@ // son étiquette seule ne le distingue pas d'un homonyme ; null, // l'étiquette le nomme. L'étiquette visible ne change pas. Le refus reçoit // nom pour nommer son remède. + // + // type est celui de l'entrée : 'text', le défaut, ou 'number', un champ + // étroit de six caractères, qui porte min, max et pas (step) quand ils + // sont donnés. La lecture reste celle de lire, et le domaine celui de + // l'exécution : le navigateur rend vide un texte qu'il ne lit pas comme + // un nombre, que lire refuse comme tout texte vide. import { getContext, onDestroy, untrack } from 'svelte'; import { effetDe } from '../raccourcis.js'; import { creerMachineChamp, estRefusSeance, refusSeanceCourant } from './champ.js'; import { creerGeste } from './geste.js'; import Refus from './Refus.svelte'; - let { cle, etiquette, valeur, lire, executer, details = {}, nom = null, gestes = {} } = $props(); + let { + cle, + etiquette, + valeur, + lire, + executer, + details = {}, + nom = null, + gestes = {}, + type = 'text', + min = null, + max = null, + pas = null, + } = $props(); const t = getContext('t'); const seance = getContext('seance') ?? null; @@ -192,7 +211,10 @@ {libelle}{#if forme === 'retrait'}{:else}{libelle}{/if} {#if refus !== null} (refus = null)} objet={nomAccessible} {gestes} /> @@ -177,6 +189,27 @@ background-color: var(--couleur-carte-fond-sourd); } + /* Le retrait : l'icône seule, de la teinte du retrait, centrée dans une + cible ronde de --cible-minimale de côté au moins ; ni fond ni filet au + repos, le fond sourd de la carte au survol. */ + button.retrait { + display: inline-flex; + align-items: center; + justify-content: center; + box-sizing: border-box; + min-width: var(--cible-minimale); + min-height: var(--cible-minimale); + padding: 0; + border-color: transparent; + border-radius: var(--rayon-pilule); + background-color: transparent; + color: var(--couleur-retrait); + } + + button.retrait:hover { + background-color: var(--couleur-carte-fond-sourd); + } + button[aria-disabled='true'] { border-style: dashed; color: var(--couleur-page-texte-secondaire); diff --git a/src/interface/primitives/Icone.svelte b/src/interface/primitives/Icone.svelte index d956a23..1ed0a1e 100644 --- a/src/interface/primitives/Icone.svelte +++ b/src/interface/primitives/Icone.svelte @@ -6,10 +6,13 @@ // sans remplissage, du trait de la couleur courante — le parent la règle // par color, d'un jeton. Elle est cachée aux technologies d'assistance et // ne prend jamais le focus : un libellé l'accompagne toujours. taille : - // le côté du carré, en px. Un nom inconnu lève RangeError au montage. + // le côté du carré, en px. marque, le nom d'une marque du registre + // (MARQUES, marques.js), se pose en data-marque sur chacun de ses tracés, + // que les épreuves de rendu mesurent ; null, aucun. Un nom inconnu lève + // RangeError au montage. import { traceIcone } from './icones.js'; - let { nom, taille = 16 } = $props(); + let { nom, taille = 16, marque = null } = $props(); const traces = $derived(traceIcone(nom)); @@ -28,7 +31,7 @@ focusable="false" > {#each traces as trace, rang (rang)} - + {/each} diff --git a/src/interface/primitives/commande.js b/src/interface/primitives/commande.js index 6bf8f9e..1ba7df4 100644 --- a/src/interface/primitives/commande.js +++ b/src/interface/primitives/commande.js @@ -39,19 +39,26 @@ import { COMMANDES } from '../../application/commandes.js'; /** Les variantes d'une commande, la secondaire en tête — celle d'une * commande qui n'en donne pas : principale, l'action à faire sur l'écran, * pleine de la teinte de l'action ; secondaire, à filet ; discrète, sans - * fond ni filet au repos. La variante ne change que l'apparence : le rôle, - * les attributs et la machine restent ceux de toute commande. */ -export const VARIANTES_COMMANDE = Object.freeze(['secondaire', 'principale', 'discrete']); + * fond ni filet au repos ; retrait, une croix cerclée de la teinte du + * retrait, sans fond ni filet au repos, dont le libellé devient le nom + * accessible. La variante ne change que l'apparence : le rôle, la + * désignation et la machine restent ceux de toute commande. */ +export const VARIANTES_COMMANDE = Object.freeze(['secondaire', 'principale', 'discrete', 'retrait']); /** - * Une variante de commande, rendue telle quelle. + * Une variante de commande, rendue telle quelle. Le retrait ne montre + * aucun texte : son libellé armé n'aurait où s'afficher, et une commande à + * confirmer ne le prend pas. * * @param {string} variante - * @returns {'secondaire'|'principale'|'discrete'} - * @throws {RangeError} hors de VARIANTES_COMMANDE, en la nommant + * @param {string|null} [confirmer] la clé de confirmation de la commande + * @returns {'secondaire'|'principale'|'discrete'|'retrait'} + * @throws {RangeError} hors de VARIANTES_COMMANDE, en la nommant ; le + * retrait d'une commande à confirmer */ -export function exigerVariante(variante) { +export function exigerVariante(variante, confirmer = null) { if (!VARIANTES_COMMANDE.includes(variante)) throw new RangeError(`commande : variante inconnue ${JSON.stringify(variante) ?? 'undefined'}`); + if (variante === 'retrait' && confirmer !== null) throw new RangeError('commande : le retrait ne se confirme pas'); return variante; } diff --git a/src/interface/primitives/commande.test.js b/src/interface/primitives/commande.test.js index f92ca0b..5abf1fa 100644 --- a/src/interface/primitives/commande.test.js +++ b/src/interface/primitives/commande.test.js @@ -352,8 +352,8 @@ describe('commande : l’action d’un remède', () => { }); describe('commande : la variante', () => { - test('trois variantes, la secondaire en tête : celle d’une commande qui n’en donne pas', () => { - assert.deepEqual(VARIANTES_COMMANDE, ['secondaire', 'principale', 'discrete']); + test('quatre variantes, la secondaire en tête : celle d’une commande qui n’en donne pas', () => { + assert.deepEqual(VARIANTES_COMMANDE, ['secondaire', 'principale', 'discrete', 'retrait']); assert.ok(Object.isFrozen(VARIANTES_COMMANDE)); }); @@ -363,4 +363,10 @@ describe('commande : la variante', () => { assert.throws(() => exigerVariante(autre), (e) => e instanceof RangeError && e.message.includes(JSON.stringify(autre) ?? 'undefined'), String(autre)); } }); + + test('le retrait ne se confirme pas : avec une clé de confirmation, exigerVariante lève RangeError ; les autres variantes la prennent', () => { + assert.throws(() => exigerVariante('retrait', 'communs.commande.confirmer'), RangeError); + assert.equal(exigerVariante('retrait', null), 'retrait'); + for (const variante of ['secondaire', 'principale', 'discrete']) assert.equal(exigerVariante(variante, 'communs.commande.confirmer'), variante); + }); }); diff --git a/src/interface/primitives/habillage.navigateur.test.js b/src/interface/primitives/habillage.navigateur.test.js index 9a199b1..1c0d164 100644 --- a/src/interface/primitives/habillage.navigateur.test.js +++ b/src/interface/primitives/habillage.navigateur.test.js @@ -415,6 +415,30 @@ describe('commande : la variante', () => { expect(style(secondaire).fontSize).toBe('16px'); expect(() => commande({ variante: 'primaire' })).toThrow(RangeError); }); + + test('retrait : la croix cerclée seule, de la teinte du retrait, sans texte ni fond, nommée par son libellé, dans une cible de 24 px au moins ; à confirmer, elle lève au montage', async () => { + const libelle = t('participants.etiquettes.retirer', { motCle: 'voile' }); + const retrait = commande({ variante: 'retrait', cle: 'participants.etiquettes.retirer', details: { motCle: 'voile' }, nom: 'retirerEtiquette' }); + expect(retrait.textContent.trim()).toBe(''); + expect(retrait.getAttribute('aria-label')).toBe(libelle); + expect(retrait.getAttribute('data-commande')).toBe('retirerEtiquette'); + expect([...retrait.querySelectorAll('svg path')].map((p) => p.getAttribute('d'))).toEqual([...ICONES.croixCerclee]); + const { width, height } = retrait.getBoundingClientRect(); + expect(Math.min(width, height)).toBeGreaterThanOrEqual(24); + for (const theme of THEMES) { + await sousTheme(theme, () => { + expect(fond(retrait), theme).toEqual(TRANSPARENT); + expect(composantes(style(retrait).borderTopColor)[3], theme).toBe(0); + for (const trace of retrait.querySelectorAll('path')) { + expect(trace.dataset.marque, theme).toBe('retrait'); + expect(composantes(getComputedStyle(trace).stroke), theme).toEqual(jeton('--couleur-retrait')); + } + }); + } + // Un nom accessible donné passe devant le libellé. + expect(commande({ variante: 'retrait', nomAccessible: 'Ôter' }).getAttribute('aria-label')).toBe('Ôter'); + expect(() => commande({ variante: 'retrait', confirmer: 'communs.commande.confirmerDeblocage' })).toThrow(RangeError); + }); }); describe('refus, infobulle, champ et fenêtre à confirmer : habillés des jetons', () => { diff --git a/src/interface/primitives/icones.js b/src/interface/primitives/icones.js index e81c03d..c131db3 100644 --- a/src/interface/primitives/icones.js +++ b/src/interface/primitives/icones.js @@ -44,6 +44,8 @@ const DESSINS = [ ['demarrer', ['M8 5l11 7-11 7z']], // Une étiquette (§ 4.5) : l'étiquette à œillet, pointe en bas à droite. ['etiquettes', ['M3 3h8l10 10-8 8L3 11z', cercle(7.5, 7.5, 1.5)]], + // Un retrait (Commande, variante retrait) : une croix dans un cercle. + ['croixCerclee', [cercle(12, 12, 9), 'M9 9l6 6M15 9l-6 6']], ]; /** Les noms des icônes, dans l'ordre du module. */ diff --git a/src/interface/primitives/icones.test.js b/src/interface/primitives/icones.test.js index b13ae9e..5954d65 100644 --- a/src/interface/primitives/icones.test.js +++ b/src/interface/primitives/icones.test.js @@ -17,7 +17,7 @@ describe('icônes des primitives', () => { assert.deepEqual(NOMS_ICONES, [ 'crayon', 'listePoints', 'coche', 'cadenas', 'liste', 'importer', 'exporter', 'ajouter', 'chercher', 'calendrier', 'information', 'triangle', 'interdit', 'dossier', 'oeil', 'stylo', 'defaire', 'imprimer', - 'etoile', 'croix', 'chevron', 'points', 'demarrer', 'etiquettes', + 'etoile', 'croix', 'chevron', 'points', 'demarrer', 'etiquettes', 'croixCerclee', ]); assert.ok(Object.isFrozen(ICONES) && Object.isFrozen(NOMS_ICONES)); assert.deepEqual(Object.keys(ICONES).sort(), [...NOMS_ICONES].sort()); diff --git a/src/interface/primitives/primitives.navigateur.test.js b/src/interface/primitives/primitives.navigateur.test.js index 4aff6bb..13d1b8d 100644 --- a/src/interface/primitives/primitives.navigateur.test.js +++ b/src/interface/primitives/primitives.navigateur.test.js @@ -251,6 +251,46 @@ describe('champ : la fin du geste (§ 2.1, § 8.2)', () => { }); }); +describe('champ : le type de l’entrée', () => { + test('texte par défaut, sans bornes ; nombre : min, max et pas posés, une lettre ne s’y inscrit pas, et lire refuse le vide qu’elle laisse ; des chiffres s’écrivent', async () => { + const montage = await preparer(); + const { entree } = monterChamp(montage); + expect(entree.type).toBe('text'); + for (const attribut of ['min', 'max', 'step']) expect(entree.hasAttribute(attribut), attribut).toBe(false); + + const recus = []; + const cible = monter( + Champ, + { + cle: 'nombre', + etiquette: 'communs.commande.modifier', + valeur: 3, + type: 'number', + min: 1, + max: 100, + pas: 1, + lire: (texte) => (/^\d+$/.test(texte) ? { valeur: Number(texte) } : { refus: { code: 'NON_ENTIER', details: { saisie: texte, remede: null } } }), + executer: (valeur) => recus.push(valeur), + }, + montage.contexte, + ); + const nombre = cible.querySelector('input[data-champ="nombre"]'); + expect([nombre.type, nombre.min, nombre.max, nombre.step, nombre.value]).toEqual(['number', '1', '100', '1', '3']); + await cliquer(nombre); + nombre.select(); + await taper('x'); + await cliquerAilleurs(); + expect(nombre.value).toBe(''); + expect(cible.querySelector('[data-refus="NON_ENTIER"]')).not.toBeNull(); + expect(recus).toEqual([]); + await cliquer(nombre); + await taper('42'); + await cliquerAilleurs(); + expect(recus).toEqual([42]); + expect(cible.querySelector('[data-refus]')).toBeNull(); + }); +}); + describe('champ : le refus de l’exécution (§ 2.1, § 5.9)', () => { test('executer refusé avec un code : le refus se pose contre le champ, la saisie gardée, rien d’écrit ; le refus levé par son remède, le champ rend la valeur enregistrée, et une saisie nouvelle s’écrit', async () => { const montage = await preparer(); diff --git a/test/navigateur/ecrans.js b/test/navigateur/ecrans.js index 0661fc0..a778c82 100644 --- a/test/navigateur/ecrans.js +++ b/test/navigateur/ecrans.js @@ -659,7 +659,7 @@ export const ECRANS = Object.freeze([ async atteindre(contexte) { // Deux personnes étiquetées, puis la fiche d'une troisième, où // « FINANCE » tapé au clavier s'enregistre sous l'orthographe que - // l'événement porte : les pastilles neutres et leur « Retirer », le + // l'événement porte : les pastilles neutres et leur croix de retrait, le // signal d'orthographe contre le champ, et la légende des étiquettes // sous celle des appartenances. let charge = chargePrincipale();