From 3028fca2ed30aeb49e83627f185c80d57a9a2864 Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Fri, 9 Oct 2026 04:01:57 -0400 Subject: [PATCH] [FIX] interface: ghost labels, keyboard, valid HTML, closed data-* list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The drag ghost names what releasing at the point changes, floor included. Escape on a seat returns to its table, a refused Tab stays put, the legend toggle exposes aria-expanded, and the header markup is valid HTML. Every data-* attribute the interface sets belongs to one closed list that a guard reads. The tree guards follow helpers a test imports, and the reporter says the whole series stays watched under coverage on a small machine. Checked: every new test red first; 3001 node, 635 browser and 63 long tests green from the index alone. --- FR --- [FIX] interface : fantôme nommé, clavier, HTML valide, data-* fermés Le fantôme du glisser nomme ce que relâcher au point modifie, sol compris. Échap sur un siège remonte à sa table, un Tab refusé reste en place, la bascule de la légende expose aria-expanded, et la tête est du HTML valide. Chaque attribut data-* que l'interface pose appartient à une liste fermée qu'une garde lit. Les gardes de l'arbre suivent les aides qu'une épreuve importe, et le rapporteur dit la série entière surveillée sous couverture. Vérifié : chaque épreuve rouge d'abord ; 3001 node, 635 navigateur et 63 longues vertes depuis l'index seul. Assisted-by: Claude Opus 5.5 --- scripts/essai_demarrage.test.js | 25 ++ src/application/executeur_travailleur.test.js | 23 ++ src/demo/livrees.js | 14 + src/interface/Bandeau.svelte | 7 +- .../accueil/Accueil.navigateur.test.js | 74 ++++ src/interface/accueil/Demonstrations.svelte | 27 +- src/interface/accueil/ListeEvenements.svelte | 5 +- src/interface/accueil/modele.js | 30 +- src/interface/accueil/modele.test.js | 9 +- src/interface/etat_ui.js | 7 +- src/interface/evenement/CadreMode.svelte | 4 +- .../evenement/EnteteEvenement.svelte | 7 +- .../evenement/Evenement.navigateur.test.js | 43 ++- src/interface/evenement/modele.test.js | 2 + .../generation/Generation.navigateur.test.js | 24 +- .../generation/SectionGeneration.svelte | 4 +- src/interface/generation/meilleure.js | 6 +- src/interface/generation/meilleure.test.js | 37 +- .../import/Import.navigateur.test.js | 24 +- src/interface/import/SectionImport.svelte | 8 +- src/interface/libelles/accueil.js | 3 + src/interface/libelles/plan.js | 19 +- src/interface/marques.js | 5 +- src/interface/palette.test.js | 3 +- src/interface/parcours.navigateur.test.js | 53 ++- src/interface/participants/Grille.svelte | 68 ++-- .../Participants.navigateur.test.js | 62 +++- .../participants/SectionParticipants.svelte | 19 +- src/interface/participants/grille.js | 99 ++++-- src/interface/participants/grille.test.js | 53 +++ src/interface/plan/BarreVue.svelte | 37 +- src/interface/plan/Dessin.navigateur.test.js | 36 +- .../plan/Panneaux.navigateur.test.js | 103 ++++-- src/interface/plan/Plan.svelte | 20 +- src/interface/plan/RefusPlace.svelte | 4 +- src/interface/plan/Vue.navigateur.test.js | 22 +- src/interface/plan/depot.js | 38 ++- src/interface/plan/gestes.js | 42 ++- src/interface/plan/gestes.test.js | 48 ++- src/interface/plan/modele.test.js | 69 ++-- .../primitives/primitives.navigateur.test.js | 121 +++++-- .../Propositions.navigateur.test.js | 83 ++++- .../propositions/SectionPropositions.svelte | 41 +-- .../propositions/TableauPropositions.svelte | 17 +- src/interface/propositions/modele.js | 22 +- src/interface/propositions/modele.test.js | 14 +- src/interface/tables/modele.js | 21 +- src/interface/transversal.navigateur.test.js | 63 +++- src/main.js | 13 +- test/arborescence.test.js | 189 ++++++++--- test/construction.long.test.js | 3 + test/livrable.test.js | 36 -- test/machine.js | 9 +- test/machine.test.js | 5 + test/navigateur/designations.js | 179 ++++++++++ test/navigateur/ecrans.js | 52 ++- test/navigateur/entrees.js | 12 +- test/navigateur/montage.js | 43 +-- test/rapporteur.js | 2 +- test/rapporteur.test.js | 2 +- test/sources_interface.test.js | 318 +++++++++++++++--- 61 files changed, 1936 insertions(+), 492 deletions(-) create mode 100644 test/navigateur/designations.js diff --git a/scripts/essai_demarrage.test.js b/scripts/essai_demarrage.test.js index 161d018..24f17c8 100644 --- a/scripts/essai_demarrage.test.js +++ b/scripts/essai_demarrage.test.js @@ -128,6 +128,31 @@ describe('essai de démarrage : dossiers et environnement (§ 8.6)', () => { assert.deepEqual(donneesApplicativesSurExe({ TMPDIR: '/' }, {}, 'linux'), []); }); + test('sous macOS, Application Support de la coquille et du poste ; sous Windows, TMP l’emporte sur TEMP', () => { + // macOS : un essai rangé sous le dossier Application Support du poste, + // que le HOME de la coquille ne déplace pas, puis sous celui de la + // coquille. + const P = path.posix; + const maison = P.join('/', 'maison-essai'); + const support = P.join(maison, 'Library', 'Application Support'); + const dossiers = dossiersDEssai(P.join(support, 'gtt-essai-demarrage-x'), P); + const env = environnementDEssai({ HOME: maison }, dossiers, P); + assert.deepEqual(donneesApplicativesSurExe(env, {}, 'darwin'), []); + assert.deepEqual(donneesApplicativesSurExe(env, { HOME: maison }, 'darwin'), [support]); + assert.deepEqual(donneesApplicativesSurExe({ ...env, HOME: maison }, {}, 'darwin'), [support]); + // Sous Linux, le même poste ne range rien sous Application Support. + assert.deepEqual(donneesApplicativesSurExe(env, { HOME: maison }, 'linux'), []); + // Windows : le temporaire est TMP, puis TEMP à défaut. + const W = path.win32; + const racine = W.join('D:\\', 'essais', 'gtt-essai-demarrage-x'); + const envW = environnementDEssai({}, dossiersDEssai(racine, W), W); + const dessus = W.join('D:\\', 'essais'); + const ailleurs = W.join('E:\\', 'temporaire'); + assert.deepEqual(donneesApplicativesSurExe({ ...envW, TMP: dessus, TEMP: ailleurs }, {}, 'win32'), [dessus]); + assert.deepEqual(donneesApplicativesSurExe({ ...envW, TMP: ailleurs, TEMP: dessus }, {}, 'win32'), []); + assert.deepEqual(donneesApplicativesSurExe({ ...envW, TMP: '', TEMP: dessus }, {}, 'win32'), [dessus]); + }); + test('la liste des données applicatives est celle que decrireSysteme compose : un emplacement qu’elle ajoute est jugé, et un app.getPath que l’essai ne sait pas émuler lève', () => { const P = path.posix; const R = P.join('/', 'racine-essai', 'gtt-essai-demarrage-x'); diff --git a/src/application/executeur_travailleur.test.js b/src/application/executeur_travailleur.test.js index 213d6e5..3f968bb 100644 --- a/src/application/executeur_travailleur.test.js +++ b/src/application/executeur_travailleur.test.js @@ -247,6 +247,29 @@ describe('exécuteur du travailleur : le groupe (§ 5.7, § 5.10)', () => { execution.annuler(); }); + test('l’avancement ne décroît jamais : un avancement plus petit, ou égal, que le dernier de sa proposition ne publie rien', () => { + const { fabrique, travailleurs } = fabriqueFactice(); + const reglages = { ...REGLAGES, nombre: 2, arret: 100 }; + const execution = creerExecuteurTravailleur(fabrique).lancer(CONFIGURATION, reglages, { coeurs: 2 }); + execution.resultat.catch(() => undefined); + const recus = []; + execution.surAvancement((fait, total) => recus.push([fait, total])); + const [premier, second] = travailleurs; + premier.poster({ type: 'avancement', fait: 60, total: 100 }); + premier.poster({ type: 'avancement', fait: 20, total: 100 }); + second.poster({ type: 'avancement', fait: 50, total: 100 }); + second.poster({ type: 'avancement', fait: 50, total: 100 }); + premier.poster({ type: 'proposition', proposition: propositionDe(1) }); + second.poster({ type: 'avancement', fait: 40, total: 100 }); + assert.deepEqual(recus, [ + [60, 200], + [110, 200], + [150, 200], + ]); + for (let i = 1; i < recus.length; i += 1) assert.ok(recus[i][0] >= recus[i - 1][0], `avancement ${i}`); + execution.annuler(); + }); + test('un abonné retiré ne reçoit plus rien', () => { const { fabrique, travailleurs } = fabriqueFactice(); const execution = creerExecuteurTravailleur(fabrique).lancer(CONFIGURATION, REGLAGES); diff --git a/src/demo/livrees.js b/src/demo/livrees.js index 32d3cf1..93bdb27 100644 --- a/src/demo/livrees.js +++ b/src/demo/livrees.js @@ -21,6 +21,7 @@ // Chaque texte décrit l'événement demo:, à la révision 1, en brouillon, // sans proposition. import { serialiser } from '../stockage/canonique.js'; +import { CATALOGUE } from './catalogue.js'; import grande from './livrees/grande.gtt.json' with { type: 'json' }; import grandeSansException from './livrees/grande-sans-exception.gtt.json' with { type: 'json' }; import petite from './livrees/petite.gtt.json' with { type: 'json' }; @@ -55,3 +56,16 @@ export function texteLivre(cle) { if (livree === undefined) throw new RangeError(`aucune démonstration livrée de clé ${JSON.stringify(cle)}`); return livree.texte; } + +/** + * Les démonstrations qu'App offre (§ 15) : chaque démonstration livrée, dans + * l'ordre de LIVREES, nommée par l'entrée de même clé du catalogue — { cle, + * nom, texte }, un tableau neuf à chaque appel. src/main.js et le montage + * des épreuves du navigateur les passent tous deux à App : la composition + * vit ici seule. + * + * @returns {Array<{cle: string, nom: string, texte: string}>} + */ +export function demonstrationsLivrees() { + return LIVREES.map(({ cle, texte }) => ({ cle, nom: CATALOGUE.find((entree) => entree.cle === cle).nom, texte })); +} diff --git a/src/interface/Bandeau.svelte b/src/interface/Bandeau.svelte index cd9b62f..a18cf01 100644 --- a/src/interface/Bandeau.svelte +++ b/src/interface/Bandeau.svelte @@ -17,7 +17,8 @@ // Refus, qui pose son texte et son remède en Commande — écrire ailleurs // sous un support rompu. Ce refus se pose sous les commandes, hors du // flux : le bandeau garde sa hauteur, et l'écran de l'événement, sa place - // fixe comprise, ne bouge pas (§ 8.4). + // fixe comprise, ne bouge pas (§ 8.4). Le refus est un bloc : les + // commandes qui le portent sont un
, non un . import { getContext } from 'svelte'; import { cleAnnonceFormat, cleDeMode } from './mode.js'; import Refus from './primitives/Refus.svelte'; @@ -41,7 +42,7 @@ {#if cleAnnonce !== null} {t(cleAnnonce)} {/if} - +
{#if etat.evenement !== null} {#if refusListe !== null} @@ -62,7 +63,7 @@ {/each} - +