From a12f3a2c91979d4501f3e66808a1d2432c3d9ecf Mon Sep 17 00:00:00 2001 From: r-zakarya <82443831+r-zakarya@users.noreply.github.com> Date: Sat, 1 Aug 2026 23:59:53 +0100 Subject: [PATCH] fix(list): toggle a checkbox in place instead of rebuilding the grid renderGrid replaces every card, so the scroll container jumped back to the top: ticking a card near the bottom of a long list threw the user back to the first row. Only the affected card and the selection bar are updated now. --- TEST_SCENARIOS.md | 6 ++++++ js/app.js | 18 +++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/TEST_SCENARIOS.md b/TEST_SCENARIOS.md index 06e5f93..e9441ad 100644 --- a/TEST_SCENARIOS.md +++ b/TEST_SCENARIOS.md @@ -279,6 +279,12 @@ Symptôme du bug : après relance, lignes au nom vide / `(no name)`. - **Historique** : les cibles s'allumaient sur n'importe quel glisser puis lisaient `text/plain` pour y trouver des identifiants — le texte d'une cellule donnait `NaN`, filtré, donc rien ne se passait : une invitation à déposer suivie d'aucune action. Nos glissers d'entrées portent désormais leur propre type `application/x-pm-entries` (comme les dossiers avec `application/x-pm-folder`), et les quatre cibles le vérifient **avant** de s'allumer. - **Non-régression** : glisser une carte ou une ligne (hors corbeille) sur un dossier, sur *(no folder)*, sur un tag et sur *Trash* fonctionne toujours, sélection multiple comprise. +### D5 · cocher une carte ne bouge ni la grille ni le défilement +- **ACTION** : liste longue → **défiler vers le bas** → cocher une carte (clic sur la case, ou `Ctrl`+clic). +- **ATTENDU** : la position de défilement **ne bouge pas**, et la grille ne se décale pas verticalement. La case se coche, la barre de sélection apparaît. +- **Deux causes distinctes, corrigées ensemble** : (1) la barre de sélection dépassait de quelques pixels la hauteur réservée par `.content-header` et poussait toute la grille ; sa hauteur est maintenant fixée à cette réservation. (2) `toggleChecked` appelait `renderGrid()`, qui reconstruit **toutes** les cartes — le conteneur de défilement repartait donc à zéro et l'utilisateur était renvoyé en haut de la liste. Le basculement se fait désormais **sur place** sur la seule carte concernée. +- **À vérifier aussi** : le ✓ apparaît / disparaît (il est ajouté au rendu, pas par CSS), l'info-bulle bascule *Select* / *Deselect*, et le compteur de la barre est juste. Idem en vue **tableau** et en vue **liste**. + ### D1 · menu kebab en table view ✅ - **ACTION** : corbeille + vue table → kebab d'une ligne - **ATTENDU** : `Restore` / `Copy…` / `Delete forever` **uniquement**. Pas de Pin / Favorite / Edit / Duplicate / Move to trash. diff --git a/js/app.js b/js/app.js index 0b618f4..3c0e7e4 100644 --- a/js/app.js +++ b/js/app.js @@ -3573,7 +3573,23 @@ let selectionAnchor = null; // last single-clicked card, used for shift+click r function toggleChecked(id) { if (state.checked.has(id)) state.checked.delete(id); else state.checked.add(id); - renderGrid(); + // In place, not renderGrid(): rebuilding the grid replaces every card, and + // the scroll container jumps back to the top — ticking a card near the + // bottom of a long list threw the user back to the first row. + const on = state.checked.has(id); + document.querySelectorAll( + '.entry-card[data-id="' + id + '"], .entry-row[data-id="' + id + '"]' + ).forEach(node => { + node.classList.toggle('is-checked', on); + const box = node.querySelector('.entry-check'); + if (!box) return; + box.classList.toggle('is-checked', on); + box.title = on ? 'Deselect' : 'Select'; + // The tick is a child element added at render time, not CSS content. + box.textContent = ''; + if (on) box.appendChild(el('span', null, '✓')); + }); + renderBatchBar(); } function handleCardClick(ev, entry, inTrash) {