feat(hotkeys): persistent conflict marker in Settings
A toast is gone by the time the user opens Settings, so a hotkey Windows refused looked perfectly configured while doing nothing. The offending row now carries a ⚠ marker (amber border + tooltip explaining the combo is taken) until a re-registration clears it. Delphi reports slot ids instead of labels; HOTKEY_SLOTS maps id → button + label in one place, so JS owns the wording and can flag the exact row. The markers are re-applied by openSettings, surviving panel re-renders. Also stop toasting success on the startup push (it fires on every unlock) — autofillPushHotkeys(true). A clash still warns: that one is actionable. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -218,15 +218,23 @@ const Bridge = (() => {
|
||||
// Called by Delphi after a setAutofillHotkeys request, with true if
|
||||
// BOTH combos registered successfully, false otherwise (clash with
|
||||
// another app holding a global hotkey). Surfaces a toast.
|
||||
// `failed` = comma-separated names of the combos Windows refused
|
||||
// (another app already owns them); empty string = all registered.
|
||||
// Naming them matters: a clashing hotkey is otherwise indistinguishable
|
||||
// from a broken feature — it just silently does nothing.
|
||||
// `failed` = comma-separated SLOT IDS Windows refused (another app
|
||||
// already owns them); empty string = all registered. A clashing hotkey
|
||||
// is otherwise indistinguishable from a broken feature — it silently
|
||||
// does nothing — so it gets both a toast AND a persistent marker on the
|
||||
// Settings row (the toast is gone by the time the user looks).
|
||||
onAutofillHotkeysResult(failed) {
|
||||
const list = String(failed || '').trim();
|
||||
if (!list) { toast('Hotkeys updated'); return; }
|
||||
toast('Already used by another app: ' + list +
|
||||
' — pick another combo in Settings', 'warning');
|
||||
state.hotkeyConflicts = String(failed || '')
|
||||
.split(',').map(s => s.trim()).filter(Boolean);
|
||||
renderHotkeyConflicts();
|
||||
if (state.hotkeyConflicts.length === 0) {
|
||||
if (!hotkeyPushSilent) toast('Hotkeys updated');
|
||||
return;
|
||||
}
|
||||
toast('Already used by another app: ' +
|
||||
state.hotkeyConflicts.map(k => HOTKEY_SLOTS[k] ? HOTKEY_SLOTS[k].label : k)
|
||||
.join(', ') +
|
||||
' — pick another combo', 'warning');
|
||||
},
|
||||
|
||||
// ---- Device-bound prefs (DPAPI-backed) ----------------------------
|
||||
@@ -683,6 +691,9 @@ const state = {
|
||||
'{"ctrl":true,"shift":true,"alt":false,"win":false,"key":"P"}'),
|
||||
autofillHotkeyTotp: JSON.parse(localStorage.getItem('autofillHotkeyTotp') ||
|
||||
'{"ctrl":true,"shift":true,"alt":false,"win":false,"key":"T"}'),
|
||||
// Slot ids whose combo Windows refused (another app owns it). Runtime-only:
|
||||
// re-evaluated on every registration, never persisted.
|
||||
hotkeyConflicts: [],
|
||||
quickSearchHotkey: JSON.parse(localStorage.getItem('quickSearchHotkey') ||
|
||||
'{"ctrl":true,"shift":true,"alt":false,"win":false,"key":"Q"}'),
|
||||
newEntryHotkey: JSON.parse(localStorage.getItem('newEntryHotkey') ||
|
||||
@@ -6313,6 +6324,7 @@ function openSettings() {
|
||||
$('#settingAutofillFullCombo').textContent = autofillComboLabel(state.autofillHotkeyFull);
|
||||
$('#settingAutofillPwdCombo').textContent = autofillComboLabel(state.autofillHotkeyPwd);
|
||||
$('#settingAutofillTotpCombo').textContent = autofillComboLabel(state.autofillHotkeyTotp);
|
||||
renderHotkeyConflicts(); // keep the clash markers after a panel re-render
|
||||
$('#settingQuickSearchCombo').textContent = autofillComboLabel(state.quickSearchHotkey);
|
||||
$('#settingNewEntryCombo').textContent = autofillComboLabel(state.newEntryHotkey);
|
||||
$('#settingAutofillHotkeysRow').style.display = Bridge.active ? '' : 'none';
|
||||
@@ -6639,7 +6651,8 @@ async function enterApp() {
|
||||
if (state.hibpEnabled) hibpCheckAllEntries();
|
||||
// Push the user-configured hotkeys (combos + enabled state) to Delphi.
|
||||
// Replaces the historical "always Ctrl+Shift+L on startup" path.
|
||||
autofillPushHotkeys();
|
||||
// silent: no success toast on every unlock — a clash still warns.
|
||||
autofillPushHotkeys(true);
|
||||
// Sync the tray notifications preference to Delphi (default ON;
|
||||
// settings_json may have flipped it).
|
||||
if (Bridge.active && typeof Bridge.setTrayNotifications === 'function')
|
||||
|
||||
Reference in New Issue
Block a user