Section J fully green: no double minimise on the fill path, the clipboard survives the copy path, and an app already on screen is not sent to the tray. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
27 KiB
Scénarios de test — écriture concurrente & fermeture de l'éditeur
Sessions du 2026-07-22. Tous les tests sont manuels (ils portent sur des
interactions UI que le harness node:test ne couvre pas). Rebuild obligatoire
avant de commencer : delphi-backend\assets\BuildAssets.cmd puis build Delphi.
Convention : PRÉ = état de départ, ACTION, ATTENDU. Un test qui échoue = régression, noter le commit courant.
Les 4 classes de bug
| # | Classe | Cause racine | Corrigé par |
|---|---|---|---|
| A | Écriture extérieure écrasée au Save | L'éditeur garde le payload chargé à l'ouverture et le re-poste entier ; toute écriture faite ailleurs entre-temps est perdue | editorAbsorbChange (d146fbe) |
| B | Éditeur fermé sans garde-fou | closeSlideOver() appelé sans condition + click-outside non filtré |
closeEditorForDeleted (3dbce78), whitelist (e1ddc65) |
| C | PUT /entries sans withEncryptedMeta |
Les colonnes en clair sont droppées côté serveur → le body ne contient rien de stockable et blanchit les *_enc |
d146fbe |
| D | UI construite sans regarder la vue | Le menu kebab est partagé entre vues ; la corbeille n'était pas testée | d146fbe |
A — Éditeur ouvert vs écriture extérieure
Préparer : 3 entrées A, B, C, dont A dans le dossier Work avec le tag
old.
A1 · batch Add tag sur l'entrée éditée
- PRÉ :
Aouverte dans l'éditeur, taper du texte dans NOTE/PASSWORD (ne pas sauver) - ACTION : cocher
A+B→ barre de sélection → Add tag →shared→ puis Save dans l'éditeur - ATTENDU : le chip
sharedapparaît immédiatement dans l'éditeur ; après Save,Aporteold, sharedet la modif tapée. Le bouton Save reste visible (l'entrée était déjà dirty) mais le tag seul ne rend pas dirty.✅
A2 · batch Add tag sur une autre entrée
- PRÉ :
Aouverte + dirty - ACTION : cocher
B+Cseulement → Add tag →x→ Save dans l'éditeur - ATTENDU : l'éditeur ne bouge pas (aucun chip ajouté),
Aconserve ses tags,B/Ctaguées.✅
A3 · batch Move to folder sur l'entrée éditée
- ACTION :
Aouverte + dirty → cocherA→ Move to folder… →Perso→ Save - ATTENDU : le select Folder de l'éditeur bascule sur
Persotout seul ; après Save l'entrée est bien dansPerso.✅
A4 · dossier choisi dans l'éditeur puis move extérieur
- ACTION :
Aouverte, changer Folder →Archive(ne pas sauver) → cocherA→ Move to folder… →Perso→ Save - ATTENDU :
Persogagne (action extérieure = intention la plus récente). Comportement documenté, pas un bug. ✅
A5 · glisser une entrée sur un dossier (moveEntryToFolder)
- ACTION :
Aouverte + dirty → glisser la ligneAsurPersodans la sidebar → Save - ATTENDU : identique à A3.✅
A6 · glisser une multi-sélection sur un dossier (moveEntriesToFolder) ⚠️
- ACTION : cocher
A+B→ glisser surPerso→ recharger (F5 impossible : relancer l'app) - ATTENDU : dossier changé et nom / username / site / tags intacts.
- Historique : ce chemin n'appelait pas
withEncryptedMeta→ il blanchissait les métadonnées chiffrées. Si des entrées ont été déplacées ainsi avant le fix, elles sont peut-être déjà vides — vérifier.✅
A7 · suppression du dossier de l'entrée éditée
- ACTION :
A(dossierWork) ouverte + dirty → sidebar → supprimer le dossierWork→ Save - ATTENDU : le select de l'éditeur passe à
All; après Save l'entrée est dansAll, le dossier supprimé ne réapparaît pas.✅
A8 · tags édités des deux côtés
- ACTION :
Aouverte, ajouter le chipminedans l'éditeur (ne pas sauver) → batch Add tagsharedsurA→ Save - ATTENDU :
old, mine, shared— aucun des trois perdu.✅
A9 · favori / épingle pendant l'édition (non-régression)
- ACTION :
Aouverte + dirty → cliquer ★ et 📌 sur la ligneA→ Save - ATTENDU : l'entrée reste favorite et épinglée (ces colonnes ne sont pas dans le PUT de l'éditeur).✅
A10 · icône / pièce jointe pendant l'édition (non-régression)
- ACTION :
Aouverte + dirty → Settings → Fetch missing favicons (ou upload d'icône) → Save - ATTENDU : l'icône survit au Save (endpoint dédié, hors payload éditeur).✅
A10b · icône rafraîchie dans l'éditeur ouvert
- ACTION :
Aouverte → Settings → Fetch missing / Re-fetch all favicons - ATTENDU : l'aperçu d'icône dans l'éditeur se met à jour tout seul (pas seulement la carte). Idem après un import en mode overwrite qui apporte une icône.✅
A11 · sync / import pendant que l'éditeur est ouvert
- ACTION :
Aouverte + dirty → lancer Sync now (ou importer un JSON en mode overwrite) qui metAà jour → Save - ATTENDU : modale « Entry changed elsewhere ». Annuler ⇒ rien n'est écrit (la version du sync/import survit, l'éditeur reste ouvert le temps de recopier). Overwrite ⇒ la version de l'éditeur gagne.✅
A12 · pas de fausse alerte (non-régression d'A11)
Aucun de ces enchaînements ne doit afficher « Entry changed elsewhere » :
- A12a :
Aouverte + dirty → Save → modifier encore → Save (le rechargement post-save re-cale la référence) - A12b :
Aouverte + dirty → batch Add tag / Move to folder surA→ Save (l'éditeur a absorbé le changement) - A12c :
Aouverte + dirty → supprimer son dossier → Save (cf. A7) - A12d :
Aouverte + dirty → Fetch missing favicons → Save (l'icône est hors payload éditeur) - A12e :
Aouverte + dirty → sync sans changement distant surA→ Save
A13 · entrée supprimée ailleurs pendant l'édition
- ACTION :
Aouverte + dirty → un sync distant supprimeA(tombstone) → Save - ATTENDU : pas de plantage. L'entrée n'est plus en mémoire → le contrôle A11 passe la main ; le PUT échoue proprement avec un toast d'erreur.
B — Fermeture de l'éditeur & message de discard
Le message n'apparaît que si confirmOnUnsaved est actif et que l'entrée
est dirty. Tester avec askBeforeDelete ON puis OFF (Settings → Security).
B1 · ✕ sur une autre ligne
- PRÉ :
Aouverte + dirty - ACTION : cliquer ✕ (Move to trash) sur la ligne
B - ATTENDU :
Bpart à la corbeille, l'éditeur deAreste ouvert avec les modifs. Vrai dans les deux réglages deaskBeforeDelete.
B2 · ✕ sur la ligne éditée, askBeforeDelete = OFF
- ACTION :
Aouverte + dirty → ✕ surA - ATTENDU : modale « Discard unsaved changes? ». Annuler ⇒ rien n'est supprimé et l'éditeur reste ouvert. Confirmer ⇒
Aà la corbeille + éditeur fermé.
B3 · ✕ sur la ligne éditée, askBeforeDelete = ON
- ATTENDU : une seule modale (« Move to trash »). Pas de double prompt.
B4 · batch Delete incluant l'entrée éditée
- ACTION :
Aouverte + dirty → cocherA+B→ Delete - ATTENDU : une seule modale (« 2 entries will be moved to trash »), puis l'éditeur se ferme.
B5 · batch Delete excluant l'entrée éditée
- ATTENDU : l'éditeur reste ouvert, modifs intactes.
B6 · corbeille
- ACTION : ouvrir une entrée de la corbeille via Trash actions, puis : (a) Delete forever dessus, (b) batch purge la contenant, (c) Empty trash
- ATTENDU : dans les trois cas l'éditeur se ferme s'il portait une entrée purgée, sinon il reste.
B7 · boutons de la barre de sélection
- ACTION :
Aouverte + dirty → cocherB→ cliquer successivement Move to folder…, Add tag, Clear - ATTENDU : aucun de ces clics ne ferme l'éditeur ni n'affiche de discard.
B8 · bouton Empty trash (barre de titre)
- ATTENDU : idem, pas de fermeture parasite de l'éditeur.
B9 · chemins de fermeture légitimes (non-régression)
- Esc, ✕ de l'éditeur, clic sur le fond de la grille, ouverture d'une autre entrée
- ATTENDU : message de discard à chaque fois si dirty ; annuler garde l'éditeur ouvert sur la bonne entrée.
B10 · verrouillage (comportement assumé)
- ACTION :
Aouverte + dirty → auto-lock ou Lock vault - ATTENDU : fermeture sans message, modifs perdues. Volontaire : la sécurité prime.
B11 · Esc au-dessus d'un modal ouvert par-dessus l'éditeur
- ACTION :
Aouverte + dirty → ouvrir un modal → Esc → Esc - ATTENDU : 1ᵉʳ Esc ferme le modal seul, l'éditeur reste ouvert ; 2ᵉ Esc traite l'éditeur (discard).
- Modals à passer un par un : + New folder (sidebar), Customize folder, Change master password, générateur de mot de passe, cheatsheet (
?), historique de mot de passe, palette (Ctrl+K), outil TOTP, quick search (Ctrl+Shift+Q), reauth (déclenché par un export), picker d'autofill (Ctrl+Shift+Lavec 2 entrées qui matchent). - Historique :
#folderModal(dynamique) n'était dans aucune liste → Esc fermait l'éditeur derrière.#reauthModal,#quickSearchModal,#autofillPickerModal(statiques) non plus.
B12 · le modal reste fonctionnel après un Esc
- ACTION : ouvrir reauth (export) → Esc → ré-ouvrir (relancer l'export). Idem quick search et picker d'autofill.
- ATTENDU : le modal se ré-affiche normalement. Un Esc ne doit jamais supprimer un modal statique du DOM (sinon la fonctionnalité est morte jusqu'au redémarrage).
B13 · clic sur un toast / l'overlay d'attente
- ACTION :
Aouverte + dirty → lancer une action longue (export, sync) → cliquer sur le toast ou sur le spinner - ATTENDU : l'éditeur ne se ferme pas, pas de discard.
B14 · dialogues natifs ouverts par un clic programmatique
- ACTION :
Aouverte + dirty → (a) Settings → Import vault ; (b) télécharger une pièce jointe (fallback web) ; (c) Upload icon ; (d) joindre un fichier. - ATTENDU : seul le dialogue natif s'ouvre. Pas de « Discard unsaved changes? » par-dessus, l'éditeur reste ouvert et modifié.
- Historique : le
<input type=file>(et le<a download>) est attaché à<body>puis.click()→ le clic synthétique remontait au handler click-outside qui le prenait pour un clic dehors. Gardé pare.isTrusted.
F — Round-trip export / import (drapeaux & provenance)
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.
- ATTENDU : l'entrée revient épinglée et favorite.
- Historique :
pinnedn'était dans aucun container ;favoritey était écrit mais jamais relu (ni parser, ni body, ni INSERT) → les deux perdus au restore.
F2 · même chose depuis une auto-backup
- ACTION : Backup now → supprimer l'entrée → importer le fichier
vault-autobackup-*.json. - ATTENDU : identique à F1.
F3 · CSV étranger → drapeaux à 0
- ACTION : importer un CSV sans colonnes favorite/pinned.
- ATTENDU : entrées importées ni épinglées ni favorites (défaut 0), pas d'erreur.
F4 · le sync ne propage PAS les drapeaux (device-local, décidé 2026-07-11)
- 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).
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.
- ATTENDU : l'icône custom revient et survit au Re-fetch all (le flag
icon_customa fait le round-trip).
C — withEncryptedMeta sur tout write /entries
Test générique, à rejouer après toute modif touchant un body POST/PUT
/entries : faire l'action, relancer l'app (pour relire depuis SQLite),
vérifier que nom / site / username / tags s'affichent encore.
Chemins à couvrir : Save éditeur (neuf + existant), duplicate, import
(bulk + overwrite), sync (applyRemoteSnapshot), addTagToEntry,
batchAddTag, moveEntryToFolder, moveEntriesToFolder, batchMoveToFolder,
migrateMetadataAtRest (au unlock), rotation du master password.
Symptôme du bug : après relance, lignes au nom vide / (no name).
D — Vue corbeille
D1 · menu kebab en table view
- ACTION : corbeille + vue table → kebab d'une ligne
- ATTENDU :
Restore/Copy…/Delete foreveruniquement. Pas de Pin / Favorite / Edit / Duplicate / Move to trash.
D2 · vues cartes et liste
- ATTENDU : boutons restore + purge (déjà le cas avant), pas de drag possible.
D3 · clic sur une ligne de corbeille
- ATTENDU : ouvre Trash actions, jamais l'éditeur (une entrée supprimée ne doit pas pouvoir être sauvegardée).
E — Auth / recovery / mémoire (série 2026-07-25)
Ces flux touchent la dérivation de clé et l'anti-lockout : tester sur un compte argon2id-v2 (défaut courant). Rebuild Delphi requis (fixes serveur) + BuildAssets pour le JS.
E1 · reauth — mauvais master pw reste inline (les 4 flux)
- ACTION : sur chacun de Set/Change PIN, Enable Quick Unlock, Generate recovery code, Export → taper un mauvais master pw.
- ATTENDU : le modal reste ouvert avec « Wrong master password. Attempt N / 5. » ; au 5ᵉ échec ou 429 → toast lockout + fermeture. Annuler à tout moment = action abandonnée, rien fait.
- Historique : PIN / Quick Unlock / recovery fermaient + toast au 1ᵉʳ essai.
E2 · recovery setup sur compte argon2id-v2
- ACTION : Generate recovery code → bon master pw.
- ATTENDU : code généré, pas de « invalid password ».
- Historique :
/recovery-key/setuprejetait tout compte -v2 / argon2id (vérif serveur ne connaissait que pbkdf2 / pbkdf2-sha256).
E3 · recovery generate → annuler n'écrit rien
- PRÉ : un code de recovery existe déjà.
- ACTION : Regenerate code → bon master pw → à l'affichage du code, Cancel (ou Esc).
- ATTENDU : toast « Cancelled — no recovery code was set » ; l'ancien code fonctionne toujours au redeem. Refaire avec I saved it → nouveau code actif, ancien invalidé.
- Historique : le setup était POSTé avant l'affichage → annuler écrasait quand même l'ancien code (chemin silencieux vers le lockout).
E4 · recovery redeem sur compte argon2id-v2
- ACTION : générer un code, se déconnecter, Use a recovery code → username + code.
- ATTENDU : déverrouillage OK, redirection vers Change master password. Le coffre se déchiffre (clé dérivée avec les bons params Argon2 renvoyés par le serveur).
- Historique : la réponse redeem n'echoait pas
argon2→ params par défaut (correct aujourd'hui, lockout latent si params non-défaut).
E5 · la clé du coffre n'est plus en clair dans le web storage
- ACTION : coffre déverrouillé → console WebView2 :
sessionStorage.getItem('cryptoKey'). - ATTENDU :
null. (Avant : une string base64 de 44 car = la clé AES.) - Bonus : dump mémoire du renderer →
this_is_passwordpeut persister (strings V8, non corrigeable) ; c'est la clé qu'on a retirée du storage.
E6 · non-régression — les 4 exporteurs de clé marchent toujours
Après le retrait du cache sessionStorage, re-tester que ces flux (qui
exportKey la clé) fonctionnent : login, Quick Unlock, PIN,
recovery redeem, change master password.
E7 · non-régression — éditeur slideover après suppression du modal mort
Le générateur, les tags et la barre de force étaient partagés. Vérifier dans l'éditeur slideover : créer + éditer une entrée, ajouter/retirer des tags, Generate → Use insère le mot de passe, la barre de force réagit.
G — Vault health (série 2026-07-25)
G1 · bouton Fix quand le changement d'entrée est annulé
- ACTION :
Aouverte + dirty → aller sur Vault health → cliquer Fix sur une autre entréeB→ Cancel au message discard. - ATTENDU : rien ne bouge.
Areste ouverte, son mot de passe reste masqué, aucun clignotement du bouton Generate. - Puis refaire → Discard :
Bs'ouvre, c'est le mot de passe de B qui est démasqué + sélectionné, et c'est le Generate deBqui pulse. - Le focus RESTE sur le mot de passe (~1 s après le clic, vérifier que le curseur n'a pas sauté sur le titre) :
openSlideOverprogramme un focus différé de 80 ms sur#soTitle, neutralisé ici par{ noFocus: true }. - Historique :
openSlideOverne renvoyait rien, doncopenEntryForFixcontinuait après une annulation et démasquait en clair le mot de passe de l'entrée restée ouverte.
G2 · Fix sur une entrée supprimée entre-temps
- ACTION : ouvrir Vault health → dans un autre onglet de l'app, mettre à la corbeille une entrée listée → revenir sur health (sans Recompute) → Fix sur cette entrée.
- ATTENDU : rien ne casse (l'éditeur ne s'ouvre pas, pas d'erreur JS). Avec G3 la liste devrait de toute façon être à jour.
G3 · le tableau de bord reflète les mutations
- ACTION : ouvrir Vault health (noter le score) → revenir à la liste → faire une mutation : import, sync, suppression, batch delete, duplicate, restore, ou changement de master pw → rouvrir Vault health.
- ATTENDU : score et listes recalculés (les entrées supprimées ont disparu, les importées apparaissent) sans cliquer Recompute.
- Historique : le cache n'était vidé qu'au lock / au save / via Recompute — après un import ou une suppression le dashboard restait figé sur l'état précédent.
H — Hotkey 2FA (Ctrl+Shift+T, série 2026-07-25)
H1 · remplissage simple ✅
- ACTION : entrée avec TOTP → ouvrir son site → cliquer le champ 2FA →
Ctrl+Shift+T. - ATTENDU : le code à 6 chiffres est tapé dans le champ focalisé, toast « 2FA code filled: … ».
H2 · entrée sans TOTP
- ACTION : fenêtre dont le titre matche une entrée sans secret TOTP →
Ctrl+Shift+T. - ATTENDU : « No 2FA code stored for "…" » (et pas le « no match » générique, ni un remplissage du mot de passe).
H3 · picker multi-match
- PRÉ : 2 entrées matchent le titre, une seule a un TOTP.
- ATTENDU : le picker s'ouvre uniquement s'il reste ≥ 2 candidats avec TOTP ; ici il ne doit pas s'ouvrir (1 seul candidat éligible → remplissage direct). En-tête du picker : « Pick entry (2FA code) — ».
H4 · combo configurable
- ACTION : Settings → Autofill → cliquer le bouton
Ctrl+Shift+T→ capturer un autre combo. - ATTENDU : accepté et actif immédiatement ; refusé s'il duplique un des 4 autres hotkeys ; Reset to defaults le remet à
Ctrl+Shift+T; le réglage survit à un redémarrage (synced viasettings_json).
H5 · coffre verrouillé
- ACTION : verrouiller →
Ctrl+Shift+T. - ATTENDU : l'app revient au premier plan avec le focus sur le master password (même comportement que les autres hotkeys), pas de no-op silencieux.
H7 · combo déjà pris par une autre application ✅
- ACTION : assigner à un hotkey un combo qu'une autre app tient déjà en global (ex.
Ctrl+Shift+Qsi Quick Assist / un lanceur l'occupe ; sinon, tester en assignant le même combo dans deux apps). - ATTENDU : toast nommant le combo refusé — « Already used by another app: 2FA code — pick another combo » — et un ⚠ ambre persistant sur cette ligne de Settings, et une pastille ambre sur l'entrée Settings de la sidebar (seul indice hors du panneau). Le toast d'avertissement reste ~10 s et se ferme au clic. Les autres hotkeys restent fonctionnels (enregistrement best-effort, pas de tout-ou-rien).
- ACTION 2 : fermer l'app concurrente → Reset to defaults (ou re-capturer le combo) → le hotkey redevient actif et le ⚠ disparaît.
- Piège de test : une app qui capture via un hook bas niveau (et non
RegisterHotKey) avale la frappe sans faire échouer notre enregistrement → hotkey inerte sans ⚠. Utiliser AutoHotkey (^+t::MsgBox) ou Lightshot pour un vrai conflit. - Historique : le message disait « one or both hotkeys » (faux depuis qu'il y en a 3) sans dire lequel ; et un échec sur Quick search ou New entry n'était écrit que dans le panel debug — invisible en prod, le hotkey semblait juste cassé.
H8 · capturer un combo déjà pris par une autre app ✅
- ACTION : Settings → cliquer le bouton du hotkey 2FA → presser un combo qu'une autre app détient (ex.
Ctrl+Shift+T). - ATTENDU : au relâchement (pas après un délai), toast « No key registered after Ctrl+Shift. If you did press one, another app already owns that combo — pick a different one. » et le bouton reprend son libellé précédent.
- ACTION 2 : presser un combo libre (ex.
Ctrl+Win+T) → accepté, actif immédiatement, pas de ⚠. - ACTION 3 : cliquer le bouton puis ne rien presser pendant 10 s → « Hotkey capture cancelled — no key was pressed. » et le bouton se restaure (sinon la capture avalerait toutes les frappes jusqu'à Échap).
- ACTION 4 : cliquer le bouton puis presser un combo déjà assigné à un autre de nos slots (ex.
Ctrl+Shift+L) → « That combo is already used by another hotkey ». Doit fonctionner sans déclencher l'autofill : nos 5 hotkeys sont libérés pendant la capture. - Historique : un combo tenu par
RegisterHotKey(le nôtre ou celui d'une autre app) n'atteint jamais WebView2 → la capture restait figée sur « Press combo… » sans message, et la vérif de collision interne était du code mort.
H6 · code en fin de période
- ACTION : presser le hotkey juste avant la fin du compte à rebours de 30 s.
- ATTENDU : le code tapé est celui de la période courante au moment de la frappe (généré à la demande, pas mis en cache).
I — État de la fenêtre (maximisé, série 2026-07-25)
L'app démarre maximisée (WindowState = wsMaximized dans la .fmx).
I1 · maximisé survit à Show desktop ✅
- ACTION : fenêtre maximisée →
Win+D(ou Show desktop) → revenir à l'app (barre des tâches, tray ou Alt+Tab). - ATTENDU : elle revient maximisée.
- Historique : FMX oublie l'état maximisé quand Windows minimise la fenêtre de l'extérieur, et aucun de nos chemins de restore ne tourne pour un clic sur la barre des tâches.
I2 · normal survit aussi ✅
- ACTION : dé-maximiser la fenêtre →
Win+D→ revenir. - ATTENDU : elle reste en taille normale — le correctif ne doit jamais forcer le maximise.
I3 · maximiser / dé-maximiser reste enregistré
- ACTION : dé-maximiser →
Win+D→ retour (normal) → maximiser →Win+D→ retour. - ATTENDU : chaque retour respecte le dernier geste de l'utilisateur.
I4 · retour depuis le tray
- ACTION : fenêtre maximisée → fermer (part au tray) → clic sur l'icône du tray.
- ATTENDU : revient maximisée (chemin distinct :
RestoreFromTrayrejoue son propre placement).
I5 · le garde de minimisation ne reste pas bloqué (autofill → tray) ⚠️
Le chemin le plus subtil : ExecuteAutofill minimise réellement la fenêtre
(SW_MINIMIZE) avant de taper, ce qui lève le garde ; si elle est ensuite
cachée dans le tray au lieu d'être restaurée, MINIMIZEEND peut ne jamais
arriver.
- ACTION : depuis le tray,
Ctrl+Shift+Q→ choisir une entrée → laisser le remplissage se faire (l'app repart au tray) → rouvrir l'app par l'icône du tray → dé-maximiser (ou maximiser) →Win+D→ revenir. - ATTENDU : le dernier geste est respecté. S'il ne l'est pas, le garde est resté levé et le suivi de l'état est gelé pour toute la session.
- Variante : même chose avec
Ctrl+Shift+Ldepuis une autre application.
I6 · le hook n'affecte pas la corbeille ni le tray (non-régression)
- ACTION : minimiser dans le tray (fermeture), restaurer ;
app/focusvia le picker d'autofill multi-match ; « Lock vault » depuis le menu du tray. - ATTENDU : comportements inchangés. Le hook n'écoute que notre process
et
MinimizeToTrayutiliseSW_HIDE, qui n'émet aucun événement de minimisation.
Diagnostic
Avec config.txt contenant debug=true, Ctrl+Shift+D affiche le journal.
Lignes utiles : [winstate] minimise hook installed=True au démarrage (un
False signifierait que le hook Win32 ne s'installe pas → préservation
inactive) et [winstate] re-maximising after restore quand la correction agit.
J — Quick search depuis le tray (série 2026-07-25)
Le flux le plus fragile : la fenêtre revient du tray, un modal s'ouvre, et la
fermeture doit renvoyer l'app au tray. Deux cmd:// s'y enchaînent, ce qui
est précisément le piège de coalescence documenté dans CLAUDE.md.
J1 · Esc dès l'ouverture, sans cliquer ✅
- PRÉ : app dans le tray, coffre déverrouillé.
- ACTION :
Ctrl+Shift+Q→ ne rien cliquer →Escimmédiatement. - ATTENDU : le modal se ferme du premier coup, et l'app repart au tray.
- Historique : deux bugs distincts ici. (1) WebView2 n'avait pas le focus
clavier de l'OS après la restauration → aucune frappe n'atteignait la page,
Esc ne faisait rien tant qu'on n'avait pas cliqué dans le champ. (2) En mode
fill,
cancelAutofillpuisminimizeToTray= deuxwindow.location.hrefconsécutifs → coalescés, la minimisation était perdue.
J2 · remplissage puis retour au tray ✅ ✅
- ACTION : app dans le tray →
Ctrl+Shift+Q→ choisir une entrée (Enter). - ATTENDU : le mot de passe est tapé dans la fenêtre cible et l'app
repart au tray — une seule fois (c'est Delphi qui minimise ici, via
hide_after, pas le JS : vérifier qu'il n'y a pas de double minimisation). - Variantes :
Shift+Enter(user+pwd),Ctrl+Enter(username seul).
J3 · copie depuis le menu du tray ✅
- ACTION : menu du tray → Quick search… → choisir une entrée (Enter).
- ATTENDU : mot de passe copié, app de retour au tray, et le presse-papiers
conserve la valeur (
keepclip=1— c'est le rôle du paramètre).
J4 · app déjà visible (non-régression) ✅
- ACTION : app ouverte à l'écran →
Ctrl+Shift+Q→Esc. - ATTENDU : le modal se ferme et l'app reste à l'écran — elle ne doit pas partir au tray (elle n'en venait pas). Idem si elle était seulement minimisée dans la barre des tâches.
Reste à traiter (constaté pendant l'audit, non corrigé)
- Attachments hors garde A11. Le contrôle « changed elsewhere » couvre le payload de l'entrée, pas ses pièces jointes (table séparée, écrites par leurs propres endpoints). Un sync qui remplace une pièce jointe pendant l'édition n'est pas signalé. Faible risque : l'éditeur ne re-ship jamais les pièces jointes existantes, donc il n'y a rien à écraser.