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 <noreply@anthropic.com>
This commit is contained in:
@@ -669,6 +669,7 @@ end;
|
|||||||
procedure TPMBridge.RestoreFromTray;
|
procedure TPMBridge.RestoreFromTray;
|
||||||
var
|
var
|
||||||
LFormHwnd, LAppHwnd: HWND;
|
LFormHwnd, LAppHwnd: HWND;
|
||||||
|
LWasTrayHidden: Boolean;
|
||||||
begin
|
begin
|
||||||
// Tray icon stays in the tray — we only show the window again.
|
// Tray icon stays in the tray — we only show the window again.
|
||||||
LFormHwnd := MainFormHWND(FMainForm);
|
LFormHwnd := MainFormHWND(FMainForm);
|
||||||
@@ -684,6 +685,10 @@ begin
|
|||||||
Exit;
|
Exit;
|
||||||
end;
|
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,
|
// Reverse order: show the app proxy first so the taskbar entry comes back,
|
||||||
// then show and foreground the form.
|
// then show and foreground the form.
|
||||||
if LAppHwnd <> 0 then
|
if LAppHwnd <> 0 then
|
||||||
@@ -697,7 +702,11 @@ begin
|
|||||||
// window). Unconditional SW_RESTORE would un-maximise a maximised
|
// window). Unconditional SW_RESTORE would un-maximise a maximised
|
||||||
// window — surprising for the user who pressed Ctrl+Shift+A / +L /
|
// window — surprising for the user who pressed Ctrl+Shift+A / +L /
|
||||||
// clicked the tray.
|
// 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
|
begin
|
||||||
// showCmd governs whether the window comes back maximised or normal;
|
// showCmd governs whether the window comes back maximised or normal;
|
||||||
// it's what SW_RESTORE clobbers. We force it ourselves. Captured while
|
// it's what SW_RESTORE clobbers. We force it ourselves. Captured while
|
||||||
@@ -1325,6 +1334,9 @@ begin
|
|||||||
ClearFieldIfWanted;
|
ClearFieldIfWanted;
|
||||||
SendUnicodeString(AUsername);
|
SendUnicodeString(AUsername);
|
||||||
Sleep(200);
|
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);
|
SendVKey(VK_TAB);
|
||||||
Sleep(200);
|
Sleep(200);
|
||||||
ClearFieldIfWanted;
|
ClearFieldIfWanted;
|
||||||
|
|||||||
@@ -7222,6 +7222,10 @@ async function init() {
|
|||||||
if (e.key !== 'Escape') return;
|
if (e.key !== 'Escape') return;
|
||||||
if (!$('#slideover').classList.contains('is-open')) return;
|
if (!$('#slideover').classList.contains('is-open')) return;
|
||||||
if (document.querySelector('.modal:not(.is-hidden)')) 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
|
// Settings panel takes priority — when both Settings AND the
|
||||||
// editor are open, the first Esc should close Settings (the
|
// editor are open, the first Esc should close Settings (the
|
||||||
// thing the user just opened on top), the second Esc handles
|
// thing the user just opened on top), the second Esc handles
|
||||||
@@ -7912,13 +7916,27 @@ async function init() {
|
|||||||
closeHistoryModal();
|
closeHistoryModal();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// 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();
|
closePalette();
|
||||||
requestCloseSlideOver();
|
return;
|
||||||
|
}
|
||||||
|
if (!$('#entryModal').classList.contains('is-hidden')) {
|
||||||
closeEntryModal();
|
closeEntryModal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!$('#genModal').classList.contains('is-hidden')) {
|
||||||
closeGen();
|
closeGen();
|
||||||
// If nothing else needed dismissing and there's an active
|
return;
|
||||||
// selection, clear it. Replaces the "click empty space to
|
}
|
||||||
// deselect" path that the marquee provided.
|
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) {
|
if (state.checked.size > 0) {
|
||||||
state.checked.clear();
|
state.checked.clear();
|
||||||
renderGrid();
|
renderGrid();
|
||||||
|
|||||||
Reference in New Issue
Block a user