feat: entry templates + tag autocomplete + slideover push + robustness bundle

- Entry templates: new vault_entries.template column drives a typed
  sub-kind ('credit-card', 'ssh-key', 'server', 'recovery-codes'). Card
  + table label off the template, badge reads "credit card" instead of
  "note". Templates seed kind=note (no site/password required), use
  custom_fields with optional dropdown options (brand, month/year,
  protocol). Round-tripped across export/import/duplicate/master-pw
  rotation, preserved by partial PUTs via a HasTemplate flag.
- Custom fields: support per-field `options[]` rendering as <select>
  (card brand, expiry MM/YYYY, SSH/server protocol).
- Tags: existing-tag autocomplete dropdown under the chip input,
  filtered against what's already selected.
- Search history: per-query X for individual delete + 1s debounced
  commit (no Enter required).
- Slideover: clicking outside closes again (drag-selection respected
  via mousedown origin tracker), Esc closes, X closes. App shell is
  pushed left by 420px when the panel is open so the table / pagination
  / sort / search stay visible and interactive.
- Export/import: JSON now round-trips custom_fields, attachments
  (decrypted to base64, re-encrypted under current key on restore),
  icon_b64, and template. CSV warning lists what's not included.
- Auto-backup: same payload shape as user-driven export.
- Notes: import (JSON + CSV) accepts kind=note with empty site,
  preserves title/template/custom_fields. CSV parser detects kind/
  template columns.
- Bulk-import response returns `ids[]` parallel to input so the
  client can map back to new entry IDs (drives attachment restore).
- Move-to-folder bugs fixed: moveEntryToFolder, batchMoveToFolder,
  addTag, batchAddTag were all silently wiping TOTP / custom_fields
  / kind / template via partial PUT. Now re-ship full payload.
- Master-pw rotation: server mints a fresh session token + csrf so
  the very next request after rotation no longer ESessionRejects.
  Client adopts the new pair. Attachments are re-encrypted client-side
  during rotation (GET old → decrypt with old key → encrypt with new
  → PUT). New endpoints: GET /attachments/all, PUT /attachments/:id.
