feat: WebDAV sync + batch DnD + clean shutdown + center-modal UX bundle

- Sync (WebDAV, auto-merge): UUID + tombstones foundations (server +
  JS), THTTPClient bridge cmds (get/put/test), runSyncNow engine with
  pull/merge/push flow, Settings UI, pre-sync backup option. Test
  connection now treats 404 as OK (snapshot not yet created) and 401/
  403 as auth failure with dedicated toast.
- Batch drag-drop: cards + table rows carry checked-set ids (CSV) when
  dragged from an active selection; folder + trash drop handlers parse
  and apply in batch via new moveEntriesToFolder helper that preserves
  TOTP / custom_fields / kind in the full PUT payload.
- Clean shutdown: WM_QUERYENDSESSION / WM_ENDSESSION captured in the
  bridge message-only window; FormCloseQuery bypasses the tray-minimize
  intercept on system shutdown / restart / logoff so FireDAC closes the
  SQLite WAL cleanly instead of leaving -shm / -wal residue after a
  force-kill.
- Center-mode modal: blur+dim backdrop via body::before pseudo-element
  in editor-position=center, swallows clicks below the panel so the
  existing outside-click handlers reliably dismiss the slideover /
  settings panel.
- Batch bar state fixes: state.checked cleared before render in
  moveEntriesToFolder, emptyTrash, and per-card restoreEntry /
  permanentDelete / deleteEntry so the action bar disappears once the
  selection is fully processed.
- Save-then-discard duplicate fix: soState reset to null before
  openSlideOver re-opens the freshly saved entry, otherwise the dirty
  check fired on the soState.id=null → newId switch and a Cancel left
  the form in new-entry mode (second Save → POST duplicate).
- TEST_SYNC.md: end-to-end checklist for validating the WebDAV sync
  with 2 real instances.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
