feat: poubelle flexible (sélection groupée, purge, tri/filtres, dates) + 4 bugs (v7.45.5)
Page /trash alignée sur ce que fait ce type de section :
- sélection multiple (Select all porté au filtre courant) + barre d'actions
groupées Restore / Delete / Clear ;
- Empty Trash via POST /board/api/trash/empty (purge en masse, 1 requête) ;
- tri Recently deleted / Oldest first / Name et filtre emplacement RÉELS —
remplacent les deux boutons décoratifs « Last edited by ▾ » et « In ▾ » qui
ne faisaient rien ;
- date de suppression + jours restants par élément (rétention 30 j alignée sur
app/services/trash.py, horodatages UTC/ISO gérés) ;
- compteur de résultats, états vides distincts (vide vs filtre sans résultat +
Clear filters), toasts sur chaque action.
Bugs trouvés en route et corrigés :
1. Restore/Delete de la page ne marchaient JAMAIS : getCsrf() renvoie la chaîne
du jeton mais le code faisait csrf?.[1] → 2e caractère → 403 CSRF silencieux
avalé par if (r.ok).
2. « Move to Trash » de l'éditeur = 404 permanent : route appelée
/board/api/pages/{id}/trash (inexistante) alors que la route réelle est
/api/pages/{id}/trash, et le .then() naviguait quand même → la page partait
à l'accueil SANS être mise à la poubelle. URL corrigée + r.ok vérifié.
3. Page restaurée invisible en Library/Recents/Private : la suppression éditeur
réécrivait parent_section='Trash' et la restauration ne le remettait pas,
alors que tous les listings filtrent parent_section != 'Trash'. Écriture
retirée (deleted_at = source de vérité unique) + réparation idempotente au
boot dans db.init_db + requête sidebar /trash alignée sur deleted_at.
4. Routes trash sans aucune authentification (le CSRF ne protège pas : cookie
lisible + en-tête forgé) : 401 ajouté sur list/restore/delete/empty et sur
POST /api/pages/{id}/trash — vérifié anonyme → 401.
Gates : tests/test_trash_api.py (5) · e2e/trash_ui.spec.js (1, avec garde « on
ne vide jamais la poubelle d'autrui ») · pytest 1099 passed · ruff OK ·
e2e probe_nav_perf + partial_nav + editor_mount + logout_dnd + smoke = 10 passed ·
OpenAPI 510 chemins / 7.45.5.
This commit is contained in:
@@ -1,5 +1,59 @@
|
||||
# Changelog - FlowDeck
|
||||
|
||||
## v7.45.5 (2026-10-03) — Poubelle : actions groupées, tri/filtres réels, purge, et 4 bugs
|
||||
|
||||
### Added
|
||||
|
||||
- **Sélection multiple** sur /trash : case « Select all » (portée au filtre
|
||||
courant), barre d'actions groupées **Restore / Delete / Clear** avec compteur,
|
||||
et compteur de résultats (`N pages` / `N of M pages`).
|
||||
- **Empty Trash** : purge totale en un clic (confirm) via le nouveau
|
||||
`POST /board/api/trash/empty` — traitement en masse côté serveur (une seule
|
||||
requête, pas N appels).
|
||||
- **Tri** : Recently deleted (défaut) / Oldest first / Page name A → Z — remplace
|
||||
le bouton décoratif « Last edited by ▾ » qui ne faisait rien.
|
||||
- **Filtre par emplacement** réel (`In ▾` décoratif remplacé) : liste déroulante
|
||||
construite depuis les workspaces présents dans la poubelle, avec état actif.
|
||||
- **Dates par élément** : « Deleted 3 days ago · 27 days left » (rétention
|
||||
30 j alignée sur `app/services/trash.py`), horodatages UTC ou ISO gérés.
|
||||
- **États vides distincts** : poubelle vide vs aucun résultat pour les filtres
|
||||
(avec bouton « Clear filters »), et **toasts** sur chaque action (succès et
|
||||
échec) au lieu d'échecs silencieux.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Les boutons Restore / Delete ne marchaient JAMAIS** : `getCsrf()` renvoie la
|
||||
CHAÎNE du jeton mais le code faisait `csrf?.[1]` → 2ᵉ caractère → 403 CSRF
|
||||
systématique, avalé par `if (r.ok)`. Corrigé + toasts d'erreur.
|
||||
- **« Move to Trash » depuis l'éditeur envoyait une 404** : la route appelée
|
||||
(`/board/api/pages/{id}/trash`) n'existe pas — route réelle
|
||||
`/api/pages/{id}/trash` — et le `.then()` naviguait quand même, donc la page
|
||||
partait à l'accueil SANS être mise à la poubelle. URL corrigée + on ne
|
||||
navigue plus si la réponse n'est pas ok.
|
||||
- **Une page restaurée disparaissait de Library / Recents / Private** : la
|
||||
suppression depuis l'éditeur réécrivait `parent_section='Trash'` et la
|
||||
restauration ne le remettait pas, alors que tous les listings filtrent
|
||||
`parent_section != 'Trash'`. `deleted_at` est maintenant la seule source de
|
||||
vérité (écriture retirée) + réparation au boot des lignes déjà cassées
|
||||
(`db.init_db`, idempotente).
|
||||
- **Routes trash sans aucune authentification** : `GET /board/api/trash`,
|
||||
`restore`, `delete` et `POST /api/pages/{id}/trash` ne vérifiaient rien (le
|
||||
CSRF ne protège pas : cookie lisible + en-tête forgé par l'attaquant) → 401
|
||||
obligatoire, comme les routes sœurs du même fichier. Vérifié anonyme : 401.
|
||||
- Le sidebar de /trash listait `parent_section='Trash'` → aligné sur
|
||||
`deleted_at IS NOT NULL`.
|
||||
|
||||
### Tests
|
||||
|
||||
- `tests/test_trash_api.py` (5 tests) : auth 401, soft-delete sans marqueur
|
||||
`Trash`, restauration, suppression définitive, purge en masse (ne touche pas
|
||||
aux pages vivantes), réparation au boot.
|
||||
- `e2e/trash_ui.spec.js` : rendu, dates, sélection groupée, restauration
|
||||
unitaire (le vrai test du fix CSRF), purge — noms uniques par exécution et
|
||||
garde « on ne vide jamais la poubelle d'autrui ».
|
||||
- `test_smoke_uncovered.py` : l'assertion qui codait le bug
|
||||
(`parent_section == 'Trash'`) corrigée.
|
||||
|
||||
## v7.45.4 (2026-10-03) — Anti-FOUC au chargement complet + morts nettoyés
|
||||
|
||||
### Fixed
|
||||
|
||||
Reference in New Issue
Block a user