diff --git a/TEST_SCENARIOS.md b/TEST_SCENARIOS.md index 5469ff7..7d61a68 100644 --- a/TEST_SCENARIOS.md +++ b/TEST_SCENARIOS.md @@ -233,6 +233,15 @@ plus bas. - **ACTION** : importer un CSV sans colonnes favorite/pinned. - **ATTENDU** : entrées importées ni épinglées ni favorites (défaut 0), pas d'erreur. +### F9 · la corbeille voyage dans les sauvegardes ✅ +- **ACTION** : vault avec par exemple 4 entrées vivantes et 2 en corbeille → *Export vault* → *Clear vault* → réimporter. +- **ATTENDU** : état **identique** — 4 dans la liste, 2 en corbeille. Le compteur *Trash* est juste sans avoir à ouvrir la vue. +- **`deleted_at` est préservé**, pas réinitialisé : sinon chaque entrée restaurée repartirait pour un délai d'auto-purge complet, et une entrée supprimée il y a 29 jours recommencerait à zéro. D'où son ajout à l'écho de `GET /entries`. +- **Deuxième test, celui qui a trouvé le bug** : mettre une entrée à la corbeille → exporter → réimporter **sans rien vider**. L'entrée doit être comptée dans les « déjà présentes » (*Overwrite* / *Skip*), **jamais** ajoutée. + La déduplication de l'import ne construisait sa table que depuis `state.entries` : une entrée dont l'`uuid` n'existait **que** dans la corbeille passait pour nouvelle et créait une **seconde ligne partageant son uuid** — l'identité sur laquelle repose toute la sync. Même angle mort que F8. +- **Ne PAS vider la corbeille pour nettoyer des doublons d'uuid** : *Empty trash* et *Delete forever* écrivent un tombstone portant cet `uuid`, et l'autre appareil supprimerait alors le jumeau vivant. Retirer les lignes en trop directement en base. +- **Le sync, lui, n'emporte pas la corbeille** — voir F8 et `buildSyncSnapshot`. + ### F8 · mettre à la corbeille puis synchroniser ne duplique pas ✅ - **ACTION** : synchroniser une première fois (l'instantané distant contient les entrées) → mettre **plusieurs** entrées à la corbeille → **Sync now**. - **ATTENDU** : les entrées restent en corbeille, et **rien n'est recréé** dans la liste vivante. Le résumé ne doit pas annoncer `pulled N new`. diff --git a/js/app.import.js b/js/app.import.js index 8672a5e..057b395 100644 --- a/js/app.import.js +++ b/js/app.import.js @@ -680,8 +680,15 @@ async function doImport() { // exists — the user is either re-importing a backup or rolling // back to an earlier version). Ask what to do with overlapping // entries so a restore isn't silently blocked by the dedup. + // The TRASH counts as "already in the vault". Its rows are absent from + // state.entries, so without this a container entry whose uuid is only + // in the trash was treated as fresh and inserted a SECOND row with the + // same uuid — the identity the whole sync engine relies on. Same blind + // spot as the resurrection bug in applyRemoteSnapshot (F8). + await loadTrash(); const localByUuid = new Map(); for (const e of state.entries) if (e && e.uuid) localByUuid.set(e.uuid, e); + for (const e of (state.trashed || [])) if (e && e.uuid) localByUuid.set(e.uuid, e); const fresh = []; const overlaps = []; for (const e of parsed.entries) {