fix(import): overwrite mode restores favorite and pinned too

F1 passed when the entry had been deleted first (INSERT path) but not in
overwrite mode: that path uses PUT, and PUT deliberately ignores
favorite/pinned so a SYNC update can never clobber the local flags — they are
device-local by design. A restore, however, must bring them back.

The overwrite loop now applies them through the dedicated toggle endpoints,
and only when they differ from the local entry. JS-only: PUT keeps ignoring
them, so sync behaviour is untouched.

Splits the F1 scenario into F1a (delete + reimport, already green) and F1b
(overwrite).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
r-zakarya
2026-07-26 00:24:38 +01:00
parent f198eaf6f3
commit af234ce049
2 changed files with 20 additions and 7 deletions
+7 -7
View File
@@ -156,7 +156,7 @@ est dirty. Tester avec `askBeforeDelete` **ON puis OFF** (Settings → Security)
## F — Round-trip export / import (drapeaux & provenance) ## F — Round-trip export / import (drapeaux & provenance)
### F1 · favori + épingle survivent au restore ### F1 · favori + épingle survivent au restore ✅
- **ACTION** : épingler **et** mettre en favori une entrée → *Export vault* → supprimer l'entrée (corbeille + purge) → *Import vault* du fichier. - **ACTION** : épingler **et** mettre en favori une entrée → *Export vault* → supprimer l'entrée (corbeille + purge) → *Import vault* du fichier.
- **ATTENDU** : l'entrée revient **épinglée et favorite**. - **ATTENDU** : l'entrée revient **épinglée et favorite**.
- **Historique** : `pinned` n'était dans aucun container ; `favorite` y était écrit mais **jamais relu** (ni parser, ni body, ni INSERT) → les deux perdus au restore. - **Historique** : `pinned` n'était dans aucun container ; `favorite` y était écrit mais **jamais relu** (ni parser, ni body, ni INSERT) → les deux perdus au restore.
@@ -173,7 +173,7 @@ est dirty. Tester avec `askBeforeDelete` **ON puis OFF** (Settings → Security)
- **ACTION** : épingler/favoriser une entrée sur le device A → *Sync now* → *Sync now* sur le device B. - **ACTION** : épingler/favoriser une entrée sur le device A → *Sync now* → *Sync now* sur le device B.
- **ATTENDU** : sur B, l'entrée **n'est pas** épinglée/favorite. Et un sync ne doit jamais **retirer** un drapeau posé localement sur B (le PUT serveur ne touche pas ces colonnes). - **ATTENDU** : sur B, l'entrée **n'est pas** épinglée/favorite. Et un sync ne doit jamais **retirer** un drapeau posé localement sur B (le PUT serveur ne touche pas ces colonnes).
### F5 · icône custom : provenance préservée ### F5 · icône custom : provenance préservée ✅
- **ACTION** : upload d'une icône custom → export → supprimer l'entrée → import → puis **Re-fetch all**. - **ACTION** : upload d'une icône custom → export → supprimer l'entrée → import → puis **Re-fetch all**.
- **ATTENDU** : l'icône custom revient **et** survit au Re-fetch all (le flag `icon_custom` a fait le round-trip). - **ATTENDU** : l'icône custom revient **et** survit au Re-fetch all (le flag `icon_custom` a fait le round-trip).
@@ -214,7 +214,7 @@ Ces flux touchent la dérivation de clé et l'anti-lockout : tester sur un compt
**argon2id-v2** (défaut courant). Rebuild **Delphi** requis (fixes serveur) + **argon2id-v2** (défaut courant). Rebuild **Delphi** requis (fixes serveur) +
BuildAssets pour le JS. BuildAssets pour le JS.
### E1 · reauth — mauvais master pw reste inline (les 4 flux) ### E1 · reauth — mauvais master pw reste inline (les 4 flux) ✅
- **ACTION** : sur chacun de *Set/Change PIN*, *Enable Quick Unlock*, - **ACTION** : sur chacun de *Set/Change PIN*, *Enable Quick Unlock*,
*Generate recovery code*, *Export* → taper un **mauvais** master pw. *Generate recovery code*, *Export* → taper un **mauvais** master pw.
- **ATTENDU** : le modal **reste ouvert** avec « Wrong master password. Attempt - **ATTENDU** : le modal **reste ouvert** avec « Wrong master password. Attempt
@@ -222,13 +222,13 @@ BuildAssets pour le JS.
moment = action abandonnée, rien fait. moment = action abandonnée, rien fait.
- **Historique** : PIN / Quick Unlock / recovery fermaient + toast au 1ᵉʳ essai. - **Historique** : PIN / Quick Unlock / recovery fermaient + toast au 1ᵉʳ essai.
### E2 · recovery setup sur compte argon2id-v2 ### E2 · recovery setup sur compte argon2id-v2 ✅
- **ACTION** : *Generate recovery code* → bon master pw. - **ACTION** : *Generate recovery code* → bon master pw.
- **ATTENDU** : code généré, **pas** de « invalid password ». - **ATTENDU** : code généré, **pas** de « invalid password ».
- **Historique** : `/recovery-key/setup` rejetait tout compte -v2 / argon2id - **Historique** : `/recovery-key/setup` rejetait tout compte -v2 / argon2id
(vérif serveur ne connaissait que pbkdf2 / pbkdf2-sha256). (vérif serveur ne connaissait que pbkdf2 / pbkdf2-sha256).
### E3 · recovery generate → annuler n'écrit rien ### E3 · recovery generate → annuler n'écrit rien ✅
- **PRÉ** : un code de recovery **existe déjà**. - **PRÉ** : un code de recovery **existe déjà**.
- **ACTION** : *Regenerate code* → bon master pw → à l'affichage du code, - **ACTION** : *Regenerate code* → bon master pw → à l'affichage du code,
**Cancel** (ou Esc). **Cancel** (ou Esc).
@@ -236,9 +236,9 @@ BuildAssets pour le JS.
code fonctionne toujours au redeem. Refaire avec **I saved it** → nouveau code fonctionne toujours au redeem. Refaire avec **I saved it** → nouveau
code actif, ancien invalidé. code actif, ancien invalidé.
- **Historique** : le setup était POSTé avant l'affichage → annuler écrasait - **Historique** : le setup était POSTé avant l'affichage → annuler écrasait
quand même l'ancien code (chemin silencieux vers le lockout). quand même l'ancien code (chemin silencieux vers le lockout).
### E4 · recovery redeem sur compte argon2id-v2 ### E4 · recovery redeem sur compte argon2id-v2 ✅
- **ACTION** : générer un code, se déconnecter, *Use a recovery code* → - **ACTION** : générer un code, se déconnecter, *Use a recovery code* →
username + code. username + code.
- **ATTENDU** : déverrouillage OK, redirection vers *Change master password*. - **ATTENDU** : déverrouillage OK, redirection vers *Change master password*.
+13
View File
@@ -728,6 +728,19 @@ async function doImport() {
// restore the file's icon separately — else overwriting // restore the file's icon separately — else overwriting
// an entry whose icon was cleared never brings it back. // an entry whose icon was cleared never brings it back.
if (src.icon_b64) await saveEntryIcon(local.id, src.icon_b64, !!src.icon_custom); if (src.icon_b64) await saveEntryIcon(local.id, src.icon_b64, !!src.icon_custom);
// favorite / pinned are the same story: PUT deliberately
// ignores them so a SYNC update can never clobber the local
// flags (they're device-local by design). But a restore
// must bring them back, so apply them here through their
// own toggle endpoints — only when they actually differ.
if (!!src.favorite !== !!local.favorite) {
await api('/entries/' + local.id + '/favorite',
{ method: 'POST', headers: authHeaders() });
}
if (!!src.pinned !== !!local.pinned) {
await api('/entries/' + local.id + '/pin',
{ method: 'POST', headers: authHeaders() });
}
overwritten++; overwritten++;
} catch (_) { /* skip the single row on failure */ } } catch (_) { /* skip the single row on failure */ }
} }