From 3d3b46b58b39cc175bdec0e7cc96b8868e2b2236 Mon Sep 17 00:00:00 2001 From: Bruno Charest Date: Thu, 8 Oct 2026 17:27:14 -0400 Subject: [PATCH] =?UTF-8?q?fix:=20s=C3=A9lection=20multi-blocs=20fonctionn?= =?UTF-8?q?elle=20(Ctrl+C/X,=20drag=20souris,=20clic=20droit)=20+=20identi?= =?UTF-8?q?t=C3=A9=20visuelle=20(v7.69.0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Trois bugs d'éditeur, chacun avec sa cause racine prouvée par trace d'événements — le mécanisme Clipboard livré en v7.68 était inutilisable. 1) Ctrl+C / Ctrl+X multi-blocs ne faisaient RIEN. Cause : le navigateur ne sait pas sérialiser une sélection qui traverse deux `contenteditable` (chaque bloc en a un). `getSelection().toString()` renvoie `""` et Chromium n'émet AUCUN événement `copy` — les handlers `ct.addEventListener('copy')` n'étaient donc jamais atteints. → le raccourci est intercepté au keydown (capture sur document, garde `closest('#_blocksCt')` pour ne pas voler Ctrl+C ailleurs) et le markdown est écrit dans le presse-papier par nos soins. Sélection mono-bloc = comportement natif inchangé. 2) Glisser la souris d'un bloc à l'autre ne sélectionnait qu'un bloc. Cause : `mouseup` (on reconstruit la plage) → `click` → `focusin` → le navigateur efface la sélection. La plage était reconstruite puis détruite dans la foulée. → la plage reconstruite est mise en attente (`_mdSelRange`) et restaurée au tour de boucle suivant si le focus l'a effectivement effacée. Chaque nouveau geste (mousedown) remet l'attente à zéro, donc un clic pour placer le curseur reste normal. 3) Le clic droit effaçait le surlignage. Cause : `.bm-search` prenait le focus 10 ms après l'ouverture du menu. → le menu ne vole plus le focus quand un texte est sélectionné. Les actions Clipboard utilisaient déjà la sélection figée à l'ouverture (`d._clipSt`) ; le `preventDefault` du clic droit est remonté sur `mousedown` (sur `contextmenu` il arrive trop tard). Identité visuelle (bannière + logo du projet ajoutés à la racine) : - assets optimisés servis : `static/img/banner.webp` 22,5 Ko (l'original fait 1,6 Mo) + JPEG de secours, `logo.webp` 5,3 Ko + `logo-512.jpg` ; originaux conservés dans `assets/` pour régénération. - icônes PWA régénérées à partir du vrai logo (72 → 512 px, marge maskable sur 192/512) + apple-touch-icon. - landing : logo dans la barre de navigation, bannière en (WebP + JPEG) avec dimensions déclarées (pas de décalage de mise en page). - `og:image`, `twitter:card` et `meta description` ajoutés sur la landing ET dans base.html — la landing a son propre . Tests : `tests/test_v769_selection_persistence.py` (19 statiques) + `e2e/v769_selection_persistence.spec.js` (5, vrais gestes : drag souris, Ctrl+C/Ctrl+X au clavier, clic droit). `e2e/v768_columns_clipboard.spec.js` corrigé : les gestes simulés (`execCommand`, plage fabriquée en evaluate) sont remplacés par de vrais gestes, et le token statique obsolète `si>=ei)return null;` aligné sur la garde `isCollapsed` réelle. pytest 1413 passed / 0 failed · ruff OK · OpenAPI 526 chemins / 7.69.0 --- CHANGELOG.md | 48 +++++++ ROADMAP.md | 36 +++++ VERSION | 2 +- WORKLOAD.md | 2 +- app/main.py | 2 +- app/templates/base.html | 11 ++ app/templates/landing.html | 37 ++++- assets/banner.jpg | Bin 0 -> 1679984 bytes assets/logo.jpg | Bin 0 -> 1159159 bytes docs/openapi-v2.json | 2 +- e2e/v768_columns_clipboard.spec.js | 77 +++++++---- e2e/v769_selection_persistence.spec.js | 164 +++++++++++++++++++++++ static/icons/apple-touch-icon.png | Bin 1344 -> 18943 bytes static/icons/icon-128x128.png | Bin 999 -> 10077 bytes static/icons/icon-144x144.png | Bin 1085 -> 12235 bytes static/icons/icon-152x152.png | Bin 1147 -> 13629 bytes static/icons/icon-192x192.png | Bin 1423 -> 13485 bytes static/icons/icon-384x384.png | Bin 2966 -> 83835 bytes static/icons/icon-512x512.png | Bin 4093 -> 93871 bytes static/icons/icon-72x72.png | Bin 624 -> 3888 bytes static/icons/icon-96x96.png | Bin 767 -> 6074 bytes static/img/banner.jpg | Bin 0 -> 65515 bytes static/img/banner.webp | Bin 0 -> 23068 bytes static/img/logo-512.jpg | Bin 0 -> 13294 bytes static/img/logo.webp | Bin 0 -> 5428 bytes static/js/page_editor_scripts.js | 119 +++++++++++++++- tests/test_v768_columns_clipboard.py | 10 +- tests/test_v769_selection_persistence.py | 138 +++++++++++++++++++ 28 files changed, 610 insertions(+), 38 deletions(-) create mode 100644 assets/banner.jpg create mode 100644 assets/logo.jpg create mode 100644 e2e/v769_selection_persistence.spec.js create mode 100644 static/img/banner.jpg create mode 100644 static/img/banner.webp create mode 100644 static/img/logo-512.jpg create mode 100644 static/img/logo.webp create mode 100644 tests/test_v769_selection_persistence.py diff --git a/CHANGELOG.md b/CHANGELOG.md index ad63555..2aaa94e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,53 @@ # Changelog - FlowDeck +## v7.69.0 (2026-10-08) — La sélection multi-blocs fonctionne enfin (+ identité visuelle) + +### Fixed + +- **Ctrl+C / Ctrl+X multi-blocs ne faisaient RIEN** — cause racine : le navigateur + ne sait pas sérialiser une sélection qui traverse deux `contenteditable` (chaque + bloc en a un). Résultat : `getSelection().toString()` renvoie `""` et Chromium + **n'émet aucun événement `copy`** — nos handlers `ct.addEventListener('copy')` + n'étaient donc jamais atteints, et le raccourci mourait en silence. + → le raccourci est maintenant intercepté au `keydown` (capture sur `document`, + garde `closest('#_blocksCt')` pour ne pas voler Ctrl+C ailleurs) : on écrit le + markdown dans le presse-papier nous-mêmes. La sélection mono-bloc reste native. + - `Ctrl+X` supprime les blocs traversés en conservant la tête du premier et la + queue du dernier. +- **Glisser la souris d'un bloc à l'autre sélectionnait un seul bloc** — cause + racine : la séquence complète est `mouseup` (on reconstruit la plage) → `click` + → `focusin` → **le navigateur efface la sélection**. La plage était donc + reconstruite puis détruite dans la foulée. → la plage reconstruite est mise en + attente (`_mdSelRange`) et restaurée au tour de boucle suivant, si le focus + l'a effectivement effacée. Un nouveau geste (mousedown) remet l'attente à zéro, + donc un clic pour placer le curseur reste normal. +- **Le clic droit effaçait le surlignage** — la recherche du menu prenait le + focus 10 ms après son ouverture, ce qui détruisait la sélection. → on ne vole + plus le focus quand un texte est sélectionné. (Les actions Clipboard utilisaient + déjà la sélection figée à l'ouverture du menu : `d._clipSt`.) +- Le `preventDefault` du clic droit est bien sur `mousedown` (sur `contextmenu` + il arrive trop tard — le curseur a déjà déplacé la sélection). + +### Added + +- **Identité visuelle** : la bannière et le logo du projet sont intégrés. + - assets optimisés : `static/img/banner.webp` **22,5 Ko** (l'original fait + 1,6 Mo) + `banner.jpg` de secours, `logo.webp` **5,3 Ko** + `logo-512.jpg` ; + originaux conservés dans `assets/` pour régénération. + - icônes PWA régénérées à partir du vrai logo (72 → 512 px, marge maskable + sur 192/512) + `apple-touch-icon`. + - landing : logo dans la barre de navigation, bannière en `` + (WebP + JPEG) avec dimensions déclarées pour éviter tout décalage de mise en + page ; `og:image`, `twitter:card` et `meta description` ajoutés au ``. + +### Tests + +- `tests/test_v769_selection_persistence.py` (19 tests statiques) + + `e2e/v769_selection_persistence.spec.js` (5 tests, **vrais gestes** : glisser + de souris, `Ctrl+C`/`Ctrl+X` au clavier, clic droit). +- `e2e/v768_columns_clipboard.spec.js` : les gestes simulés (`execCommand`, + plage fabriquée en `evaluate`) sont remplacés par de vrais gestes. + ## v7.68.0 (2026-10-08) — Colonnes rendues, tables IA, copier/couper/coller multi-blocs ### Fixed diff --git a/ROADMAP.md b/ROADMAP.md index eee1a48..c90838e 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1545,6 +1545,42 @@ Détails livrés : `v768_columns_clipboard.spec.js` (4) ; `pytest` **1394 passed / 0 failed**, `ruff` OK ; `VERSION` + `app/main.py` = 7.68.0 +### v7.69.0 — La sélection multi-blocs fonctionne enfin ✅ (2026-10-08) + +**Contexte** : v7.68.0 livrait la mécanique Clipboard (plage multi-blocs, handlers +`copy`/`cut`, groupe Clipboard du menu contextuel) mais elle était **inutilisable en +pratique** — trois rapports d'usage : Ctrl+C ne copie rien, le glisser souris ne +sélectionne qu'un bloc, le clic droit efface le surlignage. + +**Causes racines trouvées (trace d'événements à l'appui, pas de supposition)** : + +| Symptôme | Cause réelle | +|----------|--------------| +| Ctrl+C/X muet | le navigateur **n'émet aucun événement `copy`** quand la sélection traverse deux `contenteditable` : `getSelection().toString()` renvoie `""`, donc nos handlers n'étaient jamais atteints | +| Glisser = 1 bloc | séquence `mouseup` → `click` → `focusin` → **collapse** : la plage reconstruite mourait dans la foulée | +| Clic droit efface | `.bm-search` prenait le focus 10 ms après l'ouverture du menu | + +**Corrections** : interception du raccourci au `keydown` (capture `document`, +garde `closest('#_blocksCt')`) ; plage reconstruite mise en attente +(`_mdSelRange`) et restaurée au tour suivant, remise à zéro à chaque nouveau +geste ; le menu ne vole plus le focus quand un texte est sélectionné ; +`preventDefault` du clic droit remonté sur `mousedown` (sur `contextmenu` c'est +trop tard). + +**Identité visuelle** : bannière + logo intégrés — WebP 22,5 Ko / 5,3 Ko (les +originaux faisaient 1,6 Mo / 1,1 Mo), JPEG de secours, icônes PWA régénérées à +partir du vrai logo, `` sur la landing, `og:image`/`twitter:card`. + +**Vérification** : `tests/test_v769_selection_persistence.py` (19 statiques) + +`e2e/v769_selection_persistence.spec.js` (5, **vrais gestes**) ; le spec v768 a +été corrigé pour remplacer `execCommand` et les plages fabriquées par de vrais +gestes. + +**Piège de test relevé au passage** : les `···` dans les échecs Playwright sont +le marqueur de troncature du message d'erreur, **pas** un bug produit — une heure +passée à chercher une fuite inexistante. Vérifier `JSON.stringify()` de la valeur +réelle avant de croire un affichage tronqué. + --- ## ✅ Fonctionnalités livrées hors roadmap (bonus détectés dans le code) diff --git a/VERSION b/VERSION index 97e05c6..0f4b2e9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.68.0 +7.69.0 diff --git a/WORKLOAD.md b/WORKLOAD.md index c50b139..312aab8 100644 --- a/WORKLOAD.md +++ b/WORKLOAD.md @@ -1,6 +1,6 @@ # WORKLOAD — FlowDeck Notion Clone -> **Début**: 2026-07-08 | **Version**: v7.68.0 (colonnes rendues, tables IA sans pipe de tête, copier/couper/coller multi-blocs) | **Statut**: EN COURS 🔄 +> **Début**: 2026-07-08 | **Version**: v7.69.0 (sélection multi-blocs fonctionnelle : drag souris + Maj+↓, clic droit qui la préserve) | **Statut**: EN COURS 🔄 > **Cible**: parité Notion + intégration forge · **Follow-ups v7.3 livrés**: sidebar teamspaces, notif `page.updated`, charts `number` + dashboards multi-DB, unfurl forge, UI Settings → Audit — voir `ROADMAP.md § v7.3.0` ## Avancement Global diff --git a/app/main.py b/app/main.py index 36a1de1..64ca72a 100644 --- a/app/main.py +++ b/app/main.py @@ -186,7 +186,7 @@ async def lifespan(_app: FastAPI): app = FastAPI( title="FlowDeck", - version="7.68.0", + version="7.69.0", docs_url="/docs", redoc_url="/redoc", lifespan=lifespan, diff --git a/app/templates/base.html b/app/templates/base.html index d8dcc87..67fc00a 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -8,6 +8,17 @@ + + + + + + + + + + + diff --git a/app/templates/landing.html b/app/templates/landing.html index cfe2b17..04a26e1 100644 --- a/app/templates/landing.html +++ b/app/templates/landing.html @@ -11,6 +11,17 @@ + + + + + + + + + + +