diff --git a/TEST_SCENARIOS.md b/TEST_SCENARIOS.md index 08c2ed1..6f2ae71 100644 --- a/TEST_SCENARIOS.md +++ b/TEST_SCENARIOS.md @@ -47,10 +47,10 @@ Préparer : 3 entrées `A`, `B`, `C`, dont `A` dans le dossier `Work` avec le ta - **ACTION** : `A` ouverte + dirty → glisser la ligne `A` sur `Perso` dans la sidebar → Save - **ATTENDU** : identique à A3.✅ -### A6 · glisser une **multi-sélection** sur un dossier (`moveEntriesToFolder`) ⚠️ +### A6 · glisser une **multi-sélection** sur un dossier (`moveEntriesToFolder`) ✅ - **ACTION** : cocher `A` + `B` → glisser sur `Perso` → 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.✅ +- **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` (dossier `Work`) ouverte + dirty → sidebar → supprimer le dossier `Work` → Save @@ -337,22 +337,23 @@ 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**. +- **ATTENDU** : elle revient **maximisée**, **sans passer par une frame en taille normale**. - **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. +- **Historique (flash)** : une fois l'état correct, il restait un scintillement — la fenêtre revenait normale puis se maximisait. La correction était postée via `ForceQueue` ; le log a montré que l'état ne changeait **pas** entre le hook et le tour de boucle suivant, donc la file ne protégeait d'aucune course et n'ajoutait que la frame visible. Elle s'exécute maintenant **en ligne** dans `MINIMIZEEND` (`ApplyWantedWindowState`), la passe différée restant en filet idempotent. Piste écartée au passage : poser `WPF_RESTORETOMAXIMIZED` — `GetWindowPlacement` rapporte déjà ce flag sur toute fenêtre maximisée, donc le stamp est un no-op. ### 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é +### 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 +### 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 : `RestoreFromTray` rejoue son propre placement). -### I5 · le garde de minimisation ne reste pas bloqué (autofill → tray) ⚠️ +### 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 @@ -450,7 +451,7 @@ Le flag `-tray` est ce que « Start with Windows » écrit dans `HKCU\…\Run` dossier de sortie. **Ne pas** laisser `-tray` dans *Run → Parameters* de l'IDE, sinon chaque F9 démarre caché. -### L1 · démarrage caché, sans bouton de barre des tâches +### L1 · démarrage caché, sans bouton de barre des tâches ✅ - **ACTION** : lancer avec `-tray` (l'app n'étant pas déjà lancée — sinon le mutex single-instance fait sortir la nouvelle instance immédiatement). - **ATTENDU** : **aucune fenêtre**, **aucun bouton** dans la barre des tâches, @@ -462,7 +463,7 @@ sinon chaque F9 démarre caché. non plus : il faut retirer `WS_EX_APPWINDOW` et poser `WS_EX_TOOLWINDOW`, depuis le `.dpr`, juste après `Application.Initialize`. -### L2 · le bouton revient à la restauration +### L2 · le bouton revient à la restauration ✅ - **ACTION** : après L1, clic sur l'icône du tray. - **ATTENDU** : la fenêtre s'ouvre **et** le bouton réapparaît dans la barre des tâches. diff --git a/delphi-backend/UMainForm.pas b/delphi-backend/UMainForm.pas index c80bfa8..fed16c0 100644 --- a/delphi-backend/UMainForm.pas +++ b/delphi-backend/UMainForm.pas @@ -176,6 +176,7 @@ type procedure RestoreWindowFromTray; procedure WinStateMinimizeStart; procedure WinStateMinimizeEnd; + procedure ApplyWantedWindowState; end; var @@ -1657,34 +1658,43 @@ begin FIgnoreResizes := True; end; -// Back up: re-apply what the user had. Queued so it lands after FMX has -// finished sizing the window — inline loses that race. +// Back up: re-apply what the user had. procedure TMainForm.WinStateMinimizeEnd; begin + // Inline, not queued: by the time this hook fires Windows has already put the + // window back at its normal size, and measurement showed nothing touches the + // state afterwards — the extra message-loop turn a ForceQueue costs was the + // whole flicker, one painted normal-sized frame before the correction landed. + ApplyWantedWindowState; + // Second pass anyway, in case FMX sizes the window after us on some path. + // Idempotent: it only acts when the state still disagrees. TThread.ForceQueue(nil, procedure - var - LHwnd: HWND; begin - LHwnd := WindowHandleToPlatform(Self.Handle).Wnd; - if (LHwnd <> 0) and (not IsIconic(LHwnd)) then - begin - if FWantMaximized and (not IsZoomed(LHwnd)) then - begin - LogLine('[winstate] re-maximising after restore'); - ShowWindow(LHwnd, SW_SHOWMAXIMIZED); - end - else if (not FWantMaximized) and IsZoomed(LHwnd) then - begin - LogLine('[winstate] restoring to normal after restore'); - ShowWindow(LHwnd, SW_RESTORE); - end; - end; + ApplyWantedWindowState; // Release the guard only after our own ShowWindow's resize has passed. TThread.ForceQueue(nil, procedure begin FIgnoreResizes := False; end); end); end; +procedure TMainForm.ApplyWantedWindowState; +var + LHwnd: HWND; +begin + LHwnd := WindowHandleToPlatform(Self.Handle).Wnd; + if (LHwnd = 0) or IsIconic(LHwnd) then Exit; + if FWantMaximized and (not IsZoomed(LHwnd)) then + begin + LogLine('[winstate] re-maximising after restore'); + ShowWindow(LHwnd, SW_SHOWMAXIMIZED); + end + else if (not FWantMaximized) and IsZoomed(LHwnd) then + begin + LogLine('[winstate] restoring to normal after restore'); + ShowWindow(LHwnd, SW_RESTORE); + end; +end; + // Track the state the user settles on. Ignored entirely while a minimise / // restore is in flight — that is the whole point of the hook above. procedure TMainForm.FormResizeTrackState(Sender: TObject);