r-zakarya
2026-06-30 00:32:12 +01:00
parent b00da43ab0
commit 6869b7c692
10 changed files with 1132 additions and 25 deletions
+135
View File
@@ -0,0 +1,135 @@
# Test plan — WebDAV sync (auto-merge)
Server : `wsgidav --host=127.0.0.1 --port=8080 --root=E:\webdav-test --auth=anonymous`
URL Settings : `http://127.0.0.1:8080/vault-sync.json`
User/pwd Settings : vides (anonymous)
Sync password : choisir une fois, **identique sur les 2 devices**
---
## 0. Setup deux devices RÉELLEMENT séparés
Le sync est par-vault. Un même `vault.db` avec 2 comptes user ≠ 2 devices.
- [ ] **Device A** : exe actuel `Z:\password-manager\delphi-backend\Win32\Debug\PMServer.exe`
- [ ] **Device B** :
- Copier **uniquement le `.exe`** dans `D:\PMServer-B\` (assets.res est déjà embarqué dedans)
- Lancer → crée un user `userB` avec un nouveau master pw
- `vault.db` de B est créé à côté de l'exe B (séparé de A)
- [ ] Vérifier les 2 lancent sur des ports différents (chacun écrit son port au boot dans le log)
## 1. Connexion server
- [ ] wsgidav tourne (logs visibles, "Serving on http://127.0.0.1:8080")
- [ ] A : Settings → Sync → URL + Set sync password (`testsync`) → Test connection → toast `Connection OK · snapshot not created yet`
- [ ] B : idem mais **même sync password** `testsync` → Test connection → toast OK aussi
## 2. Premier push (A vide → server)
- [ ] A : créer 3 entries distinctes (`gmail`, `bank`, `github` par ex.) + 1 note + 1 folder custom `Work`
- [ ] A : **Sync now** → wsgidav log `GET 404` + `PUT 201` → toast `Sync complete — 0 added · 0 updated · 0 deleted`
- [ ] Vérifier `Get-ChildItem E:\webdav-test``vault-sync.json` existe (~quelques KB)
## 3. Premier pull (B vide ← server)
- [ ] B : **Sync now** → wsgidav log `GET 200` + `PUT 201` → toast `Sync complete — 5 added · 0 updated · 0 deleted` (3 logins + note + ??? folder ne compte pas dans `added`)
- [ ] B : vérifier que les 3 entries + la note sont visibles dans la grille
- [ ] B : ouvrir `gmail` → password déchiffrable
- [ ] B : ouvrir la note → texte lisible
- [ ] B : sidebar Folders → `Work` présent avec sa couleur+icône
## 4. Auto-merge ajout des deux côtés
- [ ] A : créer entry `slack`
- [ ] B : créer entry `discord`
- [ ] A : Sync now → toast `1 added` (récupère `discord`)
- [ ] B : Sync now → toast `1 added` (récupère `slack`)
- [ ] Les deux devices ont maintenant 5 + 2 = 7 entries
## 5. Last-write-wins
- [ ] A : éditer `gmail` → changer username en `user-from-A` → Save
- [ ] B (sans sync entre temps) : éditer `gmail` → changer username en `user-from-B` → Save (B a `updated_at` plus récent)
- [ ] A : Sync now → toast `0 added · 1 updated · 0 deleted``gmail.username` devient `user-from-B`
- [ ] Inverse pour confirmer : édit A puis édit B puis sync B en premier → B garde sa version (rien à update côté B), puis sync A → A bascule sur B
## 6. Tombstones (delete propagation)
- [ ] A : delete `slack` (soft) → Trash → Empty trash (hard-delete = tombstone créé)
- [ ] A : Sync now → push tombstone
- [ ] B : Sync now → toast inclut `1 deleted``slack` disparait côté B
- [ ] Recréer `slack` côté B → Sync now → vérifier qu'il ne ressuscite **pas** côté A (tombstone réutilisé sauf si nouveau uuid mint → vérifier ce comportement)
## 7. Custom fields + TOTP préservés
- [ ] A : créer entry `aws` avec TOTP secret valide + 2 custom fields (`access_key`, `secret_key` is_secret=true)
- [ ] A : Sync now
- [ ] B : Sync now → `aws` apparaît
- [ ] B : ouvrir `aws` → TOTP code visible et tick · les 2 custom fields visibles · `secret_key` masqué (is_secret)
- [ ] B : éditer un custom field → Save → Sync now
- [ ] A : Sync now → modif reflétée
## 8. Attachments round-trip
- [ ] A : ouvrir une entry → Attach file → upload PDF < 1 MB
- [ ] A : Sync now (wsgidav log → file size augmente sensiblement)
- [ ] B : Sync now → ouvrir la même entry → attachment visible → Download → fichier décrypté identique
## 9. Pre-sync backup
- [ ] A : Settings → cocher "Create a local backup before each sync"
- [ ] A : Sync now → `Get-ChildItem "C:\Users\zakar\Desktop\backup test\vault-presync-*.json"` → fichier daté du jour existe
- [ ] Tester restore : Import vault → choisir le `.json` → tape `testsync` → entries restaurées
## 10. Auth WebDAV (optionnel)
Si tu veux tester avec un vrai user/pwd (au lieu d'anonymous) :
```
wsgidav --host=127.0.0.1 --port=8080 --root=E:\webdav-test ^
--auth=basic --user-mapping={"/":{"alice":{"password":"s3cret","roles":["editor"]}}}
```
- [ ] A : Username `alice` + Password `s3cret` → Test connection OK
- [ ] A : vider user/pwd → Test connection → toast `Auth failed (401)`
## 11. Erreurs réseau
- [ ] Tuer wsgidav (Ctrl+C dans son terminal)
- [ ] A : Test connection → toast `Network error: ...`
- [ ] A : Sync now → toast `Sync pull failed: ...` (pas d'écrasement local)
- [ ] Relancer wsgidav → re-Sync now → reprend normalement
## 12. Conflict — édit + delete sur le même entry
- [ ] A : delete `github` → hard-delete (Empty trash) → Sync now (push tombstone)
- [ ] B (sans sync entre) : édite `github` (la version locale a un `updated_at` plus récent que la deletion)
- [ ] B : Sync now → comportement attendu = entry deletée (tombstones gagnent toujours sur update — vérifier que c'est bien ça)
## 13. Vault locked pendant sync
- [ ] A : Lock vault
- [ ] A : (pas accès Settings — locked) — vérifier qu'il n'y a pas d'auto-sync silencieux qui tenterait quand même
- [ ] Unlock → Sync now → fonctionne
## 14. Master pw rotation + sync
- [ ] A : change master password → re-encrypte toutes les entries localement avec nouvelle clé
- [ ] A : Sync now → push avec **même** sync password (encPwd indépendant du master) → toast OK
- [ ] B : Sync now → toast `0 added · 0 updated · 0 deleted` (les uuids n'ont pas changé, les `updated_at` non plus pour la plupart)
---
## Critères de réussite
- ✅ Pull récupère systématiquement les entries manquantes (added > 0 quand attendu)
- ✅ Push ne supprime **jamais** silencieusement de données (sauf via tombstone explicite)
- ✅ Pre-sync backup créé si dossier configuré (sinon skip silencieux, OK)
- ✅ Erreur réseau ou auth → toast clair, **pas** d'écrasement
- ✅ Sync password divergent → toast `Remote decrypt failed — wrong sync password?` + pas de push
- ✅ Tombstones propagent les deletes hard
## Gotchas connus
- **Tester avec 2 vraies instances séparées**, pas 2 users dans la même DB
- Sync password ≠ master password ; doit être identique sur tous les devices
- Pre-sync backup réutilise le dossier auto-backup (pas de dossier dédié actuellement)
- Le snapshot WebDAV contient TOUTES les entries en plaintext sous le sync password — ne pas confondre avec la sécurité par master (qui reste pour les blobs DB locaux)