From bf606493bdd672de5e703b72fc295bcf3bdf2cff Mon Sep 17 00:00:00 2001 From: r-zakarya <82443831+r-zakarya@users.noreply.github.com> Date: Sun, 12 Jul 2026 22:46:42 +0100 Subject: [PATCH] fix: three more bugs of the same classes (Esc fall-through, chord, placement) Hunted the classes behind the recent quick-search fixes across the codebase: - Esc priority: the command palette is .cmd-palette (not .modal), so the slideover's capture-phase Esc handler didn't see it and popped the discard-confirm UNDER the open palette. Capture handler now also yields to the palette. - Esc fall-through: the fallback Esc branch closed palette + slideover + entry modal + generator ALL on one keystroke. Now closes exactly one surface per keystroke, topmost first. - Modifier chord: the real-VK Tab between username and password becomes Shift+Tab if the user holds Shift mid-fill -> focus moves backward and the password lands in the username field. ForceReleaseModifiers before SendVKey(VK_TAB). - Stale placement: RestoreFromTray replayed the MinimizeToTray snapshot for a merely taskbar-minimised window, teleporting it to the last tray-hide position. Snapshot now replays only on a genuine tray return (captured before Show flips Visible); iconic windows use Windows' own placement. Co-Authored-By: Claude Opus 4.8 --- delphi-backend/Source/PM.Bridge.pas | 14 ++++++++++++- js/app.js | 32 ++++++++++++++++++++++------- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/delphi-backend/Source/PM.Bridge.pas b/delphi-backend/Source/PM.Bridge.pas index 402b730..7841828 100644 --- a/delphi-backend/Source/PM.Bridge.pas +++ b/delphi-backend/Source/PM.Bridge.pas @@ -669,6 +669,7 @@ end; procedure TPMBridge.RestoreFromTray; var LFormHwnd, LAppHwnd: HWND; + LWasTrayHidden: Boolean; begin // Tray icon stays in the tray — we only show the window again. LFormHwnd := MainFormHWND(FMainForm); @@ -684,6 +685,10 @@ begin Exit; end; + // Captured BEFORE Show flips Visible: tray-hidden (Hide'd by + // MinimizeToTray) vs merely taskbar-minimised (Visible but iconic). + LWasTrayHidden := not FMainForm.Visible; + // Reverse order: show the app proxy first so the taskbar entry comes back, // then show and foreground the form. if LAppHwnd <> 0 then @@ -697,7 +702,11 @@ begin // window). Unconditional SW_RESTORE would un-maximise a maximised // window — surprising for the user who pressed Ctrl+Shift+A / +L / // clicked the tray. - if FHasSavedPlacement then + // Replay the snapshot ONLY for a genuine tray return (window was hidden — + // the exact state MinimizeToTray created). A merely taskbar-minimised + // window restores via Windows' own placement memory below; replaying our + // older snapshot would teleport it to wherever it sat at the LAST tray-hide. + if FHasSavedPlacement and LWasTrayHidden then begin // showCmd governs whether the window comes back maximised or normal; // it's what SW_RESTORE clobbers. We force it ourselves. Captured while @@ -1325,6 +1334,9 @@ begin ClearFieldIfWanted; SendUnicodeString(AUsername); Sleep(200); + // Real-VK send: a physically held Shift here turns Tab into Shift+Tab — + // focus moves BACKWARD and the password lands in the username field. + ForceReleaseModifiers; SendVKey(VK_TAB); Sleep(200); ClearFieldIfWanted; diff --git a/js/app.js b/js/app.js index 42b3f84..a17d1c9 100644 --- a/js/app.js +++ b/js/app.js @@ -7222,6 +7222,10 @@ async function init() { if (e.key !== 'Escape') return; if (!$('#slideover').classList.contains('is-open')) return; if (document.querySelector('.modal:not(.is-hidden)')) return; + // Command palette is .cmd-palette, not .modal — it sits on top of + // the editor, so its Esc must close IT (bubble handler), not pop + // the editor's discard-confirm underneath. + if (!$('#cmdPalette').classList.contains('is-hidden')) return; // Settings panel takes priority — when both Settings AND the // editor are open, the first Esc should close Settings (the // thing the user just opened on top), the second Esc handles @@ -7912,13 +7916,27 @@ async function init() { closeHistoryModal(); return; } - closePalette(); - requestCloseSlideOver(); - closeEntryModal(); - closeGen(); - // If nothing else needed dismissing and there's an active - // selection, clear it. Replaces the "click empty space to - // deselect" path that the marquee provided. + // ONE surface per keystroke, topmost first — closing several at + // once meant "Esc closes the palette AND pops the editor's + // discard prompt" (same class as the quick-search Esc bug). + if (!$('#cmdPalette').classList.contains('is-hidden')) { + closePalette(); + return; + } + if (!$('#entryModal').classList.contains('is-hidden')) { + closeEntryModal(); + return; + } + if (!$('#genModal').classList.contains('is-hidden')) { + closeGen(); + return; + } + if ($('#slideover').classList.contains('is-open')) { + requestCloseSlideOver(); + return; + } + // Nothing else needed dismissing — clear an active selection. + // Replaces the "click empty space to deselect" marquee path. if (state.checked.size > 0) { state.checked.clear(); renderGrid();