- Duplicate: carries icon_b64 + template + attachments to the copy.
- HandleCreateEntry: accepts icon_b64.
- FireDAC param fix: all blob/icon/custom_fields params use ftMemo +
  .Value assignment so SQLite TEXT no longer truncates to 4000 chars
  (deepseek's 200+ KB favicon was being wiped on lock/unlock).
- HandleSetEntryIcon cap: 262144 → 524288 chars (base64 of a 256 KB
  raw fetch overflows the old cap, fails silently in saveEntryIcon).
- Native save dialog: surfaces server errors instead of swallowing.
- Modals: reauth (export) + backup-password prompt support inline
  error display, retry up to 5 attempts, then hard-stop.
- Keyboard cursor (j/k): bootstraps to current page, auto-paginates
  when the cursor crosses a page boundary, Enter opens slideover.
- Slideover focuses Title on edit-open so j/k → Enter → type Just
  Works.
- TOTP tool: Esc closes the modal.
- App version + launch mode (auto/manual): exposed via bridge,
  surfaced in Settings → Account. Autostart launches suppress the
  first-time tray balloon.
- Passkey button hidden (Delphi backend stubs WebAuthn at 501).
- TEST_PLAN.md captured for regression coverage.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
r-zakarya
2026-06-26 21:20:07 +01:00
parent fa7ea191be
commit e23a78dda7
14 changed files with 1752 additions and 203 deletions
+102
View File
@@ -0,0 +1,102 @@
# Test plan — commit `fa7ea19` (native save + auto-backup + folder customization + attachments + UX bundle)
Coche au fur et à mesure. Les **🔥 regression** sont les bugs déjà vus à
re-vérifier explicitement.
## 0. Setup
- [✅] `delphi-backend\assets\BuildAssets.cmd` + F9 (sinon embedded ≠ disque)
- [✅] Lance avec `config.txt` présent (debug panel + log)
- [✅] Crée 2 users de test ou utilise vault existant non critique
## 1. Native Save As
- [✅] Settings → Export encrypted JSON → maître pw OK → backup pw → boîte "Enregistrer sous" Windows native (pas le popup Téléchargements Edge)
- [✅] Export CSV → idem natif
- [✅] Annule la dialog → toast "save cancelled" attendu, pas d'erreur silencieuse
## 2. Auto-backup
- [✅] Settings → Auto-backup → toggle on → prompt pwd (< 6 chars rejeté)
- [✅] "Choose…" → folder Windows valide → path s'affiche
- [✅] "Backup now" → fichier `vault-autobackup-yyyymmdd-HHmmss.json` créé dans le folder
- [✅] Lance plusieurs "Backup now" (interval = 1 jour) → fichiers s'accumulent → après N+1, le plus vieux est purgé (vérifier `keep` retention)
- [✅] Toggle off → re-on → prompt pwd à nouveau (confirme que le pwd est effacé)
- [ ] Restart app, unlock → 5s plus tard, si `interval` passé, backup auto silencieux (regarde le folder)
- [✅] **Restore** : install fresh → import "Encrypted JSON" → choisis un backup → tape le pwd → entries restaurées intactes
## 3. Folder customization
- [✅] Sidebar → + Folders → modal s'ouvre → name+color+icon → Create → folder visible avec custom icon + color
- [✅] Hover sur folder → pencil apparaît → click → modal pré-rempli → change icon → Save → re-render OK
- [✅] **Folder chip sur card** : crée entry dans "Banking" → la chip sous le pwd montre l'icône shield + couleur (pas le default folder gris) 🔥
- [✅] Drag-reorder : grab folder → drag au-dessus d'un autre → ligne d'insertion cyan visible (above/below selon mouse Y) → drop → ordre changé persisté (refresh app)
## 4. Drag entries vers folders 🔥
- [✅] Drag d'une LOGIN entry → folder → toast "Moved to X" → entry change de folder
- [✅] Drag d'une NOTE entry → folder → toast "Moved to X" (régression v1, doit marcher maintenant)
- [✅] Drag d'une entry avec **TOTP configuré** → folder → ouvre l'entry après → **TOTP toujours là** (pas wipé)
- [✅] Drag d'une entry avec **custom_fields** → folder → custom_fields préservés
- [✅] Drag d'une entry vers son propre folder → no-op (pas d'erreur)
## 5. Recently used
- [✅] Vue vide au start (rien jamais touché) → "No entries"
- [✅] Copy password d'une entry → attends 2s → sidebar "Recently used" → entry visible en tête
- [✅] Ouvre une entry (slideover) → idem
- [✅] Copy 5 entries rapidement → max 10 visibles, ordre = plus récent d'abord
- [✅] Lock vault + unlock → liste rechargée du serveur (accessed_at persiste)
## 6. Encrypted attachments
- [✅] Crée entry, sauve, ré-ouvre → section "Attachments" visible (pas sur new entry)
- [✅] "Attach file" → file picker → upload PDF/PNG < 5 MB → toast OK → liste affiche filename+size+mime
- [✅] Upload fichier > 5 MB → toast erreur "File too large"
- [✅] Download → boîte Save As → fichier décrypté identique à l'original (hash check si paranoid)
- [✅] Delete → confirm modal → row disparaît
- [✅] Crée 2nd attachment, supprime entry parent → vérifier que les rows entry_attachments sont aussi delete (CASCADE FK) via `sqlite vault.db "select count(*) from entry_attachments"`
- [✅] Master pw rotation avec attachments → toast "Re-encrypting N attachment(s)…" puis "Attachments re-encrypted" → download d'un attachment **après rotation** réussit (re-chiffré client-side dans le flux change-master)
## 7. Password expiry
- [✅] Settings → Flag aged → 90 days
- [✅] Backdoor : `sqlite vault.db "UPDATE vault_entries SET password_changed_at = '2024-01-01' WHERE id = X"` → recharge → badge "Aged" visible sur la card de X
- [✅] Edit l'entry, change le password, Save → badge disparaît (password_changed_at bumpé)
- [✅] Edit l'entry, change **uniquement le title**, Save → badge **toujours là** (password_changed_at pas bumpé — le CASE SQL filtre)
- [✅] Filters dropdown → "Aged password" → liste filtrée
## 8. Print recovery code
- [✅] Settings → Recovery → Generate (ou regenerate) → modal avec code → bouton "Print"
- [✅] Click Print → preview Edge → vérifie : code 32px monospace, account name, date, instructions 3 étapes, page A4 propre (le reste de l'app caché)
- [✅] Annule la print preview → app revient normalement (cleanup du #printRecoveryArea)
## 9. Audit log
- [✅] Sidebar → Tools → Audit log → table visible avec actions chronologiques
- [✅] Fais une action (add entry, copy pwd) → refresh audit log → nouvelle ligne en tête
- [✅] Search dans audit log → filtre marche
## 10. Pinned entries
- [✅] Sur n'importe quelle card, click pushpin → toast "Pinned to top" → entry remonte en tête de la liste
- [✅] Re-click → toast "Unpinned" → entry retrouve sa place dans le sort courant
- [✅] Change le sort (name asc → date desc) → entries pinned restent en tête, le tri interne s'applique aux deux groupes
- [✅] Compact mode (Settings → Compact actions) → kebab menu contient "Pin to top" / "Unpin"
- [✅] Pin 3 entries, lock vault, unlock → les 3 sont toujours en tête (persist serveur)
- [ ] Master pw rotation → pinned préservé (pas wipé)
- [✅] Duplicate d'une entry pinned → la copie est **non pinned** (comportement voulu)
## 11. Search history
- [✅] Tape une recherche, attends 1s sans bouger → recherche ajoutée à l'historique (pas besoin d'Enter)
- [✅] Vide la searchbar → focus → dropdown "Recent searches" avec les dernières
- [✅] Click sur un item → re-filtre et bump en tête
- [✅] X individuel sur un item → suppression isolée
- [✅] "Clear" → vide tout l'historique
- [✅] Dropdown disparaît au click hors searchbar / Escape / blur => un petit probleme > focus search bar > dropdown search affiché > clique à nouveau search (focus non changé) dropdown non affiché
## 12. Regressions générales 🔥
- [✅] Quick unlock encore fonctionnel après update
- [✅] Master password rotation OK + entries déchiffrables après
- [✅] Autofill (Ctrl+Shift+L/P) toujours OK
- [ ] Recovery code redeem fonctionne (sur compte test)
- [✅] Lock vault → unlock → toutes entries présentes
- [ ] Trash → restore → entry revient avec tous ses champs (kind, custom_fields, TOTP)
## 13. Console / log scrutiny
- [ ] Ouvre debug panel → execute scénarios ci-dessus → log Delphi sans `FAILED`/`ERROR` non attendu
- [ ] Si tu peux ouvrir console JS (DevTools désactivé en prod, faut hack — sinon skip) → pas d'erreur JS rouge pendant les flows
---
**Ordre conseillé** : 0 → 4 → 6 → 7 → 1 → 2 → 3 → 5 → 8 → 9 → 10 → 11 → 12 → 13. Le drag entries (#4) est le plus à risque (régression confirmée hier), le faire tôt + à fond.