Supprime la couche de formes libres, déplace la navigation de page dans le panneau gauche
Build and deploy / test-python (push) Successful in 7m43s
Build and deploy / test-js (push) Successful in 57s
Build and deploy / lint-python (push) Successful in 5m29s
Build and deploy / lint-js (push) Failing after 1m16s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Build and deploy / sonarqube (push) Failing after 5m42s

Formes libres (rectangle/cercle/triangle/trait) retirées de bout en
bout (bibliothèque, rendu, panneau Propriétés, grille d'accroche,
JS/CSS associés) — fonctionnalité non retenue.

La bande de vignettes visuelles des pages au-dessus du canevas est
remplacée par une section "Pages" dans le panneau de gauche (liste
simple : ajouter/renommer/réordonner (haut/bas)/supprimer), à la
place de l'ex-catégorie "Mise en page" de la bibliothèque. La route
document_edit ne rend plus qu'une seule page (celle affichée) au
chargement, au lieu de toutes les pages pour alimenter les anciennes
vignettes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
william
2026-09-21 14:40:33 +02:00
co-authored by Claude Sonnet 5
parent 57c4de3d8a
commit 8dc4b35dcf
11 changed files with 220 additions and 597 deletions
+2 -2
View File
@@ -141,7 +141,7 @@ SonarQube : voir section 2, sous-section "SonarQube" — CI restaurée (non-bloq
| `static/game/js/scenes/scene-editor.js:214` (`CURRENT_SELECTED_ID = null`), `static/game/js/screen_edit/tree-panels.js:392` (`CURRENT_SELECTED_ID = selectedId \|\| null`) | `javascript:S2703` | Déclarée en **`var`** (pas `let`/`const`) dans `templates/game/scene_edit.html:833` (`var CURRENT_SELECTED_ID = {{ selected_id or 'null' }};`), chargé avant `tree-panels.js`/`scene-editor.js`/`trigger-editor.js`/`collision-rules-editor.js` (ordre vérifié, lignes 833/858/880/885/887/888) — un `var` de script classique attache directement à `window`, réassignable sans aucune restriction depuis n'importe quel autre `<script>` de la page (contrairement au cas `SCENE_OBJECT_NAMES` ci-dessous, qui lui était en `const`). Déjà documenté dans `.eslintrc.json` (`"CURRENT_SELECTED_ID": "writable"`). | Lot 2 "modernisation JS", 16/09/2026 |
| `static/game/js/scenes/collision-rules-editor.js:78` (`let _collisionWizard = null;`) et ses réassignations dans ce fichier, **et** `static/game/js/triggers/trigger-editor.js:787,937` (`triggerOpenAppendActionModal`/`screenTriggerOpenAppendActionModal`, `_collisionWizard = { bodyEl: body };` sans mot-clé) | `javascript:S2703` | Même pattern de partage inter-scripts que `SCENE_OBJECT_NAMES` : `trigger-editor.js` réutilise TELLES QUELLES les étapes de l'assistant de `collision-rules-editor.js` (`renderCollisionWizardChainStep`/`collisionWizardBuildLeafAction`, qui ne lisent que `.bodyEl` — voir commentaire ligne 784-786 de `trigger-editor.js`) pour poser "+ Ajouter une action" sur un déclencheur déjà existant, plutôt que de dupliquer ces étapes. `trigger-editor.js` est chargé AVANT `collision-rules-editor.js` (`templates/game/scene_edit.html:887-888`), mais sans risque de TDZ : les deux réassignations de `trigger-editor.js` sont à l'intérieur de fonctions déclenchées par un clic utilisateur, jamais exécutées avant que `collision-rules-editor.js` (et son `let _collisionWizard = null;`) n'ait fini de se charger. Déjà documenté dans `.eslintrc.json` (`"_collisionWizard": "writable"`). | Lot 2 "modernisation JS", 16/09/2026 ; complété lot 4, 18/09/2026 |
| `static/game/js/triggers/trigger-editor.js:28` (`let SCENE_OBJECT_NAMES = ...`) | `javascript:S2703` | **Bug réel trouvé et corrigé** (pas un faux positif comme les 4 sites ci-dessus) : était déclarée en `const`, alors que `refreshSceneObjectNames()` (`static/game/js/scenes/scene-editor.js:754-759`) la réassigne après un fetch — deux `<script>` classiques sur la même page partagent un même environnement lexical global, mais une liaison `const` posée dans l'un ne peut pas être réassignée depuis l'autre (`TypeError: Assignment to constant variable.`, reproduit empiriquement via `node:vm`). Symptôme : renommer un personnage puis ouvrir un dialogue de déclencheur sans recharger la page ne montrait jamais le nouveau nom dans "qui parle". Corrigé en `let`, couvert par un test de non-régression (`static/game/js/scenes/__tests__/collision-rules-editor.test.js`, test `refreshSceneObjectNames`) qui échoue avec `TypeError` sur l'ancien code et passe avec le nouveau. | Lot 2 "modernisation JS", 16/09/2026 |
| 32 sites `\|safe` (`templates/game/scene_edit.html`, `templates/game/play.html`, `templates/auth/register_2fa.html`, `templates/onboarding/onboarding_new.html`, `templates/game_dashboard_simple.html`, `templates/document/document_edit.html`) + 1 site dans une boucle `{% for %}` de `document_edit.html` (`rendered_pages[page.id]`, une itération par page du support — nombre variable, ajouté le 21/09/2026 pour les vignettes miniatures de la bande de pages) | `Web:S5247` (Sonar) | **Faux positif confirmé sur le fond, mais non-supprimable techniquement pour l'instant.** 3 sous-groupes : (1) `rendered_html`/`qr_svg`/`description` — HTML déjà échappé côté Python (`html.escape()`) ou généré sans texte libre utilisateur ; (2) 25 sites `*_json` — `db.json_for_script()` échappe déjà `</script>` (voir Phase 3) ; (3) `rendered_document`/`rendered_pages[page.id]` (`document_edit.html`, le second ajouté le 21/09/2026) — même sous-groupe (1) : produits par `document_engine.render_document`, qui échappe (`html.escape()`) tout contenu utilisateur avant interpolation (voir `document_engine/rendering/render_document_element.py`) — `rendered_pages[page.id]` rend simplement CHAQUE page du support de la même façon, pour construire une vraie vignette miniature de son contenu réel (retour utilisateur du 21/09/2026 : "je préfère avoir des miniatures"), jamais une nouvelle source de contenu non échappé. Plusieurs syntaxes de suppression testées (commentaire Jinja `{# #}`, commentaire JS natif dans un `<script>`, bonne position de ligne) : **aucune ne fonctionne** avec l'analyseur Web de cette version de SonarQube. La résolution "Faux positif" via l'API est bloquée par le système de permissions de session. `sonar.issue.ignore.multicriteria` existe mais sans sélecteur de ligne (exclusion fichier entier uniquement) — écarté pour `scene_edit.html`/`play.html` (masquerait un futur `\|safe` réellement dangereux). Ces sites restent visibles dans le rapport Sonar en l'état ; traités et compris, pas un point ouvert côté code. | Phase 3 + investigation du 16/09/2026 ; complété le 20/09/2026 et le 21/09/2026 |
| 31 sites `\|safe` (`templates/game/scene_edit.html`, `templates/game/play.html`, `templates/auth/register_2fa.html`, `templates/onboarding/onboarding_new.html`, `templates/game_dashboard_simple.html`, `templates/document/document_edit.html`) | `Web:S5247` (Sonar) | **Faux positif confirmé sur le fond, mais non-supprimable techniquement pour l'instant.** 3 sous-groupes : (1) `rendered_html`/`qr_svg`/`description` — HTML déjà échappé côté Python (`html.escape()`) ou généré sans texte libre utilisateur ; (2) 25 sites `*_json` — `db.json_for_script()` échappe déjà `</script>` (voir Phase 3) ; (3) `rendered_document` (`document_edit.html`) — même sous-groupe (1) : produit par `document_engine.render_document`, qui échappe (`html.escape()`) tout contenu utilisateur avant interpolation (voir `document_engine/rendering/render_document_element.py`). Plusieurs syntaxes de suppression testées (commentaire Jinja `{# #}`, commentaire JS natif dans un `<script>`, bonne position de ligne) : **aucune ne fonctionne** avec l'analyseur Web de cette version de SonarQube. La résolution "Faux positif" via l'API est bloquée par le système de permissions de session. `sonar.issue.ignore.multicriteria` existe mais sans sélecteur de ligne (exclusion fichier entier uniquement) — écarté pour `scene_edit.html`/`play.html` (masquerait un futur `\|safe` réellement dangereux). Ces sites restent visibles dans le rapport Sonar en l'état ; traités et compris, pas un point ouvert côté code. | Phase 3 + investigation du 16/09/2026 ; complété le 20/09/2026 et le 21/09/2026 |
| `static/game/js/play/offline/filter-repeater-rows.js:10,11`, `static/game/js/screen_edit/panel-init.js:254,261`, `static/game/js/play/offline/xapi-client.js:132` | `javascript:S8786` (ReDoS) | 3 regex distinctes (2 dupliquées dans 2 fichiers) testées empiriquement, aucune ne montre de backtracking super-linéaire réel — voir le détail complet juste en dessous du tableau (méthode reproductible). | Lot 1 "modernisation JS", 16/09/2026 |
| `static/game/js/play/dialogue-box-controller.js` (`forgeShowQuizBox`, ligne du `void widget.offsetWidth;`) | `javascript:S3735` | Force une lecture de mise en page (reflow) AVANT de reposer la classe `is-active`, pour que l'animation CSS d'entrée du quiz rejoue même si la boîte était déjà active juste avant (2 questions à la suite) — idiome JS standard, `void` marque explicitement une expression dont seul l'EFFET DE LECTURE compte, jamais la valeur. 3 formes essayées dans l'ordre, chacune rejetée par une règle Sonar différente : `void widget.offsetWidth;` (S3735, "retirer void") → `widget.offsetWidth;` seule (S905, "expression sans effet — accepté par ESLint ici, `no-unused-expressions` est désactivé dans ce projet, mais pas par Sonar") → `const _ = widget.offsetWidth;` (S1481, "variable jamais lue — accepté par ESLint via `varsIgnorePattern: ^_$`, pas par Sonar"). Aucune forme ne satisfait Sonar sans en recréer une autre : `void` restauré (la plus lisible/idiomatique des 3, et la seule aussi acceptée par ESLint) et documenté ici plutôt que de continuer à faire tourner ce carrousel. | Lot 4 "modernisation JS", 18/09/2026 |
| `static/game/js/screen_edit/tree-panels.js:32` (`restoreTreeCollapsedState`), `:57` (`saveFloatPanelState`), `:230` (sauvegarde état replié/déplié au clic) | `javascript:S2486` | Lecture/écriture `localStorage` purement cosmétique (éditeur seulement, jamais le jeu) : un échec (quota, storage désactivé) laisse au pire un panneau à sa position par défaut ou un nœud d'arborescence dans son état précédent — aucune donnée de jeu en jeu, aucun état perdu de façon irréversible. | Lot 3 "modernisation JS", 16/09/2026 |
@@ -152,7 +152,7 @@ SonarQube : voir section 2, sous-section "SonarQube" — CI restaurée (non-bloq
| `static/game/js/play/offline/filter-repeater-rows.js:24` (`forgeDecodeClauses`) | `javascript:S2486` | **Corrigé, même patron que ci-dessus** : `_filtres_json` est un attribut rendu par le serveur, jamais tapé à la main — un JSON invalide y trahit presque toujours un bug côté éditeur/serveur. `console.warn('_filtres_json invalide, filtre ignoré', e)` ajouté, comportement inchangé (repli sur l'ancien format à 2 filtres fixes ou aucun filtre). Couvert par un nouveau test (`filter-repeater-rows.test.js`, `forgeDecodeClauses — _filtres_json invalide`). | Lot 4 "modernisation JS", 18/09/2026 |
| `static/game/js/play/offline/filter-repeater-rows.js:59`, `:70` (`forgeResolveVariablePath`, JSON.parse + navigation `.champ`/`[index]`) | `javascript:S2486` | **Documenté, pas corrigé — nature différente du cas ci-dessus** : ici `rawValue` est la VALEUR ACTUELLE d'une variable de jeu (modifiable librement par n'importe quelle action "Modifier une variable"), pas une config interne à l'éditeur — un chemin qui ne correspond pas à sa forme actuelle est un cas normal et attendu (ex. variable encore à sa valeur par défaut non-JSON), déjà explicitement documenté par le commentaire de la fonction ("Ne lève jamais... même convention que côté serveur"). Un `console.warn` ici bruiterait la console à chaque usage légitime. | Lot 4 "modernisation JS", 18/09/2026 |
| `static/document/js/document-editor.js` (`FORGE_DOC_STYLE_PRESETS`, `FORGE_DOC_SHAPE_KINDS`, `FORGE_DOC_SNAP_SIZE` — 3 sites) | `eslint:no-var`, `eslint:vars-on-top` | Constantes de premier niveau déclarées en `var` plutôt que `const` : un `<script src>` de page est rejoué TEL QUEL par `pjax.js` (`runScriptsIn`) à chaque navigation interne — une redéclaration `let`/`const` au premier niveau lèverait `SyntaxError: already declared` à la 2e exécution (voir l'en-tête de `static/pjax.js`, et le commentaire d'en-tête de ce fichier). `var` est le seul mot-clé sûr à ce niveau ; tout le reste du fichier (état mutable, y compris à l'intérieur des fonctions) est bien en `let`/`const`, porté par `window.forgeDocState` plutôt que par des variables de module (même convention que `static/game/js/scenes/scene-editor.js` et les autres scripts de page existants, qui n'ont eux aucune constante de ce genre à déclarer). | Session du 20/09/2026 |
| `templates/document/document_edit.html` (`.docPageThumbCard`, cartes de page dans la bande de vignettes) + `static/document/js/document-editor.js` (`forgeDocRenderPageTabs`) | `Web:S6819`, `Web:MouseEventWithoutKeyboardEquivalentCheck` (Sonar) | **`div role="button"` volontaire, pas une entorse à l'accessibilité — choix imposé par un vrai bug de parsing HTML trouvé et corrigé le 21/09/2026.** Chaque vignette embarque le HTML RÉEL de la page (`rendered_pages[page.id]` / `pageThumbsHtml[p.id]`), qui peut contenir de vrais `<button>` issus d'un mini-jeu (Suivant/Recommencer d'un quiz, association, memory, mots mêlés, scénario). Avec `.docPageThumbCard` en `<button>` (version initiale), le premier `<button>` imbriqué du contenu copié force le parseur HTML à refermer prématurément le bouton englobant (algorithme de parsing standard, identique navigateur réel/jsdom) — corrompant l'arbre DOM : constaté concrètement via un test jsdom contre une vraie page support avec un quiz (`.docPageThumbCard[data-page-id]` retournait 3 éléments pour 2 pages réelles, une carte dupliquée et détachée de `#docPageTabs`, retrouvée comme enfant direct de `<main class="content">`). Un `<button>` ne peut structurellement pas contenir un autre `<button>` — le HTML embarqué étant le rendu réel et non maîtrisable à l'avance (n'importe quel mini-jeu ajouté par le créateur), `div role="button" tabindex="0"` est la seule option HTML valide. L'équivalent clavier (Entrée/Espace déclenche `forgeDocSwitchPage`, même effet que le clic) est bien posé côté JS (`card.addEventListener('keydown', ...)`), donc le finding clavier de Sonar est un faux positif : l'analyseur statique ne voit pas les `addEventListener` attachés dynamiquement. Les vrais boutons copiés à l'intérieur de la vignette (`.docPageThumbInner`) portent l'attribut `inert` (en plus de `pointer-events:none` déjà en CSS) pour ne jamais devenir atteignables au clavier — vérifié par un test jsdom dédié. | Session du 21/09/2026 |
| `templates/document/document_edit.html` (`.docPageListItem`, rangées de la section "Pages" du panneau gauche) + `static/document/js/document-editor.js` (`forgeDocRenderPageList`) | `Web:S6819`, `Web:MouseEventWithoutKeyboardEquivalentCheck` (Sonar) | `div role="button" tabindex="0"` volontaire : chaque rangée contient de vrais `<button>` d'action (monter/descendre/renommer/supprimer, voir `.docPageListActions`), qu'un `<button>` englobant ne pourrait pas contenir validement (imbrication de `<button>` invalide, le parseur HTML referme le bouton englobant trop tôt — même défaut structurel déjà rencontré et corrigé de la même façon ailleurs dans ce fichier). L'équivalent clavier (Entrée/Espace déclenche `forgeDocSwitchPage`, même effet que le clic) est posé côté JS (`item.addEventListener('keydown', ...)`), donc le finding clavier de Sonar est un faux positif : l'analyseur statique ne voit pas les `addEventListener` attachés dynamiquement. Vérifié par un test jsdom dédié (rôle `button`, équivalent clavier fonctionnel). | Session du 21/09/2026 |
### Détail — `javascript:S8786` (ReDoS), lot 1 "modernisation JS"